精华内容
下载资源
问答
  • 主要介绍了微信小程序模板消息限制实现无限制主动推送的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 需求背景基于微信的通知渠道,微信小程序为开发者提供了可以高效触达用户的模板消息能力,在用户本人与小程序页面有交互行为后触发,通过微信聊天列表中的服务通知可快捷进入查看消息,点击查看详情还能跳转到下发...

    需求背景

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

    微信小程序允许下发模板消息的条件分为两类:支付或者提交表单。通过提交表单来下发模板消息的限制为“允许开发者向用户在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 
    链接:jianshu.com/p/3b02d75ef 
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    展开全文
  • 基于微信的通知渠道,微信小程序为开发者提供了可以高效触达用户的模板消息能力,在用户本人与小程序页面有交互行为后触发,通过微信聊天列表中的服务通知可快捷进入查看消息,点击查看详情还能跳转到下发消息的小...

    需求背景

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

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

    展开全文
  • 微信小程序模板消息使用说明(官方文档):https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html 为了防止对模板消息的滥用,带来不好的用户体验,小程序对模板消息推送做了相应的限制
  • 微信小程序模板消息无限制群发

    万次阅读 多人点赞 2017-07-10 11:35:38
    微信小程序模板消息无限制群发

    模版消息推送是微信小程序采用的通知形式, 用户本人在小程序页面有交互行为后,可触发下发通知 ,通过微信聊天列表中的服务通知可快捷进入查看消息。此外,点击查看详情还能跳转到下发消息的小程序的指定页面。但是为了避免这种通知被滥用,带来不好的用户体验,小程序也对模板消息推送做了相应的限制。为了更好的优化小打卡小程序的打卡通知功能,我在开发的过程中自行摸索了一套突破推送限制的解决方案。可以实现 7天内向用户推送多条模板消息,甚至向用户群发消息的功能 。

    注意:请务必在严格遵守小程序运营规范的前提下使用本方案。

    消息通知有什么作用?

    消息通知是一个很重要的功能,如QQ空间的回复状态通知,QQ邮箱的邮件通知,微信支付成功提通知等。这种常规的 服务跟踪类 消息,便于用户掌握产品对自身服务的进度,方便客户获取必要的信息,提高效率;保证用户的知情权,让用户有安全感。同时,对于产品本身来说,可以引导用户进行下一步行为, 增加了产品的曝光率,便于用户留存,增强用户粘性。

    模板消息长什么样子?

    服务通知及模板消息

    如上图,呈现在微信聊天列表的 服务通知 ,收纳了各个小程序向用户推送模板消息,这个服务通知是用户查看模板消息的入口,用户点击服务通知后可以查看到通知列表页面,每条通知以卡片的形式呈现,包括小程序的logo、名称、通知时间、通知内容等信息。

    模板消息是什么?

    所谓『模板消息』,就如上面的通知卡片,首先通知卡片形式样子是固定的,其实卡片中的通知内容部分,可以看到每天通知的内容都具备日程描述、日程主题、日程时间等要素,通知之间不同的地方在于这些要素后面的文案,将这些通知要素制作成模板,每次针对不同的通知内容 只需要填充每条要素对应的具体的文本 即可推送给用户。上面图中两条模板消息的日程主题和时间不一样,其他的信息要素保持一致,这就是模板消息。

    使用模板消息有哪些好处?

    提到模板消息的好处,第一印象是 "多、快、好、省" 的特点。

    "快"即快捷,体现在微信用户侧的通知体验,由于在微信客户端服务通知在聊天列表中,保留了用户以往处理聊天通知的习惯,所以用户可以很 便捷地触及服务通知 ,查看小程序推送的模板消息。

    "好"即效果好,小程序的模板消息具备 跳转直达小程序特定页面 的能力,这样用户接收消息后,查看消息的通知就能便捷地回到小程序进行相应的业务处理、信息查看等后续操作,一定程度上提升了用户的活跃度,小打卡小程序的近30天访问来源数据显示,有20%左右的用户通过模板消息这个入口进入小打卡,在各种来源中排名第三位,可以见模板消息是用户使用你的小程序的重要入口。

    "省"即省钱呗,有了模板推送,自然 降低了消息通知的成本 ,节省费用。消息通知优先通过模板消息这种方式来推送给指定用户,只有才无法触及用户的情况下,才使用传统的付费短信推送等形式。

    "多"呢?上面提到"无法触及用户的情况",其实是因为小程序不具备"多"的特点。物以稀为贵,模板消息虽好,但是微信小程序官方为了保证用户体验, 平衡通知和骚扰行为 ,对模板推送做了相应限制。接下来就聊聊这个限制。

    微信对模板消息有什么样的限制?

    微信小程序允许下发模板消息的条件分为两类, 支付或者提交表单 。

    目前支付的限制有所放开,即1次支付可以下发3条模板消息。通过提交表单来下发模板消息的方式限制为一次的触发行为,7天内可以向用户推送一条模板消息。 这种消息的控制放的太宽的话,很容易对用户的体验造成很大冲击,给用户带来一定的骚扰。

    但是,用户1次触发、7天内推送1条通知明显是不够用的,比如小打卡小程序利用模板消息的推送来提醒用户每天打卡,只能在用户前一天打卡的情况下,获取一次推送模板消息的机会,然后用于第二天向用户发送打卡通知。但是很多情况下,用户如果某一天忘记打卡,小打卡便 失去了提醒用户的权限,和用户断开了联系 。

    在小打卡中还有一个迫切需要多条模板消息推送的场景,比如打卡活动每次有新的成员进入,需要通知管理员进行审核,这种情况也需要及时地通知管理员,以便管理员快速响应,处理成员的审核请求并通知成员审核结果。

    注意到下发条件中,每次触发的到的 推送码可以在未来7天内使用,多次提交触发下发的消息条数独立,相互不影响 ,那能不能突破模板消息的发送限制,更好地优化打卡提醒功能呢?

    如何突破模板消息的推送限制?

    微信小程序官方最近已经透露出可能对模板消息进一步放宽限制的信号,不过在这之前,我们可以在遵守官方相关运营规范、保证用户体验的情况下,倒腾一个 "让用户一次触发、多次推送,甚至群发模板消息" 的解决方案。

    其实仔细分析消息下发条件"1次提交表单可下发1条,多次提交下发条数独立,相互不影响",突破口就明显了,只需 收集到足够推送码 ,即每次提交表单时获取到的formId就是我们所需的“推送权限”。它是一次性的,代表着开发者有向当前用户推送模板消息的权限。

    为了打造这样一个突破限制的模版消息推送功能,做到7天内任性推送,我们将小程序前后端的工作明确一下,小程序前端,即运行在用户微信上的小程序负责 收集推送码 ,小程序后端,即运行在服务器上的应用程序负责将推送码 存储到数据库 中,并在需要推送的模版消息的时候从中取出推送码formId判断有效性并加以运用。整个方案的前后端业务流程如下:

                                                                                                                   方案前后端流程

    接下来我们设计一个能够突破当前模板消息推送限制的方案。结合 小程序前端界面、小程序逻辑层、服务器程序、数据库、异步任务系统 各自分工,来实现将小程序模板消息推送所需的推送码收集、上报、存储、调用。最终做到7日内更好地推送模板消息、触及用户。

    一.前端页面如何尽可能多地收集推送码?

    每次表单提交可以触发一次下发模版消息的机会,表单组件

    如下:

     

    Page({
      formSubmit: function(e) {
        let formId = event.detail.formId;
        console.log('form发生了submit事件,推送码为:', formId)
      }
    })

    组件中属性report-submit为true时,代表需要请求发模板消息的推送码,此时点击按钮提交表单可以获取formId,用于发送模板消息。接下来只需要对原来的页面进行改造,将用户原来绑定了点击事件的界面用表单组件中的button按钮组件来代替,也就是 把用户的交互点击的bindtap事件通过表单bindsubmit来取代 ,从而捕获用户的点击事件来产生更多的推送码formId,这里还需要对按钮组件的样式进行稍微的修改,以便更好地包裹原来界面的代码。

    /*wxss*/
    /*修改按钮样式,使其能够包裹其他组件*/
    .btn {
      border:none;
      text-align:left;
      padding:0;
      margin:0;
      line-height:1.5;
    }
    //js
    Page({
      formSubmit: function(e) {
        let formId = e.detail.formId;
        this.dealFormIds(formId); //处理保存推送码
        let type = e.detail.target.dataset.type;
        //根据type的值来执行相应的点击事件
        //...
      },
      dealFormIds: function(formId) {
        let formIds = app.globalData.gloabalFomIds;//获取全局数据中的推送码gloabalFomIds数组
        if (!formIds) formIds = [];
        let data = {
          formId: formId,
          expire: parseInt(new Date().getTime() / 1000)+604800 //计算7天后的过期时间时间戳
        }
        formIds.push(data);//将data添加到数组的末尾
        app.globalData.gloabalFomIds = formIds; //保存推送码并赋值给全局变量
      },
    })

    上面的代码主要实现了模拟表单提交事件来取代原来的点击事件,用户在点击界面进行交互的同时,能够获得多个推送码保存app.js的全局变量globalData中,等待用户下一次发起网络请求时,即可将gloabalFomIds数组数据发送给服务器。

    小打卡上的点击区域

    上图以小打卡的打卡详情页为例,用户在这个页面的点击操作可以很快收集到多个formId,所以将界面上用户高频点击的事件用表单的形式重新封装后,可以静默、快速收集到所需的"模板消息推送权限" 。

    二.小程序逻辑层如何传递推送码给服务器?

    Page({
       onLoad:function(){
        this. saveFormIds();
      },
      saveFormIds: function(){
        var formIds = app.globalData.gloabalFomIds; // 获取gloabalFomIds
        if (formIds.length) {//gloabalFomIds存在的情况下 将数组转换为JSON字符串
          formIds = JSON.stringify(formIds);
          app.globalData.gloabalFomIds = '';
        }
        wx.request({//通过网络请求发送openId和formIds到服务器
          url: 'https://www.x.com', 
          method: 'GET',
          data: {
            openId: 'openId',
            formIds: formIds
          },
          success: function(res) {
          }
        });
      },
    })

    在小程序的逻辑层中,通过全局变量gloabalFomIds收集到多个formId后,可以在新页面载入时,在onLoad生命周期函数中发送网络请求获取数据, gloabalFomIds不为空时,把gloabalFomIds数组格式化为字符串发送到服务器,并清空当前的gloabalFomIds ,以便继续获取新的formId。

    三.后端程序如何保存推送码formId?

    因为这个保存是一个高频IO的操作,我们 后端以PHP结合高性能的key-value数据库Redis来实现推送码的存储 。相关关键代码如下,简单表达了思路,针对不同的后端环境和开发语言,你可能需要做相应的调整。

    //关键代码
    public function saveFormIds(){
        $openId = $_GET['openId'];
        $formIds = $_GET['formIds'];;//获取formIds数组
        if($formIds){
            $formIds = json_decode($formIds,TRUE);//JSON解码为数组
            $this -> _saveFormIdsArray($openId,$formIds);//保存
        }
    }
    private function _get($openId){
        $cacheKey = md5('user_formId'.$openId);
        $data = $this->cache->redis->get($cacheKey);//修改为你自己的Redis调用方式
        if($data)return json_decode($data,TRUE);
        else return FALSE;
    }
    private function _save($openId,$data){
        $cacheKey = md5('user_formId'.$openId);
        return $this->cache->redis->save($cacheKey,json_encode($data),60*60*24*7);//修改为你自己的Redis调用方式
    }
    private function _saveFormIdsArray($openId,$arr){
        $res = $this->_get($openId);
        if($res){
            $new = array_merge($res, $arr);//合并数组
            return $this->_save($openId,$new);
        }else{
            $result = $arr;
            return $this->_save($openId,$result);
        }
    }

    这一步主要是构建服务器程序高效存储用户的推送码formId,这下推送机会有了,接下来我们考虑如何 利用后端程序来想特定用户发送模板消息 ,考虑怎样去合理运用推送机会。

    四.如何实现高性能的模板消息推送?

    构建高性能的服务器端异步任务推送,可以满足 模板消息的群发、以及定时发送 的需求,如小打卡就采用了高性能分布式内存队列系统 BEANSTALKD,来实现模板消息的异步定时推送。实现发送模板消息的群发、定时发送分为2个步骤:

    1. 设置任务执行时间并将该发送任务推送到异步任务队列。
    2. 通过任务发送服务轮询执行任务,获取access_token、指定你需要推送消息的用户的openId,根据openId获取用户的推送码formId,并结合模板id拼装模板上的通知内容,调用模板消息发送接口来异步发送。

    普通的模板消息的发送就不赘述了,可参考 官方文档中的模板消息功能 一步步进行操作,我们重点来看高性能异步任务推送的实现方法。涉及到的关键代码如下:

    //设置异步任务
    public function put_task($data,$priority=2,$delay=3,$ttr=60){//任务数据、优先级、时间定时、任务处理时间
        $pheanstalk = new Pheanstalk('127.0.0.1:11300');
        return $pheanstalk ->useTube('test') ->put($data,$priority,$delay,$ttr);
    }
    //执行异步任务
    public function run() {
        while(1) {
            $job = $this->pheanstalk->watch('test')->ignore('default')->reserve();//监听任务
            $this->send_notice_by_key($job->getData());//执行模板消息的发送
            $this->pheanstalk->delete($job);//删除任务
            $memory = memory_get_usage();
            usleep(10);
        }
    }
    //1.取出一个可用的用户openId对应的推送码
    public function getFormId($openId){
        $res = $this->_get($openId);
        if($res){
            if(!count($res)){
                return FALSE;
            }
            $newData = array();
            $result = FALSE;
            for($i = 0;$i < count($res);$i++){
                if($res[$i]['expire'] > time()){
                    $result = $res[$i]['formId'];//得到一个可用的formId
                    for($j = $i+1;$j < count($res);$j++){//移除本次使用的formId
                        array_push($newData,$res[$j]);//重新获取可用formId组成的新数组
                    }
                    break;
                }
            }
               $this->_save($openId,$newData);
            return $result;
        }else{
            return FALSE;
        }
    }
    //2.拼装模板,创建通知内容
    private function create_template($openId,$formId,$content){
        $templateData['keyword1']['value'] = '打卡即将开始';
        $templateData['keyword1']['color'] = '#d81e06';
        $templateData['keyword2']['value'] = '打卡名称';
        $templateData['keyword2']['color'] = '#1aaba8';
        $templateData['keyword3']['value'] = '05:00';
        $templateData['keyword4']['value'] = '备注说明';
        $data['touser'] = $openId;
        $data['template_id'] = '模板id';
        $data['page'] = 'pages/detail/detail?id=1000';//用户点击模板消息后的跳转页面
        $data['form_id'] = $formId;
        $data['data'] = $templateData;
        return json_encode($data);
    }
    
    //3.执行模板消息发布
    public function send_notice($key){
        $openId = '用户openId';
        $formId = $this -> getFormId($openId);//获取formId
        $access_token = '获取access_token';
        $content='通知内容';//可通过$key作为键来获取对应的通知数据
        if($access_token){
            $templateData = $this->create_template($openId,$formId,$content);//拼接模板数据
            $res = json_decode($this->http_post('https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token='.$access_token,$templateData));
            if($res->errcode == 0){
                return $res;
            }else{
                return false;
            }
        }
    }

    Beanstalkd是一个 高性能、轻量级的分布式内存队列系统 ,我们通过Beanstalkd将模板消息推送任务的创建以及任务的执行分开进行。

    在创建推送任务时, 设置任务的执行时间以及定义推送消息的类型和通知内容等数据 。

    在任务执行时,通过Beanstalkd的任务监听函数来捕获任务。通过预先在创建任务时标记的数据来确定模板消息的具体推送内容,比如用户openId,通过用户openId获取一个可用的推送码formId,获取推送内容等,最后在调用微信小程序模板消息下发接口完成推送。

    getFormId函数主要实现每次取出一个未过期可用的推送码formId,并且删除不可用的邀请码和当前已选中的邀请码,以保证一定数额的推送码formId在未来一周内可用。

    关于Beanstalkd的使用介绍,可用参考一下文章,深入研究。

    高性能分布式内存队列系统beanstalkd(转)

    beanstalkd消息队列使用

    最后总结一下,整个方案涉及到的关键词有 表单、按钮、formId、模板消息、Redis、Beanstalkd 等,涉及了多项技术的组合,包括 前端开发、后端开发、数据库技术 等,且前后端分工明确,共同支撑整个方案地实现。

    模板消息推送方案

    正如我之前文章里所说的, 微信小程序开发的难点不在于小程序本身,小程序开发技术是前后端一系列的技术的组合,开发者需要持续学习,掌握、提升更多的相关开发技术,来更好地支撑产品的功能实现 。最后,这个方案可以在用户最后一次使用小程序后的7天内,对用户发送多条模板消息唤回用户,但是请 一定要在遵循微信官方的运营规范的前提下 ,合理使用这样的模板消息推送功能。

    展开全文
  • 模版消息推送是微信小程序采用的通知形式,用户本人在小程序页面有交互行为后,可触发下发通知,通过微信聊天列表中的服务通知可快捷进入查看消息。此外,点击查看详情还能跳转到下发消息的小程序的指定页面。但是为...
  • 前一段时间在开发微信小...微信小程序允许下发模板消息的条件分为两类: 支付,或者提交表单。 通过 1 次支付,小程序可以向该用户下发 3 条模板消息。 通过提交表单来下发模板消息的方式,限制在 7 天内可以向用...

    前一段时间在开发微信小程序及后端接口,其中有一个功能是在a用户给b用户分配任务时,需要给b用户的微信发送一个提醒消息。但在开发过程中发现小程序发送模板消息有限制,不能满足我们的需求场景。

    1、微信对模板消息的限制

    在这里插入图片描述
    微信小程序允许下发模板消息的条件分为两类: 支付,或者提交表单。
    通过 1 次支付,小程序可以向该用户下发 3 条模板消息。
    通过提交表单来下发模板消息的方式,限制在 7 天内可以向用户推送一条模板消息。
    这种消息的控制放的太宽的话,很容易对用户的体验造成很大冲击,给用户带来骚扰。但是,用户 1 次触发、7 天内推送 1 条通知明显是不够用的。
    比如,小打卡小程序利用模板消息的推送来提醒用户每天打卡,只能在用户前一天打卡的情况下,获取一次推送模板消息的机会,用于第二天向用户发送打卡通知。
    在这里插入图片描述

    2、方案

    现有的解决方案主要是收集用户的formid,做法是让用户在小程序中主动点击按钮向服务器发送formid,百度搜索“小程序 模板消息 formid”会有一大堆文章介绍。这种解决方案还是比较复杂,一个小小功能需要编写大量辅助代码。

    市面上有公司专门针对这个开发者痛点做了一款产品,百度搜古人云即可找到。他的优点是编码少,集成到项目中可以实现推送功能。缺点就是免费版限制多,需要付款。

    3、生活中的微信消息功能

    我在麦当劳用餐时,会收到麦当劳发送的用餐评价提醒。这是在没有用户主动触发的条件下实现的,意味着微信有这样的接口满足我们的业务需求。
    在这里插入图片描述

    4、调研

    后来查看微信接口,查看到微信公众号有这样的接口能满足业务需求,即申请微信公众服务号,在发公众号模板消息,也可以跳转到小程序中。模板消息接口

    发送模板消息
    接口调用请求说明
    http请求方式: POST
    https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
    POST数据说明
    POST数据示例如下:

      {
               "touser":"OPENID",
               "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
               "url":"http://weixin.qq.com/download",  
               "miniprogram":{
                 "appid":"xiaochengxuappid12345",
                 "pagepath":"index?foo=bar"
               },          
               "data":{
                       "first": {
                           "value":"恭喜你购买成功!",
                           "color":"#173177"
                       },
                       "keyword1":{
                           "value":"巧克力",
                           "color":"#173177"
                       },
                       "keyword2": {
                           "value":"39.8元",
                           "color":"#173177"
                       },
                       "keyword3": {
                           "value":"2014年9月22日",
                           "color":"#173177"
                       },
                       "remark":{
                           "value":"欢迎再次购买!",
                           "color":"#173177"
                       }
               }
           }
    

    参数说明

    参数是否必填说明
    touser接收者openid
    template_id模板ID
    url模板跳转链接
    miniprogram跳小程序所需数据,不需跳小程序可不用传该数据
    appid所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
    pagepath所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
    data模板数据
    color模板内容字体颜色,不填默认为黑色

    注:url和miniprogram都是非必填字段,若都不传则模板无跳转;若都传,会优先跳转至小程序。开发者可根据实际需要选择其中一种跳转方式即可。当用户的微信客户端版本不支持跳小程序时,将会跳转至url。

    备注

    可借鉴点:古人云应用记录所有发送成功的消息和失败的消息,及量化形成数据报表。

    展开全文
  • “模版消息”是小程序非常重要且可主动触达用户的一种能力。爱鲜蜂小程序通过“模版消息”,建立一套...模版消息推送是微信小程序采用的通知形式,用户本人在小程序页面有交互行为后,可触发下发通知,通过微信聊天列
  • 微信小程序模板消息功能介绍使用说明步骤一:获取模板 ID步骤二:获取下发权限步骤三:调用接口下发订阅消息流程图 小程序业务需求要用到模板消息推送,在此记录。 功能介绍 订阅消息推送位置:服务通知 订阅...
  • 模版消息推送是微信小程序采用的通知形式, 用户本人在小程序页面有交互行为后,可触发下发通知 ,通过微信聊天列表中的服务通知可快捷进入查看消息。此外,点击查看详情还能跳转到下发消息的小程序的指定页面。但是...
  • 本文将从技术视角解析产品人员对微信小程序模版消息的实现方式和注意事项。 根据阿拉丁2018年上半年小程序白皮书显示: 微信小程序日活达到2.8亿,用户留存率也全线提升,7日内留存上升非常明显,同比去年期间日留存...
  • 最近参与开发的项目有用到微信模板消息推送,在这离记录一下。 1.工具类;用于访问WeChat的API接口; 这里工具类使用了静态内部类,且禁止外部使用new创建对象。 ps:URL这里用final static修饰没问题,但切记...
  • 写了几个常用的微信接口,项目时间过于紧迫,未经整理,留待有空时再来整理或者扩展吧。 建议大家使用前,先去微信官方文档那边看一看,虽然微信的文档着实有点坑,但是总归还是能了解大概的业务逻辑以及找到一些...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,355
精华内容 3,342
关键字:

微信小程序模板消息限制

微信小程序 订阅