微信post /wechat收不到消息最常见原因是未正确解析xml请求或返回非xml/非法响应;必须分开定义get和post路由,post需5秒内返回纯文本"success"或合法utf-8 xml,且禁用body重复读取、正确转义用户输入并分离签名验证与消息处理逻辑。

为什么 GET /wechat 验证通过后,POST /wechat 却收不到消息?
最常见原因是:微信服务器发来的是 XML 格式 POST 请求,而你没正确解析 body,或返回了 JSON/空字符串/非 XML 内容。微信只认两种成功响应:success(纯文本,无空格、无换行、无 XML 标签),或符合格式的 XML 响应体。
- 检查路由是否同时注册了
GET和POST:仅写router.GET("/wechat", ...)不会自动处理 POST - 确认 Gin 没启用
gin.DisableBindValidation()或全局中间件吞掉了 body(比如日志中间件调用了c.Request.Body两次) - 用
c.Request.Body读取原始 XML 时,必须用ioutil.ReadAll或io.ReadAll(Go 1.16+),不能用c.PostForm——那是表单专用 - 返回内容必须是 UTF-8 编码的 XML 字符串,且
Content-Type保持默认(微信不校验,但乱码会导致解析失败)
c.ShouldBindXML 解析失败的典型表现和绕过方式
微信发来的 XML 可能含 CDATA、大小写不规范字段(如 MsgType 实际是 msgtype)、或嵌套层级深,c.ShouldBindXML(&v) 容易因 struct tag 不匹配或字段缺失 panic 或静默失败。
- 优先用
xml.Unmarshal手动解包:更可控,可加log.Printf("raw XML: %s", string(body))调试 - struct 字段 tag 必须全小写 +
xml注解,例如:MsgType string `xml:"MsgType"`,注意大小写与 XML 实际标签一致 - 不要依赖
jsontag,微信不发 JSON;也不要加omitempty,空字段可能被忽略导致逻辑错判 - 关键字段如
FromUserName、ToUserName、CreateTime建议设为非指针,避免 nil 解引用 panic
如何安全地构造被动回复 XML 并防止 XSS
用户输入的 Content 直接拼进 XML 会触发实体注入(如 <script></script>),微信虽不执行 JS,但可能破坏 XML 结构导致回复失败。
- 用
xml.EscapeText(标准库)转义所有用户内容:xml.EscapeText(w, []byte(userInput)) - 不要用
fmt.Sprintf拼接 XML,极易漏转义;推荐用xml.Marshal序列化结构体 - 回复文本消息的 XML 必须包含
包裹正文,且 CDATA 内部不能再有]]>子串——所以先替换掉用户输入里的]]>为]]>(微信官方推荐方案) - 返回前用
c.Data(200, "text/xml; charset=utf-8", xmlBytes)显式设 Content-Type,避免 Gin 默认设成application/json
签名验证和消息处理必须分离,别共用一个 handler
GET 验证和 POST 消息处理逻辑差异大:前者只比对 signature,后者要解密(若开启加密)、解析、业务判断、生成响应。强行塞进一个函数会导致条件分支臃肿、难以测试、容易漏掉 c.Abort() 导致重复响应。
- 明确拆成两个 handler:
VerifyWechatSignature(c *gin.Context)(只处理 GET)和HandleWechatMessage(c *gin.Context)(只处理 POST) - Token 必须从配置读取(如
config.Wechat.Token),禁止硬编码在 handler 里,否则无法切换环境 - 如果启用了消息加密(AES),
POST请求体需先用wxBizMsgCrypt.DecryptMsg解密,再解析 XML;此时signature、timestamp、nonce从 URL query 提取,而非 body - 无论是否加密,
POST的响应时间必须压在 4.8 秒内——建议用context.WithTimeout包裹耗时操作,超时直接返回success
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











