- msgSecCheck 接口一直返回ok的解决方案,测试可行的msgSecCheck 接口PHP版demo
今天突然说小程序版本审核要我们处理评论中的敏感词内容,踩了一些坑终于搞好了,贴下代码出来吧,文档说的不是很详细,估计有不少小伙伴踩坑了。 public function keyCheck(){ $wxToken=wxTokenCheck(); //获取微信token $key = input(‘key’); $url = ‘https://api.weixin.qq.com/wxa/msg_sec_check?access_token=’.$wxToken['token’]; $data = json_encode([‘content’=>$key],JSON_UNESCAPED_UNICODE); //一定要把中文转成utf8!!! $ch = curl_init(); curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_URL,$url); // url curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); // json数据 $res = curl_exec($ch); // 返回值 curl_close($ch); $result = json_decode($res,true); [代码] echo "<pre>"; var_dump($result); var_dump($data); echo "</pre>"; }[代码]
2019-09-29 - 云开发·云调用生成小程序码
云开发·云调用生成小程序码 小程序云开发已经支持云调用,开放了很多接口,一直想要的获取小程序码也支持了。这下轻量的小程序也可以有自定义小程序码的功能。 1. 需求 获得一个带参数的小程序码,传播出去以后,用户扫码进入指定页面,根据参数做不同的处理。本文只讲小程序码生成、存储、展示部分。参数处理不多介绍,可以查看 项目代码 了解更多。 2. 开通云开发 新建小程序可以从开发工具的云开发模板初始化项目,根据云开发操作指引新建项目即可。 但是这里有个问题,已发布小程序的页面才能生成小程序码。如果现有的小程序没有开通云开发,需要做以下几步: 开发工具开通云开发,设定云开发的环境; 将原来的代码(除了[代码]project.config.json[代码]以外的所有文件)放到新建的 [代码]miniprogram[代码] 目录; 新增 [代码]cloudfunctions[代码] 目录; [代码]app.json[代码] 新增配置 [代码]"cloud": true[代码]; [代码]project.config.json[代码] 配置 [代码]"miniprogramRoot":"miniprogram/"[代码] 和 [代码]"cloudfunctionRoot":"cloudfunctions/"[代码]; 修改小程序基础库版本,最低要 2.3.0 [代码]"libVersion": "2.3.0"[代码]。 3. 生成小程序码 下面可以开始写代码开发了,开始之前,建议先看完官方教程。特别是开发工具的使用步骤,开发和调试时如果遇到奇怪的问题,可以尝试重启开发工具、重装开发工具,也可以去微信开放社区发帖。(重启和重装都是我在社区中发现的答案,能解决各种不应该存在的问题)。 3.1 准备文件 在 [代码]cloudfunctions[代码]目录右键新建Node.js云函数 [代码]getqr[代码]。 生成小程序码需要单独指定权限。在 [代码]getqr[代码] 目录新建 [代码]config.json[代码] ,里面写以下内容: [代码]{ "permissions": { "openapi": [ "wxacode.getUnlimited" ] } } [代码] 小程序码的获取方式有三种,这里只用到了接口 getUnlimited,选择这个接口的原因是漂亮的圆形小程序码,数量无限制。具体区别可以去 获取小程序码官方文档查看详情。 正常情况下,这个时候云函数可以部署测试了。如果遇到部署不成功、各种权限问题,可以尝试本地部署上传所有文件、重启试试。 3.2 生成小程序码 生成小程序码的代码如下,可以指定页面和页面参数 [代码]scene[代码],还有小程序码的尺寸。 注意这里的 [代码]scene[代码] 有限制: 最大32个可见字符; 只支持数字,大小写英文以及部分特殊字符:[代码]!#$&'()*+,/:;=?@-._~[代码]; 注意参数格式:下面实例代码生成小程序码后,扫码获得 [代码]pages/demo/demo?scene=id%3D6[代码] 。 [代码]try { const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/demo/demo', scene: 'id=6', width: 240, }) console.log(result) return result } catch (err) { console.log(err) return err } [代码] 直接调用,比服务端调用少了 access_token 参数。 3.3 上传到云存储 返回值中的 buffer 就是图片内容,直接上传到云存储: [代码]const uploadResult = await cloud.uploadFile({ cloudPath: 'shareqr/' + qr_name_hash + '.jpg', fileContent: result.buffer, }); [代码] 我在云存储新建了 [代码]shareqr[代码] 目录保存小程序码; 图片名根据参数取md5摘要; [代码]getUnlimited[代码] 返回的图像是 [代码]jpeg[代码] 格式,后缀硬编码写 [代码].jpg[代码]。 3.4 获取图片临时路径 直接上代码 [代码]getURLReault = await cloud.getTempFileURL({ fileList: [uploadResult.fileID] }); fileObj = getURLReault.fileList[0] return fileObj [代码] 3.5 直接从存云存储获取 生成过以后图片已经保存在云存储,用同样的参数第二次调用没必要再生成一次,去掉一次网络请求,可以节省不少时间。 前面说到文件名使用请求参数摘要,知道了目录和文件名,再加上文件bucket前缀就可以拼出来 [代码]fileID[代码],用[代码]fileID[代码] 可以查询云存储的文件。 比如我刚刚生成的 fileID 是 [代码]cloud://dev-xxxx.8888-dev-xxxx/qr/44ea42f05091c3bec771123e6e8cd4c2.jpg[代码], 前缀就是 [代码]cloud://dev-xxxx.8888-dev-xxxx/[代码]。再拼上目录、文件名、后缀就是 [代码]fileID[代码]。 注:此处的 [代码]fileID[代码]拼接方法并不是来自官方文档,只是在使用中发现这个前缀不会变。还需要官方解释说明[代码]fileID[代码]规则。 如果会改变,就需要再用云数据库存储[代码]fileID[代码],更麻烦一些。 3.6 云函数完整代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk'); const crypto = require('crypto'); const bucketPrefix = 'cloud://dev-xxxx.8888-idc-4d11a4-1257831628/qr/'; // env: 'dev-xxxx' // 云函数入口函数 exports.main = async (event, context) => { const full_path = event.page + '?' + event.scene; const qr_name_hash = crypto.createHash('md5').update(full_path).digest('hex'); const temp_id = bucketPrefix + qr_name_hash + '.jpg'; // return { // full_path, // qr_name_hash, // temp_id // } try { // 先尝试获取文件,存在就直接返回临时路径 let getURLReault = await cloud.getTempFileURL({ fileList: [temp_id] }); // return getURLReault; let fileObj = getURLReault.fileList[0]; if (fileObj.tempFileURL != '') { fileObj.fromCache = true; return fileObj; } // 生成小程序码 const wxacodeResult = await cloud.openapi.wxacode.getUnlimited({ scene: event.scene, page: event.page, width: 280 //二维码的宽度,单位 px,最小 280px,最大 1280px }) // return wxacodeResult; if (wxacodeResult.errCode != 0) { // 生成二维码失败,返回错误信息 return wxacodeResult; } // 上传到云存储 const uploadResult = await cloud.uploadFile({ cloudPath: 'qr/' + qr_name_hash + '.jpg', fileContent: wxacodeResult.buffer, }); // return uploadResult; if (!uploadResult.fileID) { //上传失败,返回错误信息 return uploadResult; } // 获取图片临时路径 getURLReault = await cloud.getTempFileURL({ fileList: [uploadResult.fileID] }); fileObj = getURLReault.fileList[0]; fileObj.fromCache = false; // 上传成功,获取文件临时url,返回临时路径的查询结果 return fileObj; } catch (err) { return err } } [代码] 4. 小程序页面调用 调用页面就比较简单了,在小程序新建一个 [代码]pages/share/share[代码] 在 [代码]onLoad[代码] 函数调用云函数。 [代码]// 使用前记得先初始化云函数,一版放到 app.js onLaunch() 中 // wx.cloud.init({env: 'dev-8888'}) wx.cloud.callFunction({ name: 'getqr', data: { page: 'pages/demo/demo', scene: 'id=6', } }).then(res => { console.log(res.result); if (res.result.status == 0) { _this.setData({ qr_url: res.result.tempFileURL }) }else{ wx.showToast({ icon: 'none', title: '调用失败', }) } }).catch(err => { console.error(err); wx.showToast({ icon: 'none', title: '调用失败', }) }) [代码] 至此完整的调用过程已经全部完成,详细代码可以到 项目代码 查看。 代码中还对入口页面和share页面的参数做了包装,云函数可以直接使用,小程序可以稍做修改适应自己业务。 写在最后 小程序云开发已经开放了很多功能,除了这次提到的生成小程序码,云调用还可以发送模板消息。有需要的开发者又一个理由可以快速上线新功能了。 云开发还开放了[代码]HTTP API[代码],也就是用自己的服务器调用云函数。以前看完云开发介绍文章最大的疑问就是,你说的都很好,可是后台数据怎么管理呢?不能跟自己的服务器结合,只能放一些轻量的小程序。有了 [代码]HTTP API[代码] 以后就可以用自己的服务器做管理后台了。这时候你要问,都用上服务器了,还需要云开发做什么。首先,云开发免费;其次,免费功能已经够强,就差不能做Web管理后台了;最后,获取access_token(小程序及小游戏调用不要求IP地址在白名单内。)
2020-07-10 - mina-lazy-image: 图片懒加载自定义组件
Github: https://github.com/alexayan/mina-lazy-image 功能 图片在视口中出现才进行加载显示,优化页面性能 使用方法 安装组件 [代码]npm install --save mina-lazy-image [代码] 在页面的 json 配置文件中添加 mina-lazy-image 使用此组件需要依赖小程序基础库 2.2.2 版本,同时依赖开发者工具的 npm 构建。具体详情可查阅官方 npm 文档。 [代码]{ "usingComponents": { "mina-lazy-image": "mina-lazy-image/index" } } [代码] WXML 文件中引用 mina-lazy-image [代码]<mina-lazy-image src="{{src}}" mode="widthFIx" image-class="custom-class-name"/> [代码] mina-lazy-image 的属性介绍如下: 字段名 类型 必填 描述 src String 是 图片链接 placeholder String 否 占位图片链接 mode String 否 请参考 image 组件 mode 属性 webp Number 否 请参考 image 组件 webp 属性 showMenuByLongpress Boolean 否 请参考 image 组件 show-menu-by-longpress 属性 styles String 否 设置图片样式 viewport Object 否 默认为 {bottom: 0},配置图片显示区域 mina-lazy-image 外部样式类 [代码]image-class[代码], [代码]image-container-class[代码]
2020-01-09 - 小程序生成海报经验分享
场景 由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有二维码的图片,然后引导用户下载图片到本地后再分享到朋友圈 遇到的难点 本来打算用canvas自己去画,但是在操作的过程中遇到两个问题 1、文本太长,需要换行的问题 技术方案 https://github.com/Kujiale-Mobile/Painter 演示 [图片] 以上文章参考并摘录自下面相关链接 小程序海报生成工具,可视化编辑直接生成代码使用,你的海报你自己做主 https://developers.weixin.qq.com/community/develop/article/doc/000e222d9bcc305c5739c718d56813 Painter 一款轻量级的小程序海报生成组件 https://developers.weixin.qq.com/community/develop/doc/000048447844f80b9107d64ab51006 利用云开发实现个性海报制作 https://developers.weixin.qq.com/community/develop/article/doc/000ea8817d0b504e08b921b275b413
2020-01-17 - 利用云开发实现个性海报制作
#0 小程序海报对用户拉新、留存、回流都有着非常重要的作用。 个性海报的制作也就成了小程序开发者的必要功课。 结合 BBC English Podcast 小程序,今天我分享一下怎么利用云开发完成个性化海报的制作。 #1 个性海报可以放在云函数里用图片处理 npm 包来制作,也可以放在小程序端制作。 为了节省云函数资源,我们放在小程序端来制作。 在开始之前,我们理一下个性化海报生成的流程: 步骤一、确定海报内容 步骤二、确定海报样式 步骤三、获取小程序页面的小程序码或者二维码 步骤四、合成海报 步骤五、让用户下载保存 步骤六、上传生成的海报并添加记录到云数据库给下一个用户分享里直接下载使用 这里我手写了一个简单的流程图。 [图片] #2 海报内容和海报样式都是个性化比较强的,就不多作介绍了。 从生成小程序或者二维码开始。 开始之前,先看一下我已经实现的效果。 [图片] [图片] [图片] 在用户点击生成时,需要先判断之前是否有用户已经生成过。 如果已生成,则直接展示生成好的图片。 [图片] 如果没有已生成的海报时,需要生成海报,在生成之前也需要先判断是否已有生成好的小程序码, 没有则先生成小程序码 [图片] 获取小程序码需要在云函数里面操作,这里需要注意给云函数配置调用生成小程序码的权限 [图片] 获取到小程序码的 fileID 之后需要转换成 url。 给海报生成函数调用。 [图片] 生成海报我们用开源库https://github.com/Kujiale-Mobile/Painter 海报样式也可以用这个在线工具拖拽生成https://lingxiaoyi.github.io/painter-custom-poster/ 通过上面的工具按自己需求生成对应的代码后, 我们可以精简一些空属性和把动态内容改成对应的参数传递进去, [图片] 模版定义好后就可以生成海报。 [图片] 生成海报之后,我们需要上传到云存储并添加记录到对应的数据库里面方便下一个用户分享时直接下载使用 [图片] 最后,就是给用户提供保存到相册的功能了。 [图片] #3 首发在 ikeeplearn 公众号 到这里,利用云开发实现个性海报就结束了。 如果你有其他疑问或者需求欢迎加我微信一起交流 [图片]
2020-01-01 - 小程序云开发模糊查询,实现数据库多字段的模糊搜索
最近做小程序云开发时,用到了一个数据库的模糊搜索功能,并且是要求多字段的模糊搜索。 网上也有一大堆资源,但是都是单个字段的搜索。如下图 [图片] 上图只可以实现time字段的模糊搜索。但是我们如果相对数据表里的多个字段做模糊查询呢?该怎么办呢。 多字段模糊搜索 一,如我们的数据表里有以下数据,我们想同时模糊查询name和address字段 [图片] [图片] 如我们搜索“周杰”可以看到我们查询到下面两条数据。 [图片] 二,如我们搜索“编程”,可以搜索到下面数据 [图片] 可以看到我们搜索到的两条数据,一个是name字段为 编程小石头, 一个是address字段里包含“编程“ 字样。 下面把代码贴给大家 [代码] let key = "编程小石头"; console.log("查询的内容", key) const db = wx.cloud.database(); const _ = db.command db.collection('qcl').where(_.or([{ name: db.RegExp({ regexp: '.*' + key, options: 'i', }) }, { address: db.RegExp({ regexp: '.*' + key, options: 'i', }) } ])).get({ success: res => { console.log(res) }, fail: err => { console.log(err) } }) [代码] key就是我们要搜索的关键字。主要是用到了数据库查询的where,or,get方法。 代码都给大家贴出来来,如果对云开发和云数据库还不是很了解的同学可以去翻看下我以前写的文章。
2019-11-06 - 【云开发】用云开发实现订阅消息
背景 10月12日微信官方发布了小程序模板消息能力调整的通知 https://developers.weixin.qq.com/community/develop/doc/00008a8a7d8310b6bf4975b635a401 相比之前的有了较大的调整,主要的调整就是从开发者主动下发消息改为用户自主订阅,也就是说必须要用户手动订阅才行,而订阅消息又分为了一次性订阅消息和长期性订阅消息,长期性订阅消息需要有特定公共服务业务这里就不介绍了,本文主要讲的是一次性订阅消息。 开发准备 如果还没有开通订阅消息的要在小程序后台里开通订阅消息后添加需要的模板,添加完后的红框中的变量就是订阅消息接口要用到的data了 [图片] [图片] 编写云函数 新建云函数,appid跟secret填你自己的即可, 由于订阅消息需要access_token,所以在使用订阅消息接口前要先获取access_token,这里我把他们都放在一个云函数里了 新建好后要安装依赖,这里用到了[代码]request-promise[代码]模块,所以要在该目录下[代码]npm install request-promise[代码] 代码如下 [图片] [图片] 页面调用 [图片] 传入对应的参数即可 返回结果 [图片] [图片] 总结 总的来说并不难,只是有个地方要注意一下,由于之前没怎么用过云开发,不知道它返回的数据结构会有差异,比如在获取access_token的时候发现一个特别之处,在小程序里进行调用的时候,成功的话返回是下图这种格式的 [图片] 但是本地调试的时候返回又是这种格式的 [图片] 少了外面一层object,导致我判断access_token一直报错,调试了好久才发现,好在最后是可以成功获取到。 再来说这次调整,作为用户的我来说我觉得是很ok的,起码我可以选择不接收订阅消息,不像之前那样莫名其妙的就收到了一个订阅消息还不知道是哪个程序的,我是很反感这种的。但是对于一些企业来说可能就不是那么友好了,降低了触达用户的机会。但是不管怎样,身为开发者的可是有得忙了。 代码就不放了,都在图片里,自己动手敲一遍比较好 相关接口 订阅接口 获取access_token接口 下发消息接口
2019-10-14 - 云开发数据库命令之地理位置查询
在进行云开发的数据更新的时候,我们可以进行不同条件的查询,从而提升我们更新的效率。在云开发支持了 Geo Point 等相关数据类型以后,我们可以用云开发实现多种不同地理位置的数据存储和利用,对于我们开发基于地理位置的小程序来说,有很大的帮助。 今天的课程中,我们来介绍小程序数据更新命令中的「地理位置查询」命令。 geoNear:查询特定点附近的数据 查询特定点附近的数据可以说是我们在进行应用开发时,最为常用的功能,它可以应用于诸如查询当前用户坐标周围的店铺、查询距离我最近的公交站等场景,这个时候,我们需要使用 [代码]geoNear[代码] 来进行数据库查询。 数据结构需求 如果你想要使用 [代码]geoNear[代码],则要求你在数据库中存储的数据对于地理位置的存储是基于 [代码]db.Geo.Point[代码] 进行的,这样你就可以完成使用 [代码]geoNear[代码] 进行查询。 数据查询实例 假设我们当前数据库内数据的结构是这样的,每一个数据下有一个 point 属性,这个属性是通过 [代码]db.Geo.Point[代码] 添加的。 [图片] 如果我们希望查询距离当前位置,1000米 ~ 2000米的数据,则可以执行这样的命令 [代码]const db = wx.cloud.database() const _ = db.command db.collection('items').where({ location: _.geoNear({ geometry: db.Geo.Point(113.323809, 23.097732), minDistance: 1000, maxDistance: 2000, }) }).get() [代码] 这里的 [代码]geometry[代码] 中的 [代码]Point[代码] 的数据是获取到的当前地理位置信息,它必须是 [代码]db.Geo.Point[代码] 类型的,你可以通过小程序的 [代码]wx.getLocation[代码] 方法获取当前的地址信息,并将其作为参数设置在这里。 [代码]minDistance[代码] 则是距离中心点的最小距离,单位是米,所以这里将其设置为 1000。类似的,[代码]maxDistance[代码] 则是距离中心点的最大距离,单位也是米,所以这里将其设置为 2000。 通过这样的方法,我们就可以查询出数据了。 在我们去做一些基于地理位置的应用的时候,[代码]db.command.geoNear[代码] 可以很大程度上简化我们的开发。 geoWithin:查询特定区域内的数据 在开发地理位置应用时,除了基于某一个点的位置进行查询以外,我们还会查询某一个区域内的数据,比如查询北京市昌平区内的所有的酒吧、查询深圳南山区内所有的博物馆,这样的需求也是切实存在,并且十分常见的需求。这个时候,我们可以考虑,使用 [代码]geoWithin[代码] 来进行数据查询。 数据结构需求 如果你想要使用 [代码]geoWithin[代码],则要求你在数据库中存储的数据对于地理位置的存储是基于 [代码]db.Geo.Point[代码] 进行的,这样你就可以完成使用 [代码]geoWithin[代码] 进行查询。 数据查询实例 假设我们当前数据库内数据的结构是这样的,每一个数据下有一个 point 属性,这个属性是通过 [代码]db.Geo.Point[代码] 添加的。 [图片] 如果我们希望查询在东经 112 度 ~ 东经 114 度,北纬 22 度 到 北纬 24 度范围内的数据,则可以执行这样的数据查询 [代码]const db = wx.cloud.database() const _ = db.command const { Point, LineString, Polygon } = db.Geo db.collection('items').where({ location: _.geoWithin({ geometry: Polygon([ LineString([ Point(112, 22), Point(112, 24), Point(114, 24), Point(114, 22), Point(112,22) ]) ]), }) }).get() [代码] 我们通过 [代码]Polygon[代码] 和 [代码]LineString[代码] 构建出了一个正方形,从而实现了查询一个特定的正方形区域内的数据。 如果你希望查询一个其他形状的范围内的数据,只需要传入多个 [代码]Point[代码] 的数据就可以完成,比如如果你要查询一个五边形内部的数据,也只需在构建 LineString 时,传入 6 个 Point的数据即可。 为什么五边形却是 6 个 Point 呢? 因为在云开发中,如果你要构建一个多边形,则需要使得整个多边形是闭合的,也就是说,你的起始点是一样的,因此,如果你想要构建一个四边形,则需要五个点。如果是构建五边形,则是六个点。 geoIntersects:查询与特定区域相交的数据 [代码]geoIntersects[代码] 是用于查询所有数据中和给定数据相交的数据,我们可以将其用作判断某一些特定的点、线、面是否在一个特定区域内。举个例子,假设你已经有了用户当前的活动范围,比如某一条街道,那么你可以基于 [代码]geoIntersects[代码] 来构建一条线,并基于这条线查询,所有数据中,是否有数据与这个线相交,如果相交,则说明对应的数据点是在用户所在的街道上。你就可以将这个数据告诉用户,让用户去找这些点。 相比于 [代码]geoWithin[代码],[代码]geoIntersects[代码] 对于当前用户的位置数据更为随意,支持 [代码]Point[代码]、[代码]LineString[代码]、[代码]MultiPoint[代码]、 [代码]MultiLineString[代码]、 [代码]Polygon[代码]、 [代码]MultiPolygon[代码] 等多种不同的数据结构,在进行查询的时候,更加的方便。 数据结构需求 如果你想要使用 [代码]geoIntersects[代码],则要求你在数据库中存储的数据对于地理位置的存储是基于 [代码]db.Geo.Point[代码] 进行的,这样你就可以完成使用 [代码]geoIntersects[代码] 进行查询。 数据查询实例 假设我们当前数据库内数据的结构是这样的,每一个数据下有一个 point 属性,这个属性是通过 [代码]db.Geo.Point[代码] 添加的。 [图片] 如果我们希望查询在东经 112 度 ~ 东经 114 度,北纬 22 度 到 北纬 24 度范围内的数据,则可以执行这样的数据查询 [代码]const db = wx.cloud.database() const _ = db.command const { Point, LineString, Polygon } = db.Geo db.collection('items').where({ location: _.geoIntersects({ geometry: Polygon([ LineString([ Point(112, 22), Point(112, 24), Point(114, 24), Point(114, 22), Point(112,22) ]) ]), }) }).get() [代码] 我们通过 [代码]Polygon[代码] 和 [代码]LineString[代码] 构建出了一个正方形,从而实现了查询一个特定的正方形区域内的数据。 如果你希望查询一个其他形状的范围内的数据,只需要传入多个 [代码]Point[代码] 的数据就可以完成,比如如果你要查询一个五边形内部的数据,也只需在构建 LineString 时,传入 6 个 Point的数据即可。 当然,在使用时,你可以根据自己的实际情况,设定不同的图形类型,作为数据库查询的对象,完成自己的数据查询需求。 总结 这节课,我们介绍了 [代码]geoNear[代码]、[代码]geoWithin[代码]、[代码]geoIntersects[代码] 三个 API,帮助大家理解其各自在什么样的场景下使用,下一节课,我们将介绍 eq、neq、lt、lte、gt、gte 几个命令。
2019-09-23 - 使用腾讯视频插件播放视频
一、添加腾讯视频插件 登录微信公众平台 --> 设置 --> 第三方设置 --> 插件管理 --> 搜索 腾讯视频 --> 添加 二、上传视频至腾讯视频 进入腾讯视频官网 --> 登录 --> 点击上传 --> 账号进行实名认证 --> 点击上传 --> 填写资料 --> 点击保存 --> 等待审核 三、小程序代码配置腾讯视频播放插件 // 在视频播放页的的wxml上插入视频元素 <txv-video vid=" " playerid=“txv1”></txv-video> // 这里的 vid 来源于腾讯视频上播放视频中视频信息的VID字段 // 在视频播放页的json里面插入 “usingComponents”: { “txv-video”: “plugin://tencentvideo/video” } // 在app.json里面引入插件,注意插件版本号填最新的版本号 “plugins”: { “tencentvideo”: { “version”: “1.2.4”, “provider”: “这里填写插件基本信息中的APPID” // 要确定填写的是插件基本信息中的APPID,不能是小程序的APPID } }
2019-09-27 - 小程序导出数据到excel表,借助云开发后台实现excel数据的保存
我们在做小程序开发的过程中,可能会有这样的需求,就是把我们云数据库里的数据批量导出到excel表里。如果直接在小程序里写是实现不了的,所以我们要借助小程序的云开发功能了。这里需要用到云函数,云存储和云数据库。可以说通过这一个例子,把我们微信小程序云开发相关的知识都用到了。 老规矩,先看效果图 [图片] 上图就是我们保存用户数据到excel生成的excel文件。 实现思路 1,创建云函数 2,在云函数里读取云数据库里的数据 3,安装node-xlsx类库(node类库) 4,把云数据库里读取到的数据存到excel里 5,把excel存到云存储里并返回对应的云文件地址 6,通过云文件地址下载excel文件 一,创建excel云函数 关于云函数的创建,我这里不多说了。如果你连云函数的创建都不知道,建议你去小程序云开发官方文档去看看。或者看下我录制的云开发入门的视频:https://edu.csdn.net/course/detail/9604 创建云函数时有两点需要注意的,给大家说下 1,一定要把app.js里的环境id换成你自己的 [图片] 2,你的云函数目录要选择你对应的云开发环境(通常这里默认选中的) 不过你这里的云开发环境要和你app.js里的保持一致 [图片] 二,读取云数据库里的数据 我们第一步创建好云函数以后,可以先在云函数里读取我们的云数据库里的数据。 1,先看下我们云数据库里的数据 [图片] 2,编写云函数,读取云数据库里的数据(一定要记得部署云函数) [图片] 3,成功读取到数据 [图片] 把读取user数据表的完整代码给大家贴出来。 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init({ env: "test-vsbkm" }) // 云函数入口函数 exports.main = async(event, context) => { return await cloud.database().collection('users').get(); } [代码] 三,安装生成excel文件的类库 node-xlsx 通过上面第二步可以看到我们已经成功的拿到需要保存到excel的源数据,我们接下来要做的就是把数据保存到excel 1,安装node-xlsx类库 [图片] 这一步需要我们事先安装node,因为我们要用到npm命令,通过命令行 [代码]npm install node-xlsx [代码] [图片] 可以看出我们安装完成以后,多了一个package-lock.json的文件 [图片] 四,编写把数据保存到excel的代码, 下图是我们的核心代码 [图片] 这里的数据是我们查询的users表的数据,然后通过下面代码遍历数组,然后存入excel。这里需要注意我们的id,name,weixin要和users表里的对应。 [代码] for (let key in userdata) { let arr = []; arr.push(userdata[key].id); arr.push(userdata[key].name); arr.push(userdata[key].weixin); alldata.push(arr) } [代码] 还有下面这段代码,是把excel保存到云存储用的 [代码] //4,把excel文件保存到云存储里 return await cloud.uploadFile({ cloudPath: dataCVS, fileContent: buffer, //excel二进制文件 }) [代码] 下面把完整的excel里的index.js代码贴给大家,记得把云开发环境id换成你自己的。 [代码]const cloud = require('wx-server-sdk') //这里最好也初始化一下你的云开发环境 cloud.init({ env: "test-vsbkm" }) //操作excel用的类库 const xlsx = require('node-xlsx'); // 云函数入口函数 exports.main = async(event, context) => { try { let {userdata} = event //1,定义excel表格名 let dataCVS = 'test.xlsx' //2,定义存储数据的 let alldata = []; let row = ['id', '姓名', '微信号']; //表属性 alldata.push(row); for (let key in userdata) { let arr = []; arr.push(userdata[key].id); arr.push(userdata[key].name); arr.push(userdata[key].weixin); alldata.push(arr) } //3,把数据保存到excel里 var buffer = await xlsx.build([{ name: "mySheetName", data: alldata }]); //4,把excel文件保存到云存储里 return await cloud.uploadFile({ cloudPath: dataCVS, fileContent: buffer, //excel二进制文件 }) } catch (e) { console.error(e) return e } } [代码] 五,把excel存到云存储里并返回对应的云文件地址 我们上面已经成功的把数据存到excel里,并把excel文件存到云存储里。可以看下效果。 [图片] 我们这个时候,就可以通过上图的下载地址下载excel文件了。 [图片] 我们打开下载的excel [图片] 其实到这里就差不多实现了基本的把数据保存到excel里的功能了,但是我们要下载excel,总不能每次都去云开发后台吧。所以我们接下来要动态的获取这个下载地址。 六,获取云文件地址下载excel文件 [图片] 通过上图我们可以看出,我们获取下载链接需要用到一个fileID,而这个fileID在我们保存excel到云存储时,有返回,如下图。我们把fileID传给我们获取下载链接的方法即可。 [图片] 1,我们获取到了下载链接,接下来就要把下载链接显示到页面 [图片] 2,代码显示到页面以后,我们就要复制这个链接,方便用户粘贴到浏览器或者微信去下载 [图片] 下面把我这个页面的完整代码贴给大家 [代码]Page({ onLoad: function(options) { let that = this; //读取users表数据 wx.cloud.callFunction({ name: "getUsers", success(res) { console.log("读取成功", res.result.data) that.savaExcel(res.result.data) }, fail(res) { console.log("读取失败", res) } }) }, //把数据保存到excel里,并把excel保存到云存储 savaExcel(userdata) { let that = this wx.cloud.callFunction({ name: "excel", data: { userdata: userdata }, success(res) { console.log("保存成功", res) that.getFileUrl(res.result.fileID) }, fail(res) { console.log("保存失败", res) } }) }, //获取云存储文件下载地址,这个地址有效期一天 getFileUrl(fileID) { let that = this; wx.cloud.getTempFileURL({ fileList: [fileID], success: res => { // get temp file URL console.log("文件下载链接", res.fileList[0].tempFileURL) that.setData({ fileUrl: res.fileList[0].tempFileURL }) }, fail: err => { // handle error } }) }, //复制excel文件下载链接 copyFileUrl() { let that=this wx.setClipboardData({ data: that.data.fileUrl, success(res) { wx.getClipboardData({ success(res) { console.log("复制成功",res.data) // data } }) } }) } }) [代码] 给大家说下上面代码的步骤。 1,下通过getUsers云函数去云数据库获取数据 2,把获取到的数据通过excel云函数把数据保存到excel,然后把excel保存的云存储。 3,获取云存储里的文件下载链接 4,复制下载链接,到浏览器里下载excel文件。 到这里我们就完整的实现了把数据保存到excel的功能了。 文章有点长,知识点有点多,但是大家把这个搞会以后,就可以完整的学习小程序云开发的:云函数,云数据库,云存储了。可以说这是一个综合的案例。 有什么不懂的地方,或者有疑问的地方,请在文章底部留言,我看到都会及时解答的。后面我还会出一系列关于云开发的文章,敬请关注。
2019-09-07 - 南苑导览 · 如何做好一款地图导览类小程序?
[图片] 南苑导览是一款由学生独立开发的以地图为载体,提供中山大学南方学院(南苑)具体地点的位置信息、导航、校园历史及文化介绍的小程序。旨在解决校园导航标识不到位、地图形式低效单一、信息设计不够好等问题,为来南苑新人和游客提供更加完美的出行体验。 仅需修改地图配置文件,即可适配任意场景(校园、景区)的小程序个性化地图定制。 原生小程序 + TypeScript + gulp + vantUI + 云开发能力 GitHub:https://github.com/Observer-L/NFU-Guide-Map 2019微信小程序高校大赛 · 华南赛区二等奖
2019-09-24 - 借助实时数据推送快速制作在线对战五子棋小游戏丨实战
1 项目概述 游戏开发,尤其是微信小游戏开发,是最近几年比较热门的话题。 本次「云开发」公开课,将通过实战「在线对战五子棋」,一步步带领大家,在不借助后端的情况下,利用「小程序 ✖ 云开发」,独立完成一款微信小游戏的开发与上线。 2 任务目标 根据项目初始框架,阅读教程的同时,逐步完成棋盘绘制、音乐播放、玩家对战、输赢判定等功能,最终实现一个可以快乐玩耍的在线对战五子棋。 在这个过程中,会了解到 Serverless 的一些概念,并且实际应用它们,比如:云数据库、云存储、云函数、增值能力。除了这些基本功能,还准备了更多的硬核概念与落地实践,比如:实时数据库、聚合搜索、权限控制。 完成开发后,上传并且设置为体验版,欢迎邀请更多人来体验。 3 准备工作 从 TencentCloudBase/tcb-game-gomoku 中下载代码到本地: [代码]git clone https://github.com/TencentCloudBase/cloudbase-examples.git cd /cloudbase-examples/minigame/tcb-demo-gomoku [代码] 切换课程专用的 [代码]minigame-study[代码] 分支: [代码]git checkout minigame-study [代码] 4 实战任务 4.1 创建云开发与小游戏环境 1、打开微信 IDE,点击左侧的小游戏,选择右侧的导入项目,导入之前下载的「在线对战五子棋」的目录,AppID 修改为你已经注册好的小游戏 AppID。 [图片] 2、进入后,点击上方的云开发按钮。如果之前没有开通过云开发,需要开通云开发,新开通的话需要等待 10 ~ 20 分钟。 [图片] 3、进入「云开发/数据库」,创建新的集合,新集合的名称是[代码]rooms[代码]。 [图片] 4、进入「云开发/存储」,点击“上传文件”。上传的内容是[代码]/static/[代码]下的[代码]bgm.mp3[代码] 和 [代码]fall.mp3[代码]。之后的代码中会通过云存储的接口,请求文件的临时 url,这样做的目的是减少用户首次进入游戏加载的静态资源。 [图片] 4.2 准备配置文件 创建配置文件: [代码]cp miniprogram/shared/config.example.js miniprogram/shared/config.js [代码] 将关键字段的信息,换成自己账号的信息即可: [图片] 4.3 创建云开发接口 打开 [代码]miniprogram/shared/cloud.js[代码],在里面初始化云开发能力,并且对外暴露云数据库以及聚合搜索的 API。 [图片] 4.4 获取云存储资源的链接 为了减少用户首屏加载的静态资源,音乐资源并没有放在[代码]miniprogram[代码]目录下,而是放在了云存储中,通过调用云存储的 api 接口,来返回静态资源的临时链接。 在 [代码]miniprogram/modules/music.js[代码]中,会调用资源接口,获取资源链接: [图片] [代码]getTempFileURL[代码]函数属于云开发相关,因此放在了 [代码]miniprogram/shared/cloud.js[代码]中。这里只需要临时链接[代码]tempFileURL[代码]属性,其它返回值直接过滤调即可。 为了方便外面调用,promise 内部不再用 reject 抛错。对于错误异常,返回空字符串。这样,加载失败的资源不会影响正常资源的加载和 Promise.all 中逻辑进行。 [图片] 4.5 游戏进入与身份判断 根据前面的流程图我们可以看到,游戏玩家的身份是分为 owner 与 player。它们的含义如下: owner:玩家进入游戏后,查找是否有空闲房间,如果不存在空闲房间,那么就会主动创建新的空闲房间。那么对于新创建的房间,玩家就是 owner。 player:玩家进入游戏后,查找是否有空闲房间,如果存在空闲房间,那么就加入空闲房间。那么对于空闲房间,玩家就是 player。 判断的依据就是 [代码]judgeIdentity[代码] 方法中,读取云数据库集合中的 rooms 的记录。如果存在多个空闲房间,需要选取创建时间最近的一个房间。因此,这里需要用到「聚合搜索」的逻辑。 聚合搜索的条件,在这里有 3 个: 标记人数的字段,是否为 1 创建时间倒叙排序 只选择 1 个 [图片] 4.6 创建新房间 在上述的身份判断函数逻辑中,如果聚合搜索查询的结果为空,说明没有空闲房间,玩家需要作为 owner 来创建新的房间,等待其它玩家加入。 创建房间的逻辑就是将约定好的字段,放进云数据库的记录中。这些字段有: roomid<[代码]String[代码]>: 6 位房间号,唯一 nextcolor<[代码]"white" | "black"[代码]>: 下一步是白棋/黑棋走 chessmen<[代码]String[代码]>: 编码后的棋盘数据 createTimestamp<[代码]String[代码]>: 记录创建时间戳,精确到 ms people<[代码]Number[代码]>: 房间人数 是的,你可能注意到了,这里需要保证 roomid 是不重复的。因此本地生成的随机 roomid,需要先调用云数据库的查询接口,检测是否存在。如果存在,那么递归调用,重新生成随机字符串。 [图片] 4.7 监听玩家进入 对于 owner 身份来说,除了要创建新房间,还需要在创建后监听 player 身份的玩家进入游戏。 对于 player 身份的玩家进入游戏后,会更新记录中的 people 字段(1 => 2)。这时候就需要利用「实时数据库」的功能,监听远程记录的 people 字段变化。 代码实现上,调用[代码]watch[代码]方法,并且传递[代码]onChange[代码]函数参数。一旦有任何风吹草动,都可以在[代码]onChange[代码]回调函数中获得。对于传递给回调函数的参数,有两个比较重要: docChanges<[代码]Array[代码]>: 数组中的每一项对应每条记录的变化类型,变化类型有 init、update、delete 等。 docs<[代码]Array[代码]>: 数组中的每一项对应每条记录的当前数据。 [图片] 4.8 越权更新字段 对于 player 身份来说,进入房间后,既不需要「创建新房间」,也不需要「监听玩家进入」。但需要更新记录的 people 字段。由于记录是由 owner 身份的玩家创建的,而云数据库只有以下 4 种权限: 所有用户可读,仅创建者可读写 仅创建者可读写 所有用户可读 所有用户不可读写 以上 4 种权限,并没有「所有用户可读写」。因此,对于越权读写的情况,需要通过调用云函数来以“管理员”的权限实现。在 [代码]cloudfunction[代码] 中创建 [代码]updateDoc[代码] 云函数,接收前端传来的 collection、docid、data 字段。对于 data 字段来说,就是数据记录的最新更新数据。 [图片] 在小游戏中,通过[代码]wx.cloud.callFunction[代码]来调用云函数。传入的 data 字段指明被调用的云函数,传入的 data 字段可以在云函数的回调函数的 event 参数中访问到(如上图所示)。 [图片] 4.9 落子更新逻辑 不论对于 player 还是 owner 身份,都需要处理落子的逻辑。落子逻辑中,下面的两种情况是属于无效落子: 点击位置已经有棋子 对方还未落子,目前依然处于等待情况 对于以上两种情况,处理的逻辑分别是: 棋盘状态保存在内部类中,调用落子的函数,会返回是否成功的字段标识 只有监听到远程棋盘更新后,才会打开本地的锁,允许落子;落子后,会重新上锁 [图片] 落子成功后,要在本地判断是否胜利。如果胜利,需要调用退出的逻辑。但无论是否胜利,都要将本地的最新状态更新到云端。 [图片] 4.10 监听远程棋盘更新 不论对于 player 还是 owner 身份的玩家,都需要监听远程棋盘的更新逻辑。当远程棋盘字段更新时,本地根据最新的棋盘状态,重绘整个棋盘。并且进行输赢判定,如果可以判定输赢,则退出游戏;否则,打开本地的锁,玩家可以落子。 因为不同身份均需要监听,因此这一块的监听逻辑可以复用。不同的是,两种身份的监听启动时间不一样。owner 身份需要等待 player 身份玩家进入游戏后才开启棋盘监听;player 身份是更新了 people 字段后,开启棋盘监听。 在监听逻辑中,需要判断远程更新的字段是否是 chessmen,这是通过前面提及的 dataType 来实现的。还徐哟啊判断记录中的 nextcolor 字段是否和本地的 color 一样,来决定是否打开本地的锁。 [图片] 如果上述的两个条件均满足,则执行更新本地棋盘、判定输赢、打开本地锁的逻辑。 [图片] 4.11 游戏结束与退出 每次需要判定输赢的地方,如果可以判定输赢,那么都会走到游戏退出逻辑。退出的逻辑分为 2 个部分,第 1 个是给用户提示,第 2 个是调用云函数清空记录。 第 1 个逻辑中用户提示,需要判定用户胜负状态: [图片] 第 2 个逻辑中清除记录的原因是为了方便调试,对于真正的业务场景,一般不会删除历史数据,方便问题定位。同时,这也是一个越权操作,需要调用云函数来实现。 [图片] 6. 课程完整源码 https://github.com/TencentCloudBase/cloudbase-examples/tree/master/minigame/tcb-demo-gomoku 7. 联系我们 更多云开发使用技巧及 Serverless 行业动态,扫码关注我们~ [图片]
02-19 - 小程序云开发之数据库自动备份
数据是无价的,我们通常会把重要的业务数据存放在数据库中,并需要对数据库做定时的自动备份工作,防止数据异常丢失,造成无法挽回的损失。 小程序云开发提供了方便的云数据库供我们直接使用,云开发使用了腾讯云提供的云数据库,拥有完善的数据保障机制,无需担心数据丢失。但是,我们还是不可避免的会担心数据库中数据的安全,比如不小心删除了数据集合,写入了脏数据等。 还好,云开发控制台提供了数据集合的导出,导入功能,我们可以手动备份数据库。不过,总是手动备份数据库也太麻烦了点,所有重复的事情都应该让代码去解决,下面我们就说说怎么搞定云开发数据库自动备份。 通过查阅微信的文档,可以发现云开发提供了数据导出接口databaseMigrateExport [代码]POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN [代码] 通过这个接口,结合云函数的定时触发功能,我们就可以做数据库定时自动备份了。梳理一下大致的流程: 创建一个定时触发的云函数 云函数调用接口,导出数据库备份文件 将备份文件上传到云存储中以供使用 1. 获取 access_token 调用微信的接口需要 access_token,所以我们首先要获取 access_token。通过文档了解到使用 auth.getAccessToken 接口可以用小程序的 appid 和 secret 获取 access_token。 [代码]// 获取 access_token request.get( `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`, (err, res, body) => { if (err) { // 处理错误 return; } const data = JSON.parse(body); // data.access_token } ); [代码] 2. 创建数据库导出任务 获取 access_token 后,就可以使用 [代码]databaseMigrateExport[代码] 接口导出数据进行备份。 [代码]databaseMigrateExport[代码] 接口会创建一个数据库导出任务,并返回一个 job_id,这个 job_id 怎么用我们下面再说。显然数据库的数据导出并不是同步的,而是需要一定时间的,数据量越大导出所要花费的时间就越多,个人实测,2W 条记录,2M 大小,导出大概需要 3~5 S。 调用 [代码]databaseMigrateExport[代码] 接口需要传入环境 Id,存储文件路径,导出文件类型(1 为 JSON,2 为 CSV),以及一个 query 查询语句。 因为我们是做数据库备份,所以这里就导出 JSON 类型的数据,兼容性更好。需要备份的数据可以用 query 来约束,这里还是很灵活的,既可以是整个集合的数据,也可以是指定的部分数据,这里我们就使用 [代码]db.collection('data').get()[代码] 备份 data 集合的全部数据。同时我们使用当前时间作为文件名,方便以后使用时查找。 [代码]request.post( `https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=${accessToken}`, { body: JSON.stringify({ env, file_path: `${date}.json`, file_type: '1', query: 'db.collection("data").get()' }) }, (err, res, body) => { if (err) { // 处理错误 return; } const data = JSON.parse(body); // data.job_id } ); [代码] 3. 查询任务状态,获取文件地址 在创建号数据库导出任务后,我们会得到一个 job_id,如果导出集合比较大,就会花费较长时间,这时我们可以使用 databaseMigrateQueryInfo 接口查询数据库导出的进度。 当导出完成后,会返回一个 [代码]file_url[代码],即可以下载数据库导出文件的临时链接。 [代码]request.post( `https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=${accessToken}`, { body: JSON.stringify({ env, job_id: jobId }) }, (err, res, body) => { if (err) { reject(err); } const data = JSON.parse(body); // data.file_url } ); [代码] 获取到文件下载链接之后,我们可以将文件下载下来,存入到自己的云存储中,做备份使用。如果不需要长时间的保留备份,就可以不用下载文件,只需要将 job_id 存储起来,当需要恢复备份的时候,通过 job_id 查询到新的链接,下载数据恢复即可。 至于 job_id 存在哪,就看个人想法了,这里就选择存放在数据库里。 [代码]await db.collection('db_back_info').add({ data: { date: new Date(), jobId: job_id } }); [代码] 4. 函数定时触发器 云函数支持定时触发器,可以按照设定的时间自动执行。云开发的定时触发器采用的 [代码]Cron[代码] 表达式语法,最大精度可以做的秒级,详细的使用方法可以参考官方文档:定时触发器 | 微信开放文档 这里我们配置函数每天凌晨 2 点触发,这样就可以每天都对数据库进行备份。在云函数目录下新建 [代码]config.json[代码]文件,写入如下内容: [代码]{ "triggers": [ { "name": "dbTrigger", "type": "timer", "config": "0 0 2 * * * *" } ] } [代码] 完整代码 最后,贴出可以在云函数中使用的完整代码,只需要创建一个定时触发的云函数,并设置好相关的环境变量即可使用 appid secret backupColl:需要备份的集合名称,如 ‘data’ backupInfoColl:存储备份信息的集合名称,如 ‘db_back_info’ 注意,云函数的默认超时时间是 3 秒,创建备份函数时,建议将超时时间设定到最大值 20S,留有足够的时间查询任务结果。 [代码]/* eslint-disable */ const request = require('request'); const cloud = require('wx-server-sdk'); // 环境变量 const env = 'xxxx'; cloud.init({ env }); // 换取 access_token async function getAccessToken(appid, secret) { return new Promise((resolve, reject) => { request.get( `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`, (err, res, body) => { if (err) { reject(err); return; } resolve(JSON.parse(body)); } ); }); } // 创建导出任务 async function createExportJob(accessToken, collection) { const date = new Date().toISOString(); return new Promise((resolve, reject) => { request.post( `https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=${accessToken}`, { body: JSON.stringify({ env, file_path: `${date}.json`, file_type: '1', query: `db.collection("${collection}").get()` }) }, (err, res, body) => { if (err) { reject(err); } resolve(JSON.parse(body)); } ); }); } // 查询导出任务状态 async function waitJobFinished(accessToken, jobId) { return new Promise((resolve, reject) => { // 轮训任务状态 const timer = setInterval(() => { request.post( `https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=${accessToken}`, { body: JSON.stringify({ env, job_id: jobId }) }, (err, res, body) => { if (err) { reject(err); } const { status, file_url } = JSON.parse(body); console.log('查询'); if (status === 'success') { clearInterval(timer); resolve(file_url); } } ); }, 500); }); } exports.main = async (event, context) => { // 从云函数环境变量中读取 appid 和 secret 以及数据集合 const { appid, secret, backupColl, backupInfoColl } = process.env; const db = cloud.database(); try { // 获取 access_token const { errmsg, access_token } = await getAccessToken(appid, secret); if (errmsg && errcode !== 0) { throw new Error(`获取 access_token 失败:${errmsg}` || '获取 access_token 为空'); } // 导出数据库 const { errmsg: jobErrMsg, errcode: jobErrCode, job_id } = await createExportJob(access_token, backupColl); // 打印到日志中 console.log(job_id); if (jobErrCode !== 0) { throw new Error(`创建数据库备份任务失败:${jobErrMsg}`); } // 将任务数据存入数据库 const res = await db.collection('db_back_info').add({ data: { date: new Date(), jobId: job_id } }); // 等待任务完成 const fileUrl = await waitJobFinished(access_token, job_id); console.log('导出成功', fileUrl); // 存储到数据库 await db .collection(backupInfoColl) .doc(res._id) .update({ data: { fileUrl } }); } catch (e) { throw new Error(`导出数据库异常:${e.message}`); } }; [代码]
2019-08-12 - databaseMigrateExport一直报错
[图片] 就一直出错。我测试了一下发送模板消息的api,也是用post提交参数到api接口,成功了,就这个数据库导出的老报错。demo的参数也是抄的网页上的例程 [图片] [图片] [图片]
2019-08-10