微信云托管(二)--消息推送手记
众所周知,小程序如果没有相应的资质的话,是没有办法主动推送消息给用户的,想要从它的服务通知里面推送,资质认证和审核比较麻烦。另一种方法就是订阅消息,但是需要用户授权,授权一次,然后这边可以推送一次,如果想要一直可以主动推送,我们通常都是借公众号的模板消息。
正题之前,描述一下之间做出的一个弯路方案:
当前处理的是一个聊天工具,我需要点击发送的时候,用户有新的消息的时候,可以接收到提醒,设想让用户在点击发送的时候,勾上“总是授权”,然后把授权事件一直绑定在发送按钮上,这样每一次消息发送的时候,就等于自动授权了一次,那就可以接收下一次的订阅消息推送了。典线完成无限次的消息推送。
正经的开始,开局一张草图,内容全靠编。
云托管的好处就是不需要维护accessToken,想要啥接口,直接调就行。
前置准备
- 需要一个认证后的公众号
- 需要一个认证并备案过的小程序
- 需要一个认证后的第三方开放平台
- 公众号和小程序是同一个主体
- 需要一个可用云托管环境
- 记下公众号的appid,小程序的appid
- 添加一个模板消息,记下模板消息的模板信息
开始配置
公众号:
添加模板消息,如果还没有开通的先进行开通,和设置类目。
绑定小程序为关联小程序(不配的话,发送跳转到小程序的消息会失败)
小程序
无
开放平台
添加小程序到开放平台
添加公众号同一个开放平台
云托管
- 资源复用
如果公众号开的云托管,就把小程序添加到资源复用
如果小程序开的云托管,就把公众号添加到资源复用
重要的知识点:
如果没有资源复用的时候,服务调用微信开放接口,直接调用就行了。不需要
accessToken 和签名这些内容,直调。
但是如果资源复用了之后,调主账号的内容的时候,可以维护原来一样,直调。
调用复用的应用开发接口的时候,则需要在接口后面增加上from_appid的参数,不然后提示未授权。
例子说明:我现在的主账号是小程序注册的云托管,调用小程序的微信开放接口直接调用
但是公众号是添加的资源复用,调用公众号的开放接口的时候,则需要这样
http://api.weixin.qq.com/cgi-bin/message/template/send?from_appid=公众号的appid
它的机制是不填默认是主应用的appid,小程序的appid调用公众号的接口,肯定不成功,但是官方不给你提示这个错误信息,只是返回未授权。
我这里是小程序开的账号,所以把公众号添加到资源复用。
- 云调用接口配置
在云调用里面,把公众号需要调用的接口路径添加到配置里面。
前面两个小程序的订阅消息和获取手机号,添加后面两个就行了,获取用户信息和发送模板消息。
- 配置消息推送功能
服务端写一个接口,接收公众号的事件推送,用户关注/取消关注等事件,会主动触发该接口,接口根据拿到的信息处理相应的逻辑就行了。
比如这里的这个接口的主要逻辑就是:
- 判断事件类型是否是关注事件
- 如果是关注事件,从接口里面获取用户的openId
- 根据这个openId调用cgi-bin/user/info接口,获取用户的unionId
- 根据unionId查询小程序的用户表,获取的用户信息,将公众号的openId更新进去
然后就是撸两个接口
第一个更新用户公众号的openId到信息表。
当然也可以用其它的逻辑,按事件同步公众号的用户列表等
// 处理微信事件推送的接口
app.post('/api/v1/sendTemplateMessage', async (req, res) => {
const msgType = req.body.Event;
const openid = req.body.FromUserName;
// 根据事件类型进行处理
switch (msgType) {
case 'subscribe':
// 用户关注事件处理逻辑
try {
// 通过openid获取unionid
const response = await axios.get(`http://api.weixin.qq.com/cgi-bin/user/info?openid=${openid}&lang=zh_CN&from_appid=你的公众号appid`);
const unionid = response.data.unionid;
if (unionid) {
// 查询Users表,检查是否存在该unionid
const user = await Users.findOne({ where: { unionId: unionid } });
if (user) {
// 更新publicId字段
user.publicId = openid;
await user.save();
console.log('用户信息已更新:', user);
} else {
console.log('未找到匹配的用户信息');
}
} else {
console.log('未能获取到unionid');
}
} catch (error) {
console.error('获取unionid或更新用户信息时出错:', error);
}
break;
case 'unsubscribe':
console.log('用户取消了关注');
break;
case 'SCAN':
console.log('用户扫描了二维码');
break;
case 'LOCATION':
console.log('用户上报了地理位置');
break;
case 'CLICK':
console.log('用户点击了菜单');
break;
case 'VIEW':
console.log('用户点击了跳转链接');
break;
default:
console.log('未知事件类型:', msgType);
}
// 返回空响应以避免重试
res.send('');
});
发送模板消息的事件
// 需要发送消息的时候,传入参数调用该事件,
async function sendTempMessage(openid,data) {
// 构造小程序跳转链接
const page = `pages/index/index`;
const {time,content} = data
// 发送模板消息
try {
const apiUrl = `http://api.weixin.qq.com/cgi-bin/message/template/send?from_appid=你的公众号appid`;
const requestBody = {
touser:openid,
template_id:"你的消息模板ID",
miniprogram:{
appid:"你的消息要跳转的小程序appid",
pagepath:page
},
data:{
// 这里的内容根据你的模板消息的格式来
"thing19": {
"value": content
},
"time04": {
"value": time
}
}
}
const response = await axios.post(apiUrl, requestBody);
if (response.data.errcode === 0) {
console.log('模板消息发送成功');
} else {
console.error('模板消息发送失败:', response.data);
throw new Error('模板消息发送失败');
}
} catch (error) {
console.error('模板消息发送失败:', error);
}
}
忽略时间吧,时间我写死了测试的。