- 定时发送模板消息功能实现(云开发实现)。
在准备开发这个功能之前,请确保你已经阅读过云开发文档和以下相关官方文档。 模板消息 获取AccessToken 发送模板消息 云函数定时触发器 我们来设定一个需求场景。以小程序【抽奖助手】为例,用户参与抽奖后,需要在开奖时间发送给用户开奖结果通知。这个通知采用模板消息形式下发。 先整理一下思路,实现这个功能我们需要哪些模块? 定时任务执行器。根据任务类型调用相应任务处理程序。 开奖任务处理程序,开奖后发送模板消息,通知用户结果。 云函数中调用 sendTemplateMessage 后端接口,发送模板消息。 周期获取AccessToken。请求后端接口需要用AccessToken,周期更新AccessToken,放入数据库中,随用随取。 模块实现 1.定时任务执行器 以云数据库形式实现。添加一个定时任务就是在该集合增加一条记录,移除同理。 记录字段设计: [代码]timeingTask{ _id: taskType: //任务类型,决定如何处理这个任务 execTime: // 触发时间。到达这个时间开始执行。 data:{} // 必要数据 } [代码] 然后设置云函数周期执行。每分钟查询一次该定时任务数据库,是否有任务到达执行时间。如果有则根据类型进行处理,并在数据库中移除该任务。 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: '你的云环境ID' }) const db = cloud.database() exports.main = async(event, context) => { const execTasks = []; // 待执行任务栈 // 1.查询是否有定时任务。(timeingTask)集合是否有数据。 let taskRes = await db.collection('timeingTask').limit(100).get() let tasks = taskRes.data; // 2.定时任务是否到达触发时间。只触发一次。 let now = new Date(); try { for (let i = 0; i < tasks.length; i++) { if (tasks[i].execTime <= now) { // 时间到 execTasks.push(tasks[i]); // 存入待执行任务栈 // 定时任务数据库中删除该任务 await db.collection('timeingTask').doc(tasks[i]._id).remove() } } } catch (e) { console.error(e) } // 3.处理待执行任务 for (let i = 0; i < execTasks.length; i++) { let task = execTasks[i]; if (task.taskType == 1) { // 定时开奖任务 const kaiJinag = require('kaiJiang.js') try { await kaiJinag.kai(task.data.activity_id) } catch(e) { console.error(e) } } } } [代码] 使云函数每分钟执行的触发器代码: [代码]{ "triggers": [ { "name": "timeingTaskExecutor", "type": "timer", "config": "0 */1 * * * * *" } ] } [代码] 2.开奖任务处理程序 [代码]kaijiang.js[代码] [代码] const cloud = require('wx-server-sdk') const templateMessage = require('templateMessage.js') const COLL_FIELD_NAME = 'publicField'; const FIELD_NAME = 'ACCESS_TOKEN' const MSGID = '你的模板消息ID'; cloud.init({ env: '你的云环境ID' }) const db = cloud.database() const kai = async activity_id => { // 根据活动id,获取参与用户信息,获取到用户的 openid 和 formid. // 开奖程序省略 // 从数据库中获取AccessToken let tokenRes = await db.collection(COLL_FIELD_NAME).doc(FIELD_NAME).get(); let token = tokenRes.data.token; // access_token let page = '点击模板消息,想要打开的小程序页面'; let msgData = { "keyword1": { "value": activity.prizeName }, "keyword2": { "value": "你参与的抽奖活动正在开奖,点击查看中奖名单" }, }; let openid = '用户openid'; let formid = '用户formid'; await templateMessage.sendTemplateMsg(token, MSGID, msgData, openid, formid, page); } module.exports = { kai: kai, } [代码] 3.发送模板消息 [代码]templateMessage.js[代码] 封装在一个 js 文件里,传入必要参数调用即可。 [代码]const rp = require('request-promise'); const sendTemplateMsg = async (token, msgid, msgData, openid, formid, page) => { await rp({ json: true, method: 'POST', uri: 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=' + token, body: { touser: openid, template_id: msgid, page: page, form_id: formid, data: msgData } }).then(res => { }).catch(err => { console.error(err) }) } module.exports = { sendTemplateMsg: sendTemplateMsg, } [代码] 4.周期获取AccessToken 使用云函数触发器,使云函数每小时请求一次AccessToken,并将AccessToken存入云数据库中。 [代码]const cloud = require('wx-server-sdk') const rq = require('request-promise') const APPID = '你的APPID'; const APPSECRET = '你的APPSECRET'; const COLLNAME = 'publicField'; const FIELDNAME = 'ACCESS_TOKEN' cloud.init({ env: '你的云环境ID' }) const db = cloud.database() exports.main = async(event, context) => { try { let res = await rq({ method: 'GET', uri: "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET, }); res = JSON.parse(res) let resUpdate = await db.collection(COLLNAME).doc(FIELDNAME).update({ data: { token: res.access_token } }) } catch (e) { console.error(e) } } [代码] 使云函数一小时执行一次的触发器代码: [代码]{ "triggers": [ { "name": "pollGetAccessToken", "type": "timer", "config": "0 0 */1 * * * *" } ] } [代码] 这是我做的产品册小程序中的部分代码,目前项目还没有发布,发布后会开源出来。项目中用到了挺多开源组件,给我很大的帮助,希望我的分享也可以帮助到一些人。 有问题可以在公众号后台联系我,我看到都会回复的。 [图片]
2019-03-15 - 云数据库.field出现BUG
云数据库查询时,where和field同时使用,field无效,下面的代码中field字段必须同时为true,如果有一个不一致就没有查询结果,如何解决,请指导。 const re = await db.collection('XXXX').where({ opid: event.userInfo.openId, }).field({ _id: true, opid: false, wxnm: true, yhnm: true, yhlx: true, fxid: true, fxcs: true, fxjf: true, dlcs: true, dldt: true, }).get(); } })
2018-11-23 - 利用云函数绕过域名校验和HTTPS配置,实现内网加端口访问
闲来无事,无意中发现云函数中的request网络请求可以不用配置校验域名和https,也就是说可以通过云函数封装一个请求通用函数来处理没有域名和https的网络请求(甚至包括内网穿透,可以用非80端口进行实验)。 适用场景: A、没有域名或使用局域网(直接使用IP访问); B、使用花生壳动态域名解析(内网穿透); C、有域名但不想申请配置HTTPS(懒人); D、连自己的服务器都没有,接口直接使用开源或者第三方接口且不能添加域名校验的情况(空壳); E、不愿意直接在小程序中直接暴露自己逻辑API实际请求地址的(安全); ······ 具体步骤如下: 1、给项目添加云函数支持(https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html) 2、新建名为“proxy”的云函数,配置支持request-promise [代码]// package.json[代码][代码]{[代码][代码] [代码][代码]"name"[代码][代码]: [代码][代码]"proxy"[代码][代码],[代码][代码] [代码][代码]"version"[代码][代码]: [代码][代码]"1.0.0"[代码][代码],[代码][代码] [代码][代码]"description"[代码][代码]: [代码][代码]""[代码][代码],[代码][代码] [代码][代码]"main"[代码][代码]: [代码][代码]"index.js"[代码][代码],[代码][代码] [代码][代码]"scripts"[代码][代码]: {[代码][代码] [代码][代码]"test"[代码][代码]: [代码][代码]"echo \"Error: no test specified\" && exit 1"[代码][代码] [代码][代码]},[代码][代码] [代码][代码]"author"[代码][代码]: [代码][代码]""[代码][代码],[代码][代码] [代码][代码]"license"[代码][代码]: [代码][代码]"ISC"[代码][代码],[代码][代码] [代码][代码]"dependencies"[代码][代码]: {[代码][代码] [代码][代码]"wx-server-sdk"[代码][代码]: [代码][代码]"latest"[代码][代码],[代码][代码] [代码][代码]"request"[代码][代码]: [代码][代码]"latest"[代码][代码],[代码][代码] [代码][代码]"request-promise"[代码][代码]: [代码][代码]"latest"[代码][代码] [代码][代码]}[代码][代码]}[代码][代码]// 云函数入口文件index.js[代码] [代码]const cloud = require([代码][代码]'wx-server-sdk'[代码][代码])[代码][代码]const rq = require([代码][代码]'request-promise'[代码][代码])[代码][代码]cloud.init()[代码][代码]// 云函数入口函数[代码][代码]// event为小程序调用的时候传递参数,包含请求参数uri、headers、body[代码][代码]exports.main = async (event, context) => {[代码][代码] [代码][代码]return[代码] [代码]await rq({[代码][代码] [代码][代码]method: [代码][代码]'POST'[代码][代码],[代码][代码] [代码][代码]uri: event.uri,[代码][代码] [代码][代码]headers: event.headers ? event.headers : {},[代码][代码] [代码][代码]body: event.body[代码][代码] [代码][代码]}).then(body => {[代码][代码] [代码][代码]return[代码] [代码]body[代码][代码] [代码][代码]}).[代码][代码]catch[代码][代码](err => {[代码][代码] [代码][代码]return[代码] [代码]err[代码][代码] [代码][代码]})[代码][代码]}[代码]3、在小程序中调用云函数请求数据请求 [代码]onLoad: [代码][代码]function[代码][代码](){[代码][代码] [代码][代码]// 初始化[代码][代码] [代码][代码]wx.cloud.init()[代码][代码]},[代码][代码]onGetItemList: [代码][代码]function[代码][代码](){[代码][代码] [代码][代码]wx.cloud.callFunction({[代码][代码] [代码][代码]name: [代码][代码]'proxy'[代码][代码],[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]// http域名 https域名 第三方域名 非验证域名 IP[:prot] 内网IP或花生壳域名[代码][代码] [代码][代码]uri: [代码][代码]'http://192.168.1.100:8081'[代码][代码],[代码][代码] [代码][代码]headers: {[代码][代码] [代码][代码]'Content-Type'[代码][代码]: [代码][代码]'application/json'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]body: {[代码][代码] [代码][代码]uid: 1[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]console.log(res)[代码][代码] [代码][代码]const data = res.result[代码][代码] [代码][代码]console.log(data)[代码][代码] [代码][代码]// do something[代码][代码] [代码][代码]})[代码][代码]}[代码]然后你会发现你已经无所不能了。 个人见解,如有不妥之处,望各位大神指正!~
2018-12-03