小程序——借助云函数实现订阅推送

328 阅读9分钟

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属性中的字段对应的上

fe67544c4b99b842a18a4cf864a96441.png

6032ee60ea7519065ded6db4f1d822bf.png

80eaeccbacd7db04041d948c99ab83a1.png

如上图,我们从公共模板库里选择一个一次性订阅的模板。然后编辑模板。 下图就是我们添加好的模板,下图的模板id就是我们需要的。

image.png

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" })
        }

      }
    })
  },

结果如下:

image.png

我们正常订阅消息授权时,用户允许的话,你只能推送一次消息。也就是用户允许一次,我们就可以推送一条消息给用户,并且这个允许不存在过期。所以我们可以让用户尽量多的点击允许,这样我们就可以尽量多的给用户发送消息了。

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云函数

image.png

image.png

新建完毕后,需要打开该文件夹,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 (上面已经通过云函数获取了),然后传递给云函数即可

  1. 前端通过wx.requestSubscribeMessage这个函数授权订阅消息

  2. 前端调用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)
    })
  },
  1. 后端编写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 发送订阅消息给多个用户

这里为了方便,直接用已注册的用户去判断推送,并且是早晚推送,再加上一个触发器,实现早晚问候

  1. 页面用于测试而写

image.png

<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")
  },
  1. 云函数
  • 早安的云函数 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分触发云函数,自动发送订阅消息

image.png

  • 晚安的云函数 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("我是最后的")
}

配置一下触发器

image.png

6.最后发现有更好点的方案

传送门:blog.csdn.net/weixin_4254…

分析他的思路:

  1. 引导用户订阅(给个button按钮,事件处理函数是onSubscribe)
  2. 在 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});
          });
        }
      },
    });
  1. 创建一个云函数 subscribe ,这个云函数的作用是将用户的订阅信息存入云开发数据库的集合 messages 中,等待将来需要通知用户时进行调用。

在微信开发者工具的云开发面板中创建数据库集合 messages image.png

创建一个 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;
  }
};
  1. 利用定时触发器来定期发送订阅消息

  2. 实现发送订阅消息的云函数,这个云函数会从云开发数据库集合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;
  }
};