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


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 相关文章推荐
把textarea中字符串里含有的回车换行替换成&amp;lt;br&amp;gt;的javascript代码
Apr 20 Javascript
node.js中的fs.fchmodSync方法使用说明
Dec 16 Javascript
JavaScript实现函数返回多个值的方法
Jun 09 Javascript
详解Angular.js指令中scope类型的几种特殊情况
Feb 21 Javascript
详解node-ccap模块生成captcha验证码
Jul 01 Javascript
js学习总结_基于数据类型检测的四种方式(必看)
Jul 04 Javascript
vue中SPA单页面应用程序详解
Nov 07 Javascript
Three.js 再探 - 写一个微信跳一跳极简版游戏
Jan 04 Javascript
webpack4 CSS Tree Shaking的使用
Sep 03 Javascript
axios 封装上传文件的请求方法
Sep 26 Javascript
详解阿里Node.js技术文档之process模块学习指南
Jan 04 Javascript
WebStorm无法正确识别Vue3组合式API的解决方案
Feb 18 Vue.js
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 array_keys 返回数组的键名
2016/10/25 PHP
详解EventDispatcher事件分发组件
2016/12/25 PHP
PHP 实现浏览记录并按日期分组
2017/05/11 PHP
laravel通过创建自定义artisan make命令来新建类文件详解
2017/08/17 PHP
PHP添加PNG图片背景透明水印操作类定义与用法示例
2019/03/12 PHP
Javascript 圆角div的实现代码
2009/10/15 Javascript
JQuery+JS实现仿百度搜索结果中关键字变色效果
2011/08/02 Javascript
javaScript让文本框内的最后一个文字的后面获得焦点实现代码
2013/01/06 Javascript
多个datatable共存造成多个表格的checkbox都被选中
2013/07/11 Javascript
jquery实现输入框动态增减的实例代码
2013/07/14 Javascript
JS操作数据库的实例代码
2013/10/17 Javascript
浅谈JavaScript 的执行顺序
2015/08/07 Javascript
轻松实现js弹框显示选项
2016/09/13 Javascript
bootstrap配合Masonry插件实现瀑布式布局
2017/01/18 Javascript
bootstrap为水平排列的表单和内联表单设置可选的图标
2017/02/15 Javascript
vue-cli的webpack模板项目配置文件分析
2017/04/01 Javascript
Vue的Flux框架之Vuex状态管理器
2017/07/30 Javascript
Angularjs上传图片实例详解
2017/08/06 Javascript
详解react-native-fs插件的使用以及遇到的坑
2017/09/12 Javascript
Bootstrap的aria-label和aria-labelledby属性实例详解
2018/11/02 Javascript
Vue.js 中的 v-cloak 指令及使用详解
2018/11/19 Javascript
JavaScript实现背景自动切换小案例
2019/09/27 Javascript
python pandas读取csv后,获取列标签的方法
2018/11/12 Python
Python中extend和append的区别讲解
2019/01/24 Python
使用Python串口实时显示数据并绘图的例子
2019/12/26 Python
基于python 取余问题(%)详解
2020/06/03 Python
python 爬取英雄联盟皮肤并下载的示例
2020/12/04 Python
python 生成正态分布数据,并绘图和解析
2020/12/21 Python
英国高档百货连锁店:John Lewis
2017/11/20 全球购物
璀璨的珍珠、密钉和个性化珠宝:Lily & Roo
2021/01/21 全球购物
介绍一下Linux文件的记录形式
2013/09/29 面试题
计算机专业毕业生求职信分享
2013/12/24 职场文书
2015年度团总支工作总结
2015/04/23 职场文书
交通事故调解协议书
2015/05/20 职场文书
建筑工程挂靠协议书
2016/03/23 职场文书
Golang 链表的学习和使用
2022/04/19 Golang