
如何制作微信推送源码解析: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.parse
def 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格式的消息数据。
你的服务器接收:
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。
微信服务器:收到你的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=xxxtimestamp=xxxnonce=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。
加密响应:
构造明文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%,是业务逻辑和异步处理。
你公司项目里是怎么处理微信推送的?是用的明文模式还是安全模式?有没有遇到过签名校验失败但日志显示参数正确的情况?欢迎在评论区分享你的踩坑经验,一起避坑。