微信小程序打怪之定时发送模板消息(node版)

这篇具有很好参考价值的文章主要介绍了微信小程序打怪之定时发送模板消息(node版)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景描述

小程序答题签到功能,为了促进日活,需要每天定时向当日未签到的用户推送消息提醒签到。

读本篇之前最好已经了解微信关于发送模板消息的相关文档:

  1. 模板消息指南

  2. 模板消息服务接口

说明: 作者也是第一次写小程序的定时模板消息功能,作为一个纯种前端攻城狮,可能在建表操作数据库等后端代码上有不严谨或不合理的地方,欢迎大佬们拍砖指正(轻拍)。本文以提供解决思路为主,仅供学习交流,如有不合理的地方还请留言哦。😆

实现思路

官方限制

微信小程序推送模板消息下发条件:

  1. 支付 当用户在小程序内完成过支付行为,可允许开发者向用户在 7天 内推送有限条数的模板消息 (1次支付可下发3条,多次支付下发条数独立,互相不影响)

  2. 提交表单 当用户在小程序内发生过提交表单行为且该表单声明为要发模板消息的,开发者需要向用户提供服务时,可允许开发者向用户在 7天 内推送有限条数的模板消息 (1次提交表单可下发1条,多次提交下发条数独立,相互不影响)

根据官方的规则,显然用户1次触发7天内推送1条通知是明显不够用的,比如签到功能,只有用户在前一天签到情况下才能获取一次推送消息的机会,然后用于第二天向该用户发送签到提醒。倘若用户忘记了签到,系统便失去了提醒用户的权限,导致和用户断开了联系。

如何突破限制?

既然用户1次提及表单可以下发1条消息通知,且多次提交下发条数独立且互不影响。 那我们可以合理利用规则,将页面绑定点击事件的按钮都用form表单 report-submit=true 包裹 button form-type=submit 伪装起来,收集formId,将formId存入数据库中,然后通过定时任务再去向用户发送模板消息。

开发步骤

后台配置消息模板

微信公众平台->功能->模板消息->我的模板中添加模板消息,如下:

微信小程序打怪之定时发送模板消息(node版),前端资讯,notepad++,微信小程序

其中模板ID和关键词需要在发送模板消息的时候用到。

数据库设计

建表之前,思考一下都需要存哪些数据?

根据微信的发送消息接口templateMessage.send可知,要给用户发送一条消息需要将touser(即用户的openid),form_id需要存入数据库。 另外获取用户form_id时的expire(过期时间)也需要存下来,另外还需要知道form_id是否使用以及过期的状态需要存一下。

于是表的结构为:

表: wx_save_form_id

id open_id user_id form_id expire status
1 xxxxxx 1234 xxxx 1562642733399 0

sql

CREATE TABLE `wx_save_form_id` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `open_id` char(100) NOT NULL DEFAULT '',
  `user_id` int(11) NOT NULL,
  `form_id` char(100) NOT NULL DEFAULT '',
  `expire` bigint(20) NOT NULL COMMENT 'form_id过期时间(时间戳)',
  `status` int(1) DEFAULT '0' COMMENT '0 未推送 1已推送 2 过期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8;


表建好了,来捋一捋逻辑:

  1. 用户提交表单,将open_id,user_id(根据自身需求存此字段),form_idexpire 以及status=0插入到wx_save_form_id表中

  2. 开启定时任务(比如每天10:00执行),到固定时间查询表wx_save_form_id,拿到status=0的数据,然后再调微信的templateMessage.send接口给对应的用户发送提示信息

  3. 发送完的用户将status字段更新为1,下次查询的时候讲筛选掉已发送的状态。

想想是不是漏掉点什么?

一条form_id的过期时间是7天,那如果过期了怎么去将状态改完已过期呢?

一个解决办法是,再开一个定时任务(比如20min执行一次),去查询哪条form_id已经过期,然后再更改状态。如果数据只存在wx_save_form_id一张表中感觉效率会很低,不方便,也不合理。于是想到再去建立一张表:

表: wx_message_push_status

id user_id count last_push
1 1234 5 20190701

sql

CREATE TABLE `wx_message_push_status` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `count` int(11) NOT NULL DEFAULT '1' COMMENT '可推送消息次数',
  `last_date` bigint(20) NOT NULL DEFAULT '0' COMMENT '最后一次推送消息时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

