需求背景
基于微信的通知渠道, 微信小程序为开发者提供了可以高效触达用户的模板消息能力, 在用户本人与小程序页面有交互行为后触发, 通过微信聊天列表中的服务通知可快捷进入查看消息, 点击查看详情还能跳转到下发消息的小程序的指定页面
微信小程序允许下发模板消息的条件分为两类: 支付或者提交表单通过提交表单来下发模板消息的限制为允许开发者向用户在 7 天内推送有限条数的模板消息 (1 次提交表单可下发 1 条, 多次提交下条数独立, 相互不影响)
然而, 用户 1 次触发 7 天内推送 1 条通知是明显不够用的比如, 签到功能利用模板消息的推送来提醒用户每天签到, 只能在用户前一天签到的情况下, 获取一次推送模板消息的机会, 然后用于第二天向该用户发送签到提醒但是很多情况下, 用户在某一天忘记签到, 系统便失去了提醒用户的权限, 导致和用户断开了联系; 再比如, 系统想主动告知用户即将做某活动, 然而由于微信小程序被动触发通知的限制, 系统将无法主动推送消息
如何突破模板消息的推送限制?
突破口: 1 次提交表单可下发 1 条, 多次提交下发条数独立, 相互不影响
为了突破模板消息的推送限制, 实现 7 天内任性推送, 只需收集到足够的推送码, 即每次提交表单时获取到的 formId 一个 formId 代表着开发者有向当前用户推送模板消息的一次权限
客户端
收集推送码
当表单组件中的属性 report-submit=true 时表示发送模板消息, 提交表单便可以获取 formId 接下来只要对原先的页面进行改造, 将用户原先绑定了点击事件的界面用表单组件中的 button 按钮组件来代替, 即把用户的交互点击的 bindtap 事件由表单 bindsubmit 来代替, 从而捕获用户的点击事件来生成更多的推送码
- // 收集推送码
- Page({
- formSubmit: funcition(e) {
- let formId = e.detail.formId;
- this.collectFormIds(formId); // 保存推送码
- let type = e.detail.target.dataset.type; // 根据 type 执行点击事件
- },
- collectFormIds: function(formId) {
- let formIds = app.globalData.globalFormIds; // 获取全局推送码数组
- if (!formIds)
- formIds = [];
- let data = {
- formId: formId,
- expire: new Data().getTime() + 60480000 // 7 天后的过期时间戳
- }
- formIds.push(data);
- app.globalData.globalFormIds = formIds;
- },
- })
上报推送码
等待用户下一次发起网络请求时, 将 globalFormIds 发送给服务器
- // 上报推送码
- Page({
- onLoad: funcition(e) {
- this.uploadFormIds(); // 上传推送码
- },
- collectFormIds: function(formId) {
- var formIds = app.globalData.globalFormIds; // 获取全局推送码
- if (formIds.length) {
- formIds = JSON.stringify(formIds); // 转换成 JSON 字符串
- app.globalData.gloabalFomIds = ''; // 清空当前全局推送码
- }
- wx.request({ // 发送到服务器
- url: 'http://xxx',
- method: 'POST',
- data: {
- openId: 'openId',
- formIds: formIds
- },
- success: function(res) {
- }
- });
- },
- })
服务端
存储推送码
高频 IO, 采用 Redis 来存储推送码
- /**
- * 收集用户推送码
- *
- * @param openId 用户的 openid
- * @param formTemplates 用户的表单模板
- */
- public void collect(String openId, List<FormTemplateVO> formTemplates) {
- redisTemplate.opsForList().rightPushAll("mina:openid:" + openId, formTemplates);
- }
推送模板消息
下面实现了群发的功能, 针对特定用户类似
- /**
- * 推送消息
- *
- * @param templateId 模板消息 id
- * @param page 跳转页面
- * @param keyWords 模板内容
- */
- public void push(String templateId, String page, String keyWords) {
- String logPrefix = "推送消息";
- // 获取 access token
- String accessToken = this.getAccessToken();
- // 创建消息通用模板
- MsgTemplateVO msgTemplateVO = MsgTemplateVO.builder().template_id(templateId).build();
- // 跳转页面
- msgTemplateVO.setPage(StringUtils.isNotBlank(page) ? page: "");
- // 模板内容
- if (StringUtils.isNotBlank(keyWords)) {
- String[] keyWordArr = keyWords.split(BaseConsts.COMMA_STR);
- Map < String,
- MsgTemplateVO.KeyWord > keyWordMap = new HashMap < >(8);
- for (int i = 0; i < keyWordArr.length; i++) {
- MsgTemplateVO.KeyWord keyWord = msgTemplateVO.new KeyWord(keyWordArr[i]);
- keyWordMap.put(MsgTemplateVO.KEYWORD + (i + 1), keyWord);
- }
- msgTemplateVO.setData(keyWordMap);
- } else {
- msgTemplateVO.setData(Collections.emptyMap());
- }
- // 获取所有用户
- List < String > openIdList = minaRedisDao.getAllOpenIds();
- for (String openId: openIdList) {
- // 获取有效推送码
- String formId = minaRedisDao.getValidFormId(openId);
- if (StringUtils.isBlank(formId)) {
- LOGGER.error("{}>>>openId={}>>> 已无有效推送码 [失败]", logPrefix, openId);
- continue;
- }
- // 指派消息
- MsgTemplateVO assignMsgTemplateVO = msgTemplateVO.assign(openId, formId);
- // 发送消息
- Map < String,
- Object > resultMap;
- try {
- String jsonBody = JsonUtils.getObjectMapper().writeValueAsString(assignMsgTemplateVO);
- String resultBody = OkHttpUtils.getInstance().postAsString(messageUrl + accessToken, jsonBody);
- resultMap = JsonUtils.getObjectMapper().readValue(resultBody, Map.class);
- } catch(IOException e) {
- LOGGER.error("{}>>>openId={}>>>{}[失败]", logPrefix, openId, e.getMessage(), e);
- continue;
- }
- if ((int) resultMap.get(ResponseConsts.Mina.CODE) != 0) {
- LOGGER.error("{}>>>openId={}>>>{}[失败]", logPrefix, openId, resultMap.get(ResponseConsts.Mina.MSG));
- continue;
- }
- LOGGER.info("{}>>>openId={}>>>[成功]", logPrefix, openId);
- }
- }
- /**
- * 根据用户获取有效的推送码
- *
- * @param openId 用户的 openid
- * @return 推送码
- */
- public String getValidFormId(String openId) {
- List < FormTemplateVO > formTemplates = redisTemplate.opsForList().range("mina:openid:" + openId, 0, -1);
- String validFormId = "";
- int trimStart = 0;
- int size;
- for (int i = 0; i < (size = formTemplates.size()); i++) {
- if (formTemplates.get(i).getExpire() > System.currentTimeMillis()) {
- validFormId = formTemplates.get(i).getFormId();
- trimStart = i + 1;
- break;
- }
- }
- // 移除本次使用的和已过期的
- redisTemplate.opsForList().trim(KEY_MINA_PUSH + openId, trimStart == 0 ? size: trimStart, -1);
- return validFormId;
- }
以上方案可以实现在用户最后一次使用小程序后的 7 天内, 对用户发送多条模板消息唤回用户
作者: Joker_Coding
链接:
https://www.jianshu.com/p/3b02d75ef0dc
来源: http://blog.csdn.net/rolan1993/article/details/79398362