Files
yunzerwebsiteallinone/go/controllers/api_wechat_mp.go
T
2026-09-16 18:06:18 +08:00

125 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controllers
import (
"io"
"strconv"
"strings"
"time"
"server/services/wechatmp"
beego "github.com/beego/beego/v2/server/web"
)
// WechatMpCallbackController 微信公众号服务器回调(微信服务器调用,无需平台鉴权)
//
// 公众号后台「设置与开发 → 服务器配置」填写:
// URL(服务器地址):https://<你的域名>/api/wechat/mp/callback
// Token:与平台「通知设置 → 微信配置」中的 Token 保持一致
// 消息加解密方式:明文 / 兼容 / 安全(三种均支持)
type WechatMpCallbackController struct {
beego.Controller
}
// Callback 服务器地址验证(GET,回显 echostr)与消息/事件推送(POST,被动回复)
func (c *WechatMpCallbackController) Callback() {
cfg, err := wechatmp.LoadEnabledConfig()
if err != nil {
c.Ctx.Output.SetStatus(403)
c.Ctx.Output.Body([]byte("wechat mp not configured"))
return
}
q := c.Ctx.Request.URL.Query()
timestamp := q.Get("timestamp")
nonce := q.Get("nonce")
// ---------------- GET:服务器地址校验 ----------------
if strings.EqualFold(c.Ctx.Input.Method(), "GET") {
echostr := q.Get("echostr")
if cfg.EncryptMode == wechatmp.EncryptModeSafe {
if !wechatmp.CheckMsgSignature(cfg.Token, timestamp, nonce, echostr, q.Get("msg_signature")) {
c.Ctx.Output.SetStatus(403)
c.Ctx.Output.Body([]byte("invalid signature"))
return
}
plain, derr := wechatmp.DecryptMessage(cfg.AESKey, cfg.AppID, echostr)
if derr != nil {
c.Ctx.Output.SetStatus(403)
c.Ctx.Output.Body([]byte("decrypt failed"))
return
}
c.Ctx.Output.Body([]byte(plain))
return
}
if !wechatmp.CheckSignature(cfg.Token, timestamp, nonce, q.Get("signature")) {
c.Ctx.Output.SetStatus(403)
c.Ctx.Output.Body([]byte("invalid signature"))
return
}
c.Ctx.Output.Body([]byte(echostr))
return
}
// ---------------- POST:消息 / 事件 ----------------
body, _ := io.ReadAll(c.Ctx.Request.Body)
rawBody := string(body)
if strings.TrimSpace(rawBody) == "" {
c.Ctx.Output.Body([]byte(""))
return
}
needEncryptReply := false
if strings.Contains(rawBody, "<Encrypt>") {
// 安全/兼容模式:外层为加密 XML,先验签再解密
outer, perr := wechatmp.ParseInboundXML(rawBody)
if perr != nil {
c.Ctx.Output.Body([]byte(""))
return
}
if !wechatmp.CheckMsgSignature(cfg.Token, timestamp, nonce, outer.Encrypt, q.Get("msg_signature")) {
c.Ctx.Output.Body([]byte(""))
return
}
plain, derr := wechatmp.DecryptMessage(cfg.AESKey, cfg.AppID, outer.Encrypt)
if derr != nil {
c.Ctx.Output.Body([]byte(""))
return
}
rawBody = plain
needEncryptReply = true
} else if !wechatmp.CheckSignature(cfg.Token, timestamp, nonce, q.Get("signature")) {
// 明文模式:签名校验不通过直接拒绝(微信会重试)
c.Ctx.Output.Body([]byte(""))
return
}
msg, merr := wechatmp.ParseInboundXML(rawBody)
if merr != nil {
c.Ctx.Output.Body([]byte(""))
return
}
reply := wechatmp.HandleInbound(cfg, msg)
if strings.TrimSpace(reply) == "" {
// 无需回复:返回空串,微信视为处理成功
c.Ctx.Output.Body([]byte(""))
return
}
now := time.Now().Unix()
plainReply := wechatmp.BuildTextReply(msg.FromUserName, msg.ToUserName, reply, now)
c.Ctx.Output.Header("Content-Type", "application/xml; charset=utf-8")
if needEncryptReply {
ts := strconv.FormatInt(now, 10)
enc, eerr := wechatmp.BuildEncryptedReply(cfg, plainReply, ts, nonce)
if eerr != nil {
c.Ctx.Output.Body([]byte(""))
return
}
c.Ctx.Output.Body([]byte(enc))
return
}
c.Ctx.Output.Body([]byte(plainReply))
}