其中 user_id(根据自身需求,也可以是open_id) 用户id, count 可向用户推送消息的次数 last_date 上一次推送消息的时间,用来判断当天是否再推送

再重新捋一捋逻辑:

  1. 用户提交表单,将open_id,user_id(根据自身需求存此字段),form_idexpire 以及status=0插入到wx_save_form_id表中,同时将wx_message_push_status表中的count自身+1

  2. 开启定时任务(比如每天10:00执行),到固定时间查询表wx_message_push_status,通过筛选条件 count>0last_date不为当天,拿到可以推送消息的user_id再去查询wx_save_form_id

  3. 查询条件user_id=上面拿到的status=0, expire >= 当前时间戳,然后再调微信的templateMessage.send接口给对应的用户发送提示信息

  4. 发送完的用户将status字段更新为1,下次查询的时候讲筛选掉已发送的状态。

  5. 开启另一个定时任务(比如间隔20分钟执行一次),先去查询wx_save_form_id,筛选条件status=0exprie<当前时间戳(即未发送,且过期的数据)

  6. 将筛选到的数据status改为2,且查询wx_message_push_status表对应的user_id,将count自身减1。

完美结束。

理清开发逻辑,就准备动手写码

代码实现
前端页面

页面的 form 组件,属性 report-submittrue 时,可以声明为需要发送模板消息,此时点击按钮提交表单可以获取 formId

demo.wxml

<form report-submit="true" bindsubmit="uploadFormId">
    <button form-type="submit" hover-class="none" >提交</button>
</form>

可以将页面中的绑定事件都用form组件来伪装,换取更多的formId

注: 获取form_id必须在真机上获取,模拟器会报the formId is a mock one;

demo.js

Page({
    ...
    uploadFormId(e){
        //上传form_id 发模板消息
        wx.request({
            url: 'xx/xx/uploadFormId',
            data: {
                form_id: e.detail.formId
            }
        });
    }
    ...
})

服务端接口

server.js //node中间层 去调底层接口

async updateFormIdAction(){
    /*
     *我们的userId和openId是存在server端,不需从前端传回。
     *不必纠结接口的实现语法,和自身框架有关。
     */
    const {ctx} = this;
    const user = ctx.user;
    const userId = user ? user.userId : '';
    const loginSession = ctx.loginSession;
    const body = ctx.request.body;

    let openId = loginSession.getData().miniProgram_openId || '';

    const result = await this.callService('nodeMarket.saveUserFormId', openId, userId, body.form_id);
    return this.json(result);
}


底层接口以及定时任务

service.js //Node 操作数据库接口

const request = require('request');

/*
 * 根据用户userId openId 保存用户的formId
 * 存储formId的表 wx_save_form_id
 */
async saveUserFormIdAction(){
    const http = this.http;
    const req = http.req;
    const body = req.body;
    
    //7天后过期时间戳
    let expire = new Date().getTime() + (7 * 24 * 60 * 60 *1000); 
    const sql = `INSERT INTO wx_save_form_id (open_id, user_id, form_id, expire) VALUES(${body.openId}, ${body.userId}, ${body.formId}, ${expire}) `;
    //自行封装好的mysql实例 
    let tmpResult = await mysqlClient.query(sql);
    let result = tmpResult.results;
    if (! result || result.affectedRows !== 1) {
        ...
    }
    
    await this._updateMessagePushStatusByUserId(body.userId);
    return this.json({
        status: 0,
        message: '成功'
    });
}

// 更新用户可推送消息次数
_updateMessagePushStatusByUserId(user_id){
    const http = this.http;
    try{
        const selectSql = `SELECT user_id, count from wx_message_push_status WHERE user_id = ${user_id}`;
        let temp = await mysqlClient.query(sql);
        let result = temp.results;
        if(result.length){
            //有该user_id的记录 则更新数据
            const updateSql = `UPDATE wx_message_push_status SET count = count + 1 WHERE user_id = ${user_id}`;
            await mysqlClient.query(sql);
            ...
        }else {
            //无记录 则插入新的记录
            const insertSql = `INSERT INTO wx_message_push_status user_id VALUES $(user_id)`;
            await mysqlClient.query(sql);
            ...
        }
    }catch(err){
        ...
    }
}

