commits
This commit is contained in:
+498
-498
@@ -1,498 +1,498 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PlatformSMSController 短信配置(yz_system_sms),兼容旧前端 /platform/sms/* 接口
|
||||
type PlatformSMSController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *PlatformSMSController) platformClaims() (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "platform" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (c *PlatformSMSController) jsonErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetSmsInfo GET /platform/sms/info
|
||||
// 返回 data[0],字段兼容 backend_url/api_key 与 backendUrl/apiKey(沿用旧前端)
|
||||
func (c *PlatformSMSController) GetSmsInfo() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := models.GetPlatformSettingValue("sms_custom_url", "")
|
||||
apiKey := models.GetPlatformSettingValue("sms_custom_key", "")
|
||||
|
||||
data := []map[string]interface{}{{
|
||||
"backend_url": backendURL,
|
||||
"api_key": apiKey,
|
||||
"backendUrl": backendURL,
|
||||
"apiKey": apiKey,
|
||||
}}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": data}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type smsEditPayload struct {
|
||||
BackendUrl string `json:"backendUrl"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
APIKey string `json:"api_key"`
|
||||
}
|
||||
|
||||
// EditSmsInfo POST /platform/sms/editinfo
|
||||
// 将旧前端的 backendUrl/apiKey 落到 yz_platform_normal_setting 表中
|
||||
func (c *PlatformSMSController) EditSmsInfo() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p smsEditPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := strings.TrimSpace(p.BackendUrl)
|
||||
if backendURL == "" {
|
||||
backendURL = strings.TrimSpace(p.BackendURL)
|
||||
}
|
||||
apiKey := strings.TrimSpace(p.ApiKey)
|
||||
if apiKey == "" {
|
||||
apiKey = strings.TrimSpace(p.APIKey)
|
||||
}
|
||||
if backendURL == "" {
|
||||
c.jsonErr(400, 400, "请输入短信网关地址")
|
||||
return
|
||||
}
|
||||
if apiKey == "" {
|
||||
c.jsonErr(400, 400, "请输入API KEY")
|
||||
return
|
||||
}
|
||||
|
||||
settings := []struct {
|
||||
code string
|
||||
name string
|
||||
value string
|
||||
remark string
|
||||
}{
|
||||
{"sms_custom_url", "自定义短信网关地址", backendURL, ""},
|
||||
{"sms_custom_key", "自定义短信API KEY", apiKey, ""},
|
||||
}
|
||||
|
||||
for _, item := range settings {
|
||||
var setting models.PlatformNormalSetting
|
||||
err := models.Orm.QueryTable(new(models.PlatformNormalSetting)).
|
||||
Filter("code", item.code).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&setting)
|
||||
if err == nil {
|
||||
setting.Value = item.value
|
||||
setting.Name = item.name
|
||||
setting.Remark = item.remark
|
||||
now := time.Now()
|
||||
setting.UpdateTime = &now
|
||||
_, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime")
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
newSetting := models.PlatformNormalSetting{
|
||||
Name: item.name,
|
||||
Code: item.code,
|
||||
Value: item.value,
|
||||
Remark: item.remark,
|
||||
CreateTime: time.Now(),
|
||||
}
|
||||
_, err = models.Orm.Insert(&newSetting)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updated := []map[string]interface{}{{
|
||||
"backend_url": backendURL,
|
||||
"api_key": apiKey,
|
||||
}}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功", "data": updated}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type smsTestPayload struct {
|
||||
BackendUrl string `json:"backendUrl"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
APIKey string `json:"api_key"`
|
||||
Tid *uint64 `json:"tid"`
|
||||
Phone string `json:"phone"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// SendTestSms POST /platform/sms/sendtest
|
||||
// 调用短信网关入队接口:{backendUrl}/api/v1/business/outbound-tasks,header: X-Api-Key
|
||||
func (c *PlatformSMSController) SendTestSms() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p smsTestPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
phone := strings.TrimSpace(p.Phone)
|
||||
if phone == "" {
|
||||
c.jsonErr(400, 400, "缺少测试手机号")
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(phone, "+") {
|
||||
c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)")
|
||||
return
|
||||
}
|
||||
for _, ch := range phone[1:] {
|
||||
if ch < '0' || ch > '9' {
|
||||
c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
backendURL := strings.TrimSpace(p.BackendUrl)
|
||||
if backendURL == "" {
|
||||
backendURL = strings.TrimSpace(p.BackendURL)
|
||||
}
|
||||
apiKey := strings.TrimSpace(p.ApiKey)
|
||||
if apiKey == "" {
|
||||
apiKey = strings.TrimSpace(p.APIKey)
|
||||
}
|
||||
|
||||
// 兜底:body 未带时从默认配置取
|
||||
if backendURL == "" || apiKey == "" {
|
||||
if backendURL == "" {
|
||||
backendURL = models.GetPlatformSettingValue("sms_custom_url", "")
|
||||
}
|
||||
if apiKey == "" {
|
||||
apiKey = models.GetPlatformSettingValue("sms_custom_key", "")
|
||||
}
|
||||
}
|
||||
if backendURL == "" {
|
||||
c.jsonErr(400, 400, "请先配置短信网关地址 backendUrl")
|
||||
return
|
||||
}
|
||||
if apiKey == "" {
|
||||
c.jsonErr(400, 400, "请先配置短信网关 API KEY")
|
||||
return
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(p.Content)
|
||||
code := randomDigits6()
|
||||
if content == "" {
|
||||
content = "短信测试验证码:" + code
|
||||
}
|
||||
|
||||
enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks"
|
||||
payload := map[string]interface{}{
|
||||
"phone": phone,
|
||||
"content": content,
|
||||
}
|
||||
bs, _ := json.Marshal(payload)
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs))
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "创建请求失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Api-Key", apiKey)
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "短信网关入队失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
msg := strings.TrimSpace(string(body))
|
||||
if msg == "" {
|
||||
msg = resp.Status
|
||||
}
|
||||
c.jsonErr(500, 500, "短信网关入队失败: "+msg)
|
||||
return
|
||||
}
|
||||
|
||||
bodyStr := string(body)
|
||||
report := strings.TrimSpace(bodyStr)
|
||||
var reportPtr *string
|
||||
if report != "" {
|
||||
reportPtr = &bodyStr
|
||||
}
|
||||
|
||||
// 网关 HTTP 2xx:平台侧视为「已受理并成功提交」;与前端 tasklist 中 status=3「发送成功」对齐
|
||||
taskStatus := 3
|
||||
|
||||
// 若网关返回 JSON 且含通用状态字段,则优先映射(便于以后网关回传异步状态)
|
||||
var gw map[string]interface{}
|
||||
if json.Unmarshal(body, &gw) == nil {
|
||||
if v, ok := gw["status"]; ok {
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
taskStatus = mapGatewayStatus(int(x))
|
||||
case string:
|
||||
if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil {
|
||||
taskStatus = mapGatewayStatus(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入本地任务表(用于前端列表/对账)
|
||||
now := time.Now()
|
||||
task := &models.SystemSMSTask{
|
||||
Tid: p.Tid, // 测试可为空
|
||||
ApiKey: apiKey,
|
||||
Phone: phone,
|
||||
Content: &content,
|
||||
Status: taskStatus,
|
||||
Code: code,
|
||||
ReportRaw: reportPtr,
|
||||
CreateTime: &now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
taskID, terr := models.Orm.Insert(task)
|
||||
if terr != nil {
|
||||
// 入队已成功,任务写库失败也不影响短信发送,只返回提示
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "短信测试任务入队成功(任务写库失败)",
|
||||
"data": map[string]interface{}{
|
||||
"taskId": nil,
|
||||
"code": code,
|
||||
"gatewayResp": json.RawMessage(body),
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "短信测试任务入队成功",
|
||||
"data": map[string]interface{}{
|
||||
"taskId": uint64(taskID),
|
||||
"code": code,
|
||||
"gatewayResp": json.RawMessage(body),
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetSmsTaskList GET /platform/sms/taskList
|
||||
func (c *PlatformSMSController) GetSmsTaskList() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
phoneKw := strings.TrimSpace(c.GetString("phone"))
|
||||
tidStr := strings.TrimSpace(c.GetString("tid"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("delete_time__isnull", true)
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
qs = qs.Filter("status", st)
|
||||
}
|
||||
}
|
||||
if phoneKw != "" {
|
||||
qs = qs.Filter("phone__icontains", phoneKw)
|
||||
}
|
||||
if tidStr != "" {
|
||||
if tid, err := strconv.ParseUint(tidStr, 10, 64); err == nil && tid > 0 {
|
||||
qs = qs.Filter("tid", tid)
|
||||
}
|
||||
}
|
||||
|
||||
var rows []models.SystemSMSTask
|
||||
_, err := qs.OrderBy("-id").All(&rows)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取短信任务列表失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
item := map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"api_key": rows[i].ApiKey,
|
||||
"phone": rows[i].Phone,
|
||||
"content": "",
|
||||
"status": rows[i].Status,
|
||||
"code": rows[i].Code,
|
||||
"report_raw": rows[i].ReportRaw,
|
||||
"create_time": "",
|
||||
"update_time": "",
|
||||
}
|
||||
if rows[i].Tid != nil {
|
||||
item["tid"] = *rows[i].Tid
|
||||
}
|
||||
if rows[i].Content != nil {
|
||||
item["content"] = *rows[i].Content
|
||||
}
|
||||
if rows[i].CreateTime != nil {
|
||||
item["create_time"] = rows[i].CreateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
if rows[i].UpdateTime != nil {
|
||||
item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "list": list}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// EditSmsTask POST /platform/sms/taskEdit/:id
|
||||
func (c *PlatformSMSController) EditSmsTask() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.jsonErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p map[string]interface{}
|
||||
_ = json.Unmarshal(raw, &p)
|
||||
|
||||
up := map[string]interface{}{}
|
||||
if v, ok := p["status"]; ok {
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
up["status"] = int(x)
|
||||
case string:
|
||||
if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil {
|
||||
up["status"] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := p["report_raw"]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
up["report_raw"] = s
|
||||
}
|
||||
}
|
||||
if v, ok := p["content"]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
up["content"] = s
|
||||
}
|
||||
}
|
||||
if len(up) == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
up["update_time"] = now
|
||||
n, err := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("id", id).Update(up)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.jsonErr(404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func randomDigits6() string {
|
||||
// 生成 6 位数字字符串
|
||||
b := make([]byte, 4)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "123456"
|
||||
}
|
||||
n := int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])
|
||||
if n < 0 {
|
||||
n = -n
|
||||
}
|
||||
code := n%900000 + 100000
|
||||
return strconv.Itoa(code)
|
||||
}
|
||||
|
||||
// mapGatewayStatus 将网关侧 status 粗略映射到前端列表:0待发送 1发送中 2失败 3成功
|
||||
func mapGatewayStatus(st int) int {
|
||||
switch st {
|
||||
case 0:
|
||||
return 0
|
||||
case 1, 4, 5:
|
||||
return 1
|
||||
case 2, 6:
|
||||
return 2
|
||||
case 3:
|
||||
return 3
|
||||
default:
|
||||
// 网关枚举未约定时:HTTP 已 2xx,按「已成功提交」显示为发送成功
|
||||
return 3
|
||||
}
|
||||
}
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PlatformSMSController 短信配置(yz_system_sms),兼容旧前端 /platform/sms/* 接口
|
||||
type PlatformSMSController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *PlatformSMSController) platformClaims() (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "platform" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (c *PlatformSMSController) jsonErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetSmsInfo GET /platform/sms/info
|
||||
// 返回 data[0],字段兼容 backend_url/api_key 与 backendUrl/apiKey(沿用旧前端)
|
||||
func (c *PlatformSMSController) GetSmsInfo() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := models.GetPlatformSettingValue("sms_custom_url", "")
|
||||
apiKey := models.GetPlatformSettingValue("sms_custom_key", "")
|
||||
|
||||
data := []map[string]interface{}{{
|
||||
"backend_url": backendURL,
|
||||
"api_key": apiKey,
|
||||
"backendUrl": backendURL,
|
||||
"apiKey": apiKey,
|
||||
}}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": data}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type smsEditPayload struct {
|
||||
BackendUrl string `json:"backendUrl"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
APIKey string `json:"api_key"`
|
||||
}
|
||||
|
||||
// EditSmsInfo POST /platform/sms/editinfo
|
||||
// 将旧前端的 backendUrl/apiKey 落到 yz_platform_normal_setting 表中
|
||||
func (c *PlatformSMSController) EditSmsInfo() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p smsEditPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := strings.TrimSpace(p.BackendUrl)
|
||||
if backendURL == "" {
|
||||
backendURL = strings.TrimSpace(p.BackendURL)
|
||||
}
|
||||
apiKey := strings.TrimSpace(p.ApiKey)
|
||||
if apiKey == "" {
|
||||
apiKey = strings.TrimSpace(p.APIKey)
|
||||
}
|
||||
if backendURL == "" {
|
||||
c.jsonErr(400, 400, "请输入短信网关地址")
|
||||
return
|
||||
}
|
||||
if apiKey == "" {
|
||||
c.jsonErr(400, 400, "请输入API KEY")
|
||||
return
|
||||
}
|
||||
|
||||
settings := []struct {
|
||||
code string
|
||||
name string
|
||||
value string
|
||||
remark string
|
||||
}{
|
||||
{"sms_custom_url", "自定义短信网关地址", backendURL, ""},
|
||||
{"sms_custom_key", "自定义短信API KEY", apiKey, ""},
|
||||
}
|
||||
|
||||
for _, item := range settings {
|
||||
var setting models.PlatformNormalSetting
|
||||
err := models.Orm.QueryTable(new(models.PlatformNormalSetting)).
|
||||
Filter("code", item.code).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&setting)
|
||||
if err == nil {
|
||||
setting.Value = item.value
|
||||
setting.Name = item.name
|
||||
setting.Remark = item.remark
|
||||
now := time.Now()
|
||||
setting.UpdateTime = &now
|
||||
_, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime")
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
newSetting := models.PlatformNormalSetting{
|
||||
Name: item.name,
|
||||
Code: item.code,
|
||||
Value: item.value,
|
||||
Remark: item.remark,
|
||||
CreateTime: time.Now(),
|
||||
}
|
||||
_, err = models.Orm.Insert(&newSetting)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updated := []map[string]interface{}{{
|
||||
"backend_url": backendURL,
|
||||
"api_key": apiKey,
|
||||
}}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功", "data": updated}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type smsTestPayload struct {
|
||||
BackendUrl string `json:"backendUrl"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
APIKey string `json:"api_key"`
|
||||
Tid *uint64 `json:"tid"`
|
||||
Phone string `json:"phone"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// SendTestSms POST /platform/sms/sendtest
|
||||
// 调用短信网关入队接口:{backendUrl}/api/v1/business/outbound-tasks,header: X-Api-Key
|
||||
func (c *PlatformSMSController) SendTestSms() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p smsTestPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
phone := strings.TrimSpace(p.Phone)
|
||||
if phone == "" {
|
||||
c.jsonErr(400, 400, "缺少测试手机号")
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(phone, "+") {
|
||||
c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)")
|
||||
return
|
||||
}
|
||||
for _, ch := range phone[1:] {
|
||||
if ch < '0' || ch > '9' {
|
||||
c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
backendURL := strings.TrimSpace(p.BackendUrl)
|
||||
if backendURL == "" {
|
||||
backendURL = strings.TrimSpace(p.BackendURL)
|
||||
}
|
||||
apiKey := strings.TrimSpace(p.ApiKey)
|
||||
if apiKey == "" {
|
||||
apiKey = strings.TrimSpace(p.APIKey)
|
||||
}
|
||||
|
||||
// 兜底:body 未带时从默认配置取
|
||||
if backendURL == "" || apiKey == "" {
|
||||
if backendURL == "" {
|
||||
backendURL = models.GetPlatformSettingValue("sms_custom_url", "")
|
||||
}
|
||||
if apiKey == "" {
|
||||
apiKey = models.GetPlatformSettingValue("sms_custom_key", "")
|
||||
}
|
||||
}
|
||||
if backendURL == "" {
|
||||
c.jsonErr(400, 400, "请先配置短信网关地址 backendUrl")
|
||||
return
|
||||
}
|
||||
if apiKey == "" {
|
||||
c.jsonErr(400, 400, "请先配置短信网关 API KEY")
|
||||
return
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(p.Content)
|
||||
code := randomDigits6()
|
||||
if content == "" {
|
||||
content = "短信测试验证码:" + code
|
||||
}
|
||||
|
||||
enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks"
|
||||
payload := map[string]interface{}{
|
||||
"phone": phone,
|
||||
"content": content,
|
||||
}
|
||||
bs, _ := json.Marshal(payload)
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs))
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "创建请求失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Api-Key", apiKey)
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "短信网关入队失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
msg := strings.TrimSpace(string(body))
|
||||
if msg == "" {
|
||||
msg = resp.Status
|
||||
}
|
||||
c.jsonErr(500, 500, "短信网关入队失败: "+msg)
|
||||
return
|
||||
}
|
||||
|
||||
bodyStr := string(body)
|
||||
report := strings.TrimSpace(bodyStr)
|
||||
var reportPtr *string
|
||||
if report != "" {
|
||||
reportPtr = &bodyStr
|
||||
}
|
||||
|
||||
// 网关 HTTP 2xx:平台侧视为「已受理并成功提交」;与前端 tasklist 中 status=3「发送成功」对齐
|
||||
taskStatus := 3
|
||||
|
||||
// 若网关返回 JSON 且含通用状态字段,则优先映射(便于以后网关回传异步状态)
|
||||
var gw map[string]interface{}
|
||||
if json.Unmarshal(body, &gw) == nil {
|
||||
if v, ok := gw["status"]; ok {
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
taskStatus = mapGatewayStatus(int(x))
|
||||
case string:
|
||||
if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil {
|
||||
taskStatus = mapGatewayStatus(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入本地任务表(用于前端列表/对账)
|
||||
now := time.Now()
|
||||
task := &models.SystemSMSTask{
|
||||
Tid: p.Tid, // 测试可为空
|
||||
ApiKey: apiKey,
|
||||
Phone: phone,
|
||||
Content: &content,
|
||||
Status: taskStatus,
|
||||
Code: code,
|
||||
ReportRaw: reportPtr,
|
||||
CreateTime: &now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
taskID, terr := models.Orm.Insert(task)
|
||||
if terr != nil {
|
||||
// 入队已成功,任务写库失败也不影响短信发送,只返回提示
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "短信测试任务入队成功(任务写库失败)",
|
||||
"data": map[string]interface{}{
|
||||
"taskId": nil,
|
||||
"code": code,
|
||||
"gatewayResp": json.RawMessage(body),
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "短信测试任务入队成功",
|
||||
"data": map[string]interface{}{
|
||||
"taskId": uint64(taskID),
|
||||
"code": code,
|
||||
"gatewayResp": json.RawMessage(body),
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetSmsTaskList GET /platform/sms/taskList
|
||||
func (c *PlatformSMSController) GetSmsTaskList() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
phoneKw := strings.TrimSpace(c.GetString("phone"))
|
||||
tidStr := strings.TrimSpace(c.GetString("tid"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("delete_time__isnull", true)
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
qs = qs.Filter("status", st)
|
||||
}
|
||||
}
|
||||
if phoneKw != "" {
|
||||
qs = qs.Filter("phone__icontains", phoneKw)
|
||||
}
|
||||
if tidStr != "" {
|
||||
if tid, err := strconv.ParseUint(tidStr, 10, 64); err == nil && tid > 0 {
|
||||
qs = qs.Filter("tid", tid)
|
||||
}
|
||||
}
|
||||
|
||||
var rows []models.SystemSMSTask
|
||||
_, err := qs.OrderBy("-id").All(&rows)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取短信任务列表失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
item := map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"api_key": rows[i].ApiKey,
|
||||
"phone": rows[i].Phone,
|
||||
"content": "",
|
||||
"status": rows[i].Status,
|
||||
"code": rows[i].Code,
|
||||
"report_raw": rows[i].ReportRaw,
|
||||
"create_time": "",
|
||||
"update_time": "",
|
||||
}
|
||||
if rows[i].Tid != nil {
|
||||
item["tid"] = *rows[i].Tid
|
||||
}
|
||||
if rows[i].Content != nil {
|
||||
item["content"] = *rows[i].Content
|
||||
}
|
||||
if rows[i].CreateTime != nil {
|
||||
item["create_time"] = rows[i].CreateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
if rows[i].UpdateTime != nil {
|
||||
item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "list": list}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// EditSmsTask POST /platform/sms/taskEdit/:id
|
||||
func (c *PlatformSMSController) EditSmsTask() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.jsonErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p map[string]interface{}
|
||||
_ = json.Unmarshal(raw, &p)
|
||||
|
||||
up := map[string]interface{}{}
|
||||
if v, ok := p["status"]; ok {
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
up["status"] = int(x)
|
||||
case string:
|
||||
if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil {
|
||||
up["status"] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := p["report_raw"]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
up["report_raw"] = s
|
||||
}
|
||||
}
|
||||
if v, ok := p["content"]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
up["content"] = s
|
||||
}
|
||||
}
|
||||
if len(up) == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
up["update_time"] = now
|
||||
n, err := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("id", id).Update(up)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.jsonErr(404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func randomDigits6() string {
|
||||
// 生成 6 位数字字符串
|
||||
b := make([]byte, 4)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "123456"
|
||||
}
|
||||
n := int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])
|
||||
if n < 0 {
|
||||
n = -n
|
||||
}
|
||||
code := n%900000 + 100000
|
||||
return strconv.Itoa(code)
|
||||
}
|
||||
|
||||
// mapGatewayStatus 将网关侧 status 粗略映射到前端列表:0待发送 1发送中 2失败 3成功
|
||||
func mapGatewayStatus(st int) int {
|
||||
switch st {
|
||||
case 0:
|
||||
return 0
|
||||
case 1, 4, 5:
|
||||
return 1
|
||||
case 2, 6:
|
||||
return 2
|
||||
case 3:
|
||||
return 3
|
||||
default:
|
||||
// 网关枚举未约定时:HTTP 已 2xx,按「已成功提交」显示为发送成功
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user