如何制作微信推送源码解析:3步搞定跑不通的代码
复制来的代码跑不通,是不是让你抓狂?报错信息像天书,调试半天没头绪。别急,今天咱们直接扒开【如何制作微信推送】的底层逻辑,用源码解析帮你理清思路。
一句话原理:回调机制与签名校验
微信推送的本质,不是你去“发”消息,而是微信服务器“推”给你,或者你“推”给微信。对于公众号主动推送,核心是HTTP POST请求;对于模板消息,核心是接口调用。所有交互都建立在HTTPS安全通道和Token签名校验之上。
这就好比你去银行柜台办事。你不能直接跑进金库拿钱(直接访问数据库),必须出示身份证(Token)、填写单据(XML/JSON数据),银行核验无误后,才给你办理业务。如果身份证不对(签名错误),或者单据格式乱(数据解析失败),业务直接拒绝。很多初学者失败,不是因为代码逻辑错,而是“身份证”没带对,或者“单据”写错了。
类比解释:微信推送像什么?
把微信开放平台想象成一个高速收费站。
- Token是通行证:你在微信后台设置的那个随机字符串,就是收费站发给你这辆车的专用通行证。
- Timestamp是时间戳:每辆车过站时,记录一个精确到秒的时间。
- Nonce是随机数:防止你拿同一个凭证反复刷站,每次过站都换一个随机编号。
- Signature是签名:把通行证、时间、随机数按特定顺序排序,拼起来,用MD5加密,生成的那串字符就是签名。
当微信服务器向你发请求时,它会带上这四个参数。你的服务器必须立刻做三件事:
- 拿到微信发来的Token(你自己在后台设的)。
- 拿到微信发来的Timestamp、Nonce、Signature。
- 用你手里的Token,加上微信发来的Timestamp、Nonce,按字典序排序,拼接,MD5加密,算出一个新签名。
- 比对:如果你算出来的签名,和微信发来的Signature一模一样,说明请求来自微信,是合法的。否则,直接丢弃,返回403错误。
这个过程,就是所谓的签名校验。90%的“跑不通”,都卡在这一步。
源码/伪代码片段:Python实现签名校验
很多人用Java或Node.js,但Python代码最简洁,逻辑最清晰。下面这段代码,是微信官方文档逻辑的Python实现。你可以直接复制到本地运行,输入测试参数,看它是否能通过校验。
import hashlib
import urllib.parsedef check_wechat_signature(token, timestamp, nonce, signature):"""验证微信服务器发来的请求签名是否合法:param token: 你在微信后台设置的Token:param timestamp: 微信发来的时间戳:param nonce: 微信发来的随机数:param signature: 微信发来的签名:return: True 如果合法,False 如果非法"""# 1. 将 token、timestamp、nonce 三个参数进行字典序排序# 注意:是字典序(ASCII码顺序),不是时间顺序list_params = [token, timestamp, nonce]list_params.sort()# 2. 将三个参数字符串拼接成一个字符串进行sha1加密# 微信早期用MD5,现在主流用SHA1,务必确认你使用的算法# 很多旧教程还在用MD5,这是个大坑!string_to_sign = ''.join(list_params)# 使用SHA1哈希hash_object = hashlib.sha1(string_to_sign.encode('utf-8'))hex_digest = hash_object.hexdigest()# 3. 判断是否相等return hex_digest == signature# 测试案例
if __name__ == '__main__':# 假设你在微信后台设置的Token是 "my_secret_token_123"token = "my_secret_token_123"# 模拟微信发来的参数(这些是示例,实际运行需从请求头获取)timestamp = "1409659813"nonce = "265438"signature = "e76278513048d3f8869f3c1e88f27f0f6026f18e"is_valid = check_wechat_signature(token, timestamp, nonce, signature)print(f"签名校验结果: {is_valid}")# 如果返回False,说明你的Token、时间戳、随机数组合算出来的值不等于微信发来的Signature# 此时,你应该返回403,并记录日志,方便排查
逐行讲解关键坑点:
list_params.sort():这是最容易出错的地方。排序必须是字典序(ASCII码)。比如 "1", "2", "a",排序后是 "1", "2", "a"。如果你用了Python的sorted(),默认就是字典序,没问题。但如果你手动拼接,顺序错了,结果必错。hashlib.sha1:微信在2015年左右将签名算法从MD5升级为SHA1。很多网上流传的旧代码还在用md5,导致签名永远对不上。务必检查你使用的算法。- 编码问题:
.encode('utf-8')。字符串必须转成字节串才能哈希。如果编码不一致(比如用了GBK),结果必错。微信要求UTF-8。
流程描述:从请求到响应的完整链路
理解了签名校验,我们来看整个推送流程。以公众号被动回复消息为例,这是最基础的场景。
流程图(文字版):
- 用户操作:用户在微信里给公众号发一条文本消息“你好”。
- 微信服务器:微信服务器收到消息,封装成XML格式,加上
ToUserName、FromUserName、CreateTime、MsgType、Content等字段。 - HTTP POST:微信服务器向你的服务器发送一个HTTPS POST请求,URL是你配置的服务器地址。
- Query Parameters:
signature,timestamp,nonce,echostr(如果是验证URL则带echostr,正常消息则不带)。 - Body:XML格式的消息数据。
- Query Parameters:
- 你的服务器接收:
- Step 1: 解析URL参数:从请求URL中提取
signature,timestamp,nonce。 - Step 2: 签名校验:调用上面的
check_wechat_signature函数。- 如果校验失败:返回HTTP 403,记录日志,流程结束。
- 如果校验成功:继续下一步。
- Step 3: 解析Body:读取POST请求的Body,解析XML,提取
Content(用户发的消息)、FromUserName(用户ID)等。 - Step 4: 业务逻辑:根据你的业务规则,决定回复什么。比如,如果内容是“你好”,就回复“您好,有什么可以帮您?”。
- Step 5: 构造回复XML:将回复内容封装成XML格式。
- Step 6: 返回响应:将构造好的XML字符串作为HTTP Response Body返回,Content-Type设置为
text/xml。
- Step 1: 解析URL参数:从请求URL中提取
- 微信服务器:收到你的XML响应,解析后推送给用户。
- 用户看到:用户在微信对话框里看到你回复的“您好,有什么可以帮您?”。
关键细节:
- 响应时间:微信要求你的服务器必须在5秒内返回响应。如果超时,微信会认为你的服务挂了,不再推送,且用户会看到“消息已发出,但被对方拒收”。所以,如果你的业务逻辑很复杂(比如查数据库、调第三方API),必须使用异步处理。
- 异步处理方案:
- 收到请求,校验签名。
- 立即返回一个空的XML或简单的“正在处理”提示。
- 将用户消息放入消息队列(如RabbitMQ、Kafka)。
- 后台Worker从队列取消息,处理业务逻辑。
- 处理完成后,调用客服消息接口或模板消息接口主动推送给用户。
实战验证:如何调试“跑不通”的代码?
现在,你有了原理和代码,怎么验证你的环境是通的?
方法一:本地调试(推荐)
- 安装微信开发者工具:这是微信官方提供的调试工具,可以在本地模拟微信服务器的请求。
- 配置本地服务器:
- 确保你的服务器监听80或443端口(微信要求HTTPS,本地调试需配置SSL证书,或使用微信提供的调试工具绕过)。
- 在微信后台填写你的服务器URL、Token、EncodingAESKey(如果用消息加解密)。
- 使用Postman模拟请求:
- 在Postman中创建POST请求。
- URL:
http://your-local-ip/wechat/callback?signature=xxx×tamp=xxx&nonce=xxx - Body: 选择
raw,类型XML,填入模拟的XML数据。 - 发送请求,查看你的服务器是否返回200,以及返回的Body是否正确。
- 日志记录:
- 在签名校验前后,打印
token,timestamp,nonce,signature。 - 在解析XML前后,打印原始Body和解析后的字典。
- 这是调试的核心。没有日志,你就是在盲猜。
- 在签名校验前后,打印
方法二:GitHub开源仓库参考
如果你不想从零写,可以参考GitHub上的开源项目。比如搜索wechat-official-account,找到Star数高的仓库。
- 推荐仓库:
wechatpy/wechatpy(Python库,封装了微信接口,处理了签名、加解密等细节)。 - 为什么推荐:
- 它封装了
check_signature方法,你只需传入参数即可。 - 它提供了
wechat对象,可以方便地发送消息、获取用户信息。 - 它的文档详细,社区活跃,遇到问题容易找到解决方案。
- 它封装了
避坑指南:
- Token不一致:微信后台设置的Token,和你代码里写死的Token,必须完全一致。包括大小写、空格。
- 端口不通:确保你的服务器80/443端口对外开放,防火墙没拦截。
- HTTPS证书:微信要求必须使用HTTPS。如果你用自签名证书,微信服务器会拒绝。请使用Let's Encrypt等免费证书,或购买商业证书。
- IP白名单:如果你配置了IP白名单,确保微信服务器的IP(203.205.244.0/24等)在白名单内。
进阶技巧:消息加解密与安全性
上面讲的是明文模式。现在,微信强烈建议使用安全模式(明文模式已被逐步淘汰)。
安全模式引入了AES加解密。这意味着:
- 请求Body是加密的:微信发来的XML是Base64编码的密文。
- 响应Body也要加密:你返回的XML必须加密后Base64编码。
- 需要EncodingAESKey:你在微信后台生成的那个43位字符串,是AES解密密钥。
加解密流程:
- 解密请求:
- 从URL获取
msg_signature,timestamp,nonce。 - 从Body获取
Encrypt字段。 - 用
EncodingAESKey、timestamp、nonce、Encrypt计算msg_signature,校验合法性。 - 校验通过后,用
EncodingAESKey对Encrypt进行AES解密,得到明文XML。
- 从URL获取
- 加密响应:
- 构造明文XML。
- 用
EncodingAESKey、当前时间戳、随机数,对明文XML进行AES加密。 - 对加密结果进行Base64编码。
- 用
EncodingAESKey、时间戳、随机数、Encrypt计算msg_signature。 - 返回包含
Encrypt和msg_signature的XML响应。
代码佐证(Python,使用wechatpy库):
from wechatpy.crypto import WeChatCrypto# 初始化加密器
# 注意:encoding_aes_key 是43位字符串,从微信后台获取
# app_id 是你的公众号AppID
# token 是你设置的Token
crypto = WeChatCrypto(token='my_secret_token_123',encoding_aes_key='your_43_char_aes_key_here',app_id='wx1234567890abcdef'
)# 解密微信发来的密文
# encrypted_msg: 微信发来的Base64编码的密文
# timestamp: URL中的timestamp
# nonce: URL中的nonce
# msg_signature: URL中的msg_signature
try:plaintext = crypto.decrypt_msg(encrypted_msg, timestamp, nonce, msg_signature)print("解密成功:", plaintext)
except Exception as e:print("解密失败:", e)# 加密你的回复
# reply_xml: 你要回复的明文XML
encrypted_reply = crypto.encrypt_msg(reply_xml)
print("加密后的回复:", encrypted_reply)
避坑:
- AESKey长度:必须是43位。如果少了或多了,解密必失败。
- 时间戳:解密时的时间戳必须和URL中的完全一致。
- Padding:AES加密需要填充。wechatpy库已经处理了,但如果你自己写,务必注意PKCS7填充。
结尾互动引导
微信推送的底层原理,核心就是签名校验和加解密。掌握了这两点,你就能搞定90%的问题。剩下的10%,是业务逻辑和异步处理。
你公司项目里是怎么处理微信推送的?是用的明文模式还是安全模式?有没有遇到过签名校验失败但日志显示参数正确的情况?欢迎在评论区分享你的踩坑经验,一起避坑。