1、新增对外短信接口;
2、新增硕软相关接口;
This commit is contained in:
parent
120379a0f9
commit
ef1a39a0a3
|
@ -1,6 +1,7 @@
|
||||||
package bus_apis
|
package bus_apis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"go-admin/app/admin/service/bus_service"
|
"go-admin/app/admin/service/bus_service"
|
||||||
"go-admin/tools/app"
|
"go-admin/tools/app"
|
||||||
"go-admin/tools/crypto"
|
"go-admin/tools/crypto"
|
||||||
|
"go-admin/tools/sms"
|
||||||
"go-admin/tools/utils"
|
"go-admin/tools/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"io"
|
"io"
|
||||||
|
@ -2164,3 +2166,173 @@ func (e SmsApi) ExportSmsTemplate(c *gin.Context) {
|
||||||
|
|
||||||
app.OK(c, bus_models.ExportTemplateResp{ExportUrl: fileUrl}, "导出成功")
|
app.OK(c, bus_models.ExportTemplateResp{ExportUrl: fileUrl}, "导出成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendMessage 发送短信
|
||||||
|
// @Summary 发送短信
|
||||||
|
// @Description 根据手机号发送短信
|
||||||
|
// @Tags 短信管理-V1.0.0
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body bus_models.SendMessageMassReq true "短信发送请求参数"
|
||||||
|
// @Success 200 {object} bus_models.SendMessageMassResp
|
||||||
|
// @Router /api/v1/sms/send_message [post]
|
||||||
|
func (e SmsApi) SendMessage(c *gin.Context) {
|
||||||
|
var req bus_models.SendMessageMassReq
|
||||||
|
smsService := bus_service.SmsService{}
|
||||||
|
|
||||||
|
err := e.MakeContext(c).
|
||||||
|
MakeOrm().
|
||||||
|
Bind(&req, binding.JSON).
|
||||||
|
MakeService(&smsService.Service).
|
||||||
|
Errors
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Error(err)
|
||||||
|
//e.Error(400, err, "参数绑定失败")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -1,
|
||||||
|
Msg: "参数绑定失败",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间戳校验:3分钟内有效
|
||||||
|
nowMillis := time.Now().UnixMilli()
|
||||||
|
if nowMillis-req.Timestamp > 3*60*1000 {
|
||||||
|
//e.Error(400, nil, "时间戳已失效,请重新发起请求")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -8,
|
||||||
|
Msg: "时间戳已失效,请重新发起请求",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询账户汇总信息
|
||||||
|
var summary bus_models.SmsSummary
|
||||||
|
err = e.Orm.Where("name = ?", req.UserName).First(&summary).Error
|
||||||
|
if err != nil {
|
||||||
|
//e.Error(400, err, "未找到该账号的短信汇总信息")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -2,
|
||||||
|
Msg: "未找到该账号的短信汇总信息",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if summary.Name == "" {
|
||||||
|
//e.Error(400, err, "未找到该账号")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -3,
|
||||||
|
Msg: "未找到该账号",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查余额
|
||||||
|
if summary.Balance <= 0 {
|
||||||
|
//e.Error(400, nil, "短信余额不足,请充值后再发送")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -4,
|
||||||
|
Msg: "短信余额不足,请充值后再发送",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 签名校验
|
||||||
|
expectedSign := sms.GenerateSign(req.UserName, summary.PassWord, req.Content, req.Phone, req.Timestamp)
|
||||||
|
if strings.ToLower(req.Sign) != expectedSign {
|
||||||
|
//e.Error(400, nil, "签名验证失败")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -5,
|
||||||
|
Msg: "签名验证失败",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Phone == "" || req.Content == "" {
|
||||||
|
//e.Error(400, nil, "手机号和短信内容不能为空")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -6,
|
||||||
|
Msg: "手机号和短信内容不能为空",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var phoneList []string
|
||||||
|
phoneList = append(phoneList, req.Phone)
|
||||||
|
|
||||||
|
// 执行发送
|
||||||
|
err = sms.GtSendMessage(phoneList, req.Content)
|
||||||
|
if err != nil {
|
||||||
|
//e.Error(500, err, "短信发送失败")
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: -7,
|
||||||
|
Msg: "短信发送失败",
|
||||||
|
}, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新短信使用记录
|
||||||
|
err = e.Orm.Model(&bus_models.SmsSummary{}).
|
||||||
|
Where("id = ?", summary.ID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"used": gorm.Expr("used + ?", 1),
|
||||||
|
"balance": gorm.Expr("balance - ?", 1),
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Errorf("短信发送成功,但更新短信汇总记录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.OK(c, bus_models.SendMessageMassResp{
|
||||||
|
Code: 0,
|
||||||
|
Msg: "短信已发送",
|
||||||
|
}, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback 短信状态报告回调
|
||||||
|
// @Summary 短信状态回调
|
||||||
|
// @Description 短信服务商调用此接口,推送短信发送状态
|
||||||
|
// @Tags 短信管理-V1.0.0
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body bus_models.SmsCallbackReq true "短信状态回调请求参数"
|
||||||
|
// @Success 200 {object} bus_models.SmsCallbackResp
|
||||||
|
// @Router /api/v1/sohan/notice [post]
|
||||||
|
func (e SmsApi) Callback(c *gin.Context) {
|
||||||
|
fmt.Printf("enter Callback")
|
||||||
|
// 读取原始请求体
|
||||||
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Error("读取回调请求体失败:", err)
|
||||||
|
fmt.Printf("读取回调请求体失败: %+v", err)
|
||||||
|
app.OK(c, bus_models.SmsCallbackResp{StatusReturn: false}, "读取请求失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 重新设置 Body,以便后续 Bind 使用
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
|
|
||||||
|
// 打印原始请求体
|
||||||
|
fmt.Printf("收到短信回调原始请求体: %s", string(bodyBytes))
|
||||||
|
|
||||||
|
var req bus_models.SmsCallbackReq
|
||||||
|
err = e.MakeContext(c).
|
||||||
|
Bind(&req, binding.JSON).
|
||||||
|
Errors
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Error("回调参数绑定失败:", err)
|
||||||
|
app.OK(c, bus_models.SmsCallbackResp{
|
||||||
|
StatusReturn: false,
|
||||||
|
}, "参数绑定失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("response: %+v", req)
|
||||||
|
|
||||||
|
// 打印回调内容
|
||||||
|
e.Logger.Infof("收到短信回调: phone=%s, outOrderId=%s, status=%d, receiveTime=%s, message=%v",
|
||||||
|
req.PhoneNumber, req.OutOrderID, req.SendStatus, req.ReceiveTime, req.Message)
|
||||||
|
|
||||||
|
// 返回成功
|
||||||
|
app.OK(c, bus_models.SmsCallbackResp{
|
||||||
|
StatusReturn: true,
|
||||||
|
}, "回调处理成功")
|
||||||
|
}
|
||||||
|
|
|
@ -630,3 +630,47 @@ type SmsTemplateCreateRequest struct {
|
||||||
ExpireAt *time.Time `json:"expire_at"` // 到期时间
|
ExpireAt *time.Time `json:"expire_at"` // 到期时间
|
||||||
Remark string `json:"remark"` // 备注
|
Remark string `json:"remark"` // 备注
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SmsSummary 短信使用汇总表
|
||||||
|
type SmsSummary struct {
|
||||||
|
models.Model
|
||||||
|
|
||||||
|
Name string `gorm:"type:varchar(255);index;not null" json:"name"` // 帐户名称
|
||||||
|
PassWord string `gorm:"type:varchar(255);index;not null" json:"pass_word"` // 帐户密码
|
||||||
|
Used int `gorm:"default:0" json:"used"` // 已发送
|
||||||
|
Balance int `gorm:"default:0" json:"balance"` // 帐户余额
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessageMassReq 发送短信入参
|
||||||
|
type SendMessageMassReq struct {
|
||||||
|
UserName string `json:"userName"` //账号用户名
|
||||||
|
Content string `json:"content"` //短信内容
|
||||||
|
Phone string `json:"phone"` //手机号
|
||||||
|
Timestamp int64 `json:"timestamp"` //时间戳(毫秒)
|
||||||
|
Sign string `json:"sign"` //签名
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessageMassResp 发送短信出参
|
||||||
|
type SendMessageMassResp struct {
|
||||||
|
Code int `json:"code"` // 0成功,其他失败
|
||||||
|
Msg string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmsCallbackReq 硕软回调接口入参
|
||||||
|
type SmsCallbackReq struct {
|
||||||
|
Message *string `json:"message,omitempty"`
|
||||||
|
ModelID *int64 `json:"modelId,omitempty"`
|
||||||
|
OutOrderID string `json:"outOrderId"`
|
||||||
|
PhoneNumber string `json:"phoneNumber"`
|
||||||
|
ReceiveTime string `json:"receiveTime"`
|
||||||
|
SendStatus int64 `json:"sendStatus"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
SignatureID *int64 `json:"signatureId,omitempty"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
UserAccount string `json:"userAccount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmsCallbackResp 硕软回调接口出参
|
||||||
|
type SmsCallbackResp struct {
|
||||||
|
StatusReturn bool `json:"statusReturn"`
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ func registerSmsManageRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMidd
|
||||||
|
|
||||||
sms := v1.Group("/sms").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
sms := v1.Group("/sms").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||||
{
|
{
|
||||||
|
|
||||||
sms.POST("/self_import_phone", api.SelfImportPhone) // 导入号码(个性短信)
|
sms.POST("/self_import_phone", api.SelfImportPhone) // 导入号码(个性短信)
|
||||||
sms.POST("/file_import_phone", api.FileImportPhone) // 导入号码(文件短信)
|
sms.POST("/file_import_phone", api.FileImportPhone) // 导入号码(文件短信)
|
||||||
sms.POST("/excel_import_phone", api.ExcelImportPhone) // 导入号码(EXCEL短信)
|
sms.POST("/excel_import_phone", api.ExcelImportPhone) // 导入号码(EXCEL短信)
|
||||||
|
@ -79,3 +78,18 @@ func registerSmsManageRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMidd
|
||||||
sms.POST("/template/export", api.ExportSmsTemplate) // 导出短信模版 OK
|
sms.POST("/template/export", api.ExportSmsTemplate) // 导出短信模版 OK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerSmsManageUnAuthRouter(v1 *gin.RouterGroup) {
|
||||||
|
api := bus_apis.SmsApi{}
|
||||||
|
sms := v1.Group("/sms")
|
||||||
|
{
|
||||||
|
// 临时对外提供的短信接口
|
||||||
|
sms.POST("/send_message", api.SendMessage) // 批量发送短信
|
||||||
|
}
|
||||||
|
|
||||||
|
sohan := v1.Group("/sohan")
|
||||||
|
{
|
||||||
|
sohan.POST("/notice", api.Callback) // 硕软回调接口
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,9 @@ func businessNoCheckRoleRouter(r *gin.Engine) {
|
||||||
for _, f := range routerNoCheckRole {
|
for _, f := range routerNoCheckRole {
|
||||||
f(v1)
|
f(v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 短信管理
|
||||||
|
registerSmsManageUnAuthRouter(v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 需要认证的路由示例
|
// 需要认证的路由示例
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go-admin/app/admin/models/bus_models"
|
"go-admin/app/admin/models/bus_models"
|
||||||
"go-admin/tools/crypto"
|
"go-admin/tools/crypto"
|
||||||
|
"go-admin/tools/sms"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -150,3 +151,8 @@ func EncryptWithRSA(publicKeyPath string, aesKey []byte) (string, error) {
|
||||||
// 返回加密后的密钥(base64 编码)
|
// 返回加密后的密钥(base64 编码)
|
||||||
return base64.StdEncoding.EncodeToString(encryptedKey), nil
|
return base64.StdEncoding.EncodeToString(encryptedKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateSign(t *testing.T) {
|
||||||
|
sign := sms.GenerateSign("test", "123456", "测试", "13800001111", 1749524000)
|
||||||
|
fmt.Println("**********sign**********:", sign)
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -198,3 +199,10 @@ func (m *ExchangeClient) post(amApi string, params, resp interface{}) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateSign 生成签名:MD5(userName + content + phone + timestamp + MD5(password))
|
||||||
|
func GenerateSign(userName, passWord, content, phone string, timestamp int64) string {
|
||||||
|
md5Pwd := MD5Encode32(passWord)
|
||||||
|
raw := userName + content + phone + strconv.FormatInt(timestamp, 10) + md5Pwd
|
||||||
|
return MD5Encode32(raw)
|
||||||
|
}
|
||||||
|
|
209
tools/sms/sohan.go
Normal file
209
tools/sms/sohan.go
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
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
|
||||||
|
}
|
84
tools/sms/sohan_test.go
Normal file
84
tools/sms/sohan_test.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package sms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------- 模拟服务 ----------------
|
||||||
|
|
||||||
|
// mockServer 返回一个 httptest.Server,用于模拟API响应
|
||||||
|
func mockServer(t *testing.T, path string, response interface{}) *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != path {
|
||||||
|
t.Errorf("unexpected path: got %s, want %s", r.URL.Path, path)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(response)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- 测试发送短信 ----------------
|
||||||
|
|
||||||
|
func TestSendSMS(t *testing.T) {
|
||||||
|
client := NewClient()
|
||||||
|
|
||||||
|
content := "【明慧科技】提醒:您的go2ns租卡会员时长仅剩余一个月,现在续费最高立减200元!赶快进入小程序领取优惠吧!"
|
||||||
|
returnUrl := "https://telecom.2016js.com/api/v1/sohan/notice"
|
||||||
|
body := BusinessBody{
|
||||||
|
Content: &content,
|
||||||
|
SendList: []SendList{
|
||||||
|
{
|
||||||
|
OutOrderID: "ORDER777",
|
||||||
|
PhoneNumber: "15019230751"},
|
||||||
|
},
|
||||||
|
ReturnURL: &returnUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.SendSMS(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendSMS error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("response: %+v", resp)
|
||||||
|
|
||||||
|
if resp.StatusCode != 1 {
|
||||||
|
t.Errorf("unexpected response: %+v", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- 测试查询余额 ----------------
|
||||||
|
|
||||||
|
func TestGetBalance(t *testing.T) {
|
||||||
|
client := NewClient()
|
||||||
|
|
||||||
|
resp, err := client.GetBalance()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBalance error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("response: %+v", resp)
|
||||||
|
|
||||||
|
if resp.StatusCode != 1 {
|
||||||
|
t.Errorf("unexpected response: %+v", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- 测试查询状态 ----------------
|
||||||
|
|
||||||
|
func TestQueryState(t *testing.T) {
|
||||||
|
client := NewClient()
|
||||||
|
|
||||||
|
resp, err := client.QueryState("ORDER123", "15019230751")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryState error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("response: %+v", resp)
|
||||||
|
|
||||||
|
if resp.StatusCode != 1 {
|
||||||
|
t.Errorf("unexpected sendStatus: %s", resp.Data.SendStatus)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user