Files
urldb/handlers/wechat_handler.go

286 lines
8.0 KiB
Go
Raw Permalink Normal View History

2025-10-31 13:36:07 +08:00
package handlers
import (
"crypto/sha1"
"encoding/xml"
"fmt"
"io"
"net/http"
"sort"
"strings"
2025-10-31 23:32:57 +08:00
"time"
2025-10-31 13:36:07 +08:00
"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
}
2025-10-31 23:32:57 +08:00
utils.Info("[WECHAT:MESSAGE] 回复对象: %v", reply)
2025-10-31 13:36:07 +08:00
// 如果有回复内容,发送回复
if reply != nil {
2025-10-31 23:32:57 +08:00
// 为微信消息设置正确的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
}
}
2025-10-31 13:36:07 +08:00
responseXML, err := xml.Marshal(reply)
if err != nil {
utils.Error("[WECHAT:MESSAGE] 序列化回复消息失败: %v", err)
c.String(http.StatusInternalServerError, "回复失败")
return
}
2025-10-31 23:32:57 +08:00
utils.Info("[WECHAT:MESSAGE] 回复XML: %s", string(responseXML))
2025-10-31 13:36:07 +08:00
c.Data(http.StatusOK, "application/xml", responseXML)
} else {
2025-10-31 23:32:57 +08:00
utils.Warn("[WECHAT:MESSAGE] 没有回复内容返回success")
2025-10-31 13:36:07 +08:00
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 {
2025-10-31 23:32:57 +08:00
utils.Error("[WECHAT:VALIDATE] 获取配置失败: %v", err)
2025-10-31 13:36:07 +08:00
return false
}
var token string
for _, config := range configs {
if config.Key == entity.ConfigKeyWechatToken {
token = config.Value
break
}
}
2025-10-31 23:32:57 +08:00
utils.Debug("[WECHAT:VALIDATE] Token配置状态: %t", token != "")
2025-10-31 13:36:07 +08:00
if token == "" {
// 如果没有配置Token跳过签名验证开发模式
2025-10-31 23:32:57 +08:00
utils.Warn("[WECHAT:VALIDATE] 未配置Token跳过签名验证")
2025-10-31 13:36:07 +08:00
return true
}
signature := c.Query("signature")
timestamp := c.Query("timestamp")
nonce := c.Query("nonce")
2025-10-31 23:32:57 +08:00
utils.Debug("[WECHAT:VALIDATE] 接收到的参数 - signature: %s, timestamp: %s, nonce: %s", signature, timestamp, nonce)
2025-10-31 13:36:07 +08:00
// 验证签名
tmpArr := []string{token, timestamp, nonce}
sort.Strings(tmpArr)
tmpStr := strings.Join(tmpArr, "")
tmpStr = fmt.Sprintf("%x", sha1.Sum([]byte(tmpStr)))
2025-10-31 23:32:57 +08:00
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
}
2025-10-31 13:36:07 +08:00
}