//发送消息的定时任务
async sendMessageTaskAction(){
    const http = this.http;
    const Today = utils.getCurrentDateInt(); //当天日期 返回YYYYMMDD格式 具体实现忽略
    //筛选count>0 且当天没有推送过的user_id
    const selectCanPushSql = `select user_id from wx_message_push_status WHERE count > 0 AND last_date != ${Today}`;
    let temp = await mysqlClient.query(selectCanPushSql);
    let selectCanPush = temp.results;
    
    if(selectCanPush.length){
        selectCanPush.forEach(async (record)=>{
            try{
                let user_id = record.user_id;
                //筛选出 status = 0, 且formId未过期 且 过期时间最近的数据
                const currentTime = new Date().getTime();
                const getFormIdSql = `select open_id, user_id, form_id from wx_save_form_id WHERE user_id = ${user_id} AND status = 0 AND expire >= ${currentTime} AND form_id != 'the formId is a mock one' ORDER BY expire ASC`;
                let getFormIdTemp = await mysqlClient.query(getFormIdSql);
                //获取可用的form_id列表
                let getUserFormIds = getFormIdTemp.results;
                //取出第一条可用的formId记录 发送消息
                const { open_id, form_id } = getUserFormIds[0];
                let sendStatus = await this._sendMessageToUser(open_id, form_id);
                /*
                 *发送完消息之后
                 * 无论成功失败 将这条form_id置为已使用 最后推送时间为当天
                 * 将可发消息次数减1
                 */
                let updateCountSql = `UPDATE wx_message_push_status SET count = count - 1, last_date = ${Today} WHERE count >0 AND user_id = ${user_id}; ` ;
                await mysqlClient.query(updateCountSql);
                
                let updateStatusSql = `UPDATE wx_save_form_id SET status = 1 WHERE user_id = ${user_id} AND open_id = ${open_id} AND form_id = ${form_id}`;
                await mysqlClient.query(updateStatusSql);
                ...
            }catch(err){
                ...
            }
        });
    }
    this.json({
        status: 0
    });
}

//发送模板消息
_sendMessageToUser(open_id, form_id){
    let accessToken = await this._getAccessToken();//获取token方法省略
    const oDate = new Date();
    const time = oDate.getFullYear() + '.' + (oDate.getMonth()+1) + '.' + oDate.getDate();
    if(accessToken){
        const url = `https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=${accessToken}`;
        request({
            url,
            method: 'POST',
            data: {
                access_token,
                touser: open_id,
                form_id,
                page: 'pages/xxx/xxx',
                template_id: '你的模板ID',
                data: {
                    keyword1: {
                        value: "日领积分"
                    },
                    keyword2: {
                        value: '已经连续答题N天,连续答题7天有惊喜,加油~'
                    },
                    keyword3: {
                        value: "叮!该签到啦~锲而不舍,金石可镂。"
                    },
                    keyword4: {
                        value: time
                    }
                }
            }
        },(res)=>{
            ...
        })
    }
}

/*
 * 检查wx_save_form_id表中的 expire字段是否过期,如果过期则将status 置为2 并且
 * 对应的 wx_message_push_status表中的count字段减1
 */
 async amendExpireTaskAction(){
    let now = new Date().getTime();
    try {
        //筛选已经过期且未使用的记录
        const expiredSql = `select * from wx_save_form_id WHERE status = 0 AND expire < ${now}`;
        let expiredTemp = await mysqlClient.query(expiredSql);
        let expired = expiredTemp.results;
        if (expired.length){
            expired.forEach(async (record)=>{
                //将过期的记录状态更新我为2
                const updateStatusSql = `UPDATE wx_save_form_id SET status = 2 WHERE open_id = '${record.open_id}' AND user_id = ${record.user_id} AND form_id = '${record.form_id}' `;
                await mysqlClient.query(updateStatusSql);

                //将推送次数减1
                let updateCountSql = `UPDATE wx_message_push_status SET count = count - 1 WHERE count >0 AND user_id = ${record.user_id}; ` ;
                await mysqlClient.query(updateCountSql);
            });
        }

    }catch (e) {
    }
    this.json({
        status: 0
    });
 }
 

执行定时任务发送消息

呼~ 完整代码码完了。 大概思路是这样的,操作数据库没有考虑性能问题,如果数据量大会出现的问题,也没有考虑事务,索引等操作(主要是不会T_T),读者可以自行优化。

最后需要开两个定时任务分别执行sendMessageTask接口和amendExpireTask接口,我们的定时任务也是找的开源的node框架,具体实现不陈述。

最终效果:

