在Spring Boot中对接微信小程序发送订阅消息,需要通过微信服务端API实现。以下是完整的实现步骤和代码示例:
1. 准备工作
- 在小程序后台申请订阅消息模板,获取模板ID
- 确保小程序已获取用户订阅授权(需用户主动触发)
- 获取小程序的AppID和AppSecret
2. 配置参数(application.yml)
perl
wechat:
miniapp:
appid: 你的小程序AppID
secret: 你的小程序AppSecret
subscribe-msg-url: https://api.weixin.qq.com/cgi-bin/message/subscribe/send
access-token-url: https://api.weixin.qq.com/cgi-bin/token
3. 配置类
less
@Data
@Configuration
@ConfigurationProperties(prefix = "wechat.miniapp")
public class WechatMiniConfig {
private String appid;
private String secret;
private String subscribeMsgUrl;
private String accessTokenUrl;
}
4. AccessToken管理(带缓存)
arduino
@Component
public class WechatTokenManager {
private static final Logger logger = LoggerFactory.getLogger(WechatTokenManager.class);
@Autowired
private WechatMiniConfig config;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String ACCESS_TOKEN_KEY = "wechat:miniapp: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 = config.getAccessTokenUrl() + "?grant_type=client_credential" +
"&appid=" + config.getAppid() +
"&secret=" + config.getSecret();
try {
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
if (json.has("errcode")) {
logger.error("获取AccessToken失败:{}", response);
throw new RuntimeException("微信接口调用失败");
}
String newToken = json.get("access_token").getAsString();
int expiresIn = json.get("expires_in").getAsInt();
redisTemplate.opsForValue().set(
ACCESS_TOKEN_KEY,
newToken,
expiresIn - 200, // 提前200秒过期
TimeUnit.SECONDS
);
return newToken;
} catch (Exception e) {
logger.error("刷新AccessToken异常", e);
throw new RuntimeException("微信服务不可用");
}
}
}
5. 订阅消息发送服务
typescript
@Service
public class SubscribeMessageService {
@Autowired
private WechatTokenManager tokenManager;
@Autowired
private WechatMiniConfig config;
public void sendSubscribeMessage(String openid, String templateId,
Map<String, Object> data) {
String accessToken = tokenManager.getAccessToken();
String url = config.getSubscribeMsgUrl() + "?access_token=" + accessToken;
Map<String, Object> params = new HashMap<>();
params.put("touser", openid);
params.put("template_id", templateId);
params.put("data", buildMessageData(data));
params.put("page", "pages/index/index"); // 可选跳转页面
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 Map<String, Map<String, String>> buildMessageData(Map<String, Object> data) {
Map<String, Map<String, String>> result = new HashMap<>();
data.forEach((key, value) -> {
result.put(key, Collections.singletonMap("value", value.toString()));
});
return result;
}
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);
}
}
}
6. 控制器示例
less
@RestController
@RequestMapping("/api/msg")
public class MessageController {
@Autowired
private SubscribeMessageService messageService;
@PostMapping("/send")
public ResponseEntity<?> sendMessage(@RequestBody MessageRequest request) {
Map<String, Object> data = new HashMap<>();
data.put("thing1", new HashMap<String, String>(){{ put("value", "订单状态更新"); }});
data.put("time2", new HashMap<String, String>(){{ put("value", LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); }});
messageService.sendSubscribeMessage(
request.getOpenid(),
"你的模板ID",
data
);
return ResponseEntity.ok().build();
}
}
7. 消息请求DTO
kotlin
@Data
public class MessageRequest {
@NotBlank
private String openid;
// 其他业务参数...
}
8. 注意事项
-
模板参数格式:
- 必须严格按照模板要求的参数格式发送
- 每个参数值需要用
{"value": "内容"}
格式包裹 - 参数值长度需符合模板限制
-
AccessToken管理:
- 必须缓存access_token,每天获取次数有限(2000次/天)
- 推荐使用Redis缓存,设置合理过期时间
-
用户授权:
- 需要用户主动订阅消息(前端需调用wx.requestSubscribeMessage)
- 每个模板的授权是独立的
-
错误处理:
- 40001:access_token无效,需要重新获取
- 43101:用户拒绝接受消息
- 47003:模板参数不准确
-
性能优化:
- 使用连接池(推荐使用OkHttp或Apache HttpClient)
- 异步发送非关键性消息
9. 完整调用流程
- 小程序端获取用户openid
- 用户触发订阅授权(前端调用wx.requestSubscribeMessage)
- 服务端接收发送请求
- 构造符合模板规范的消息内容
- 调用微信消息接口发送
10. 最佳实践建议
- 消息去重:对相同内容的消息添加业务去重机制
- 发送频率控制:避免对用户造成骚扰
- 模板版本管理:当模板修改时做好版本兼容
- 日志记录:记录消息发送日志以便排查问题
- 监控报警:对发送失败的情况建立监控机制
完整实现时建议配合使用:
- Spring Boot Actuator 进行健康监控
- Swagger 生成API文档
- Spring Cache 优化Token缓存
- Hibernate Validator 进行参数校验
以上方案已包含微信订阅消息的核心实现逻辑,可根据具体业务需求扩展消息模板管理、发送记录追踪等功能。