Ai
1 Star 0 Fork 0

Spume/toolkit

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
space_iot.go 11.40 KB
一键复制 编辑 原始数据 按行查看 历史
package providers
import (
"context"
"crypto/md5"
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
// SpaceIoTProvider Space IoT SMS提供商实现
type SpaceIoTProvider struct {
*BaseProvider
config *SpaceIoTConfig
}
// SpaceIoTConfig Space IoT配置
type SpaceIoTConfig struct {
BaseURL string `json:"base_url"` // API基础URL
Sign string `json:"sign"` // 签名
Key string `json:"key"` // 密钥
Timeout time.Duration `json:"timeout"` // 超时时间
}
// SpaceIoTSendRequest Space IoT发送请求
type SpaceIoTSendRequest struct {
AccountName string `json:"accountName"` // 账户名称
Phone string `json:"phone"` // 手机号
Content string `json:"content"` // 短信内容
Timestamp string `json:"timestamp"` // 时间戳
RandomString string `json:"randomString"` // 随机字符串
Sign string `json:"sign"` // 签名
}
// SpaceIoTSendResponse Space IoT发送响应
type SpaceIoTSendResponse struct {
Code int `json:"code"` // 响应代码
Message string `json:"message"` // 响应消息
Data bool `json:"data"` // 响应数据
}
// SpaceIoTQueryRequest Space IoT查询请求
type SpaceIoTQueryRequest struct {
AccountName string `json:"accountName"` // 账户名称
Phone string `json:"phone"` // 手机号
Timestamp string `json:"timestamp"` // 时间戳
RandomString string `json:"randomString"` // 随机字符串
Sign string `json:"sign"` // 签名
}
// SpaceIoTQueryResponse Space IoT查询响应
type SpaceIoTQueryResponse struct {
Code int `json:"code"` // 响应代码
Message string `json:"message"` // 响应消息
Data *SpaceIoTQueryResultData `json:"data"` // 响应数据
}
// SpaceIoTQueryResultData Space IoT查询结果数据
type SpaceIoTQueryResultData struct {
Status string `json:"status"` // 状态
SentAt string `json:"sentAt"` // 发送时间
DeliveredAt string `json:"deliveredAt"` // 送达时间
ErrorCode string `json:"errorCode"` // 错误代码
ErrorMsg string `json:"errorMsg"` // 错误消息
}
// NewSpaceIoTProvider 创建Space IoT提供商实例
func NewSpaceIoTProvider(config *SpaceIoTConfig) *SpaceIoTProvider {
provider := &SpaceIoTProvider{
BaseProvider: &BaseProvider{
name: "space_iot",
},
config: config,
}
if config.Timeout > 0 {
provider.SetTimeout(config.Timeout)
}
return provider
}
// SendSMS 发送单条短信
func (p *SpaceIoTProvider) SendSMS(ctx context.Context, req *SendSMSRequest) (*SendSMSResponse, error) {
// 验证请求参数
if err := p.ValidateRequest(req); err != nil {
return nil, err
}
// 构建请求
spaceReq := p.buildSendRequest(req)
// 发送请求
start := time.Now()
resp, err := p.sendRequest(ctx, "/sms/sendByContent", spaceReq)
latency := time.Since(start)
if err != nil {
return nil, p.wrapNetworkError(err, latency)
}
// 解析响应
spaceResp := &SpaceIoTSendResponse{}
if err := gjson.Unmarshal(resp, spaceResp); err != nil {
return nil, p.CreateProviderError("PARSE_ERROR", "响应解析失败: "+err.Error(), false)
}
// 检查响应状态
if spaceResp.Code != 0 {
retryable := p.isRetryableError(spaceResp.Code)
return nil, p.CreateProviderError(
strconv.Itoa(spaceResp.Code),
spaceResp.Message,
retryable,
)
}
// 构建响应
response := &SendSMSResponse{
MessageID: req.MessageID,
Status: "sent",
ProviderID: req.MessageID, // Space IoT没有返回独立的Provider ID
Cost: EstimateCost(req.Content, req.Type),
SentAt: time.Now(),
Metadata: req.Metadata,
}
return response, nil
}
// SendBatchSMS 批量发送短信
func (p *SpaceIoTProvider) SendBatchSMS(ctx context.Context, req *BatchSMSRequest) (*BatchSMSResponse, error) {
// 验证批量请求参数
if err := p.ValidateBatchRequest(req); err != nil {
return nil, err
}
startTime := time.Now()
results := make([]*BatchSMSResult, len(req.Requests))
successCount := 0
failedCount := 0
totalCost := 0.0
// 逐个发送短信(Space IoT不支持真正的批量发送)
for i, smsReq := range req.Requests {
result := &BatchSMSResult{
Index: i,
Phone: smsReq.Phone,
}
// 发送单条短信
resp, err := p.SendSMS(ctx, smsReq)
if err != nil {
result.Status = "failed"
if providerErr, ok := err.(*ProviderError); ok {
result.ErrorCode = providerErr.Code
result.ErrorMessage = providerErr.Message
} else {
result.ErrorCode = "UNKNOWN_ERROR"
result.ErrorMessage = err.Error()
}
failedCount++
} else {
result.MessageID = resp.MessageID
result.ProviderID = resp.ProviderID
result.Status = resp.Status
result.Cost = resp.Cost
result.SentAt = &resp.SentAt
totalCost += resp.Cost
successCount++
}
results[i] = result
}
finishTime := time.Now()
// 构建批量响应
response := &BatchSMSResponse{
BatchID: req.BatchID,
Total: len(req.Requests),
Success: successCount,
Failed: failedCount,
Results: results,
StartedAt: startTime,
FinishedAt: finishTime,
Cost: totalCost,
}
return response, nil
}
// QuerySMS 查询短信状态
func (p *SpaceIoTProvider) QuerySMS(ctx context.Context, req *QuerySMSRequest) (*QuerySMSResponse, error) {
if req.Phone == "" {
return nil, p.CreateProviderError("INVALID_PHONE", "查询短信状态需要提供手机号", false)
}
// 构建查询请求
queryReq := p.buildQueryRequest(req.Phone)
// 发送查询请求
resp, err := p.sendRequest(ctx, "/sms/query", queryReq)
if err != nil {
return nil, p.wrapNetworkError(err, 0)
}
// 解析响应
queryResp := &SpaceIoTQueryResponse{}
if err := gjson.Unmarshal(resp, queryResp); err != nil {
return nil, p.CreateProviderError("PARSE_ERROR", "查询响应解析失败: "+err.Error(), false)
}
// 检查响应状态
if queryResp.Code != 0 {
retryable := p.isRetryableError(queryResp.Code)
return nil, p.CreateProviderError(
strconv.Itoa(queryResp.Code),
queryResp.Message,
retryable,
)
}
// 构建查询响应
response := &QuerySMSResponse{
MessageID: req.MessageID,
ProviderID: req.ProviderID,
Phone: req.Phone,
}
if queryResp.Data != nil {
response.Status = queryResp.Data.Status
response.ErrorCode = queryResp.Data.ErrorCode
response.ErrorMessage = queryResp.Data.ErrorMsg
// 解析时间
if queryResp.Data.SentAt != "" {
if sentAt, err := time.Parse("2006-01-02 15:04:05", queryResp.Data.SentAt); err == nil {
response.SentAt = &sentAt
}
}
if queryResp.Data.DeliveredAt != "" {
if deliveredAt, err := time.Parse("2006-01-02 15:04:05", queryResp.Data.DeliveredAt); err == nil {
response.DeliveredAt = &deliveredAt
}
}
}
return response, nil
}
// GetQuota 获取剩余配额
func (p *SpaceIoTProvider) GetQuota(ctx context.Context) (*QuotaResponse, error) {
// Space IoT API可能不支持配额查询,返回模拟数据
return &QuotaResponse{
Total: 10000,
Used: 0,
Remaining: 10000,
ResetAt: time.Now().Add(24 * time.Hour),
}, nil
}
// ValidateConfig 验证配置
func (p *SpaceIoTProvider) ValidateConfig() error {
if p.config == nil {
return p.CreateProviderError("INVALID_CONFIG", "配置不能为空", false)
}
if p.config.BaseURL == "" {
return p.CreateProviderError("INVALID_BASE_URL", "Base URL不能为空", false)
}
if p.config.Sign == "" {
return p.CreateProviderError("INVALID_SIGN", "签名不能为空", false)
}
if p.config.Key == "" {
return p.CreateProviderError("INVALID_KEY", "密钥不能为空", false)
}
return nil
}
// HealthCheck 健康检查
func (p *SpaceIoTProvider) HealthCheck(ctx context.Context) error {
// 发送一个测试请求来检查服务健康状态
testReq := &SendSMSRequest{
Phone: "+8618888888888", // 测试手机号
Content: "健康检查测试",
Sign: p.config.Sign,
MessageID: fmt.Sprintf("health_check_%d", time.Now().Unix()),
}
// 构建请求但不实际发送
spaceReq := p.buildSendRequest(testReq)
if spaceReq == nil {
return p.CreateProviderError("HEALTH_CHECK_FAILED", "健康检查请求构建失败", true)
}
return nil
}
// buildSendRequest 构建发送请求
func (p *SpaceIoTProvider) buildSendRequest(req *SendSMSRequest) *SpaceIoTSendRequest {
timestamp := gconv.String(gtime.Now().Unix())
randomString := strconv.Itoa(rand.Intn(1000))
// 构建签名
signStr := p.config.Sign + p.config.Key + timestamp + randomString
m := md5.New()
m.Write([]byte(signStr))
sign := strings.ToLower(fmt.Sprintf("%x", m.Sum(nil)))
// 构建内容(添加签名前缀)
content := req.Content
if req.Sign != "" {
content = fmt.Sprintf("【%s】%s", req.Sign, content)
} else {
content = fmt.Sprintf("【%s】%s", p.config.Sign, content)
}
return &SpaceIoTSendRequest{
AccountName: p.config.Sign,
Phone: req.Phone,
Content: content,
Timestamp: timestamp,
RandomString: randomString,
Sign: sign,
}
}
// buildQueryRequest 构建查询请求
func (p *SpaceIoTProvider) buildQueryRequest(phone string) *SpaceIoTQueryRequest {
timestamp := gconv.String(gtime.Now().Unix())
randomString := strconv.Itoa(rand.Intn(1000))
// 构建签名
signStr := p.config.Sign + p.config.Key + timestamp + randomString
m := md5.New()
m.Write([]byte(signStr))
sign := strings.ToLower(fmt.Sprintf("%x", m.Sum(nil)))
return &SpaceIoTQueryRequest{
AccountName: p.config.Sign,
Phone: phone,
Timestamp: timestamp,
RandomString: randomString,
Sign: sign,
}
}
// sendRequest 发送HTTP请求
func (p *SpaceIoTProvider) sendRequest(ctx context.Context, endpoint string, body interface{}) ([]byte, error) {
url := p.config.BaseURL + endpoint
// 创建HTTP客户端
client := g.Client().Discovery(nil).ContentJson().Timeout(p.GetTimeout())
// 发送请求
resp, err := client.Post(ctx, url, body)
if err != nil {
return nil, err
}
defer resp.Close()
// 读取响应
return resp.ReadAll(), nil
}
// wrapNetworkError 包装网络错误
func (p *SpaceIoTProvider) wrapNetworkError(err error, latency time.Duration) error {
retryable := true
code := "NETWORK_ERROR"
message := "网络请求失败: " + err.Error()
// 根据错误类型判断是否可重试
if strings.Contains(err.Error(), "timeout") {
code = "TIMEOUT"
message = "请求超时"
} else if strings.Contains(err.Error(), "connection refused") {
code = "CONNECTION_REFUSED"
message = "连接被拒绝"
} else if strings.Contains(err.Error(), "no such host") {
code = "DNS_ERROR"
message = "DNS解析失败"
retryable = false
}
return p.CreateProviderError(code, message, retryable)
}
// isRetryableError 判断错误是否可重试
func (p *SpaceIoTProvider) isRetryableError(code int) bool {
// 根据Space IoT的错误代码判断是否可重试
switch code {
case 0:
return false // 成功
case 1001:
return false // 参数错误
case 1002:
return false // 签名错误
case 1003:
return false // 账户不存在
case 1004:
return false // 余额不足
case 1005:
return true // 系统繁忙
case 1006:
return false // 手机号格式错误
case 1007:
return false // 内容包含敏感词
case 1008:
return true // 发送频率过快
case 1009:
return false // IP不在白名单
case 1010:
return true // 网关错误
default:
return true // 未知错误,默认可重试
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/spume/toolkit.git
git@gitee.com:spume/toolkit.git
spume
toolkit
toolkit
v1.0.1

搜索帮助