突破微信小程序模板消息限制,实现无限制主动推送

需求背景

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260