背景
消息订阅是解决小程序对用户主动发起通知的问题。
通知示例如下:


消息订阅实现有三种:

由于本小程序不属于线下公共服务,也无微信支付功能,因此选用一次性订阅消息(弹窗订阅)。(注意:一次性订阅,是指用户调用了wx.requestSubscribeMessage一次,小程序可发送一次通知,不能多次发送,若再次发送通知,只能用户再调用wx.requestSubscribeMessage一次)
消息订阅全流程:

前端实现
- 步骤一:获取模板 ID
从公共模版库选用一个符合的模版,记录模版ID

- 步骤二:获取下发权限
前端只能通过点击事件触发wx.requestSubscribeMessage。
typescript
// 做了防多次点击处理。
const subscribe = (item?: any) => {
if (item.disabled) {
return
}
if (isSubscribing) {
uni.showToast({
title: '正在订阅中,请稍后',
icon: 'none',
})
return
}
isSubscribing = true
uni.requestSubscribeMessage({
tmplIds: [TEMPLATE_ID],// TEMPLATE_ID替换为选用的模版id
success(res: any) {
uni.hideLoading()
if (res[TEMPLATE_ID] === 'accept') {
setSubscribeStatus(true, '已订阅')
uni.showToast({
title: '订阅成功',
icon: 'success',
})
}
},
fail() {
uni.showToast({
title: '订阅失败',
icon: 'error',
})
},
complete: () => {
isSubscribing = false
},
})
}
后端实现
技术栈:springboot 本小程序业务需要,需要每天执行一次通知请求。因此先实现了一个定时任务类。
实现定时任务
1.在入口类,开启定时任务:@EnableScheduling
less
@EnableScheduling // 开启定时任务
@SpringBootApplication
public class BirthdayReminderApplication {
public static void main(String[] args) {
SpringApplication.run(BirthdayReminderApplication.class, args);
}
}
- 定时任务。每天定时发送通知
ini
public class SubscriptionScheduler {
@Autowired
BirthdayService birthdayService;
@Autowired
WxSubscribeService wxSubscribeService;
@Scheduled(cron = "0 30 11 * * ?") // 每天上午11:30点执行
public void sendDailySubscription() {
// 1. 查询需要发送订阅消息的用户列表
List<Birthday> allBirthdayList = birthdayService.allList();
List<Birthday> remindBirthdayList = CommonUtil.getReminderDate(allBirthdayList);
String token = wxSubscribeService.getToken();
for (Birthday birthday : remindBirthdayList) {
wxSubscribeService.sendMsg(token, birthday);
}
}
}
订阅实现
流程:
需调用两个接口:先获取access_token 微信官方文档-获取token api-> 发送订阅消息请求微信官方文档-发送订阅api
获取access_token api参数需要用户小程序appid 以及secret
订阅消息请求 api参数需要 发送的用户openid,模版id,模版字段
注意:
- 有些模版的字段长度可能有限制,而模版并未注明,不满足会导致发送通知请求失败。如本小程序选择的模版,字段name1 限制为3个字符,若超过,通知并未成功发送。
ini
@Service
public class WxSubscribeServiceImpl implements WxSubscribeService {
@Autowired
private WeChatProperties weChatProperties;
@Override
public String getToken() {
String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
Map<String, String> map = new HashMap<>();
map.put("appId", weChatProperties.getAppid());
map.put("secret", weChatProperties.getSecret());
map.put("grant_type", "client_credential");
String result = HttpClientUtil.doGet(tokenUrl, map);
JSONObject jsonObject = JSON.parseObject(result);
String token = jsonObject.getString("access_token");
System.out.println(result + token);
return token;
}
@Override
public void sendMsg(String token, Birthday birthday) {
String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + token;
HashMap<String, Object> map = new HashMap<>();
map.put("touser", birthday.getCreator());
map.put("template_id", weChatProperties.getTemplateId());
map.put("page", "pages/index/index");
try {
HashMap<String, Object> data = new HashMap<>();
String name = birthday.getName().length() > 3 ? birthday.getName().substring(0, 3) : birthday.getName();
data.put("name1", formatParam(name));
String solarBirthdayInThisYear = birthdayInThisYear(birthday.getBirthday());
if (birthday.getBirthdayType().equals(BirthdayType.LUNAR)) {
solarBirthdayInThisYear = CommonUtil.lunar2Solar(solarBirthdayInThisYear);
}
long daysDiff = ChronoUnit.DAYS.between(parseDate(solarBirthdayInThisYear), LocalDate.now());
String dayDiffStr = daysDiff <= 0 ? "今天" : (daysDiff + "天后");
data.put("thing2", formatParam(dayDiffStr));
String dateStr = birthday.getBirthdayType().equals(BirthdayType.LUNAR) ?
CommonUtil.lunarBirthdayToText(birthday.getBirthday()) :
CommonUtil.getMonthDay(birthday.getBirthday());
data.put("thing6", formatParam(dateStr));
data.put("thing5", formatParam("别忘了送上生日祝福哦"));
map.put("data", data);
String jsonObject = JSON.toJSONString(map);
HttpUtil.post(url, jsonObject);
} catch (Exception e) {
throw new RuntimeException("消息发送失败", e);
}
}
}
效果:
小程序端:发起一次性消息的订阅:(示例图) (可理解为用户授权允许消息通知)

服务器端发送消息通知: