在Spring Boot中实现微信公众号模板消息推送,主要分为以下步骤:
一、准备工作
-
微信公众号配置
- 认证服务号(订阅号无模板消息权限)
- 在公众号后台申请模板消息,获取模板ID
- 配置IP白名单(服务器公网IP)
-
获取关键信息
- AppID 和 AppSecret
- 用户OpenID(通过网页授权获取)
二、Spring Boot 实现步骤
1. 添加依赖
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- JSON 处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
2. 配置文件 (application.yml)
wechat:
appId: your_app_id
appSecret: your_app_secret
templateId: your_template_id # 申请的模板消息ID
3. 配置类读取参数
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatConfig {
private String appId;
private String appSecret;
private String templateId;
// Getters & Setters
}
4. AccessToken 管理(带缓存)
@Service
public class AccessTokenService {
@Autowired
private WechatConfig wechatConfig;
private String accessToken;
private long expireTime;
// 获取AccessToken(2小时有效)
public String getAccessToken() throws IOException {
if (accessToken == null || System.currentTimeMillis() > expireTime) {
refreshToken();
}
return accessToken;
}
private void refreshToken() throws IOException {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
"&appid=" + wechatConfig.getAppId() +
"&secret=" + wechatConfig.getAppSecret();
CloseableHttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet(url);
CloseableHttpResponse response = client.execute(get);
String result = EntityUtils.toString(response.getEntity());
JSONObject json = JSON.parseObject(result);
if (json.containsKey("access_token")) {
accessToken = json.getString("access_token");
// 提前5分钟刷新
expireTime = System.currentTimeMillis() + (json.getLong("expires_in") - 300) * 1000;
} else {
throw new RuntimeException("获取AccessToken失败: " + json.getString("errmsg"));
}
}
}
5. 模板消息推送服务
@Service
public class TemplateMessageService {
@Autowired
private AccessTokenService tokenService;
@Autowired
private WechatConfig wechatConfig;
public void sendTemplateMessage(String openId, Map<String, String> data) throws IOException {
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="
+ tokenService.getAccessToken();
// 构建请求体
JSONObject body = new JSONObject();
body.put("touser", openId);
body.put("template_id", wechatConfig.getTemplateId());
body.put("url", "https://yourdomain.com"); // 跳转链接(可选)
// 构建模板数据
JSONObject templateData = new JSONObject();
data.forEach((key, value) -> {
JSONObject item = new JSONObject();
item.put("value", value);
item.put("color", "#173177"); // 文字颜色
templateData.put(key, item);
});
body.put("data", templateData);
// 发送请求
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost(url);
post.setHeader("Content-Type", "application/json");
post.setEntity(new StringEntity(body.toJSONString(), "UTF-8"));
CloseableHttpResponse response = client.execute(post);
String result = EntityUtils.toString(response.getEntity());
JSONObject json = JSON.parseObject(result);
if (json.getInteger("errcode") != 0) {
throw new RuntimeException("模板消息发送失败: " + json.getString("errmsg"));
}
}
}
6. 控制器示例
@RestController
@RequestMapping("/wechat")
public class WechatController {
@Autowired
private TemplateMessageService messageService;
@PostMapping("/send")
public String sendMessage(@RequestParam String openId) {
try {
// 示例模板数据(根据实际模板字段修改)
Map<String, String> data = new HashMap<>();
data.put("first", "您好,您有新的订单!");
data.put("orderNo", "202306270001");
data.put("amount", "¥99.00");
data.put("remark", "点击查看详情");
messageService.sendTemplateMessage(openId, data);
return "消息发送成功";
} catch (Exception e) {
return "发送失败: " + e.getMessage();
}
}
}
三、模板消息格式说明
微信模板消息结构示例:
{
"touser": "OPENID",
"template_id": "TEMPLATE_ID",
"url": "https://example.com",
"data": {
"first": {"value": "通知内容", "color": "#173177"},
"key1": {"value": "值1"},
"key2": {"value": "值2"},
"remark": {"value": "备注信息"}
}
}
四、关键注意事项
-
AccessToken管理
- 必须全局缓存,避免频繁刷新
- 推荐使用Redis存储(分布式环境必备)
-
模板字段匹配
- 模板内容需与公众号申请的模板完全一致
- 字段名(如
first、keyword1)必须匹配
-
用户OpenID获取
- 通过网页授权获取(
snsapi_userinfo) - 或用户与公众号交互时收集
- 通过网页授权获取(
-
错误处理
- 处理微信返回的
errcode(如40001 token失效) - 实现重试机制
- 处理微信返回的
-
性能优化
- 使用连接池管理HTTP请求
- 异步发送消息(结合Spring Async)
五、常见错误码
| 错误码 | 说明 | 解决方案 |
|---|---|---|
| 40001 | Invalid credential | AccessToken过期或无效 |
| 41028 | Form id expired | 表单ID过期(小程序场景) |
| 45009 | API freq out of limit | 接口调用频率超限 |
六、扩展建议
- 消息队列:使用RabbitMQ/Kafka异步处理发送请求
- 发送记录:数据库存储发送状态便于追踪
- 多公众号支持:通过不同配置实现多账号管理
完整实现需结合具体业务场景调整模板内容和用户OpenID获取逻辑。建议先使用微信公众平台接口测试工具(微信官方调试工具)验证消息格式是否正确。