From 651a596a02cf52bd7ab24bdddd46839992b7fbdb Mon Sep 17 00:00:00 2001 From: engigu Date: Wed, 8 Oct 2025 13:16:27 +0800 Subject: [PATCH] feat: adjust api send token --- pkg/util/token_codec.go | 54 ++++++++++++++++++++++++++++++++++ routers/api/v1/send_message.go | 42 +++++++++++++++++++------- web/src/util/viewApi.js | 23 ++++++++++++++- 3 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 pkg/util/token_codec.go diff --git a/pkg/util/token_codec.go b/pkg/util/token_codec.go new file mode 100644 index 0000000..dce6d20 --- /dev/null +++ b/pkg/util/token_codec.go @@ -0,0 +1,54 @@ +package util + +import ( + "encoding/hex" + "errors" +) + +//// getDeterministicSalt 根据字符串内容生成确定性 salt(范围 0~255) +//func getDeterministicSalt(text string) byte { +// var sum int +// for i := 0; i < len(text); i++ { +// sum = (sum + int(text[i])*(i+1)) & 0xFF +// } +// return byte(sum) +//} + +//// EncryptTokenHex 按照确定性 salt + 异或规则生成十六进制 token +//// 规则:首字节为 salt;其余字节为 char ^ key ^ ((salt + idx) & 0xFF) +//func EncryptTokenHex(text string, key byte) string { +// salt := getDeterministicSalt(text) +// // 预分配:2个hex字符的salt + 每个字符2个hex +// out := make([]byte, 0, 2+len(text)*2) +// // 写入salt +// out = append(out, []byte(hex.EncodeToString([]byte{salt}))...) +// // 写入加密数据 +// for i := 0; i < len(text); i++ { +// b := text[i] ^ key ^ byte((int(salt)+i)&0xFF) +// enc := make([]byte, 2) +// hex.Encode(enc, []byte{b}) +// out = append(out, enc...) +// } +// return string(out) +//} + +// DecryptTokenHex 解析十六进制 token 为原始字符串 +// 规则:首字节为salt;后续字节与 key ^ ((salt + idx) & 0xFF) 异或 +func DecryptTokenHex(enc string, key byte) (string, error) { + if enc == "" { + return "", errors.New("empty token") + } + bs, err := hex.DecodeString(enc) + if err != nil { + return "", err + } + if len(bs) < 1 { + return "", errors.New("invalid token length") + } + salt := bs[0] + out := make([]byte, len(bs)-1) + for i := 1; i < len(bs); i++ { + out[i-1] = bs[i] ^ key ^ byte((int(salt)+i-1)&0xFF) + } + return string(out), nil +} diff --git a/routers/api/v1/send_message.go b/routers/api/v1/send_message.go index fb2bf81..99736b5 100644 --- a/routers/api/v1/send_message.go +++ b/routers/api/v1/send_message.go @@ -2,16 +2,21 @@ package v1 import ( "fmt" - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" "message-nest/pkg/app" "message-nest/pkg/e" + utilpkg "message-nest/pkg/util" "message-nest/service/send_message_service" "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" ) type SendMessageReq struct { - TaskID string `json:"task_id" validate:"required,len=12" label:"任务id"` + // 兼容旧参数:task_id;不再必填 + TaskID string `json:"task_id" validate:"omitempty,len=12" label:"任务id"` + // 新参数:token(与task_id互斥,优先使用task_id) + Token string `json:"token" label:"任务token"` Text string `json:"text" validate:"required" label:"文本内容"` Title string `json:"title" label:"消息标题"` HTML string `json:"html" label:"html内容"` @@ -33,14 +38,31 @@ func DoSendMassage(c *gin.Context) { return } + // 解析token为task_id(如提供) + taskID := req.TaskID + // 如果使用了token就使用token解析出task_id + if req.Token != "" { + dec, err := utilpkg.DecryptTokenHex(req.Token, 71) // 71 为简单对称密钥 + if err != nil { + appG.CResponse(http.StatusBadRequest, fmt.Sprintf("token解析失败:%v", err), nil) + return + } + taskID = dec + } + + if taskID == "" { + appG.CResponse(http.StatusBadRequest, "参数缺失:token 或 task_id 必须提供其一", nil) + return + } + msgService := send_message_service.SendMessageService{ - TaskID: req.TaskID, - Title: req.Title, - Text: req.Text, - HTML: req.HTML, - URL: req.URL, - MarkDown: req.MarkDown, - CallerIp: c.ClientIP(), + TaskID: taskID, + Title: req.Title, + Text: req.Text, + HTML: req.HTML, + URL: req.URL, + MarkDown: req.MarkDown, + CallerIp: c.ClientIP(), DefaultLogger: logrus.WithFields(logrus.Fields{ //"prefix": "[Message Instance]", }), diff --git a/web/src/util/viewApi.js b/web/src/util/viewApi.js index 26a539e..188619f 100644 --- a/web/src/util/viewApi.js +++ b/web/src/util/viewApi.js @@ -6,8 +6,29 @@ const gethttpOrigin = () => { class ApiStrGenerate { + // 根据字符串内容生成确定性 salt(范围 0~255) + static getDeterministicSalt(text) { + let sum = 0; + for (let i = 0; i < text.length; i++) { + sum = (sum + text.charCodeAt(i) * (i + 1)) & 0xFF; + } + return sum; + } + + // 加密:首字节为salt,后续为按位异或后的数据 + static encryptHex(text, key) { + const salt = ApiStrGenerate.getDeterministicSalt(text); + let result = salt.toString(16).padStart(2, '0'); + for (let i = 0; i < text.length; i++) { + const code = text.charCodeAt(i) ^ (key & 0xFF) ^ ((salt + i) & 0xFF); + result += code.toString(16).padStart(2, '0'); + } + return result; + } + static getDataString(task_id, options) { - let data = { task_id: task_id }; + // 新版仅展示 token;兼容旧版 task_id(后端依然支持) + let data = { token: ApiStrGenerate.encryptHex(task_id, 71) }; data.title = 'message title'; data.text = 'Hello World!'; if (options.html) {