2026-02-17 13:06:23 +08:00
|
|
|
|
package db
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"crypto/hmac"
|
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"regexp"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"time"
|
2026-02-26 10:44:13 +08:00
|
|
|
|
"xiawan/wx/db/table"
|
2026-02-17 13:06:23 +08:00
|
|
|
|
"xiawan/wx/protobuf/wechat"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/lunny/log"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// MessageCallbackPayload 消息回调内容体
|
|
|
|
|
|
type MessageCallbackPayload struct {
|
|
|
|
|
|
UUID string `json:"uuid"` // 微信UUID标识
|
|
|
|
|
|
MsgId string `json:"msg_id"` // 消息ID
|
|
|
|
|
|
FromUser string `json:"from_user"` // 发送方
|
|
|
|
|
|
ToUser string `json:"to_user"` // 接收方
|
|
|
|
|
|
MsgType int `json:"msg_type"` // 消息类型
|
|
|
|
|
|
Content string `json:"content"` // 消息内容
|
|
|
|
|
|
CreateTime int64 `json:"create_time"` // 消息创建时间
|
|
|
|
|
|
IsGroup bool `json:"is_group"` // 是否群消息
|
|
|
|
|
|
Attachments map[string]interface{} `json:"attachments,omitempty"` // 附件信息
|
|
|
|
|
|
RawData map[string]interface{} `json:"raw_data,omitempty"` // 原始数据
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// decodeUnicodeEscapes 解码Unicode转义字符(复用自 wxsocketmsg.go)
|
|
|
|
|
|
func decodeUnicodeEscapes(s string) string {
|
|
|
|
|
|
// 处理JSON中的Unicode转义字符,如\u003c
|
|
|
|
|
|
re := regexp.MustCompile(`\\u([0-9a-fA-F]{4})`)
|
|
|
|
|
|
result := re.ReplaceAllStringFunc(s, func(match string) string {
|
|
|
|
|
|
// 提取十六进制数字部分
|
|
|
|
|
|
hexStr := match[2:] // 去掉\u前缀
|
|
|
|
|
|
// 将十六进制转换为整数
|
|
|
|
|
|
if codePoint, err := strconv.ParseInt(hexStr, 16, 32); err == nil {
|
|
|
|
|
|
// 转换为Unicode字符
|
|
|
|
|
|
return string(rune(codePoint))
|
|
|
|
|
|
}
|
|
|
|
|
|
return match // 如果转换失败,返回原字符串
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GenerateSignature 生成签名
|
|
|
|
|
|
func GenerateSignature(payload []byte, key string) string {
|
|
|
|
|
|
h := hmac.New(sha256.New, []byte(key))
|
|
|
|
|
|
h.Write(payload)
|
|
|
|
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SendMessageCallback 发送消息回调
|
|
|
|
|
|
func SendMessageCallback(msg *wechat.AddMsg, uuid string) {
|
|
|
|
|
|
// 获取回调配置
|
|
|
|
|
|
config, err := GetMessageCallbackConfig(uuid)
|
2026-02-26 10:44:13 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Errorf("获取回调配置失败 [UUID: %s]: %v", uuid, err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if config == nil {
|
|
|
|
|
|
log.Debugf("[回调调试] 未找到回调配置 [UUID: %s]", uuid)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if !config.Enabled {
|
|
|
|
|
|
log.Debugf("[回调调试] 回调未启用 [UUID: %s]", uuid)
|
|
|
|
|
|
return
|
2026-02-17 13:06:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用消息包装器包含UUID信息
|
|
|
|
|
|
msgWrapper := NewCallbackMessageWrapper(uuid, msg, "message")
|
|
|
|
|
|
jsonBytes, err := json.Marshal(msgWrapper)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Errorf("Failed to marshal CallbackMessageWrapper: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对JSON内容进行Unicode解码处理(与 WebSocket 保持一致)
|
|
|
|
|
|
decodedJSON := decodeUnicodeEscapes(string(jsonBytes))
|
|
|
|
|
|
|
|
|
|
|
|
// 创建HTTP请求
|
|
|
|
|
|
req, err := http.NewRequest("POST", config.CallbackURL, bytes.NewBuffer([]byte(decodedJSON)))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Errorf("Failed to create callback request: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置请求头
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", time.Now().Unix()))
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
|
|
Timeout: 5 * time.Second,
|
|
|
|
|
|
}
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Errorf("Failed to send callback request: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 读取响应内容
|
|
|
|
|
|
_, err = ioutil.ReadAll(resp.Body)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Errorf("Failed to read callback response: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 10:44:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestMessageCallback 测试消息回调配置
|
|
|
|
|
|
func TestMessageCallback(config *table.MessageCallbackConfig) (bool, string) {
|
|
|
|
|
|
// 构造测试消息
|
|
|
|
|
|
testPayload := map[string]interface{}{
|
|
|
|
|
|
"uuid": config.UUID,
|
|
|
|
|
|
"type": "test",
|
|
|
|
|
|
"data": map[string]interface{}{
|
|
|
|
|
|
"message": "这是一条测试回调消息",
|
|
|
|
|
|
"timestamp": time.Now().Unix(),
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
jsonBytes, err := json.Marshal(testPayload)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Sprintf("序列化测试数据失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建HTTP请求
|
|
|
|
|
|
req, err := http.NewRequest("POST", config.CallbackURL, bytes.NewBuffer(jsonBytes))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Sprintf("创建请求失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置请求头
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", time.Now().Unix()))
|
|
|
|
|
|
req.Header.Set("X-Test", "true")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
|
|
Timeout: 5 * time.Second,
|
|
|
|
|
|
}
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Sprintf("发送请求失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 读取响应
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Sprintf("读取响应失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
|
|
|
|
log.Infof("✓ 测试回调成功 [UUID: %s] -> %s, 状态码: %d",
|
|
|
|
|
|
config.UUID, config.CallbackURL, resp.StatusCode)
|
|
|
|
|
|
return true, fmt.Sprintf("状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Warnf("✗ 测试回调失败 [UUID: %s] -> %s, 状态码: %d",
|
|
|
|
|
|
config.UUID, config.CallbackURL, resp.StatusCode)
|
|
|
|
|
|
return false, fmt.Sprintf("状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
|
|
|
|
|
}
|
2026-02-17 13:06:23 +08:00
|
|
|
|
}
|