package db import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "net/http" "regexp" "strconv" "time" "xiawan/wx/db/table" "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) 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 } // 使用消息包装器包含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 } } // 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)) } }