以下是使用 Spring Boot 对接微信公众号发送模板消息的详细步骤和完整实现方案:
1. 准备工作
-
公众号申请:
- 拥有认证的服务号(订阅号不支持模板消息)
- 在公众号后台开通「模板消息」功能
-
获取模板ID:
- 在公众号后台 -> 功能 -> 模板消息 -> 添加模板
- 选择行业模板或自定义模板内容
- 记录生成的模板ID(如:
XyZ1234...
)
-
获取凭证:
AppID
和AppSecret
(公众号后台 -> 开发 -> 基本配置)
2. 项目配置
2.1 application.yml
配置
python
# 微信配置
wechat:
public:
app-id: wx1234567890abcdef
app-secret: 0123456789abcdef0123456789abcdef
template-id: XyZ1234567890abcdefg # 模板消息ID
access-token-url: https://api.weixin.qq.com/cgi-bin/token
template-msg-url: https://api.weixin.qq.com/cgi-bin/message/template/send
2.2 配置类
less
@Data
@Configuration
@ConfigurationProperties(prefix = "wechat.public")
public class WechatPublicConfig {
private String appId;
private String appSecret;
private String templateId;
private String accessTokenUrl;
private String templateMsgUrl;
}
3. AccessToken 管理
3.1 Redis 缓存实现
arduino
@Component
public class WechatAccessTokenManager {
@Autowired
private WechatPublicConfig config;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String ACCESS_TOKEN_KEY = "wechat:public:access_token";
public String getAccessToken() {
String token = redisTemplate.opsForValue().get(ACCESS_TOKEN_KEY);
if (StringUtils.isNotBlank(token)) {
return token;
}
return refreshAccessToken();
}
private synchronized String refreshAccessToken() {
String url = String.format("%s?grant_type=client_credential&appid=%s&secret=%s",
config.getAccessTokenUrl(), config.getAppId(), config.getAppSecret());
try {
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
if (json.has("errcode")) {
throw new RuntimeException("获取Token失败: " + response);
}
String newToken = json.get("access_token").getAsString();
int expiresIn = json.get("expires_in").getAsInt();
redisTemplate.opsForValue().set(
ACCESS_TOKEN_KEY,
newToken,
expiresIn - 300, // 提前5分钟过期
TimeUnit.SECONDS
);
return newToken;
} catch (Exception e) {
throw new RuntimeException("刷新AccessToken失败", e);
}
}
}
4. 模板消息发送服务
4.1 消息发送核心类
typescript
@Service
public class TemplateMessageService {
@Autowired
private WechatAccessTokenManager tokenManager;
@Autowired
private WechatPublicConfig config;
public void sendTemplateMessage(String openId, Map<String, TemplateData> data) {
String accessToken = tokenManager.getAccessToken();
String url = config.getTemplateMsgUrl() + "?access_token=" + accessToken;
Map<String, Object> params = new HashMap<>();
params.put("touser", openId);
params.put("template_id", config.getTemplateId());
params.put("url", "https://yourdomain.com/detail"); // 跳转链接(可选)
params.put("data", data);
try {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
handleResponse(response.getBody());
} catch (Exception e) {
throw new RuntimeException("发送模板消息失败", e);
}
}
private void handleResponse(String responseBody) {
JsonObject json = JsonParser.parseString(responseBody).getAsJsonObject();
int errcode = json.get("errcode").getAsInt();
if (errcode != 0) {
String errmsg = json.get("errmsg").getAsString();
throw new RuntimeException("微信接口错误[" + errcode + "]: " + errmsg);
}
}
}
// 模板数据封装类
@Data
@AllArgsConstructor
public class TemplateData {
private String value;
private String color; // 可选颜色值,如"#173177"
}
5. 控制器调用示例
less
@RestController
@RequestMapping("/wechat/msg")
public class MessageController {
@Autowired
private TemplateMessageService messageService;
@PostMapping("/send")
public ResponseEntity<String> sendMsg(@RequestParam String openId) {
// 构造模板参数(需与模板内容匹配)
Map<String, TemplateData> data = new HashMap<>();
data.put("first", new TemplateData("订单支付成功通知", "#173177"));
data.put("keyword1", new TemplateData("2023123456789", null));
data.put("keyword2", new TemplateData("¥199.00", "#FF0000"));
data.put("remark", new TemplateData("感谢您的支持!", null));
messageService.sendTemplateMessage(openId, data);
return ResponseEntity.ok("消息已发送");
}
}
6. 模板消息参数规范
假设模板内容为:
{{first.DATA}}
订单编号:{{keyword1.DATA}}
订单金额:{{keyword2.DATA}}
{{remark.DATA}}
发送时 data
参数必须包含:
json
{
"data": {
"first": {"value": "..."},
"keyword1": {"value": "..."},
"keyword2": {"value": "..."},
"remark": {"value": "..."}
}
}
7. 注意事项
-
模板匹配:
- 参数名称必须与模板中的
{{keywordX.DATA}}
完全一致 - 参数个数必须匹配模板定义
- 参数名称必须与模板中的
-
颜色值:
- 可选参数,默认黑色
- 使用十六进制格式(如
#173177
)
-
用户授权:
- 用户必须关注公众号
- 模板消息需要用户授权(不同场景授权方式不同)
-
频率限制:
- 单个用户每日接收上限为 5 条
- 同一内容 30 秒内不可重复发送
-
错误处理:
- 40001:AccessToken 失效,需刷新后重试
- 41028:form_id 不正确(适用于一次性订阅场景)
- 45009:API 调用太频繁
8. 完整调用流程
- 用户关注公众号,获取用户
openid
- 用户触发业务事件(如支付成功)
- 服务端构造模板参数
- 获取有效的
access_token
- 调用微信模板消息接口发送
- 处理发送结果(成功/失败)
9. 高级优化建议
- 异步发送 :使用
@Async
异步处理非实时消息 - 消息队列:通过 RabbitMQ/Kafka 解耦消息发送
- 模板管理:将模板ID存入数据库,实现动态配置
- 日志追踪:记录消息发送日志用于审计和排查问题
- HTTPS 证书:确保回调地址使用有效的 HTTPS 证书
通过以上步骤即可实现微信公众号模板消息的发送功能,建议结合具体业务需求进行扩展。