共计 2861 个字符,预计需要花费 8 分钟才能阅读完成。
消息推送的三大痛点
刚接触微信公众号开发时,消息推送系统经常遇到这些头疼问题:

- 消息丢失 :用户发了消息但服务器没收到,尤其是网络抖动时
- 接口限流 :微信对 access_token 接口调用有严格限制(2000 次 / 天)
- 加解密复杂 :必须处理 AES 加密报文,新手容易踩坑
技术方案实现
微信消息机制原理解析
微信服务器与开发者服务器的交互就像快递柜取件:
- 用户发送消息到微信服务器(相当于快递员放件)
- 微信服务器将 XML 格式消息 POST 到开发者配置的 URL(发送取件码)
- 开发者服务器需在 5 秒内响应(超时会导致微信重试 3 次)
关键点:消息类型分为事件(event)和普通消息(text/image 等),处理逻辑需区分。
Spring Boot 集成 WxJava
推荐使用 WxJava 开发包,Maven 配置如下:
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.1.0</version>
</dependency>
核心配置类示例:
@Configuration
public class WxMpConfig {@Value("${wx.appId}")
private String appId;
@Bean
public WxMpService wxMpService() {WxMpConfigStorage config = new WxMpInMemoryConfigStorage();
config.setAppId(appId);
config.setSecret("your_app_secret");
config.setToken("your_token"); // 需与公众号后台配置一致
config.setAesKey("your_encoding_aes_key");
WxMpService service = new WxMpServiceImpl();
service.setWxMpConfigStorage(config);
return service;
}
}
消息加解密实现
处理加密消息的核心代码(含异常处理):
@RestController
@RequestMapping("/wechat")
public class WechatController {
@Autowired
private WxMpService wxService;
@PostMapping(produces = "text/plain;charset=utf-8")
public String handleMessage(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam(value = "echostr", required = false) String echostr,
@RequestBody String requestBody) {
try {
// 校验消息真实性
if (!wxService.checkSignature(timestamp, nonce, signature)) {throw new IllegalArgumentException("签名校验失败");
}
// 首次配置验证
if (StringUtils.isNotBlank(echostr)) {return echostr;}
// 解密消息
WxMpXmlMessage inMessage = WxMpXmlMessage
.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
// 业务处理逻辑...
return "success"; // 必须返回 success 避免微信重试
} catch (WxErrorException e) {log.error("消息处理异常", e);
return "";
}
}
}
生产环境验证
JMeter 压力测试
模拟用户消息并发的测试步骤:
- 创建线程组(建议 100 并发)
- 添加 HTTP 请求采样器,配置 POST 到 /wechat 接口
- 在 HTTP 头管理器中添加 Content-Type: text/xml
- 使用 CSV Data Set Config 导入测试消息模板
关键指标:95% 响应时间应 <3 秒,错误率 <0.1%
AccessToken 缓存策略
| 方案 | 优点 | 缺点 |
|---|---|---|
| 内存缓存 | 零延迟 | 集群环境会重复获取 |
| Redis 缓存 | 支持分布式 | 需要维护 Redis 可用性 |
推荐 Redis 实现代码:
public class RedisTokenCache implements WxMpConfigStorage {
@Override
public String getAccessToken() {return redisTemplate.opsForValue().get("wx:access_token:" + appId);
}
@Override
public void updateAccessToken(String accessToken, int expiresInSeconds) {redisTemplate.opsForValue().set(
"wx:access_token:" + appId,
accessToken,
expiresInSeconds - 200, // 提前 200 秒过期
TimeUnit.SECONDS);
}
}
避坑指南
动态 IP 列表处理
微信服务器 IP 会不定期变更,必须定时获取最新列表(文档 v3.0.9):
// 每天凌晨执行
@Scheduled(cron = "0 0 0 * * ?")
public void refreshWechatIps() {List<String> ipList = wxService.getCallbackIP();
securityRule.updateAllowedIps(ipList); // 更新防火墙白名单
}
消息重试机制
微信重试消息的特征:
- 相同的 MsgId
- 时间间隔为 60/180/1800 秒
正确处理方式:
// 使用 Redis 实现消息去重
String key = "wx:msg:dedup:" + msgId;
if (redisTemplate.opsForValue().setIfAbsent(key, "1", 4, TimeUnit.HOURS)) {
// 首次收到该消息
processMessage(content);
} else {
// 重复消息直接返回 success
return "success";
}
延伸思考
分布式环境下消息去重系统设计要点:
- 唯一标识生成:MsgId+FromUserName 组合
- 存储选型:Redis 的 SETNX 适合高频写场景
- 过期策略:建议 4 小时(覆盖微信最大重试间隔)
- 分片设计:按用户 OpenID 哈希分片存储
期待你在评论区分享自己的实现方案!
正文完
发表至: 技术开发
近一天内
