GitHub Demo 地址: jh-weapp-demo 实现一些常用效果、封装通用组件和工具类
小程序码
前言
受众已有小程序和云开发经验没有的话照着流程和官方文档也应该可以实现
关于小程序的消息推送我了解到的有以下几种实现方式 1、模板消息已于2020 年 1 月 10 日下线 2、通过服务端的统一服务消息下发推送因为模板消息现已下线现只支持公众号。统一服务消息官方文档 2、通过关注公众号通过公众号实现长期的消息推送 3、订阅消息包含一次性订阅消息和长期订阅消息 订阅消息官方文档
关于技术实现的选择
关于小程序的消息推送的几种实现方式先简单说一下各自的优缺点
1、统一服务消息
优点可以长期多次发送 缺点因为模板消息现已下线现只支持公众号
2、通过公众号实现
优点可以长期多次发送 缺点需要引导关注公众号没有公众号还得注册一个以下还有一些注意事项
1、公众号和小程序需要在同一个微信开放平台下保证拿到相同的UnionID 2、如果需要在消息模板上加上小程序的入口需要微信公众号和小程序做关联 3、小程序和公众号都必须是认证过的 4、小程序需要提前知道公众号的appid和appsecret 5、发送消息之前需要拿到用户对应于公众号的openid
3、订阅消息实现
订阅消息包含一次性订阅消息和长期订阅消息可惜长期订阅消息只对指定类目开放
一次性订阅消息 优点服务端云开发都可以实现推送 缺点每次需要授权每次授权同意只推送一次
哎没什么可挑的最终选择的是订阅消息的一次性订阅消息下面是微信官方介绍
小程序订阅消息
功能介绍
消息能力是小程序能力中的重要组成我们为开发者提供了订阅消息能力以便实现服务的闭环和更优的体验。
- 订阅消息推送位置服务通知
- 订阅消息下发条件用户自主订阅
- 订阅消息卡片跳转能力点击查看详情可跳转至该小程序的页面
消息类型
1. 一次性订阅消息
一次性订阅消息用于解决用户使用小程序后后续服务环节的通知问题。用户自主订阅后开发者可不限时间地下发一条对应的服务消息每条消息可单独订阅或退订。
2. 长期订阅消息
一次性订阅消息可满足小程序的大部分服务场景需求但线下公共服务领域存在一次性订阅无法满足的场景如航班延误需根据航班实时动态来多次发送消息提醒。为便于服务我们提供了长期性订阅消息用户订阅一次后开发者可长期下发多条消息。
目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放后期将逐步支持到其他线下公共服务业务。
3. 设备订阅消息
设备订阅消息是一种特殊类型的订阅消息它属于长期订阅消息类型且需要完成「设备接入」才能使用。 设备订阅消息用于在设备触发某些需要人工介入的事件时例如设备发生故障、设备耗材不足等向用户发送消息通知。详见设备订阅消息文档。
使用说明
步骤一获取模板 ID
在微信公众平台手动配置获取模板 ID 登录 https://mp.weixin.qq.com 获取模板如果没有合适的模板可以申请添加新模板审核通过后可使用。
步骤二获取下发权限
一次性订阅消息、长期订阅消息详见接口 wx.requestSubscribeMessage
设备订阅消息详见接口 wx.requestSubscribeDeviceMessage
步骤三调用接口下发订阅消息
一次性订阅消息、长期订阅消息详见服务端接口 subscribeMessage.send
设备订阅消息详见服务端接口 hardwareDevice.send
注意事项
- 用户勾选 “总是保持以上选择不再询问” 之后下次订阅调用 wx.requestSubscribeMessage 不会弹窗保持之前的选择修改选择需要打开小程序设置进行修改。
实现
设计思路
我这里的需求是纪念日的推送到用户设置的纪念日当天或者前几天需要给用户推送一条消息提醒用户纪念日已到。因为是一次性授权首先要考虑授权的时机因此考虑是在用户新增或者编辑纪念日信息那里设置一个开关开启时判断通知权限无权限提醒到设置页开启通知有权限请求授权授权成功开关开启失败或者未开启通知权限开关关闭最后提交信息到数据库推送的接口会每天轮询一次根据这个状态和是否到纪念日提醒时间判断是否进行推送推送成功重置状态。推送之后用户再次开启推送开关可实现下一次推送形成一个闭环。
实现流程
- 首先要去小程序后台选择一个模板
- 然后在新增编辑页面实现设计的逻辑包含通知权限的判断请求一次性订阅失败关闭开关状态保存数据到数据库
- 云开发轮询推送消息先查出需要推送的数据然后对应模板的数据格式发送推送
具体实现
1、选择模板
登录小程序开发后台 - 订阅消息 - 公共模板库搜索选择一个合适的模板然后在我的模板那里可以查看模板详情模板id和详情里的字段在云开发那里需要使用
2、编辑页代码实现
首先先把开关相关的页面和逻辑实现这里就不细说。 然后是判断通知权限和请求一次性订阅
2.1、判断通知权限
判断通知权限使用的是wx.getSetting
wx.getSetting({withSubscriptions: true,success (res) {console.log(res.authSetting)// res.authSetting {// "scope.userInfo": true,// "scope.userLocation": true// }console.log(res.subscriptionsSetting)// res.subscriptionsSetting {// mainSwitch: true, // 订阅消息总开关// itemSettings: { // 每一项开关// SYS_MSG_TYPE_INTERACTIVE: accept, // 小游戏系统订阅消息// SYS_MSG_TYPE_RANK: accept// zun-LzcQyW-edafCVvzPkK4de2Rllr1fFpw2A_x0oXE: reject, // 普通一次性订阅消息// ke_OZC_66gZxALLcsuI7ilCJSP2OJ2vWo2ooUPpkWrw: ban,// }// }}})
2.2、请求一次性订阅
请求一次性订阅使用的是wx.requestSubscribeMessage
wx.requestSubscribeMessage({tmplIds: [], // 模板idsuccess (res) { }})
2.3、把数据存储到云数据库
每个项目数据结构不一样这里也不展开说
核心代码
// 是否提醒onSwitchChange: function (event) {this.setData({["dict.isPush"]: event.detail.value})if (!event.detail.value) {this.setData({["dict.pushTime"]: })}if (event.detail.value) {this.checkAndRequestSubscribeMessage()}},// 检查订阅消息权限未开启提示前往开启已开启请求订阅消息checkAndRequestSubscribeMessage() {let that thiswx.getSetting({withSubscriptions: true,success(res) {console.log(res.subscriptionsSetting)// 订阅消息总开关是否开启if (!res.subscriptionsSetting.mainSwitch) {that.subscriptionFailed()wx.showModal({title: 提示,content: 当前暂未开启接消息提醒是否前往设置页开启,success(res) {if (res.confirm) {wx.openSetting()}}})} else {let templateId c64Gp5-89xyD55rnDr0oBWQNphWlNm_l4MX-Sduuj2c // 模板IDwx.requestSubscribeMessage({tmplIds: [templateId],success(res) {console.log(res)// 申请订阅成功将订阅信息调用云函数存入云开发数据if (res.errMsg requestSubscribeMessage:ok) {// res[templateId]: accept、reject、ban、filterif (res[templateId] accept) {} else {that.subscriptionFailed()}}},fail(err) {console.log(err)that.subscriptionFailed()wx.showToast({title: 订阅失败,icon: none})}})}}})},// 订阅失败subscriptionFailed() {this.setData({["dict.isPush"]: false})},
3、云开发轮询推送
这里才是重点首先在项目中选择云函数目录右键新建一个云函数然后就需要在这个云函数中实现轮询推送的代码了我这里建的云函数是push
3.1、推送
推送使用的是服务端的subscribeMessage.send方法。 这里是的是云调用进行推送这样可以使用云开发 subscribeMessage.send使用需要在云函数代码的config.json文件中配置 uniformMessage.send API 的权限详情
配置如下
"permissions": {"openapi": ["subscribeMessage.send"]},
请求参数
属性类型默认值必填说明touserstring是接收者用户的 openidtemplateIdstring是所需下发的订阅模板idpagestring否点击模板卡片后的跳转页面仅限本小程序内的页面。支持带参数,示例index?foobar。该字段不填则模板无跳转。dataObject是模板内容格式形如 { “key1”: { “value”: any }, “key2”: { “value”: any } }miniprogramStatestring否跳转小程序类型developer为开发版trial为体验版formal为正式版默认为正式版langstring否进入小程序查看”的语言类型支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文)默认为zh_CN官方请求示例
const cloud require(wx-server-sdk)cloud.init({env: cloud.DYNAMIC_CURRENT_ENV,})exports.main async (event, context) > {try {const result await cloud.openapi.subscribeMessage.send({"touser": OPENID,"page": index,"lang": zh_CN,"data": {"number01": {"value": 339208499},"date01": {"value": 2015年01月05日},"site01": {"value": TIT创意园},"site02": {"value": 广州市新港中路397号}},"templateId": TEMPLATE_ID,"miniprogramState": developer})return result} catch (err) {return err}}
miniprogramState在正式环境要换成formal或注释掉这行
3.2、轮询
轮询使用的云开发的定时触发器
定时触发器官方介绍
该功能需开发者工具 1.02.1811270 及以上版本方可使用 从开发者工具 1.02.1910182 开始新上传的定时触发器内支持使用云调用
-
如果云函数需要定时 / 定期执行也就是定时触发我们可以使用云函数定时触发器。配置了定时触发器的云函数会在相应时间点被自动触发函数的返回结果不会返回给调用方。
-
在需要添加触发器的云函数目录下新建文件 config.json格式如下
{// triggers 字段是触发器数组目前仅支持一个触发器即数组只能填写一个不可添加多个"triggers": [{// name: 触发器的名字规则见下方说明"name": "myTrigger",// type: 触发器类型目前仅支持 timer (即 定时触发器)"type": "timer",// config: 触发器配置在定时触发器下config 格式为 cron 表达式规则见下方说明"config": "0 0 2 1 * * *"}]}
官方示例
下面展示了一些 Cron 表达式和相关含义的示例
- */5 * * * * * * 表示每5秒触发一次
- 0 0 2 1 * * * 表示在每月的1日的凌晨2点触发
- 0 15 10 * * MON-FRI * 表示在周一到周五每天上午10:15触发
- 0 0 10,14,16 * * * * 表示在每天上午10点下午2点4点触发
- 0 */30 9-17 * * * * 表示在每天上午9点到下午5点内每半小时触发
- 0 0 12 * * WED * 表示在每个星期三中午12点触发
triggers中的config字段可以控制触发的频率具体开发测试时我使用的是50秒调用一次
"config": "*/50 * * * * * *"
这里有个坑如果代码实现上传并部署云函数之后左等右等在日志中看不到日志因为少了一个步骤在上传并部署云函数之后需要右键云函数上传触发器这样才生效想关闭可以删除触发器
轮询推送完整代码
config.json
{"permissions": {"openapi": ["subscribeMessage.send"]},"triggers": [{"name": "myTimer","type": "timer","config": "0 0 8 * * * *"}]}
index.js
// 云函数入口文件const cloud require(wx-server-sdk)// 初始化 cloudcloud.init({// API 调用都保持和云函数当前所在环境一致env: cloud.DYNAMIC_CURRENT_ENV})const db cloud.database()const _ db.commandconst $ db.command.aggregateconst kTableName 换成自己的表名// 云函数入口函数exports.main async (event, context) > {try {// 从云开发数据库中查询等待发送的消息列表const msgArr await db.collection(kTableName)// 查询条件已开启推送并且提醒时间为今天.where({A_IsPush: true,A_PushTime: timeStampToTime(new Date().getTime(), {y}/{m}/{d})}).get()// 循环消息列表const sendPromises msgArr.data.map(async msgData > {try {// 发送订阅消息await cloud.openapi.subscribeMessage.send({touser: msgData._openid, // 要发送用户的openidpage: pages/home/home, // 用户通过消息通知点击进入小程序的页面lang: zh_CN,templateId: c64Gp5-89xyD55rnDr0oBWQNphWlNm_l4MX-Sduuj2c, // 订阅消息模板ID// 跳转小程序类型developer为开发版trial为体验版formal为正式版默认为正式版// miniprogramState: developer,// 要发送的数据要和模板一致data: {// 纪念日名称thing5: {value: msgData.A_Title},// 纪念日时间time2: {value: msgData.A_Time},// 备注thing4: {value: msgData.A_Remarks ? msgData.A_Remarks : 无},}})// 发送成功后将数据状态重置return db.collection(kTableName).doc(msgData._id).update({data: {A_IsPush: false,A_PushTime: ,A_NextTime: ,},})} catch (e) {return e}})return Promise.all(sendPromises)} catch (err) {console.log(err)return err}}function timeStampToTime(time, cFormat) {if (arguments.length 0) {return null}const format cFormat || {y}-{m}-{d} {h}:{i}:{s}let dateif (typeof time object) {} else {if (( time).length 10) time parseInt(time) * 1000date new Date(time)}const formatObj {y: date.getFullYear(),m: date.getMonth() 1,d: date.getDate(),h: date.getHours(),i: date.getMinutes(),s: date.getSeconds(),w: date.getDay()}const time_str format.replace(/{(y|m|d|h|i|s|w)}/g, (result, key) > {let value formatObj[key]if (key w) {return [日, 一, 二, 三, 四, 五, 六][value]}if (result.length > 0 0 value}return value || 0})return time_str}// 定时触发器 // https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/triggers.html// 50秒一次// "config": "*/50 * * * * * *"// 每天上午8点一次// "config": "0 0 8 * * * *"
package.json
{"name": "push","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" 可以先把项目环境设置为开发环境然后右键上传并部署云函数然后上传触发器
然后打开云函数控制台选择云函数-日志进行查看状态如果成功并且有需要推送的数据手机会收到推送消息失败的话根据日志进行修改。
至此结束
最后推荐一下我的小程序我的纪念日小助手