mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
286 lines
8.0 KiB
Go
286 lines
8.0 KiB
Go
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
|
||
}
|
||
} |