Go处理微信签名验证需按字典序排序token/timestamp/nonce后SHA1比对;解析XML须用xml tag严格绑定字段并支持CDATA;HTTP handler需显式限定POST方法;access_token应缓存带过期时间、串行刷新、失败兜底。

怎么用 Go 处理微信公众号的签名验证
微信服务器在推送事件或接收消息前,会要求你校验 signature、timestamp、nonce 和你配置的 token。Go 里没现成封装,得自己拼接、排序、SHA1。不注意顺序或编码,100% 验证失败。
实操建议:
- 微信传来的
signature是 hex 字符串(如"a1b2c3..."),别拿它直接跟字节数组比,要用hex.DecodeString转成原始字节再比对 - 拼接字符串时,必须按字典序对
token、timestamp、nonce三个值排序(不是按参数名,是按值本身字符串排序),常见错误是硬写死顺序:token + timestamp + nonce - 所有参与签名的字符串都必须是 UTF-8 编码,不能含 BOM,也不能被 URL 解码两次——微信发来的
timestamp是纯数字字符串,别误当成 int 再转回 string
示例关键片段:
func checkSignature(token, signature, timestamp, nonce string) bool {
tmpArr := []string{token, timestamp, nonce}
sort.Strings(tmpArr)
tmpStr := strings.Join(tmpArr, "")
sha := sha1.Sum([]byte(tmpStr))
return signature == hex.EncodeToString(sha[:])
}
Go 怎么解析微信 XML 消息体
微信所有推送(文本、事件、图片等)都是 POST 的 XML,不是 JSON。Go 的 encoding/xml 默认不支持带命名空间的标签(比如 <xml><ToUserName>...</ToUserName></xml>),字段映射错一个就全 nil。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 结构体字段必须用
xmltag 显式绑定,且大小写严格匹配微信文档里的标签名(ToUserName不是tousername) - 微信消息里有 CDATA 包裹的内容(比如
<Content><![CDATA[hello]]></Content>),Go 的xml包默认会自动解包,但前提是字段类型为string,别用[]byte或自定义类型 - 别忽略
MsgType字段——它是路由核心,不同值对应不同结构体,硬塞进一个 struct 会导致字段覆盖或静默丢数据
典型结构体示例:
type WechatMessage struct {
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime int64 `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
Event string `xml:"Event"`
}
为什么用 http.ServeMux 直接注册微信回调会 405 错误
微信只接受 POST 请求,而 http.ServeMux 对未注册的 HTTP 方法默认返回 405。如果你只写了 http.HandleFunc("/wechat", handler),没显式处理 OPTIONS 或检查 r.Method,本地调试可能通,上线后微信服务器预检失败。
实操建议:
- 在 handler 开头强制判断
r.Method != "POST",非 POST 立即返回 405,别依赖 mux 默认行为 - 微信服务器可能带
Content-Type: text/xml,但 Go 的http.Request不会自动识别,别用r.FormValue——它只读application/x-www-form-urlencoded,必须用ioutil.ReadAll(r.Body)(Go 1.19+ 改用io.ReadAll) - 别在 handler 里做耗时操作(比如远程调用),微信 5 秒超时,超时后重发,容易造成重复处理;先存 DB 或发队列,再异步执行
如何安全地存储和刷新微信 access_token
access_token 是调用微信 API(如发送客服消息、获取用户信息)的凭据,有效期 2 小时,且每个公众号每天调用 /cgi-bin/token 接口上限 2000 次。硬编码或每次请求都去拉,要么失效,要么被限流。
实操建议:
- 用内存缓存 + 过期时间控制,推荐
sync.Map存access_token和expires_in时间戳,避免并发刷新;别用全局变量配time.Timer定时拉取——精度差、重启丢失 - 刷新逻辑必须加锁(
sync.Once或互斥锁),否则高并发下多个 goroutine 同时发起刷新请求,浪费配额还可能写入过期 token - 首次获取失败要重试(网络抖动常见),但最多 2 次;如果连续失败,记录日志并 fallback 到上次有效 token,别直接 panic 或返回空
关键点就这三条:缓存要带时间戳、刷新要串行、失败要有兜底。



















