описание фона
Для функции регистрации мини-программы, чтобы продвигать повседневную деятельность, необходимо регулярно отправлять сообщения пользователям, которые не зарегистрировались в этот день, чтобы напомнить им о регистрации.
Прежде чем читать эту статью, лучше всего ознакомиться с документами, связанными с WeChat, об отправке шаблонных сообщений:
проиллюстрировать:Автор также впервые пишет функцию временного сообщения шаблона небольшой программы.Как чистый осадный лев переднего плана, во внутреннем коде могут быть неточные или необоснованные места, такие как создание таблиц и работа с базами данных.Добро пожаловать внести исправления (пат). Эта статья в основном предназначена для предоставления решений, только для обучения и обмена, если есть какое-либо необоснованное место, пожалуйста, оставьте сообщение. 😆
Реализовать идеи
официальные ограничения
Условия доставки push-сообщения шаблона апплета WeChat:
-
платить Когда пользователь выполнил платежное поведение в апплете, разработчику может быть разрешено7 днейОтправьте ограниченное количество шаблонных сообщений(за один платеж можно оформить 3 штуки, а количество штук, выданных за несколько платежей, не зависит друг от друга и не влияет друг на друга)
-
представить форму Когда пользователь отправляет форму в апплете, и форма объявлена для отправки сообщения шаблона, и разработчику необходимо предоставить услуги пользователю, разработчик может7 днейОтправьте ограниченное количество шаблонных сообщений(1 представление формы может быть оформлено один раз, а количество представлений, оформленных после нескольких представлений, не зависит друг от друга и не влияет друг на друга)
По официальным правилам явно недостаточно, чтобы пользователь отправил уведомление в течение 7 дней, например, для функции регистрации только пользователь может получить возможность отправить сообщение после проверки в предыдущий день, а затем используйте его, чтобы отправить сообщение пользователю на следующий день Пользователь отправляет напоминание о регистрации. Если пользователь забывает зарегистрироваться, система теряет право напоминать пользователю, что приводит к отключению от пользователя.
Как пробить лимит?
Поскольку пользователь упоминает форму один раз, может быть отправлено одно сообщение-уведомление, а количество отправленных сообщений после многократной отправки не зависит и не влияет друг на друга.
Тогда мы можем разумно использовать правила и использовать все кнопки, которые связывают событие клика на странице.form
формаreport-submit=true
пакетbutton
form-type=submit
маскировать, собиратьformId
,БудуformId
Сохраните его в базе данных, а затем отправляйте шаблонные сообщения пользователям через временные задачи.
Шаги развития
Шаблон сообщения о фоновой конфигурации
Публичная платформа Wechat -> Функции -> Сообщение шаблона -> Добавить сообщение шаблона в мой шаблон следующим образом:
Идентификатор шаблона и ключевые слова необходимо использовать при отправке шаблонных сообщений.
Дизайн базы данных
Перед созданием таблицы подумайте, какие данные вам нужно хранить?
Согласно интерфейсу отправки сообщений WeChattemplateMessage.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;
Таблица построена, давайте посмотрим на логику:
- Пользователь отправляет форму,
open_id
,user_id
(Сохраните это поле в соответствии с вашими потребностями),form_id
,expire
так же какstatus=0
вставить вwx_save_form_id
стол - Запускать запланированные задачи (например, выполнение в 10:00 каждый день) и запрашивать таблицу в фиксированное время.
wx_save_form_id
, получатьstatus=0
данные, а затем настроить WeChattemplateMessage.send
Интерфейс отправляет оперативную информацию соответствующему пользователю - После отправки пользователь
status
Поле обновлено до1
, при следующем запросе отфильтруйте отправленный статус.
Думаете, что-то упустили?
одинform_id
Срок годности 7 дней.Если истекает, как изменить статус на просроченный?
Одним из решений является открытие другой задачи по времени (например, выполнение ее каждые 20 минут), чтобы узнать, какая из них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
Время последнего push-сообщения, используемое для определения необходимости повторной отправки сообщения в текущий день.
Попробуем еще раз по логике:
- Пользователь отправляет форму,
open_id
,user_id
(Сохраните это поле в соответствии с вашими потребностями),form_id
,expire
так же какstatus=0
вставить вwx_save_form_id
стол, в то время какwx_message_push_status
в таблицеcount
Я +1 - Запускать запланированные задачи (например, выполнение в 10:00 каждый день) и запрашивать таблицу в фиксированное время.
wx_message_push_status
, по фильтруcount>0
а такжеlast_date
Не в тот же день, получить сообщение, которое можно нажатьuser_id
спросить сноваwx_save_form_id
поверхность - Условия запроса
user_id=上面拿到的
,status=0
,expire >= 当前时间戳
, а затем настройте WeChattemplateMessage.send
Интерфейс отправляет оперативную информацию соответствующему пользователю - После отправки пользователь
status
Поле обновлено до1
, при следующем запросе отфильтруйте отправленный статус. - Запустить другое запланированное задание (например, выполнять его каждые 20 минут), сначала перейти к запросу
wx_save_form_id
, состояние фильтраstatus=0
а такжеexprie<当前时间戳
(т. е. неотправленные данные с истекшим сроком действия) - данные для фильтрации
status
Измените на 2 и запроситеwx_message_push_status
соответствует таблицеuser_id
,Будуcount
Уменьшить себя на 1.
Идеальная отделка.
После прояснения логики разработки вы готовы писать код
Код
титульная страница
страницыform
компонент, свойствоreport-submit
дляtrue
, вы можете объявить, что сообщение шаблона необходимо отправить, а затем нажать кнопку, чтобы отправить форму, чтобы получить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 // средний уровень узла для настройки базового интерфейса
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 //Интерфейс базы данных операций узла
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
});
}
Выполнение запланированной задачи для отправки сообщения
Уф~ Полный код готов. Общая идея такова, работа БД не рассматривает вопросы производительности, а если есть проблемы с объемом данных, транзакций, индексов и прочих операций (в основном не Т_Т) не рассматриваются, читалки могут оптимизировать сами.
Наконец, две запланированные задачи должны быть открыты для выполнения по отдельности.sendMessageTask
интерфейс иamendExpireTask
Интерфейс, наша временная задача также является фреймворком узла с открытым исходным кодом, и конкретная реализация не указана.
окончательный эффект:
использованная литература
Блог-центр Renrendai Big Front-end Technology
Наконец-то прорекламировали. Добро пожаловать в гостиБлог-центр Renrendai Big Front-end Technology
внутри оnodejs
react
reactNative
Небольшие программы, фронтенд-инжиниринг и другие сопутствующие технические статьи обновляются одна за другой. Добро пожаловать в гости и жалуйтесь~
Предыдущий:Функция автоответчика онлайн-сервиса для небольших программ по борьбе с монстрами (версия узла)