210 lines
5.4 KiB
Go
210 lines
5.4 KiB
Go
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
|
||
}
|