package sms import ( "bytes" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "net/http" "time" ) const ( DefaultUserAccount = "8871f88e273edb8c20834c61ce97b287" DefaultUserSecret = "76d9ae4fe2ac3523d7c051e158c1477d" DefaultBaseURL = "https://apiext.szshanyun.com:8443" ) // ---------------- 工具函数 ---------------- // GenTimestamp 生成 13 位时间戳(毫秒) func GenTimestamp() string { return fmt.Sprintf("%d", time.Now().UnixNano()/1e6) } // MD5 md5加密 func MD5(s string) string { h := md5.Sum([]byte(s)) return hex.EncodeToString(h[:]) } // SignWithBusinessBody 生成发送/状态查询接口签名:md5(businessBody + userSecret + timestamp) func SignWithBusinessBody(body interface{}, userSecret, timestamp string) (string, error) { b, err := json.Marshal(body) if err != nil { return "", err } return MD5(string(b) + userSecret + timestamp), nil } // SignSimple 生成余额接口签名:md5(userAccount + userSecret + timestamp) func SignSimple(userAccount, userSecret, timestamp string) string { return MD5(userAccount + userSecret + timestamp) } // ---------------- 数据结构 ---------------- // SendRequest ===== 短信发送 ===== type SendRequest struct { BusinessBody BusinessBody `json:"businessBody"` Sign string `json:"sign"` Timestamp string `json:"timestamp"` UserAccount string `json:"userAccount"` } type BusinessBody struct { ChildUserNumber *string `json:"childUserNumber,omitempty"` Content *string `json:"content,omitempty"` ModelID *string `json:"modelId,omitempty"` Number *string `json:"number,omitempty"` ReturnURL *string `json:"returnUrl,omitempty"` SendList []SendList `json:"sendList"` SignatureID *string `json:"signatureId,omitempty"` SignatureStr *string `json:"signatureStr,omitempty"` } type SendList struct { Content *string `json:"content,omitempty"` ModelReplace *ModelReplace `json:"modelReplace,omitempty"` OutOrderID string `json:"outOrderId"` PhoneNumber string `json:"phoneNumber"` } type ModelReplace struct { FlowSize *string `json:"FlowSize,omitempty"` ISPNumber *string `json:"ispNumber,omitempty"` Time *string `json:"Time,omitempty"` TimeLimit *string `json:"TimeLimit,omitempty"` } type SendResponse struct { Message string `json:"message"` StatusCode int64 `json:"statusCode"` } // BalanceRequest ===== 余额查询 ===== type BalanceRequest struct { Sign string `json:"sign"` Timestamp string `json:"timestamp"` UserAccount string `json:"userAccount"` } type BalanceResponse struct { Data BalanceData `json:"data"` Message string `json:"message"` StatusCode int64 `json:"statusCode"` } type BalanceData struct { Balance string `json:"balance"` UserName string `json:"userName"` } // StateRequest ===== 状态查询 ===== type StateRequest struct { BusinessBody StateBusinessBody `json:"businessBody"` Sign string `json:"sign"` Timestamp string `json:"timestamp"` UserAccount string `json:"userAccount"` } type StateBusinessBody struct { OutOrderID string `json:"outOrderId"` PhoneNumber string `json:"phoneNumber"` } type StateResponse struct { Data StateData `json:"data"` Message string `json:"message"` StatusCode int64 `json:"statusCode"` } type StateData struct { SendStatus string `json:"sendStatus"` } // ---------------- Client 封装 ---------------- type Client struct { UserAccount string UserSecret string BaseURL string } func NewClient() *Client { return &Client{ UserAccount: DefaultUserAccount, UserSecret: DefaultUserSecret, BaseURL: DefaultBaseURL, } } // SendSMS 发送短信 func (c *Client) SendSMS(body BusinessBody) (*SendResponse, error) { timestamp := GenTimestamp() sign, err := SignWithBusinessBody(body, c.UserSecret, timestamp) if err != nil { return nil, err } req := SendRequest{ BusinessBody: body, Sign: sign, Timestamp: timestamp, UserAccount: c.UserAccount, } return post[SendResponse](c.BaseURL+"/receive", req) } // GetBalance 查询余额 func (c *Client) GetBalance() (*BalanceResponse, error) { timestamp := GenTimestamp() sign := SignSimple(c.UserAccount, c.UserSecret, timestamp) req := BalanceRequest{ Sign: sign, Timestamp: timestamp, UserAccount: c.UserAccount, } return post[BalanceResponse](c.BaseURL+"/balance", req) } // QueryState 查询状态 func (c *Client) QueryState(outOrderID, phone string) (*StateResponse, error) { body := StateBusinessBody{ OutOrderID: outOrderID, PhoneNumber: phone, } timestamp := GenTimestamp() sign, err := SignWithBusinessBody(body, c.UserSecret, timestamp) if err != nil { return nil, err } req := StateRequest{ BusinessBody: body, Sign: sign, Timestamp: timestamp, UserAccount: c.UserAccount, } return post[StateResponse](c.BaseURL+"/state", req) } // ---------------- POST工具 ---------------- func post[T any](url string, payload interface{}) (*T, error) { b, err := json.Marshal(payload) if err != nil { return nil, err } resp, err := http.Post(url, "application/json", bytes.NewBuffer(b)) if err != nil { return nil, err } defer resp.Body.Close() var result T if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil }