微信小程序订阅消息升级,以前的消息模板不适合使用了
官方文档 微信官方文档
需求
使用小程序开直播宣讲会,学生可以预约,已经预约的学生发送开播前提醒以及开播提醒
整体流程
1、由产品在微信小程序中设计好模板,并且把模板id及内容给到前端及后端
2、前端在小程序页面发起消息订阅授权,用户同意授权后,把微信返回的templateIds传给后端
3、后端保存templateIds及组装好相应的内容,保存到数据库
4、使用定时器去查询待发送的数据,发送给用户(开播前提醒)
5、用户播时,发送开播提醒
后端关键设计及代码
定义模板
public interface ConstantTemplate {
/**
* 提前提醒
*/
String CLOUD_PREACH_TEMPLATE_ID = "xxxxxxxxxxxxxx";
String CLOUD_PREACH_TEMPLATE_DATA = "{\"time1\":{\"value\":\"%s\"},\"thing2\":{\"value\":\"%s\"},\"thing3\":{\"value\":\"%s\"}}";
/**
* 开播提醒
*/
String INTERVIEW_NOTICE_TEMPLATE_ID = "xxxxxxxxxxxxxxxx";
String INTERVIEW_NOTICE_TEMPLATE_DATA ="{\"thing1\":{\"value\":\"%s\"},\"thing2\":{\"value\":\"%s\"},\time3\":{\"value\":\"%s\"},\"thing4\":{\"value\":\"%s\"},\"thing5\":{\"value\":\"%s\"}}";
}
@Getter
public enum WxMiniMsgTemplateEnum {
/**
* 微信小程序消息模板
*/
CLOUD_PREACH_TEMPLATE(CLOUD_PREACH_TEMPLATE_ID, CLOUD_PREACH_TEMPLATE_DATA),
INTERVIEW_NOTICE_TEMPLATE(INTERVIEW_NOTICE_TEMPLATE_ID, INTERVIEW_NOTICE_TEMPLATE_DATA);
private final String id;
private final String data;
WxMiniMsgTemplateEnum(String id, String data) {
this.id = id;
this.data = data;
}
public static String getDataById(String id) {
return Arrays.stream(WxMiniMsgTemplateEnum.values()).filter(r -> r.getId().equals(id)).findFirst().map(WxMiniMsgTemplateEnum::getData).orElse(null);
}
}
保存消息
for (String templateId : dto.getTemplateIds()) {
//发送小程序订阅消息
XyWxMiniMsgDTO wxMsgDTO = new XyWxMiniMsgDTO();
wxMsgDTO.setPage(dto.getPage());
wxMsgDTO.setEndpoint(com.xyedu.sims.common.enums.SourceTypeEnum.STUDENT_MINI.getCode());
wxMsgDTO.setLang("zh_CN");
wxMsgDTO.setMiniprogramState(ConstantUtil.MiniprogramState);
wxMsgDTO.setTouser(social.getOpenId());
LocalDateTime sendTime = topicInfo.getStartTime();
LocalDateTime pastTime = topicInfo.getEndTime();
//提前20分钟发送通知
wxMsgDTO.setSendTime(sendTime.minusMinutes(20));
wxMsgDTO.setPastTime(pastTime);
String date = DateUtil.getDefaultFormatDate(DateUtil.localDateTimeToDate(topicInfo.getStartTime()));
//组装模板数据
String data = WxMiniMsgTemplateEnum.getDataById(templateId);
if (ToolUtil.isNotEmpty(data)) {
data = String.format(data, date, StrUtil.subWithLength(topicInfo.getTitle(), 0, 20), userCache.getRealName() + "同学,云宣讲即将开始~");
wxMsgDTO.setData(JSONUtil.toJsonStr(data));
}
wxMsgDTO.setTemplateId(templateId);
XyWxMiniMsg miniMsg = BeanUtil.copy(wxMsgDTO, XyWxMiniMsg.class);
//保存至数据库
remoteXyWxMiniMsgService.add(miniMsg);
}
封装发送工具
@Slf4j
public class WxMiniMsgUtil {
//官方文档: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
private static final String WX_MINI_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=";
public static Object send(String accessToken, XyWxMiniMsgDTO dto) {
String url = WX_MINI_MSG_URL + accessToken;
Map<String, Object> reqBody = new HashMap<>();
reqBody.put("touser", dto.getTouser());
reqBody.put("template_id", dto.getTemplateId());
reqBody.put("miniprogram_state", dto.getMiniprogramState());
reqBody.put("lang", dto.getLang());
reqBody.put("page", dto.getPage());
reqBody.put("data", JSONUtil.parseObj(dto.getData()));
try {
return HttpRequest.post(url)
.body(JSONUtil.toJsonStr(reqBody))
.timeout(5000)
.execute()
.body();
} catch (Exception e) {
log.error("send wx mini msg occur an exception,e={}", e.getMessage());
return null;
}
}
}
使用定时器发送,定时器每3分钟执行一次查询,查询未发送及发送失败的消息,超过时间的,则会设置发送状态为过期
@Getter
public enum WxMiniMsgSendStatusEnum {
/**
*
*/
SENDING(0, "未发送"),
SEND_SUCCESS(1, "发送成功"),
SEND_FAIL(2, "发送失败"),
OVERDUE(3,"过期");
private Integer code;
private String msg;
WxMiniMsgSendStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static String getMsg(Object code) {
if (code == null) {
return null;
}
return Arrays.stream(values()).filter(e -> e.getCode().equals(code)).findFirst().map(e -> e.getMsg()).orElse("");
}
}
@Override
public ReturnT<String> execute(String param) throws Exception {
log.debug("[send wx mini msg]-start");
XyWxMiniMsgQueryDTO dto = new XyWxMiniMsgQueryDTO();
dto.setEndpoint(SourceTypeEnum.STUDENT_MINI.getCode());
//从数据库中获取应发送的数据列表
List<XyWxMiniMsg> list = remoteXyWxMiniMsgService.list(dto);
if (CollectionUtil.isNotEmpty(list)) {
log.debug("[send wx mini msg]-query list,list.size={}", list.size());
final WxMaService wxService = WxMaConfiguration.getMaService();
String accessToken = wxService.getAccessToken();
for (XyWxMiniMsg msg : list) {
XyWxMiniMsgDTO msgDTO = BeanUtil.copy(msg, XyWxMiniMsgDTO.class);
//通知微信发送订阅消息
Object response = WxMiniMsgUtil.send(accessToken, msgDTO);
if (ToolUtil.isNotEmpty(response)) {
msg.setResponse(response.toString());
JSONObject object = JSONUtil.parseObj(response);
//处理微信响应信息
if (object.containsKey("errcode")
&& NumberUtil.isNumber(object.get("errcode").toString())
&& Integer.valueOf(object.get("errcode").toString()) == 0) {
msg.setSendStatus(WxMiniMsgSendStatusEnum.SEND_SUCCESS.getCode());
} else if (object.containsKey("errcode")
&& NumberUtil.isNumber(object.get("errcode").toString())
&& Integer.valueOf(object.get("errcode").toString()) == 42001) {
log.error("[send wx mini msg]-response fail,refresh accessToken={}", response);
msg.setSendStatus(WxMiniMsgSendStatusEnum.SEND_FAIL.getCode());
WxMaConfiguration.getMaService().getAccessToken(true);
} else {
msg.setSendStatus(WxMiniMsgSendStatusEnum.SEND_FAIL.getCode());
log.error("[send wx mini msg]-response fail,response={}", response);
}
} else {
msg.setSendStatus(WxMiniMsgSendStatusEnum.SEND_FAIL.getCode());
log.error("[send wx mini msg]-response empty,may be catch exception,params[accessToken={}\n,msgDTO={}]", accessToken, msgDTO);
}
}
remoteXyWxMiniMsgService.updateBatch(list);
}
log.debug("[send wx mini msg]-finish");
return ReturnT.SUCCESS;
}
需求第5点未完成,思路大概是在组装提前提醒模板时,同时保存开播提醒消息至数据库,并使用templateId+宣讲会id作为唯一标识,在开播时查询数据库对应的待发送消息,最后通过工具类进行发送即可