package com.train.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.train.config.WechatProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClientException;
import javax.annotation.Resource;
import java.util.Map;
/**
* 订阅消息服务。
*
触发点(设计文档 §4.7):报名成功、课前 T-7/T-3/T-1、成果被退回、投票结果揭晓、T+ 跟进提醒。
*
test-mode:直接打日志模拟发送,不依赖微信 access_token 与模板;
* 生产:调用微信 subscribeMessage.send(需 access_token,见 WechatService.getAccessToken),
* 模板 ID 通过 {@code wechat.message-template.} 配置(默认随 messageKey 变化)。
* 一次性模板每人每周至多 1 条/模板——发送方需在业务侧保证不触频控。
*/
@Slf4j
@Service
public class SubscribeMessageService {
@Value("${wechat.test-mode}")
private boolean testMode;
/** 微信订阅消息接口地址 */
@Value("${wechat.subscribe-url:https://api.weixin.qq.com/cgi-bin/message/subscribe/send}")
private String subscribeUrl;
@Resource
private WechatService wechatService;
private final RestTemplate restTemplate = new RestTemplate();
/**
* 发送微信订阅消息(一次性模板)。
*
* @param openid 接收者 openid(必填,空则直接返回 false)
* @param templateId 消息模板 ID(train_message_template 配置;空则回退 {@code wechat.message-template.} 默认值)
* @param page 点击跳转的小程序页面路径
* @param data 模板字段 {字段名: {value: xx}}
* @param messageKey 消息语义键(audit_log.action 等);用于按配置取模板 ID
* @return 是否发送成功(test-mode 恒 true,且仅打日志)
*/
public boolean sendSubscribeMessage(String openid, String templateId, String page, Map data, String messageKey) {
if (openid == null || openid.trim().isEmpty()) {
log.debug("订阅消息跳过:openid 为空,templateId={}", templateId);
return false;
}
if (testMode) {
log.info("【测试模式】模拟订阅消息发送: openid={}, templateId={}, page={}, data={}",
openid, templateId, page, data);
return true;
}
try {
// 模板 ID 优先级:显式传入 > 按 messageKey 配置 > 空字符串(返回 false 不误报)
String realTemplateId = templateId;
if (!org.springframework.util.StringUtils.hasText(realTemplateId)) {
realTemplateId = messageKey != null
? templateIdByKey(messageKey)
: "";
}
if (!org.springframework.util.StringUtils.hasText(realTemplateId)) {
log.warn("订阅消息未配置模板 ID,跳过发送: messageKey={}", messageKey);
return false;
}
String accessToken = wechatService.getAccessToken();
String url = subscribeUrl + "?access_token=" + accessToken;
JSONObject body = new JSONObject();
body.put("touser", openid);
body.put("template_id", realTemplateId);
body.put("page", page == null ? "pages/mine/index" : page);
JSONObject dataJson = new JSONObject();
if (data != null) {
for (Map.Entry entry : data.entrySet()) {
JSONObject item = new JSONObject();
item.put("value", entry.getValue() == null ? "" : String.valueOf(entry.getValue()));
dataJson.put(entry.getKey(), item);
}
}
body.put("data", dataJson);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity response = restTemplate.postForEntity(
url, new HttpEntity<>(body.toJSONString(), headers), String.class);
JSONObject json = JSON.parseObject(response.getBody());
int errcode = json.getIntValue("errcode");
if (errcode != 0) {
log.warn("订阅消息发送失败 errcode={} errmsg={} (openid={}, templateId={})",
errcode, json.getString("errmsg"), openid, realTemplateId);
return false;
}
return true;
} catch (RestClientException e) {
log.error("订阅消息发送异常 (openid={})", openid, e);
return false;
}
}
/**
* 从配置读取 messageKey 对应的模板 ID(缺省为空字符串)。
* 配置形如:wechat.message-template.followup-t1:
*/
@Resource
private WechatProperties wechatProperties;
private String templateIdByKey(String messageKey) {
Map messageTemplates = wechatProperties.getMessageTemplate();
if (messageTemplates == null) {
return "";
}
String templateId = messageTemplates.get(messageKey);
return templateId == null ? "" : templateId;
}
/**
* 按 openid + messageKey 直接发送(模板 ID 从配置取,page 默认「我的」)。
*
* @return 是否发送成功
*/
public boolean sendToUser(String openid, String messageKey, String page, Map data) {
return sendSubscribeMessage(openid, null, page, data, messageKey);
}
}