微信小程序打怪之定时发送模板消息(node版),前端资讯,notepad++,微信小程序

参考文献

突破微信小程序模板消息限制,实现无限制主动推送文章来源地址https://www.toymoban.com/news/detail-771237.html

到了这里,关于微信小程序打怪之定时发送模板消息(node版)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序模板消息推送

    时序图 ​​​​​​​   首先,我们需要知道一件事情,小程序的模板推送分为“一次性订阅”和“长期订阅” 一次性订阅:用户订阅小程序后,程序只能对指定OpenId进行一次推送模板消息,无法多次推送 长期订阅:用户长期订阅,能够多次推送模板消息(长期订阅模板需

    2024年02月11日
    浏览(46)
  • 微信小程序服务通知(订阅消息)定时推送消息功能

    首先先说项目需求:向预约参观的用户提前一天晚上8点推送消息。小程序端主要用到的 API 是我是小程序用到的API。以及服务端用到的 API :我是服务端用到的API。 1. 开通订阅消息功能 (1)、 首先需要在小程序管理后台开通订阅消息功能。没开通前如下图所示: (2)、开通之

    2024年02月08日
    浏览(80)
  • 微信小程序——服务通知,发送订阅消息

    1 小程序开通订阅消息 2 postApi测试效果 这里有个需要特别注意的点,我们要给用户发送消息,就必须引导用户授权,如下 因为用户不点击允许,你是没有办法给用户推送消息的。每一次授权只允许发送一条消息,所以如果你想尽量多的发送消息,就得尽量多的引导用户授权

    2024年02月11日
    浏览(73)
  • 微信小程序消息推送、接收消息事件、发送客服消息

    文档地址消息推送 | 微信开放文档 接收消息和事件 | 微信开放文档 发送客服消息 | 微信开放文档 代码参考

    2024年02月12日
    浏览(44)
  • 微信小程序订阅模板消息推送

    背景 在实际的小程序开发过程中往往需要用到给用户发送订阅消息,比如:我们在店里扫码点餐时在付款时往往弹出一个授权窗口(比如‘取餐通知’)这个时候我们就需要对接微信小程序的模板消息。 [https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscrib

    2024年02月11日
    浏览(47)
  • 微信小程序云开发定时推送订阅消息

    (1)点击订阅消息 (2)点击公共模板库,然后找到想要选用的模板,点击选用。 (3)在我的模板里面,复制模板id。 如果找不到想要用的模板,可以在公共模板的最后一页,点击下图中圈出来的,去申请自己想要的模板。 (1)云函数部分的代码 config.json 云函数配置文件

    2024年02月09日
    浏览(47)
  • 微信小程序消息模板设计及实现

    本文以微信小程序内置的两个模板:购买成功和评论回复提醒为例来阐述第三方微信小程序平台的设计。 小程序端     微信用户支付成功后,微信服务通知中会收到支付成功服务提醒。见下图: 商家端 用户完成评价后,商家管理端可以查看评论。见下图: 商家进行回复:

    2024年02月06日
    浏览(37)
  • 保姆级微信小程序对接蓝牙设备教程。微信小程序发送不同蓝牙指令(定时发送,断开重连,判断是否有蓝牙权限等)

    本文是一个完整的对接设备,发送不同指令监听不同返回的完整示例,可根据实际项目按需更改。 注: app.showModal 为在app.js中封装的showModal方法, then(()={}) 代表用户点击 confirm ,可用 wx.showModal 代替。 公用方法 请求设备列表 1. 判断是否有蓝牙权限 2. 初始化蓝牙 wx.openBluet

    2024年03月20日
    浏览(65)
  • 微信小程序通过公众号服务号发送消息

    一、基础概念: 准备条件:      1、公众号和小程序必须在同一个公司主体下。      2、在公众号后台需要对小程序进程绑定操作。 公众号提供了两种消息,一种是订阅消息,一种是模板消息。 订阅消息需要用户主动订阅,然后才能接收消息,微信提供前端组件用于用户进

    2024年02月07日
    浏览(46)
  • 微信小程序向公众号推送消息模板

    由于微信小程序长期订阅的消息模板全部失效以后,对于小程序的消息推送可以改成往公众号推。 这里将介绍如何使用小程序向公众号推送消息,并且消息可以跳转到小程序 1、微信公众平台注册 服务号 (订阅号是不可以推送的)与小程序,两者都需要认证并且 认证主体是

    2024年02月06日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包