package handlers import ( "crypto/sha1" "encoding/xml" "fmt" "io" "net/http" "sort" "strings" "time" "github.com/ctwj/urldb/db/converter" "github.com/ctwj/urldb/db/dto" "github.com/ctwj/urldb/db/entity" "github.com/ctwj/urldb/db/repo" "github.com/ctwj/urldb/services" "github.com/ctwj/urldb/utils" "github.com/gin-gonic/gin" "github.com/silenceper/wechat/v2/officialaccount/message" ) // WechatHandler 微信公众号处理器 type WechatHandler struct { wechatService services.WechatBotService systemConfigRepo repo.SystemConfigRepository } // NewWechatHandler 创建微信公众号处理器 func NewWechatHandler( wechatService services.WechatBotService, systemConfigRepo repo.SystemConfigRepository, ) *WechatHandler { return &WechatHandler{ wechatService: wechatService, systemConfigRepo: systemConfigRepo, } } // HandleWechatMessage 处理微信消息推送 func (h *WechatHandler) HandleWechatMessage(c *gin.Context) { // 验证微信消息签名 if !h.validateSignature(c) { utils.Error("[WECHAT:VALIDATE] 签名验证失败") c.String(http.StatusForbidden, "签名验证失败") return } // 处理微信验证请求 if c.Request.Method == "GET" { echostr := c.Query("echostr") utils.Info("[WECHAT:VERIFY] 微信服务器验证成功, echostr=%s", echostr) c.String(http.StatusOK, echostr) return } // 读取请求体 body, err := io.ReadAll(c.Request.Body) if err != nil { utils.Error("[WECHAT:MESSAGE] 读取请求体失败: %v", err) c.String(http.StatusBadRequest, "读取请求体失败") return } // 解析微信消息 var msg message.MixMessage if err := xml.Unmarshal(body, &msg); err != nil { utils.Error("[WECHAT:MESSAGE] 解析微信消息失败: %v", err) c.String(http.StatusBadRequest, "消息格式错误") return } // 处理消息 reply, err := h.wechatService.HandleMessage(&msg) if err != nil { utils.Error("[WECHAT:MESSAGE] 处理微信消息失败: %v", err) c.String(http.StatusInternalServerError, "处理失败") return } utils.Info("[WECHAT:MESSAGE] 回复对象: %v", reply) // 如果有回复内容,发送回复 if reply != nil { // 为微信消息设置正确的ToUserName和FromUserName switch v := reply.(type) { case *message.Text: if v.CommonToken.ToUserName == "" { v.CommonToken.ToUserName = msg.FromUserName } if v.CommonToken.FromUserName == "" { v.CommonToken.FromUserName = msg.ToUserName } if v.CommonToken.CreateTime == 0 { v.CommonToken.CreateTime = time.Now().Unix() } // 确保MsgType正确设置 if v.CommonToken.MsgType == "" { v.CommonToken.MsgType = message.MsgTypeText } case *message.Image: if v.CommonToken.ToUserName == "" { v.CommonToken.ToUserName = msg.FromUserName } if v.CommonToken.FromUserName == "" { v.CommonToken.FromUserName = msg.ToUserName } if v.CommonToken.CreateTime == 0 { v.CommonToken.CreateTime = time.Now().Unix() } // 确保MsgType正确设置 if v.CommonToken.MsgType == "" { v.CommonToken.MsgType = message.MsgTypeImage } } responseXML, err := xml.Marshal(reply) if err != nil { utils.Error("[WECHAT:MESSAGE] 序列化回复消息失败: %v", err) c.String(http.StatusInternalServerError, "回复失败") return } utils.Info("[WECHAT:MESSAGE] 回复XML: %s", string(responseXML)) c.Data(http.StatusOK, "application/xml", responseXML) } else { utils.Warn("[WECHAT:MESSAGE] 没有回复内容,返回success") c.String(http.StatusOK, "success") } } // GetBotConfig 获取微信机器人配置 func (h *WechatHandler) GetBotConfig(c *gin.Context) { configs, err := h.systemConfigRepo.GetOrCreateDefault() if err != nil { ErrorResponse(c, "获取配置失败", http.StatusInternalServerError) return } botConfig := converter.SystemConfigToWechatBotConfig(configs) SuccessResponse(c, botConfig) } // UpdateBotConfig 更新微信机器人配置 func (h *WechatHandler) UpdateBotConfig(c *gin.Context) { var req dto.WechatBotConfigRequest if err := c.ShouldBindJSON(&req); err != nil { ErrorResponse(c, "请求参数错误", http.StatusBadRequest) return } // 转换为系统配置实体 configs := converter.WechatBotConfigRequestToSystemConfigs(req) // 保存配置 if len(configs) > 0 { err := h.systemConfigRepo.UpsertConfigs(configs) if err != nil { ErrorResponse(c, "保存配置失败", http.StatusInternalServerError) return } } // 重新加载配置缓存 if err := h.systemConfigRepo.SafeRefreshConfigCache(); err != nil { ErrorResponse(c, "刷新配置缓存失败", http.StatusInternalServerError) return } // 重新加载机器人服务配置 if err := h.wechatService.ReloadConfig(); err != nil { ErrorResponse(c, "重新加载机器人配置失败", http.StatusInternalServerError) return } // 配置更新完成后,尝试启动机器人(如果未运行且配置有效) if startErr := h.wechatService.Start(); startErr != nil { utils.Warn("[WECHAT:HANDLER] 配置更新后尝试启动机器人失败: %v", startErr) // 启动失败不影响配置保存,只记录警告 } // 返回成功 SuccessResponse(c, map[string]interface{}{ "success": true, "message": "配置更新成功,机器人已尝试启动", }) } // GetBotStatus 获取机器人状态 func (h *WechatHandler) GetBotStatus(c *gin.Context) { // 获取机器人运行时状态 runtimeStatus := h.wechatService.GetRuntimeStatus() // 获取配置状态 configs, err := h.systemConfigRepo.GetOrCreateDefault() if err != nil { ErrorResponse(c, "获取配置失败", http.StatusInternalServerError) return } // 解析配置状态 configStatus := map[string]interface{}{ "enabled": false, "auto_reply_enabled": false, "app_id_configured": false, "token_configured": false, } for _, config := range configs { switch config.Key { case entity.ConfigKeyWechatBotEnabled: configStatus["enabled"] = config.Value == "true" case entity.ConfigKeyWechatAutoReplyEnabled: configStatus["auto_reply_enabled"] = config.Value == "true" case entity.ConfigKeyWechatAppId: configStatus["app_id_configured"] = config.Value != "" case entity.ConfigKeyWechatToken: configStatus["token_configured"] = config.Value != "" } } // 合并状态信息 status := map[string]interface{}{ "config": configStatus, "runtime": runtimeStatus, "overall_status": runtimeStatus["is_running"].(bool), "status_text": func() string { if runtimeStatus["is_running"].(bool) { return "运行中" } else if configStatus["enabled"].(bool) { return "已启用但未运行" } else { return "已停止" } }(), } SuccessResponse(c, status) } // validateSignature 验证微信消息签名 func (h *WechatHandler) validateSignature(c *gin.Context) bool { // 获取配置中的Token configs, err := h.systemConfigRepo.GetOrCreateDefault() if err != nil { utils.Error("[WECHAT:VALIDATE] 获取配置失败: %v", err) return false } var token string for _, config := range configs { if config.Key == entity.ConfigKeyWechatToken { token = config.Value break } } utils.Debug("[WECHAT:VALIDATE] Token配置状态: %t", token != "") if token == "" { // 如果没有配置Token,跳过签名验证(开发模式) utils.Warn("[WECHAT:VALIDATE] 未配置Token,跳过签名验证") return true } signature := c.Query("signature") timestamp := c.Query("timestamp") nonce := c.Query("nonce") utils.Debug("[WECHAT:VALIDATE] 接收到的参数 - signature: %s, timestamp: %s, nonce: %s", signature, timestamp, nonce) // 验证签名 tmpArr := []string{token, timestamp, nonce} sort.Strings(tmpArr) tmpStr := strings.Join(tmpArr, "") tmpStr = fmt.Sprintf("%x", sha1.Sum([]byte(tmpStr))) utils.Debug("[WECHAT:VALIDATE] 计算出的签名: %s, 微信提供的签名: %s", tmpStr, signature) if tmpStr == signature { utils.Info("[WECHAT:VALIDATE] 签名验证成功") return true } else { utils.Error("[WECHAT:VALIDATE] 签名验证失败 - 计算出的签名: %s, 微信提供的签名: %s", tmpStr, signature) return false } }