微信小程序模板消息限制实现无限制主动推送的示例代码


Posted in Javascript onAugust 27, 2019

需求背景

基于微信的通知渠道,微信小程序为开发者提供了可以高效触达用户的模板消息能力,在用户本人与小程序页面有交互行为后触发,通过微信聊天列表中的服务通知可快捷进入查看消息,点击查看详情还能跳转到下发消息的小程序的指定页面。

微信小程序允许下发模板消息的条件分为两类:支付或者提交表单。通过提交表单来下发模板消息的限制为“允许开发者向用户在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天内,对用户发送多条模板消息唤回用户。

Javascript 相关文章推荐
Firefox/Chrome/Safari的中可直接使用$/$$函数进行调试
Feb 13 Javascript
文本框获得焦点和失去焦点的判断代码
Mar 18 Javascript
JS时间选择器 兼容IE6,7,8,9
Jun 26 Javascript
javascript为下拉列表动态添加数据项
May 23 Javascript
jQuery实现响应鼠标滚动的动感菜单效果
Sep 21 Javascript
Bootstrap基本组件学习笔记之缩略图(13)
Dec 08 Javascript
CodeMirror js代码加亮使用总结
Mar 25 Javascript
JS判断非空至少输入两个字符的简单实现方法
Jun 23 Javascript
vue使用mint-ui实现下拉刷新和无限滚动的示例代码
Nov 06 Javascript
Vue2.0结合webuploader实现文件分片上传功能
Mar 09 Javascript
使用React手写一个对话框或模态框的方法示例
Apr 25 Javascript
vue实现的封装全局filter并统一管理操作示例
Feb 02 Javascript
jQuery实现判断滚动条滚动到document底部的方法分析
Aug 27 #jQuery
JS多个表单数据提交下的serialize()应用实例分析
Aug 27 #Javascript
解决Vue中 父子传值 数据丢失问题
Aug 27 #Javascript
原生javascript自定义input[type=radio]效果示例
Aug 27 #Javascript
基于JS抓取某高校附近共享单车位置 使用web方式展示位置变化代码实例
Aug 27 #Javascript
使用Vue.js中的过滤器实现幂方求值的方法
Aug 27 #Javascript
Vue+ElementUI项目使用webpack输出MPA的方法
Aug 27 #Javascript
You might like
php下关于中英数字混排的字符串分割问题
2010/04/06 PHP
详解php中反射的应用
2016/03/15 PHP
PHP实现基于mysqli的Model基类完整实例
2016/04/08 PHP
ThinkPHP中Common/common.php文件常用函数功能分析
2016/05/20 PHP
PHP封装的非对称加密RSA算法示例
2018/05/28 PHP
图标线性回归斜着移动到指定的位置
2013/08/16 Javascript
可选择和输入的下拉列表框示例
2013/11/05 Javascript
jQuery实现仿QQ空间装扮预览图片的鼠标提示效果代码
2015/10/30 Javascript
JavaScript知识点总结(五)之Javascript中两个等于号(==)和三个等于号(===)的区别
2016/05/31 Javascript
JS实现可编辑的后台管理菜单功能【附demo源码下载】
2016/09/13 Javascript
JS+DIV实现的卷帘效果示例
2017/03/22 Javascript
详解mpvue中小程序自定义导航组件开发指南
2019/02/11 Javascript
JS div匀速移动动画与变速移动动画代码实例
2019/03/26 Javascript
javascript事件循环event loop的简单模型解释与应用分析
2020/03/14 Javascript
python网络编程学习笔记(八):XML生成与解析(DOM、ElementTree)
2014/06/09 Python
Django实现图片文字同时提交的方法
2015/05/26 Python
Numpy数组转置的两种实现方法
2018/04/17 Python
python实现超市扫码仪计费
2018/05/30 Python
python绘制散点图并标记序号的方法
2018/12/11 Python
Python3实现从排序数组中删除重复项算法分析
2019/04/03 Python
python3 字符串/列表/元组(str/list/tuple)相互转换方法及join()函数的使用
2019/04/03 Python
Pytorch实现各种2d卷积示例
2019/12/30 Python
Python中的xlrd模块使用原理解析
2020/05/21 Python
世界著名的顶级牛排:Omaha Steak(奥马哈牛排)
2016/09/20 全球购物
日本著名化妆品零售网站:Cosme Land
2019/03/01 全球购物
Tea Collection官网:一家位于旧金山的童装公司
2020/08/07 全球购物
几道数据库的概念性面试题
2014/05/30 面试题
药学专业大学生个人的自我评价
2013/11/04 职场文书
写好自荐信的要点
2013/11/06 职场文书
大学在校生求职信范文
2013/11/21 职场文书
社区志愿者心得体会
2014/01/03 职场文书
企业2014年度工作总结
2014/12/10 职场文书
药品销售员2015年终工作总结
2015/10/22 职场文书
Python使用sql语句对mysql数据库多条件模糊查询的思路详解
2021/04/12 Python
在CSS中映射鼠标位置并实现通过鼠标移动控制页面元素效果(实例代码)
2021/04/22 HTML / CSS
pandas中关于apply+lambda的应用
2022/02/28 Python