ARTICLE · 1089287
微信公众号模板消息通知:若依框架集成实战
微信公众号模板消息通知:若依框架集成实战订单通知、打卡提醒这类业务,绕不开微信公众号的模板消息。这篇文章用一个测试号,在若依框架上集成微信通知。 一、准备工作 1、申请测试号 打开浏览器,搜索微信公众平台测试号,或者直接访问https://mp.weixin.qq.com/debug/cgi-bin/sandbox)。 用微信扫码登录,登录成功后,就可以看到appID和appsecret,先保存下来。 
它们是调用微信接口的凭证。测试号的appID和appsecret是系统自动生成的,不需要自己申请。 2、关注测试号 在测试号管理页面往下滚动,找到一个二维码。用你的个人微信扫描这个二维码,点击关注。 关注成功后,可以在用户列表看到你的昵称和微信号。这个微信号就是后面发消息的收件人,复制下来保存好。 
注意:测试号的openId和正式公众号的openId不通用。测试阶段用测试号的,上线后要换成正式号重新获取。 3、配置模板 继续下拉页面,找到模板消息接口区域,点击新增测试模板。 弹出表单,填写模板标题和内容。 模板标题:随便写,例如:订单通知,你自己知道模板的含义 模板内容:按微信规定的格式填写,每个变量用 {{xxx.DATA}} 表示: 订单号:{{orderNo.DATA}} 金额:{{amount.DATA}} 备注:{{remark.DATA}} 
点击提交,就会在模板消息接口区域下方出现一条新记录,包含一个模板ID,形如abcdefg1234567890,复制下来存好。 
都完成后,准备工作就完成了,这3个缺一不可。 二、核心代码 1、添加配置 打开若依项目的ruoyi-admin/src/main/resources/application.yml,在文件末尾加上: wx: appId: 你的AppID appSecret: 你的appsecret templateId: 你的模板ID token: 你的token 
2、后端编码 在若依的ruoyi-admin模块下,找到com.ruoyi.web.controller包,新建一个wx子包,在里面创建WxController.java: 
3、启动测试 启动项目,打开浏览器,访问:http://localhost:8080/wx/send?openid=你的微信号。把微信号换成关注公众号拿到的那个。 
对了,若依这里要配置下接口白名单,找到SecurityConfig文件,找到下面这行,添加接口白名单: 
打开手机,微信里应该已经收到一条订单通知了。 
如果返回错误码,对照排查:
三、前端授权 微信规定,发模板消息之前必须由用户主动点击授权,允许接收消息。 在uni-app项目里,新建一个页面或者找一个已有页面: 四、流程总结 ① 用户点击下单按钮 ② uni.requestSubscribeMessage弹出授权确认框 ③ 用户点击同意 ④ 前端调用后端接口,把openid和订阅状态存储起来 ⑤ 订单支付成功,后端调用/wx/send ⑥ 后端从Redis获取access_token(没有就重新请求) ⑦ 后端调用微信template/send接口 ⑧ 用户收到模板消息 先把测试链路跑通,收到消息之后,再去申请生产模板(下一期介绍)。跑不通的时候,上面这些代码足够排查了。 官方指南:https://developers.weixin.qq.com/doc/service/guide/product/template_message/Template_Message_Interface.html





