1.参考地址
订阅消息初步: developers.weixin.qq.com/miniprogram…
调用方法: developers.weixin.qq.com/miniprogram…
2.前提步骤
2.1 思路
云开发实现订阅消息推送
步骤1:获取模板ID 需要到开发者平台中开通订阅消息并且设置模板
步骤2:获取用户授权 ---> wx.requestSubscribeMessag
步骤3:获取用户的openid ---> 在cloudfunctions文件夹中新建一个getOpenID的node.js云函数
步骤4:用云函数实现消息推送
*/
2.2 实现方式 --> 步骤1:获取模板ID
需要到微信小程序平台上开通订阅消息的功能,并且选择好模板,模板的内容字段要与cloud.openapi.subscribeMessage函数的data属性中的字段对应的上
如上图,我们从公共模板库里选择一个一次性订阅的模板。然后编辑模板。 下图就是我们添加好的模板,下图的模板id就是我们需要的。
3.步骤2:前端获取用户授权
我们做订阅消息授权时,只能是用户点击或者支付完成后才可以调起来授权弹窗,官方是这么要求的:
调起客户端小程序订阅消息界面,返回用户订阅消息的操作结果。当用户勾选了订阅面板中的“总是保持以上选择,不再询问”时,模板消息会被添加到用户的小程序设置页,通过 wx.getSetting 接口可获取用户对相关模板消息的订阅状态
我们这里用到了wx.requestSubscribeMessage这个方法,来获取用户的授权。
<view class="tips" wx:if="{{hasUserInfo}}">
<van-button round type="info" bindtap="handlePushTest">获取消息提醒</van-button>
</view>
// 点击订阅授权
handlePushTest(){
// 调起客户端小程序订阅消息界面,返回用户订阅消息的操作结果
wx.requestSubscribeMessage({
tmplIds: [
'5LYVadV3c9omx4i2edorqlzyQVOVavY6YqA8vnbsujo', // 模板ID 而不是模板编号
'YP_KT5yBRsW813tqSfHBpqGQgleL6CtS9pgnXhV7NOY'
], // 模板ID
success:res=>{
console.log('授权状态',res)
const {
'5LYVadV3c9omx4i2edorqlzyQVOVavY6YqA8vnbsujo':a,
'YP_KT5yBRsW813tqSfHBpqGQgleL6CtS9pgnXhV7NOY':b
} = res
console.log(a,b)
if (a == 'accept' || b == 'accept') {
wx.showToast({ title: '订阅成功!', duration: 1000 })
}else{
wx.showToast({ title: '订阅失败!', duration: 1000, icon:"none" })
}
}
})
},
结果如下:
我们正常订阅消息授权时,用户允许的话,你只能推送一次消息。也就是用户允许一次,我们就可以推送一条消息给用户,并且这个允许不存在过期。所以我们可以让用户尽量多的点击允许,这样我们就可以尽量多的给用户发送消息了。
4.获取用户的openID
4.1 首先在前端app.js中初始化云函数
onLaunch: function () {
if (!wx.cloud) {
console.error('请使用 2.2.3 或以上的基础库以使用云能力')
} else {
// 初始化云开发能力
wx.cloud.init({
// env 参数说明:
// env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源
// 此处请填入环境 ID, 环境 ID 可打开云控制台查看
// 如不填则使用默认环境(第一个创建的环境)
env: 'orange666',
traceUser: true,
})
}
},
4.2 新建getOpenId云函数
新建完毕后,需要打开该文件夹,cmd 然后npm install一下,最后右键云函数“上传并部署所有文件”,下一次如果不安装插件的话,可以对index.js文件进行增量更新
4.3 编写getOpenId云函数
注意:这里我要写云函数的环境,可以使用cloud.DYNAMIC_CURRENT_ENV常量来设置当前环境,不写则默认获取第一个环境
获取openid很简单的,
- 在云函数内使用 wx-server-sdk 提供的
getWXContext方法获取到每次调用的上下文(appid、openid 等),无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openid)
// 云函数入口文件
const cloud = require('wx-server-sdk')
// 如果有多个云函数环境 那么要初始化一下当前 云开发环境的id 无须加wx.
cloud.init({
env:cloud.DYNAMIC_CURRENT_ENV // 默认取当前云开发环境的ID 当环境变更时,系统会自动变成更换后的环境ID
})
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
// 在云函数内使用 wx-server-sdk 提供的 getWXContext 方法获取到每次调用的上下文(appid、openid 等),
// 无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openid)
return {
event, // 就是你传递过来的对象
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
}
5.步骤4 云函数(后端这边)
5.1 用到的API
openapi.subscribeMessage.send
5.2 发送订阅消息到指定用户
推送之前要先获取用户的openid (上面已经通过云函数获取了),然后传递给云函数即可
-
前端通过
wx.requestSubscribeMessage这个函数授权订阅消息 -
前端调用pushOneMsg这个云函数
<van-button type="primary" bindtap="handleOnePush" round >发送订阅消息给一个指定用户</van-button>
// 发送订阅消息给一个用户
handleOnePush(){
wx.cloud.callFunction({
name:"pushOneMsg",
data:{
openid:"oPVpH40CQMmbGN6W86tN4e-Z1hyg" // 传递openid给云函数
}
}).then(res=>{ // 走进来不代表一定发送成功 因为这里的成功只是指调用这个云函数成功 真正的结果需点开看
console.log(res)
if(res.result.errCode == 0){
wx.showToast({ title: '发送成功', icon: 'success', duration: 1000 })
}
}).catch(rej=>{
console.log(rej)
})
},
- 后端编写pushOneMsg云函数
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env:cloud.DYNAMIC_CURRENT_ENV
})
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
console.log(event,wxContext)
// 发送订阅消息给用户
try {
const result = await cloud.openapi.subscribeMessage.send({
touser:event.openid, // 通过传递的openid对该用户发送消息
page: 'pages/index/index', // 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数(index?foo=bar)。不填则模板无跳转
lang: 'zh_CN',
data: { // 模板内容
thing4: {
value:'测试标题',
},
thing2: {
value:'测试内容测试内容哈哈哈',
},
thing1: {
value:"测试者哈哈哈",
},
time3: {
value:'2020年1月1日 12:00', // 格式要求 2020-1-1 12:00
},
},
templateId: '5LYVadV3c9omx4i2edorqlzyQVOVavY6YqA8vnbsujo', // 推送的模板
miniprogramState: 'developer' // 跳转小程序类型:developer为开发版 formal为正式版
})
return result
} catch (err) {
return err
}
}
5.3 发送订阅消息给多个用户
这里为了方便,直接用已注册的用户去判断推送,并且是早晚推送,再加上一个触发器,实现早晚问候
- 页面用于测试而写
<van-button type="info" bindtap="handlePushTestSend2" round style="display: block;margin: 10px;">
模拟早安消息自动推送
</van-button>
<van-button type="warning" bindtap="handlePushTestSend3" round style="display: block;margin: 10px;">
模拟晚安消息自动推送
</van-button>
// 封装 模拟触发器触发早安晚安函数
triggerPushText(cloudFnName){
wx.cloud.callFunction({
name:cloudFnName,
}).then(res=>{ // 走进来不代表一定发送成功 因为这里的成功只是指调用这个云函数成功 真正的结果需点开看
console.log(res)
}).catch(rej=>{
console.log(rej)
})
},
// 发送早安提醒
handlePushTestSend2(){
this.triggerPushText("TriggerPushTest")
},
// 发送晚安提醒
handlePushTestSend3(){
this.triggerPushText("TriggerPushNight")
},
- 云函数
- 早安的云函数 TriggerPushTest
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database({
env: cloud.DYNAMIC_CURRENT_ENV
})
// 云函数入口函数
exports.main = async (event, context) => {
let newDate = new Date();
let ymr = newDate.toLocaleDateString().split('/')
let time = newDate.toLocaleTimeString().slice(2).slice(0, 5)
let sendTime = `${ymr[1]}月${ymr[2]}日 ${time}`
// 发送订阅消息给用户
let result = await db.collection('user').get()
for(let item of result.data){
console.log(item)
cloud.openapi.subscribeMessage.send({
touser: item.openid,
page: 'pages/index/index',
lang: 'zh_CN',
data: { // 模板内容
thing4: {
value:'早起的鸟儿有虫吃',
},
thing2: {
value:'早安,注意天气变化,祝你有个美好的一天',
},
thing1: {
value: "小橙",
},
time3: {
value: new Date().toLocaleDateString(), // 格式要求 2020-1-1 12:00
},
},
templateId: '5LYVadV3c9omx4i2edorqlzyQVOVavY6YqA8vnbsujo', // 推送的模板
miniprogramState: 'developer' // 跳转小程序类型:developer为开发版 formal为正式版
}).then(res=>{
console.log(res)
}).catch(rej=>{
console.log(rej)
})
console.log(200)
// return result;
}
console.log("我是最后的")
}
配置触发器,每日早上7.30分触发云函数,自动发送订阅消息
- 晚安的云函数 TriggerPushNight
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database({
env: cloud.DYNAMIC_CURRENT_ENV
})
// 云函数入口函数
exports.main = async (event, context) => {
let newDate = new Date();
let ymr = newDate.toLocaleDateString().split('/')
let time = newDate.toLocaleTimeString().slice(2).slice(0, 5)
let sendTime = `${ymr[1]}月${ymr[2]}日 ${time}`
// 发送订阅消息给用户
let result = await db.collection('user').get()
for(let item of result.data){
console.log(item)
cloud.openapi.subscribeMessage.send({
touser: item.openid,
page: 'pages/index/index',
lang: 'zh_CN',
data: { // 模板内容
thing3: {
value:'睡前一刻',
},
thing1: {
value:'深夜来临,早点休息哦,晚安~',
},
thing6: {
value: "小橙",
},
time8: {
value: new Date().toLocaleDateString(), // 格式要求 2020-1-1 12:00
},
},
templateId: 'YP_KT5yBRsW813tqSfHBpqGQgleL6CtS9pgnXhV7NOY', // 推送的模板
miniprogramState: 'developer' // 跳转小程序类型:developer为开发版 formal为正式版
}).then(res=>{
console.log(res)
}).catch(rej=>{
console.log(rej)
})
console.log(200)
// return result;
}
console.log("我是最后的")
}
配置一下触发器
6.最后发现有更好点的方案
传送门:blog.csdn.net/weixin_4254…
分析他的思路:
- 引导用户订阅(给个button按钮,事件处理函数是onSubscribe)
- 在 onSubscribe 函数内,调用 wx.requestSubscribeMessage 申请发送订阅消息权限,当用户在弹窗同意订阅之后,我们会收到 success 回调,将订阅的课程信息调用云函数 subscribe 存入云开发数据库
// 调用微信 API 申请发送订阅消息
wx.requestSubscribeMessage({
tmplIds: [lessonTmplId], // 传入订阅消息的模板id
success(res) {
// 订阅成功 接口调用成功时errMsg值为'requestSubscribeMessage:ok'
if (res.errMsg === 'requestSubscribeMessage:ok') {
// 这里将订阅的课程信息调用云函数存入云开发数据
wx.cloud.callFunction({
name: 'subscribe', // 触发云函数
data: {
data: item, // 这个item就是模板消息显示的内容 subscribeMessage.send需要
templateId: lessonTmplId, // 模板id subscribeMessage.send需要
},
})
.then(() => {
wx.showToast({ title: '订阅成功', icon: 'success', duration: 2000});
})
.catch(() => {
wx.showToast({ title: '订阅失败', duration: 2000});
});
}
},
});
- 创建一个云函数
subscribe,这个云函数的作用是将用户的订阅信息存入云开发数据库的集合messages中,等待将来需要通知用户时进行调用。
在微信开发者工具的云开发面板中创建数据库集合 messages
创建一个 subscribe 云函数,在云函数中我们将小程序端发送过来的课程订阅信息,存储在云开发数据库集合中,开发完成后,在微信开发者工具中右键上传并部署云函数。
const cloud = require('wx-server-sdk');
cloud.init();
const db = cloud.database();
exports.main = async (event, context) => {
try {
const {OPENID} = cloud.getWXContext();
// 在云开发数据库中存储用户订阅的课程
const result = await db.collection('messages').add({
data: {
touser: OPENID, // 订阅者的openid 订阅者的openid可以传过来也可以直接通过getWXContext获取
page: 'index', // 订阅消息卡片点击后会打开小程序的哪个页面
data: event.data, // 订阅消息的数据
templateId: event.templateId, // 订阅消息模板ID
done: false, // 消息发送状态设置为 false
},
});
return result;
} catch (err) {
console.log(err);
return err;
}
};
-
利用定时触发器来定期发送订阅消息
-
实现发送订阅消息的云函数,这个云函数会从云开发数据库集合
messages中查询等待发送的消息列表,检查数据库中是否有需要发送给用户的订阅消息,发送条件可以根据自己的业务实现,比如开课提醒可以根据课程开课日期来检查是否需要发送订阅消息,在我们下面的代码示例里做了简化,筛选条件只检查了状态为未发送。
查询到待发送的消息列表之后,我们会循环消息列表(done:为false的),依次发送每条订阅消息,发送成功后将数据库中消息的状态改为已发送。
const cloud = require('wx-server-sdk');
exports.main = async (event, context) => {
cloud.init();
const db = cloud.database();
try {
// 从云开发数据库中查询等待发送的消息列表
const messages = await db.collection('messages')
// 查询条件这里做了简化,只查找了状态为未发送的消息
// 在真正的生产环境,可以根据开课日期等条件筛选应该发送哪些消息
.where({ done: false }).get();
// 循环消息列表
const sendPromises = messages.data.map(async message => {
try {
// 发送订阅消息
await cloud.openapi.subscribeMessage.send({
touser: message.touser, // 这个就是那个订阅者的openid
page: message.page,
data: message.data,
templateId: message.templateId,
});
// 发送成功后将消息的状态改为已发送
return db.collection('messages').doc(message._id)
.update({
data: {
done: true,
},
});
} catch (e) {
return e;
}
});
return Promise.all(sendPromises);
} catch (err) {
console.log(err);
return err;
}
};