微信小程序模板消息限制实现无限制主动推送的示例代码
需求背景
基于微信的通知渠道,微信小程序为开发者提供了可以高效触达用户的模板消息能力,在用户本人与小程序页面有交互行为后触发,通过微信聊天列表中的服务通知可快捷进入查看消息,点击查看详情还能跳转到下发消息的小程序的指定页面。
微信小程序允许下发模板消息的条件分为两类:支付或者提交表单。通过提交表单来下发模板消息的限制为“允许开发者向用户在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天内,对用户发送多条模板消息唤回用户。