- 实战分享: 小程序云开发玩转订阅消息(二)
[图片]这是实战分享: 小程序云开发玩转订阅消息的第二部分 第一部分链接 《实战分享: 小程序云开发玩转订阅消息(一)》 将订阅消息存入云开发数据库接下来我们创建一个云函数 [代码]subscribe[代码] ,这个云函数的作用是将用户的订阅信息存入云开发数据库的集合 [代码]messages[代码] 中,等待将来需要通知用户时进行调用。 在微信开发者工具的云开发面板中创建数据库集合 [代码]messages[代码] [图片]微信开发者工具新增数据库集合 创建一个 [代码]subscribe[代码] 云函数,在云函数中我们将小程序端发送过来的课程订阅信息,存储在云开发数据库集合中,开发完成后,在微信开发者工具中右键上传并部署云函数。 cloudfunctions/subscribe/index.js [代码]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 page: 'index', // 订阅消息卡片点击后会打开小程序的哪个页面 data: event.data, // 订阅消息的数据 templateId: event.templateId, // 订阅消息模板ID done: false, // 消息发送状态设置为 false }, }); return result; } catch (err) { console.log(err); return err; } }; [代码]利用定时触发器来定期发送订阅消息接下来我们需要实现一个定时执行的云函数[代码]send[代码],来检查数据库中是否有需要发送给用户的订阅消息。如果有需要发送的订阅消息,会通过云调用 [代码]cloud.openapi.subscribeMessage.send[代码] 将订阅消息发送给用户。 创建一个名叫 [代码]send[代码] 的云函数,首先要配置云函数,在 [代码]config.json[代码] 的 [代码]permissions[代码] 中新增 [代码]subscribeMessage.send[代码]的云调用权限,然后新增一个 [代码]sendMessagerTimer[代码] 的定时触发器,定时触发器的语法和 [代码]linux[代码] 的 [代码]crontab[代码] 类似,比如,我们配置的 [代码]"0 * * * * * *"[代码] 代表每分钟执行一次云函数。 cloudfunctions/send/config.json [代码]{ "permissions": { "openapi": ["subscribeMessage.send"] }, "triggers": [ { "name": "sendMessagerTimer", "type": "timer", "config": "0 * * * * * *" } ] } [代码]接下来是实现发送订阅消息的云函数,这个云函数会从云开发数据库集合[代码]messages[代码]中查询等待发送的消息列表,检查数据库中是否有需要发送给用户的订阅消息,发送条件可以根据自己的业务实现,比如开课提醒可以根据课程开课日期来检查是否需要发送订阅消息,在我们下面的代码示例里做了简化,筛选条件只检查了状态为未发送。 查询到待发送的消息列表之后,我们会循环消息列表,依次发送每条订阅消息,发送成功后将数据库中消息的状态改为已发送。 cloudfunctions/send/index.js [代码]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, 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; } }; [代码]最终效果 [图片]开课提醒订阅消息截图 源代码https://github.com/binggg/tcb-subscribe-demo[3] 参考资料 [1]注册小程序帐号: https://tencentcloudbase.github.io/2019-09-03-wx-dev-guide-register/ [2]开通云开发服务: https://tencentcloudbase.github.io/2019-09-03-wx-dev-guide-service/ [3]https://github.com/binggg/tcb-subscribe-demo: https://github.com/binggg/tcb-subscribe-demo
2019-10-23 - 小程序纯前端将数据导出为excel表格
网上有许多文章是讲述小程序将数据导出为excel表格的,但大多需要经过请求服务端,再加上云存储。那一套逻辑之前做后端的时候就玩过了。很多时候,我们浏览页面时数据已经从服务端获取到本地了,直接将之导出即可,再走服务端,实为多此一举。为了减轻服务端压力,于是便有了这篇文章。本文章介绍如何在小程序使用纯前端技术将以获取到的数据导出为excel表格。文末有代码片段 xlsx插件文档 sheetjs插件文档 [代码]const XLSX = require('../utils/excel.js') Page({ data: { }, onLoad() { }, exportData() { // 数据源 const data = [{ code: 1, name: 'A', }, { code: 2, name: 'B', }, { code: 3, name: 'C', }, { code: 4, name: 'D', }] // 构建一个表的数据 let sheet = [] let title = ['序号', '姓名'] sheet.push(title) data.forEach(item => { let rowcontent = [] rowcontent.push(item.code) rowcontent.push(item.name) sheet.push(rowcontent) }) // XLSX插件使用 var ws = XLSX.utils.aoa_to_sheet(sheet); var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "用户表"); var fileData = XLSX.write(wb, { bookType: "xlsx", type: 'base64' }); let filePath = `${wx.env.USER_DATA_PATH}/用户表.xlsx` // 写文件 const fs = wx.getFileSystemManager() fs.writeFile({ filePath: filePath, data: fileData, encoding: 'base64', bookSST: true, success(res) { console.log(res) const sysInfo = wx.getSystemInfoSync() // 导出 if (sysInfo.platform.toLowerCase().indexOf('windows') >= 0) { // 电脑PC端导出 wx.saveFileToDisk({ filePath: filePath, success(res) { console.log(res) }, fail(res) { console.error(res) util.tips("导出失败") } }) } else { // 手机端导出 // 打开文档 wx.openDocument({ filePath: filePath, showMenu: true, success: function (res) { console.log('打开文档成功') }, fail: console.error }) } }, fail(res) { console.error(res) if (res.errMsg.indexOf('locked')) { wx.showModal({ title: '提示', content: '文档已打开,请先关闭', }) } } }) } }) [代码] 导出效果如下,如需高级设置请参照xlsx插件文档 sheetjs插件文档 [图片] 代码片段 -完-
2023-08-11 - 微信小程序前后端开发快速入门(完结篇)
前言 累计现在已经有 10 篇教程,用备忘录项目从小程序前端开发到后端开发都有分享。 微信小程序前端开发快速入门(5篇+视频) 微信小程序云开发快速入门(1/4) 微信小程序云开发快速入门(2/4) 微信小程序云开发快速入门(3/4) 微信小程序云开发快速入门(4/4) 这篇是微信小程序前后端快速入门完结篇了,今天利用之前学习过的所有知识做一个新的项目「群登记助手v1.0」小程序。 群登记助手小程序是我在2020年开发的,当时还被小程序云开发邀请分享过这个项目《从想法到上线千万访问量的产品,1个开发就搞定,如何做到?》整体技术架构:小程序原生前端+小程序云开发。 [图片] 经历了前面教程的学习,大家有了一定的基础,所以本次分享重心主要是带着大家理清楚逻辑相关的云开发处理方案和之前未讲解过的重要组件,之前已经讲解过的重复知识就不会重新再讲解,需要大家利用之前已经学习过的知识来组合今天学习的新知识对接龙小程序进行整体的完善。 业务分析 流程分析 接龙小程序使用者角色上会有两种,分别是发起者和参与者。这个接龙是由发起者来让参与者接龙,所以他们两之间的使用逻辑是: [图片] 一共有以下七步,来完成整个接龙行为的闭环。 发起者 - 创建接龙活动 发起者 - 进入接龙列表 发起者 - 转发到微信群 参与者 - 点击程序卡片 参与者 - 进入活动详情 参与者 - 接龙信息填写 参与者 - 进入活动详情 界面如下: [图片] 数据库设计 首先数据库设计来看,我们需要三张表: 用户表(users),用于用户体系的基础搭建 [图片] 接龙活动表(solitaire),用于存放发起者接龙活动 [图片] 接龙信息表(solitaire_info),用于存放参与者接龙信息 [图片] 实现路线图 难点部分会进行分析讲解,简单部分需自行实现(之前教过的知识点) 创建活动 -> 获取用户信息 -> 用户表插入用户数据 -> 活动表插入活动信息 转发活动 -> 通过联合查询出活动列表 -> 将接龙活动转发到群里 查询信息 -> 通过分享的活动ID查询详情 -> 跳转到填写信息 填写信息 -> 获取用户信息(同上)-> 信息表插入接龙信息 -> 更新活动参与人数 -> 发送订阅消息 回到详情 -> 刷新接龙信息列表(使用聚合查询) 其他功能 -> 导出表格 复杂查询 由于接龙信息和用户信息分别在两张表中实现,所以这里需要用到联表查询。这个时候就需用到小程序的聚合查询能力。 联表查询 如我们现在已经有一条活动数据了,那么现在数据库的数据结构应该是这样的: 用户表 users: [图片] 接龙表 solitaire: [图片] 然后使用 lookup 函数进行关联起来。 以下为属性含义 [代码]lookup({ from: <要连接的集合名>, localField: <输入记录的要进行相等匹配的字段>, foreignField: <被连接集合的要进行相等匹配的字段>, as: <输出的数组字段名> }) [代码] 结合以上使用方式,我们使用下lookup连接查询 [代码]async queryLookupList(context, params) { let res = await db.collection('solitaire').aggregate() .match({ openid: context.OPENID }) .lookup({ from: 'users', localField: 'openid', foreignField: '_openid', as: 'users', }) .sort({ date: -1 }) .end() return res } [代码] 最后查询出来的结果是: [代码][ { "_id": "cd045e756110ed09047443683dd70ecf", "content": "312312", "date": "2021-08-09 16:53", "title": "12312", "type": 1, "openid": "oyfiv5Z90bqbQ6BJ6A273eP68j-w", "number": 0, "users": [ { "_id": "8937eaa96110ea39039e900278a1529e", "_openid": "oyfiv5Z90bqbQ6BJ6A273eP68j-w", "date": "2021-08-09T08:41:29.878Z", "userInfo": { "avatarUrl": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJ9VBHPzRxk4M7bc4xxwXOaw6DpciahEjzeZ4GP0UoSmiaqBMFQznROZlVG5ukvpv8dSXNzl34oaP7g/132", "city": "Changsha", "country": "China", "gender": 1, "language": "zh_CN", "nickName": "111陈宇明", "province": "Hunan" } } ] } ] [代码] 由于在实际复杂业务中,聚合查询使用的比较多的,所以再次我们带大家来对聚合查询进行更深入的了解。 聚合查询 聚合是非常强大的数据分析工具,主要用于对记录进行批量处理,可以对记录进行按条件分组、跨集合联表等一系列批量而又复杂操作。类似于Excel整列整列跨字段的运算(如加、减、合并、比较等)、对内嵌的字段可以进行整列拆分、类型变换、组合等。 聚合查询 VS 普通查询 聚合aggregate和普通数据查询get是两套不同的体系,聚合更偏向于数据的复杂查询。聚合查询和普通数据查询都能对数据库进行查询,两个的很多方法都特别类似,我们可以通过对之前普通查询的理解来理解聚合查询的部分功能,具体查看以下表格对比。 [图片] 在这里需要注意的是使用聚合查询之前需要先 aggregate() 发起一个聚合操作。以上是普通查询可以做到的,聚合查询也可以做到,接下来是普通查询做不到的。 聚合阶段 聚合阶段是聚合管理流水线作业的组成单元,是一个个功能节点,有的可以联表lookup、有的可以组合group、有的可以拆分unwind等等。每个聚合阶段可以使用表达式、操作符对输入文档进行计算综合、均值、拼接、分割、转换格式等操作,操作完成之后会输出给下一个阶段,直到end返回结果。 [图片] 小技巧 在这里告诉大家一个小技巧,其实写查询的时候可以在数据库的高级操作区间先写好测试然后再放到函数中去使用,这样可以提高效率。 [图片] 在这里不需要获取数据库对象,直接通过db就能使用,数据也不需要打印出来,只要使用了结束函数就可以了。 订阅消息 当发起者转发到群里之后,参与者就可以填写接龙信息,当接龙信息填写完成之后,可以在这里给到参与者发送一个订阅消息,告知参与者接龙成功。 这样设计有两个好处: 便于再次激活用户,多一个入口就多一些用户打开的概率。 更快捷的打开方式(提升1倍的效率) 正常打开路径: 下拉聊天界面进入小程序列表 点击接龙小程序 找到参与的接龙活动 找到具体接龙点击查看详情 订阅模版 进入服务通知列表 找到具体模版点击查看详情 那么如何给用户发送订阅消息呢?请接着往下看: 申请消息模版 第一步,先登录到后台,找到订阅消息菜单->申请订阅消息模版 [图片] 第二步,进入订阅消息列表页面,点击选用按钮 [图片] 第三步,进入选用模版库,通过关键词搜索进行查找,消息模版和小程序的类目有关,以“接龙”为例,小程序类目是「预约/报名,笔记」所以搜索到了这两个类目下的消息模版。 [图片] 第四步,选择自己需要的模版,点击「选用」进入详情页面。模版有很多关键词,只需要勾选自己需要的关键词即可,然后填写下场景说明即可点击提交 [图片] 第五步,填写完成后,会在我的模版看到刚才申请好的消息模版,复制模版ID即可,到时候然后切换到小程序端进行使用 [图片] 获取订阅授权 第六步,找到小程序需要让用户授权的触发方法。如:需求是当用户填写完成接龙资料,让用户授权报名成功提醒,然后发一条报名成功的订阅消息,那么这个时候就需要找到填写信息的方法。如果只是单独先对这个功能进行学习,那么就可以在一个页面写个按钮,然后按钮绑定一个点击事件触发即可。 写在任意测试页面wxml [代码]<button bindtap="onMsg" >测试订阅消息</button> [代码] 当前测试页面对应的js [代码]wx.requestSubscribeMessage({ tmplIds: ['模版ID'], success(res) { console.log(res) } }) [代码] 第七步,用真机调试,模拟器不支持。点击之后界面会出现授权页面。 [图片] 以下为我分别点击取消和允许的日志输出。用户可以支持一次调用最多可订阅3条消息。 [图片] 然后我们再来看下 requestSubscribeMessage 文档中对返回体的解释 [图片] 对于开发者来说,需要关心的就是是否用户允许来,所以我们需要通过以下方式获取结果,当结果是允许的时候我们插入就发送成功通知给到用户即可。当然我这里指的是用户添加完后发送添加成功通知的业务路径,如果不是需要当前动作完成后发送的话,那么就需要存储一条记录到数据库,等需要用到的时候再去做发送消息模版的动作。 [代码]onMsg() { wx.requestSubscribeMessage({ tmplIds: ['模版ID'], success(res) { if(res.模版ID=='accept'){ // 发送消息给到用户 } } }) } [代码] 发送模版消息 第七步,发送模版消息,新建一个发送模版消息的云函数 sendMessage ,然后打开 subscribeMessage. send 文档,可以看到这个方法支持云调用,也就是说官方已经帮开发者封装好了方法使用起来非常简单。 云调用是云开发提供的基于云函数使用小程序开放接口的能力 [图片] 那么我们就用云调用方法来试试,首先在 sendMessage 的config.json文件配置权限 [代码]{ "permissions": { "openapi": [ "subscribeMessage.send" ] } } [代码] 然后在js中编写调用发送模版消息的方法,方法参数如下: [图片] 我把重要的参数用红色框框标记起来了,看下代码。 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const result = await cloud.openapi.subscribeMessage.send({ "touser": wxContext.OPENID, // 发给自己直接从 getWXContext 获取 "templateId":'模版ID', "page": '目标页面路径', "lang": 'zh_CN', "data": { "thing2": { "value": '报名接龙2021' }, "phrase8": { "value": '报名成功' }, "thing19": { "value": '详细点击查看=>' } }, "miniprogramState": 'developer' }) return result } [代码] 注意 data 这个参数需要回到小程序管理后台的消息订阅列表查看订阅模版的详情 [图片] 这里需要注意每个不同的数据类型都有些限制条件详细可见文档,然后data参数需要和上面的模版内容一对一对应上,方法写完上传并部署即可。 第八步,调用模版消息。 [代码]onMsg() { wx.requestSubscribeMessage({ tmplIds: ['模版ID'], success(res) { if (res.XXXXID == 'accept') { wx.cloud.callFunction({ name: 'sendMessage' }).then(res => { console.log(res) }) } } }) } [代码] 调用成功后会在微信聊天页面的服务通知中收到模版消息提醒,点击即可进入小程序,效果如下: [图片] 导出表格 使用云函数使用Node.js的node-xlsx模块 安装模块 新建云函数 excel 右键云函数选择在外部终端窗口开打 [图片] 输入命令 [代码]npm install node-xlsx [代码] 安装成功 [图片] 文件结构 [图片] 使用模块 导入模块 [代码]const xlsx = require('node-xlsx') [代码] 准备数据 [代码]let row = ['姓名', '电话', '备注']; //表格的属性 let allData = [] //表格内容 // 添加表头 allData.push(row) // 假数据,真实数据需要从小程序端传递过来或在云函数中云数据库查询出来 allData.push(['陈宇明', '13148123123', '']) allData.push(['陈X明', '13148123123', '不知道']) [代码] 生成表格 [代码]// 生成表格 var buffer = await xlsx.build([{ name: 'mySheetName', data: allData }]); [代码] 最后生成完成之后还需要用到我们之前学习过的上传文件 uploadFile 上传到云存储中 [代码] let cloudPath = `xlsx/${Math.floor(Math.random()*1000000000)}.xlsx` //上传文件返回结果 return await cloud.uploadFile({ cloudPath: cloudPath, fileContent: buffer, //excel二进制文件 }) [代码] 调试一下 [图片] 查看文件 [图片] 通过复制下载链接,打开查看表格内容 [图片] 在线查看文档 当获取到文件ID,在使用 getTempFileURL 用云文件 ID 换取真实链接,然后 downloadFile 下载文件资源到本地,通过 openDocument 新开页面打开文档。 [代码]openExcel(){ wx.cloud.callFunction({ name: "excel", data: { infos: {} //表格数据 }, complete: res => { wx.cloud.getTempFileURL({ fileList: [res.result.fileID], success: res => { this.setData({ tempFileURL: res.fileList[0].tempFileURL }) console.log(this.data.tempFileURL) wx.downloadFile({ url: this.data.tempFileURL, success: (res) => { const filePath = res.tempFilePath console.log(filePath) wx.openDocument({ filePath: filePath, showMenu: true, success: res => { console.log(res) } }) } }) } }) } }) }, [代码] 复制下载链接 当获取到文件ID,在使用 getTempFileURL 用云文件 ID 换取真实链接,然后 setClipboardData 设置系统剪贴板的内容。 [代码] getExcelUrl() { wx.cloud.callFunction({ name: "excel", data: { infos: {} //表格数据 }, complete: res => { wx.cloud.getTempFileURL({ fileList: [res.result.fileID], success: res => { this.setData({ tempFileURL: res.fileList[0].tempFileURL }) wx.setClipboardData({ //复制到粘贴板 data: this.data.tempFileURL, success(res) { wx.getClipboardData({ success(res) { } }) } }) } }) } }) }, [代码] 最后 这篇教程相比之前的备忘录教程更像是一道填空题,需要大家利用之前教程学习到的知识进行融合才能实现这个小程序,独立完成这个项目才是检验学习效果的最佳方式。 在实现的过程中有遇到问题,可以留言讨论。
2023-08-16