本篇文章需要实现的是一个基于云开发搭配CMS实现的消息下发系统。当然不是简简单单的实现功能就好,在实现之前我们需要考虑这个功能的可拓展性以及可复性。
具体的业务场景:管理员登陆CMS系统后可以创建一条消息,下发给指定的用户。或者更新订单状态后,自动下发订阅消息给该用户。
这种类似的场景在项目开发中还是很普遍的。稍加分析,其实核心的模块是实现下发订阅消息的系统,把这个做了,稍加改造就可以实现管理员主动下发以及订单状态更改后自动下发的功能,毕竟这两个功能本质都是给指定的用户下发消息。
小程序触达用户,实现业务逻辑的闭环,这是一个非常重要的事情,基本是每个较为完善的小程序都必须实现功能。使用场景非常的广泛,例如订单处理进度状态的改变,我们需要在CMS上更新订单的状态,从处理中改为处理完成,需要通知到用户。
当前小程序主动发送消息给用户的常用途径有以下2种:
订阅消息是目前较为主流的做法,基本是小程序长期维护稳定的一种方式,例如小程序的模板消息就被砍掉了,不是很稳定。订阅消息熟悉小程序开发的都知道,最大的痛点就是,必须用户订阅才可下发,而且是订阅一次发送一次。(长期订阅消息除外,但是普通企业以及用户基本申请不了)。但它依旧是目前的主流做法,也是官方推荐的做法。
统一服务消息,其实就是当前服务号的模板消息,只不过它可以使用小程序的openid通过绑定同一个开放平台的服务号下发模板消息。虽然需要操作的步骤多了一些,但是好处是不言而喻的,只要关注了服务号的用户,就可以无限次给他发送模板消息,不需要像订阅消息那样,订阅一次发送一次。缺点也有,一是需要绑定开放平台,比较繁琐一点。二是需要挂个服务号,用户没有关注也没办法给他下发。三是不稳定,公众号的模板消息在2020年就准备被下架,但是由于开发者的反对意见比较多,也就一直延迟到现在还在使用,不排除后期砍掉这个功能,假设砍掉的话,这个基本就废了。
简单讲了一下,希望大家在小程序需求分析以及评估的时候能够考虑这两者的优缺点,进行取舍。至于哪个好哪个坏,其实没有一个结论,只需要根据项目的规划进行选择就好。合适就好。当然,如果选择统一服务消息的话,就要考虑好后期此功能被砍掉的替代方案。
本文的话,选择小程序的订阅消息进行实现。两种方案的主要实现过程大致相同,所以基本可以类比的。
创建一个消息队列(msgList)集合,专门用来存放要下发的消息的数据,例如接收者的openid,发送的参数啥的。当需要下发消息的时候就往这个表里插入一条记录,然后下发就可以了。下发完成后顺便把下发的结果啥的更新进来,就可以确定有没有通知到位了。
但是此时需要考虑的问题有:
1、订阅消息的模板的参数个数以及参数名都不一致,而且对于参数的类型也有较为严格的限制,如何将所有参数统一。
例如模版A需要三个参数:订单进度、订单金额、购买物品。而模板B需要的是5个参数:收货人、手机号、快递名称、快递订单号、预计送达时间。
这种情况下,参数类型、参数名、参数值都不尽相同该如何进行统一?
解决方案有两种:一是将数据库记录的参数名称改为参数一、参数二、参数三依此类推下去,然后在代码里根据下发的模版类型进行参数的插入即可。至于参数个数,直接定为10个,大部分情况下10个参数已经足够了。二是将参数存放在数组里。这样就不用在意参数的个数限制了。我个人的话,采用第二种方案,因为可拓展性比较强。
当然这是基于当后台是云开发自带的CMS的情况下需要考虑的事情,如果是自己开发的管理后台就没这种必要了。直接根据选择的模板类型进行参数的个数以及类型的显示即可。
2、当插入消息记录后,如何触发订阅消息的下发,这个也是一个问题。解决方案也有两种:第一种是通过定时器触发,每分钟遍历一下集合里有没有需要下发的记录,如果有就执行下发逻辑。这种做法的好处就是开发方便;坏处就是下发不是即时的,也会比较消耗资源,毕竟每分钟都要执行一下。第二种是通过数据库底层的触发器来实现,创建一个触发器,监听消息集合的插入事件,当有记录插入时就触发消息下发逻辑。这种的好处就是即时下发、不会浪费资源;坏处是开发麻烦一点,需要多挂一个触发器。我个人的话,直接采用第二种方案,因为它值得选择。
tips:这就会有小伙伴有疑问了。为啥不使用CMS的云函数类型WebHook呢?因为CMS触发是没有登录态的,简单来说就是它没办法触发云调用的API,因为它没有权限。所以使用这种方案目前是行不通的,只是目前行不通,不排除它后期更新后支持云调用,大家留意一下官方消息吧。
很多步骤都是很基础的,如果已经做了的,就可以跳过,没有的话,看一下需要的准备的东西,按照自己的实际情况去弄一下就好,基本没什么难度。
首先肯定要创建一个用户集合啦,不然哪来的openid呢。然后就是创建消息列表集合以及订单集合。再然后就是去申请模板啥的了。不会的自行百度即可。如果没有创建以及配置CMS的当然也需要创建配置一下,创建以及配置的流程我就不说了,简简单单,自行搞定。
直接引用我之前写的文章吧,没有的朋友直接cv大法即可。
订阅消息模块
用户注册模块
名字随意,后面用到的地方保持一致就好。我因为已经创建了一个sendSubMsg了,所以这个就叫sendSubMsgV2好了。不想删除之前那个。创建好,先不用编写代码。等下再编写。
前往腾讯云控制台,选择小程序公众号登陆。
控制台登陆直达链接=
登陆后选择云开发
然后选择你对应的环境
创建完成。这时候,只要往subMsgList集合里插入数据的时候就会触发指定的云函数了。
关于触发器更多的用法可以看一下官方文档。
触发器官方文档
创建一下简单的用户列表以及消息列表的模型,至于订单列表的话,可以先不创建,看一下就会了,不一定要创建。后面我也会实现一下搭配订单来下发消息的过程。
简单搞一个实例,其他的,根据自己的实际需求来就好。
顺便提一下:订阅消息模板我是申请了两个,没有的话,去申请一下就好。
首先是编写下发订阅消息的云函数代码。
创建一个js文件:templateData.js。这个文件主要是放置我们所需要的各种模板消息的数据,然后返回一个发送数据对象。
对于一些参数类型的限制以及参数名称,直接根据文档以及申请的模板详情来写就行。每个模板的参数都不尽相同。对于点击跳转的页面、跳转的小程序版本(开发版、体验版、正式版)等,需要动态设置的就使用参数的形式实现就可以了。
/**
* 会员充值成功模板消息数据
* @param {string} openid 接收者openid
* @param {string} name 会员姓名
* @param {string} cardNumber 会员卡号
* @param {number} payMoney 充值金额
* @param {number} balance 账户余额
* @param {string} remark 备注 20字内
*
*/
const vipPaySuccess = (openid, name, cardNumber, payMoney, balance, remark) => {
const sendObj = {
"touser": openid,
"page": 'pages/APITest/APITest',
data: {
"name1": {
"value": name
},
"character_string2": {
"value": cardNumber
},
"amount4": {
"value": payMoney
},
"amount5": {
"value": balance
},
"thing13": {
"value": remark
}
},
"templateId": 'AcxnCbDhMKA0bunvuvBa5RJT-OkxmfZfDPB_mTvirxw',
"miniprogramState": 'developer'
}
return sendObj
}
/**
* 订单状态通知模板消息数据
* @param {string} openid 接收者的openid
* @param {string} goodsName 商品名称
* @param {number} goodsPrice 商品价格
* @param {string} orderStatus 订单配送状态
* @param {string} remark 备注
*/
const orderStatusMsg = (openid, goodsName, goodsPrice, orderStatus, remark) => {
const sendObj = {
"touser": openid,
"page": 'pages/APITest/APITest',
data: {
"thing2": {
"value": goodsName
},
"amount3": {
"value": goodsPrice + "元"
},
"thing11": {
"value": orderStatus
},
"thing5": {
"value": remark
}
},
"templateId": 'zT99mpoUCb28M02K7ohTO3h81IvBUlgt8JVRn6rkNSc',
"miniprogramState": 'developer'
}
return sendObj
}
module.exports = {
vipPaySuccess,
orderStatusMsg
}
接下来是云函数index.js的代码编写。编写前先配置一下index.json,因为需要订阅下发的权限,具体的可以查看官方文档。
index.json
{
"permissions": {
"openapi": [
"subscribeMessage.send"
]
}
}
index.js
// 云函数入口文件
const cloud = require('wx-server-sdk')
const templateData = require("./templateData")
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
// 云函数入口函数
exports.main = async (event) => {
console.log(event) //打印一下触发器的入参,看看传入了啥给我们。
const docData = event.data.doc // 获取插入的记录的详细数据
const templateId = docData.templateId
const docId = docData._id
const openid = docData.openid
const parameterArr = docData.parameterArr
let sendData = {
}
let sendRes = {
}
switch (templateId) {
// 会员充值
case "AcxnCbDhMKA0bunvuvBa5RJT-OkxmfZfDPB_mTvirxw":
sendData = templateData.vipPaySuccess(openid, parameterArr[0], parameterArr[1], parameterArr[2], parameterArr[3], parameterArr[4])
break
// 订单通知
case "zT99mpoUCb28M02K7ohTO3h81IvBUlgt8JVRn6rkNSc":
sendData = templateData.orderStatusMsg(openid, parameterArr[0], parameterArr[1], parameterArr[2], parameterArr[3])
break
default:
return "没有符合的消息模板"
}
// 发送
try {
sendRes = await cloud.openapi.subscribeMessage.send(sendData)
} catch (error) {
sendRes = error
}
// 更新发送状态
const nowTime = new Date().getTime()
const sendStatus = sendRes.errCode == 0 ? 'success':'fail'
const sendStatusMsg = sendRes.errMsg
await updateSendStatus(docId, {
updateTime: nowTime, sendTime: nowTime,sendStatus, sendStatusMsg})
return sendRes
}
/**
* 更新发送的状态以及返回值
* @param {string} docid 记录唯一ID
* @param {object} updateObj 更新的数据对象
*/
const updateSendStatus = async (docid, updateObj) => {
return await db.collection("subMsgList").doc(docid).update({
data: updateObj
})
}
编写完成记得上传一下。
基本上是做完了。现在我们创建一条订单的消息,看看效果如何。
CMS创建记录参数
发送结果
完美。这样一个单一指定用户发送订阅消息的功能模块就完成了。如果还需要执行其他的业务逻辑,自己根据自己的业务需求去编写就好了。
从此只要是涉及指定用户下发订阅消息的需求都可以基于这个功能模块去拓展。
接下来实现的是基于订单的状态更新去下发订阅消息的功能了。这次下发的模板选择会员充值成功通知的来做。
简单来说,就是管理员操作订单,将充值状态从充值中更新为充值成功时,下发订阅消息。通知用户充值会员成功。但是这里只是简单的实现一下主要的思路,具体的业务还是需要额外进行处理。这里最主要的还是实现自动下发的功能。
思路是这样的:在CMS创建一个云函数类型的webHook,监听order集合的更新 ,当符合下发条件的时候,就执行云函数,插入一条数据到消息列表,剩下的就基本跟上面的一致了。但是这里的话,有个小细节,并不是每次更新订单都需要通知用户,所以加个标记,当管理员选择需要通知用户的时候再去通知。
创建完成后,也需要创建一个云函数cmsHook,用来执行触发后的逻辑。
index.js
过程的异常捕获以及处理在实际开发中自己看情况需不需要做吧。我这里就不做了,快速演示一遍就好。主要是将思路分享出来。
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
// 云函数入口函数
exports.main = async (event) => {
console.log(event) // 打印一下入参,看看传入的参数
const docId = event.actionFilter._id // 更新的记录唯一ID
const collection = event.collection // 可以用来判断是哪个集合更新而触发的进而执行不同的逻辑
let res = {
} // 执行过程结果
switch (collection) {
case "order":
let orderData = await getOrderData(docId)
orderData = orderData.data
if (!orderData.isSendMsg) {
return "此次更新无需通知用户"
}
const openid = orderData._openid
// 这里可以根据订单的类型来选择触发那种模板,这里直接写死了
const templateId = "AcxnCbDhMKA0bunvuvBa5RJT-OkxmfZfDPB_mTvirxw"
// 实际上这些数据应该来自会员卡数据集合才对,但是这里为了演示,就直接从订单表拿了。
//合理的做法是再创建一个会员集合的。
const parameterArr = [orderData.name, orderData.cardNumber, orderData.payMoney, orderData.balance, orderData.remark]
const nowTime = new Date().getTime()
const addSendMsgData = {
openid,
templateId,
sendStatus: "wait",
parameterArr: parameterArr,
createTime: nowTime,
updateTime: nowTime,
}
// 插入消息
const addRes = await insterSubMsg(addSendMsgData)
// 更新订单
const updateRes = await updateOrder(docId, {
isSendMsg: false
})
res.addSendMsg = addRes
res.updateOrderRes = updateRes
break
default:
return "不存在此集合的更新逻辑"
}
return res
}
/** 根据_id获取订单信息 */
const getOrderData = async (docId) => {
return await db.collection("order").doc(docId).get()
}
/**
* 插入发送记录到订阅消息发送集合
*/
const insterSubMsg = async (addObj) => {
return await db.collection("subMsgList").add({
data: addObj
})
}
/** 更新订单数据,主要是将通知标记恢复 */
const updateOrder = async (docId, updateObj) => {
return await db.collection("order").doc(docId).update({
data: updateObj
})
}
创建一条订单。(实际订单表这样设计并不合理,所以这个的话就不要吸收了。)
对于是否发送通知标记。这个细节需要注意一下。因为不是每次通知都需要通知到用户。
创建完后更新一下,因为我们只监听了更新的事件。
运行结果
发送失败是正常的,因为我没有订阅这个模板消息,所以逻辑是跑得通的。我也就懒得写订阅这个模板的代码了。大家自己搞定。
分享的是思维不是技术。所以很多地方写得并不是很严谨,仅仅是把逻辑跑了一遍。(大佬们手下留情,谢谢)
实际开发中的其他逻辑就不写了,这里只是最简单的实现。
有任何疑问可以在评论区留下。我每天都会进行回复,私聊不回。(为了刷积分)
以上均是本人开发过程中的一些经验总结与领悟,如果有什么不正确的地方,希望大佬们评论区斧正。
最后!!!不管这篇文章对你有没有用,既然都看到最后了。
赞一个!!!
当然,顺带收藏就最好了。
欢迎转载,原创不易,转载请注明出处✍。
如果你对小程序开发有兴趣或者正在学习小程序开发,可以关注我。每一篇都是原创,每一篇都是干货噢~。
————————————————
版权声明:本文为CSDN博主「super–Yang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。