微信小程序模板消息限制实现无限制主动推送的示例代码
需求背景
基于微信的通知渠道,微信小程序为开发者提供了可以高效触达用户的模板消息能力,在用户本人与小程序页面有交互行为后触发,通过微信聊天列表中的服务通知可快捷进入查看消息,点击查看详情还能跳转到下发消息的小程序的指定页面。
微信小程序允许下发模板消息的条件分为两类:支付或者提交表单。通过提交表单来下发模板消息的限制为“允许开发者向用户在7天内推送有限条数的模板消息(1次提交表单可下发1条,多次提交下条数独立,相互不影响)”。
然而,用户1次触发7天内推送1条通知是明显不够用的。比如,签到功能利用模板消息的推送来提醒用户每天签到,只能在用户前一天签到的情况下,获取一次推送模板消息的机会,然后用于第二天向该用户发送签到提醒。但是很多情况下,用户在某一天忘记签到,系统便失去了提醒用户的权限,导致和用户断开了联系;再比如,系统想主动告知用户即将做某活动,然而由于微信小程序被动触发通知的限制,系统将无法主动推送消息。
如何突破模板消息的推送限制?
突破口:“1次提交表单可下发1条,多次提交下发条数独立,相互不影响”
为了突破模板消息的推送限制,实现7天内任性推送,只需收集到足够的推送码,即每次提交表单时获取到的formId。一个formId代表着开发者有向当前用户推送模板消息的一次权限。
客户端
收集推送码
当表单组件中的属性report-submit=true时表示发送模板消息,提交表单便可以获取formId。接下来只要对原先的页面进行改造,将用户原先绑定了点击事件的界面用表单组件中的button按钮组件来代替,即把用户的交互点击的bindtap事件由表单bindsubmit来代替,从而捕获用户的点击事件来生成更多的推送码。
//收集推送码 Page({ formSubmit:funcition(e){ letformId=e.detail.formId; this.collectFormIds(formId);//保存推送码 lettype=e.detail.target.dataset.type;//根据type执行点击事件 }, collectFormIds:function(formId){ letformIds=app.globalData.globalFormIds;//获取全局推送码数组 if(!formIds) formIds=[]; letdata={ formId:formId, expire:newData().getTime()+60480000//7天后的过期时间戳 } formIds.push(data); app.globalData.globalFormIds=formIds; }, })
上报推送码
等待用户下一次发起网络请求时,将globalFormIds发送给服务器。
//上报推送码 Page({ onLoad:funcition(e){ this.uploadFormIds();//上传推送码 }, collectFormIds:function(formId){ varformIds=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来存储推送码。
/** *收集用户推送码 * *@paramopenId用户的openid *@paramformTemplates用户的表单模板 */ publicvoidcollect(StringopenId,ListformTemplates){ redisTemplate.opsForList().rightPushAll("mina:openid:"+openId,formTemplates); }
推送模板消息
下面实现了群发的功能,针对特定用户类似。
/** *推送消息 * *@paramtemplateId模板消息id *@parampage跳转页面 *@paramkeyWords模板内容 */ publicvoidpush(StringtemplateId,Stringpage,StringkeyWords){ StringlogPrefix="推送消息"; //获取accesstoken StringaccessToken=this.getAccessToken(); //创建消息通用模板 MsgTemplateVOmsgTemplateVO=MsgTemplateVO.builder().template_id(templateId).build(); //跳转页面 msgTemplateVO.setPage(StringUtils.isNotBlank(page)?page:""); //模板内容 if(StringUtils.isNotBlank(keyWords)){ String[]keyWordArr=keyWords.split(BaseConsts.COMMA_STR); MapkeyWordMap=newHashMap<>(8); for(inti=0;i openIdList=minaRedisDao.getAllOpenIds(); for(StringopenId:openIdList){ //获取有效推送码 StringformId=minaRedisDao.getValidFormId(openId); if(StringUtils.isBlank(formId)){ LOGGER.error("{}>>>openId={}>>>已无有效推送码[失败]",logPrefix,openId); continue; } //指派消息 MsgTemplateVOassignMsgTemplateVO=msgTemplateVO.assign(openId,formId); //发送消息 Map resultMap; try{ StringjsonBody=JsonUtils.getObjectMapper().writeValueAsString(assignMsgTemplateVO); StringresultBody=OkHttpUtils.getInstance().postAsString(messageUrl+accessToken,jsonBody); resultMap=JsonUtils.getObjectMapper().readValue(resultBody,Map.class); }catch(IOExceptione){ 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); } } /** *根据用户获取有效的推送码 * *@paramopenId用户的openid *@return推送码 */ publicStringgetValidFormId(StringopenId){ List formTemplates=redisTemplate.opsForList().range("mina:openid:"+openId,0,-1); StringvalidFormId=""; inttrimStart=0; intsize; for(inti=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); returnvalidFormId; }
以上方案可以实现在用户最后一次使用小程序后的7天内,对用户发送多条模板消息唤回用户。