package com.ruoyi.web.controller.wx;import com.alibaba.fastjson2.JSONObject;import com.ruoyi.common.core.redis.RedisCache;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.*;import org.springframework.web.client.RestTemplate;import java.security.MessageDigest;import java.util.*;import java.util.concurrent.TimeUnit;@RestController@RequestMapping("/wx")public class WxController {@Value("${wx.appId}")private String appId;@Value("${wx.appSecret}")private String appSecret;@Value("${wx.templateId}")private String templateId;// 服务器配置里的 Token,必须和公众号后台填的完全一致@Value("${wx.token}")private String token;@Autowiredprivate RedisCache redisCache;private final RestTemplate restTemplate = new RestTemplate();/*** 微信服务器配置验证接口* 微信提交配置时会发 GET 请求过来* 校验通过必须原样返回 echostr*/@GetMapping("/check")public String check(@RequestParam("signature") String signature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam("echostr") String echostr) {// 1. 将 token、timestamp、nonce 三个参数进行字典序排序String[] arr = {token, timestamp, nonce};Arrays.sort(arr);// 2. 将三个参数字符串拼接成一个字符串进行 sha1 加密StringBuilder sb = new StringBuilder();for (String s : arr) {sb.append(s);}String sha1 = sha1(sb.toString());// 3. 开发者获得加密后的字符串可与 signature 对比,标识该请求来源于微信if (sha1 != null && sha1.equals(signature)) {return echostr;}return "error";}/*** SHA1 加密工具方法*/private String sha1(String str) {try {MessageDigest md = MessageDigest.getInstance("SHA-1");byte[] digest = md.digest(str.getBytes("UTF-8"));StringBuilder hex = new StringBuilder();for (byte b : digest) {String s = Integer.toHexString(b & 0xFF);if (s.length() == 1) {hex.append("0");}hex.append(s);}return hex.toString();} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取 access_token* 微信规定这个 token 有效期 7200 秒,且全局唯一* 每次发消息都重新获取会被限流,必须缓存*/private String getAccessToken() {String cacheKey = "wx:access_token";// 先看 Redis 里有没有String token = redisCache.getCacheObject(cacheKey);if (token != null) {return token;}// 没有就请求微信String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"+ "&appid=" + appId + "&secret=" + appSecret;JSONObject result = restTemplate.getForObject(url, JSONObject.class);token = result.getString("access_token");// 缓存 7000 秒,比微信给的 7200 秒少 200 秒,提前刷新redisCache.setCacheObject(cacheKey, token, 7000, TimeUnit.SECONDS);return token;}/*** 发送模板消息* 访问方式:GET /wx/send?openid=xxx*/@GetMapping("/send")public String send(@RequestParamString openid) {String accessToken = getAccessToken();String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;// 组装 data// 微信要求的格式:{"key": {"value": "实际内容"}}// key 必须和模板里 {{xxx.DATA}} 的 xxx 完全一致Map<String, Object> data = new HashMap<>();data.put("orderNo", Collections.singletonMap("value", "20240923001"));data.put("amount", Collections.singletonMap("value", "99.00"));data.put("remark", Collections.singletonMap("value", "感谢购买"));// 组装请求体Map<String, Object> body = new HashMap<>();body.put("touser", openid); // 收件人 OpenIDbody.put("template_id", templateId); // 模板 IDbody.put("data", data); // 填充内容// 发送请求JSONObject result = restTemplate.postForObject(url, body, JSONObject.class);return result.toJSONString();}}
这里有2个接口,一个与微信通信校验,一个是发送微信消息的。

.antMatchers("/wx/check", "/wx/send").permitAll()添加后重启服务应该就可以了,如果返回下图信息,说明发送成功。


| appsecret 错误,或缓存失效后未正确刷新 | ||
| 不合法的openID | 用户未关注公众号,或传入了其他公众号的openID | |
| 不合法的template_id | 模板ID错误、已删除,或属于其他公众号 | |
| 41030 | page不正确/用户未互动 | 用户未在48小时内与公众号互动,超出模板消息发送窗口期 |
| 43004 | 需要接收者关注 | 用户已取消关注公众号 |
<template><view><button @tap="handleOrder">下单</button></view></template><script>export default {methods: {handleOrder() {// 必须在用户点击时调,自动触发会被微信拦截uni.requestSubscribeMessage({tmplIds: ['你的模板ID'],success: (res) => {// res 的 key 是模板ID,值是授权状态// accept = 同意,reject = 拒绝,ban = 已被封禁if (res['你的模板ID'] === 'accept') {console.log('用户同意订阅');// 这里可以调后端接口,把 openid 和订阅状态存起来}},fail: (err) => {console.error('订阅失败', err);}});this.submitOrder();},submitOrder() {// 你的下单逻辑}}}</script>
往期回顾
NO.1
01
02