package api import ( "crypto/sha1" "encoding/json" "fmt" "io" "sort" "strings" "time" "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model" "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service" "github.com/gin-gonic/gin" "go.uber.org/zap" ) type WechatWebhookApi struct{} var webhookMessageService = service.ServiceGroupApp.MpMessageService // OfficialAccountWebhook 公众号消息接收 // @Tags WechatWebhook // @Summary 公众号消息接收 // @Description 接收微信公众号推送的消息和事件 // @Accept xml // @Produce xml // @Param signature query string true "微信加密签名" // @Param timestamp query string true "时间戳" // @Param nonce query string true "随机数" // @Param echostr query string false "随机字符串" // @Success 200 {string} string "success" // @Router /wechat/official/webhook [get,post] func (w *WechatWebhookApi) OfficialAccountWebhook(c *gin.Context) { startTime := time.Now() // 获取参数 signature := c.Query("signature") timestamp := c.Query("timestamp") nonce := c.Query("nonce") echostr := c.Query("echostr") // 准备webhook日志记录 webhookLog := &model.MpWebhookLog{ ConfigID: 1, // 默认配置ID,实际应该根据具体配置获取 RequestURL: c.Request.URL.String(), RequestMethod: c.Request.Method, Signature: &signature, Timestamp: ×tamp, Nonce: &nonce, Echostr: &echostr, } // 记录请求头 if headers, err := json.Marshal(c.Request.Header); err == nil { headersStr := string(headers) webhookLog.RequestHeaders = &headersStr } // 验证签名 if !w.verifySignature(signature, timestamp, nonce) { global.GVA_LOG.Error("微信签名验证失败") // 记录失败信息 webhookLog.ResponseStatus = 403 responseBody := "签名验证失败" webhookLog.ResponseBody = &responseBody errorMsg := "签名验证失败" webhookLog.ErrorMessage = &errorMsg webhookLog.ProcessTime = int(time.Since(startTime).Milliseconds()) // 保存到数据库 w.saveWebhookLog(webhookLog) c.String(403, "签名验证失败") return } // GET请求用于验证服务器配置 if c.Request.Method == "GET" { webhookLog.ResponseStatus = 200 webhookLog.ResponseBody = &echostr webhookLog.ProcessTime = int(time.Since(startTime).Milliseconds()) // 保存到数据库 w.saveWebhookLog(webhookLog) c.String(200, echostr) return } // POST请求处理消息 body, err := io.ReadAll(c.Request.Body) if err != nil { global.GVA_LOG.Error("读取请求体失败", zap.Error(err)) // 记录失败信息 webhookLog.ResponseStatus = 400 responseBody := "读取请求体失败" webhookLog.ResponseBody = &responseBody errorMsg := err.Error() webhookLog.ErrorMessage = &errorMsg webhookLog.ProcessTime = int(time.Since(startTime).Milliseconds()) // 保存到数据库 w.saveWebhookLog(webhookLog) c.String(400, "读取请求体失败") return } // 记录请求体 bodyStr := string(body) webhookLog.RequestBody = &bodyStr // 简化消息处理 - 暂时只记录日志 global.GVA_LOG.Info("收到微信消息", zap.String("body", bodyStr)) // TODO: 实现完整的消息解析和处理 // 由于微信SDK API变化,暂时简化处理 // 记录成功响应 webhookLog.ResponseStatus = 200 responseBody := "success" webhookLog.ResponseBody = &responseBody webhookLog.ProcessTime = int(time.Since(startTime).Milliseconds()) // 保存到数据库 w.saveWebhookLog(webhookLog) c.String(200, "success") } // verifySignature 验证微信签名 func (w *WechatWebhookApi) verifySignature(signature, timestamp, nonce string) bool { token := global.GVA_CONFIG.Wechat.OfficialToken if token == "" { global.GVA_LOG.Error("微信公众号Token未配置") return false } // 将token、timestamp、nonce三个参数进行字典序排序 strs := []string{token, timestamp, nonce} sort.Strings(strs) // 将三个参数字符串拼接成一个字符串进行sha1加密 str := strings.Join(strs, "") h := sha1.New() h.Write([]byte(str)) encrypted := fmt.Sprintf("%x", h.Sum(nil)) // 将加密后的字符串与signature对比 return encrypted == signature } // saveWebhookLog 保存Webhook日志到数据库 func (w *WechatWebhookApi) saveWebhookLog(log *model.MpWebhookLog) { if err := global.GVA_DB.Create(log).Error; err != nil { global.GVA_LOG.Error("保存Webhook日志失败", zap.Error(err)) // 数据库记录失败不影响webhook正常响应 } else { global.GVA_LOG.Debug("Webhook日志保存成功", zap.Uint("id", log.ID)) } }