增加微信通知功能
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user