- 云托管体验——部署ThinkPHP商城后台到微信云托管
云托管体验——部署ThinkPHP商城后台到微信云托管(含本地云调试配置)2022年的今天,云计算蓬勃发展,除了强大的云平台建设经验的支持外,差异化竞争也很重要。微信云托管背靠腾讯云,坐拥微信生态的优势使其在同类云容器托管产品中让人眼前一亮,以微信为接入点的业务向微信云托管靠拢在安全、用户体验、运维、开发上都具备优势。微信云托管的能力已经上线不少,趁着最近云托管有活动,不花钱体验一下云托管的魅力。本文包括两块主要内容:(Thinkphp项目容器构建思路及实践、在本地搭建模拟云环境完成调试)开始前请在本地开发环境部署好Docker与Vscode一、开通云服务(一)云环境开通系统响应一般比较快,遇到转圈圈超过1分钟可以直接刷新页面。在微信云托管 (qq.com)里,扫码登录,选择自己所需要的账号进行开通[图片] 选择自定义部署[图片] 点击确定或者绑定腾讯云账号[图片] 如果需要绑定腾讯云账号,绑定授权完成后跳转回来可以先点一下取消,再点“自定义”部署一般来说,点一次确定即可,如果响应慢可以等一下,避免造成环境重复创建[图片] [图片] (二)数据库创建创建数据库并创建子账号[图片] [图片] 二、容器构建如果您已经打包成容器,请跳过这一步~这一步主要是根据项目本身的需求去对运行环境作相应的配置,这里列举了本次项目所需依赖,记得最后记录一个清单。(一)收集待迁移项目所需依赖所需依赖的话,如果是新建项目的话可以从文档去找1、项目所需运行环境操作系统:这里因为需要作PHP的版本适配,选择了Alpine:3.10Nginx/Apache:本次项目选择了engineX;PHP: 7.1 ~ 7.3;PHP-FPM:配套即可;PHP插件:如[代码]php7-pcntl php7-posix[代码]等等,这里比较多,详细插件会放在Dockerfile2、 项目所需数据库MySQL:因为云环境提供了数据库,我们就不在本地部署数据库了~Redis:因为当前云托管还没推出,如果是不是测试,强烈建议使用云Redis数据库!3、项目运行脚本Nginx启动脚本:[代码]nginx -g 'daemon off;'[代码] 文件目录权限:chmod -R 777 /app/runtime \ chmod -R 777 /app/public \ chmod -R 777 /app/ \ Redis启动脚本:EXEC=/usr/bin/redis-server CLIEXEC=/usr/bin/redis-cli /usr/bin/redis-server /etc/redis.conf PHP-FPM启动脚本:php-fpm -D Think的Workerman和 Timer/usr/bin/php /app/think workerman start --d /usr/bin/php /app/think timer start --d (二)检查依赖在构建之前,我们还需要检查一下这些依赖是否能够轻松引入,方法很简单,去Alpine依赖包管理搜索对应依赖,并记录一下他们的一些必要信息:在容器内安装位置、配置文件位置,方便后续覆盖容器内的配置文件1、前往Alpine依赖包管理依次搜索所需插件 nginx php php-fpm # 此处省略... php7-gd \ [图片] 2、 搜索Nginx、PHP、PHP-FPM、Redis的软件内容别忘了看一看软件在文件内的安装目录,以及一些配置文件的地址,覆盖默认配置的时候会用到。同时,里面包含一些软件所依赖的包,因为本次所选用的Alpine体积很小,如果缺少了依赖包导致无法安装的话,可以安装一下[图片] 三、本地测试及云发布(一)本地测试1、拉取待迁移项目源码,准备配置文件本次需要两个项目的源码,一个是微信云托管官方的ThinkPHP+Nginx示例项目,另一个是电商项目-CRMEB (gitee.com)的源码。[图片] 需要自己的配置文件有:redis.conf,百度一下redis-5.0的配置文件即可,其余的话放在官方项目里面的conf目录里。因为篇幅原因,源码会放在附录的仓库里。然后导入一下项目并打开Dockerfile[图片] 2、制作Dockerfile及启动脚本(1)添加redis.conf到[代码]/conf[代码]、增加nginx反向代理规则该步主要是为了让redis-server以守护模式去运行,配置文件在dockerfile中被指定去覆盖掉redis原有的配置[图片] 依照商城要求,在[代码]/conf/nginx.conf[代码]中加入反向代理的配置,主要是为了即时通讯模块 location /notice { proxy_pass http://127.0.0.1:20002/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; } #提示:v4.3.0 以前版本,可不用配置一下代码 location /msg { proxy_pass http://127.0.0.1:20003/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; } (2)Dockerfile制作更改基础镜像[代码]alpine[代码]至[代码]3.10[代码]版本在php7插件添加的地方,补充了待移植项目特定的一些依赖(可选)添加redis配置redis的文件覆盖按照商城的要求更改特定目录的权限# 为了兼容php7.3,alpine更改为3.10 FROM alpine:3.10 # 安装依赖包,如需其他依赖包,请到alpine依赖包管理(https://pkgs.alpinelinux.org/packages?name=php7**&branch=v3.10)查找。 RUN apk add --update --no-cache \ nginx \ # php及其插件 php7 \ php7-fpm \ php7-json \ php7-ctype \ php7-exif \ php7-pdo \ php7-pdo_mysql\ # =======商城所需依赖========= php7-pcntl\ php7-posix\ php7-mysqli \ php7-pecl-redis\ php7-fileinfo \ php7-curl \ php7-bcmath \ php7-gd \ # =======END========== # ====(可选)Redis==== redis \ # =======END========== # 结束后清空缓存 && rm -f /var/cache/apk/* # 设定工作目录 WORKDIR /app # 将当前目录下所有文件拷贝到/app COPY . /app # 替换nginx、fpm、php配置 RUN cp /app/conf/nginx.conf /etc/nginx/conf.d/default.conf \ && cp /app/conf/fpm.conf /etc/php7/php-fpm.d/www.conf \ && cp /app/conf/php.ini /etc/php7/php.ini \ # =======redis配置覆盖========== && cp /app/conf/redis.conf /etc/redis.conf \ # =======END========== && mkdir -p /run/nginx \ && chmod -R 777 /app/runtime \ # =======商城要求的特定目录权限======= && chmod -R 777 /app/public \ && chmod -R 777 /app/ \ # =======END========== && mv /usr/sbin/php-fpm7 /usr/sbin/php-fpm # 暴露端口 EXPOSE 80 # 容器启动执行脚本 CMD ["/bin/sh", "/app/run.sh"] 然后在同目录下新增[代码].dockerignore[代码]文件,用于忽略无需构建的东西.git .dockerignore Dockerfile* composer* container.config.json LICENSE README.md (3)启动脚本run.sh制作也就是把刚刚我们在找出来的脚本粘贴进去即可#!/bin/sh # 后台启动 php-fpm -D # 关闭后台启动,hold住进程 nginx -g 'daemon off;' #Think Workerman /usr/bin/php /app/think workerman start --d /usr/bin/php /app/think timer start --d EXEC=/usr/bin/redis-server CLIEXEC=/usr/bin/redis-cli /usr/bin/redis-server /etc/redis.conf 3、(可选)composer依赖处理去找个[代码]composer.phar[代码]放在根目录,然后更新一下即可,然后可以看到[代码]/vendor[代码]里面有新装的包 php composer.phar update 4、构建镜像 首先请确保你的开发环境有Docker在根目录上,使用命令行运行 [代码]docker build -t crmeb-on-wxcloud:1.0 .[代码] [图片] 大概一分钟后,构建完成,跑一下 [代码]docker run -p 80:80 -d crmeb-on-wxcloud:1.0[代码] 全绿!过关 [图片] 5、(额外)如果是第一次创建,可以在微信云托管连接控制台,然后把示例数据创建在云数据库上[图片] 可以看到,数据库连接成功,如果不成功,检查一下数据库外网是否放开,是否为非Root账号。 [图片] (二)云发布1、发布前的测试成功创建演示数据后,我们需要在上线前清空[代码]/public/install[代码]目录,最后在目录 建立[代码]install.lock[代码],并在根目录下创建[代码].env[代码]环境配置文件。 [图片] 配置文件里面,把我们一些数据库连接写好,但这次数据库主机地址我们写内网的即可 APP_DEBUG = true [APP] DEFAULT_TIMEZONE = Asia/Shanghai [DATABASE] TYPE = mysql HOSTNAME = 10.0.224.16 HOSTPORT = 3306 USERNAME = crmeb PASSWORD = xeWmJ466f3 DATABASE = crmeb PREFIX = eb_ CHARSET = utf8 DEBUG = true [LANG] default_lang = zh-cn [REDIS] REDIS_HOSTNAME = 127.0.0.1 PORT = 6379 REDIS_PASSWORD = SELECT = 0 [QUEUE] QUEUE_NAME = 322f1860c16e7 然后再次重复镜像构建过程,确保能跑即可,然后准备发布! 2、上云托管!这里是大伙各显神通的地方了 你可以在本地构建并发布镜像到镜像仓库不在本地构建,通过Git仓库建立工作流或者直接压缩文件上传直接上传这里演示直接压缩文件上传 [图片] 进入云托管控制台,点击左侧“服务管理-服务列表”,新建服务,打开公网访问,然后选择“版本列表-新建版本”,选择一个自己喜欢的方式,这里选择“上传代码包” [图片] 通过Git仓库构建流水线第一次选择仓库时,可能需要授权,授权后返回即可建议勾选发布策略中的通过webhook触发构建,这样每次Git推送到仓库的时候就会触发构建[图片] 发布版本服务构建完成后,直接发布版本,全量发布 [图片] 两端同时跑通,大功告成![图片] 3、(非必要仅为拓展)本地云调试开放接口服务在Vscode里安装两款插件,下面会从安装到配置再到使用进行介绍: Docker:Docker插件Weixin Cloudbase Docker Extension - Visual Studio Marketplace:微信云托管本地调试插件配置微信云托管调试插件打开设置,搜索"wxcloud"进入配置页面,按照上面的信息进行配置 [图片] 前往微信云托管控制台打开及配置云调试[图片] 安装完后重启vscode,在左侧栏打开Docker [图片] 然后我们进入业务容器的命令行体验一下无需鉴权信息[代码]cloudbase_access_token[代码]的请求!(这边偷懒,就从DockerDesktop进了敲指令也可) [图片] 开放接口调用成功!这里如果调用失败,请返回上一步,检查控制台检查一下接口是否在云调用的白名单内[图片]
2021-12-21 - 小程序二维码接口B返回代码nodejs怎么保存图片?
请问下node.js怎么把小程序二维码接口B返回的二维码二进制流保存成图片文件?直接使用fs.writefile方法保存的图片无法打开
2017-10-26 - wx.requestSubscribeMessage()为什么一调用就会触发云函数推送消息?
[图片][图片]正常不应该是先调用第一个授权然后通过调用第二个触发云函数进行消息发送嘛 为什么第一个唤起窗口之后 用户选择允许就会立即发送消息呢
2022-03-24 - 服务商批量代开发云函数本地调试怎么操作?
1.需不需要配置ext.json 2.添加了ext.json的情况下,云函数文件夹显示,当前环境(空),但是云开发控制台可以看到环境 2.extEnable配置为false,云开发面板可以显示所有已绑定环境,并可以切换,但是前台窗口仍然显示默认为环境并无法切换 4.本地调试可以打开,但是请求去了云端 5.开发工具版本最新:Nightly1.05.2109262 [图片][图片]
2021-09-27 - 开放平台批量代云开发的云函数调用报错?-404011 -501000
Error: errCode: -404011 cloud function execution error | errMsg: cloud.callFunction:fail requestID , cloud function service error code -501000, error message Environment not found (callid 1614672784655-0.41237383937735395); at cloud.callFunction api; 我们用了开放平台下的批量代云开发创建的环境和云函数。在小程序云开发控制台上传了静态文件html,在访问调试sendSms云函数的时候报错。 选择了开放平台的环境 [图片] 发送短信的云函数内容 [图片] 初始化 + 调用 [图片] 结果调不通,报错-404011 -501000 [图片]
2021-03-02 - 平台型服务商代实现企业官网小程序(云开发版)
一、帐号准备1 第三方平台类型选择:平台型服务商 2 权限选择:1)开发管理与数据分析权限 2)云开发管理权限集 3 开发配置1)登录授权的发起页域名:w.cloudbase.vip 2)授权事件接收URL:wx.cloudbase.vip/call 注意:在这里只是教程演示的URL,请根据自己的域名设置 请根据以上内容在微信开放平台注册一个第三方平台帐号,具体流程可以参照第一章节的步骤。 二、基础信息设置1 开发环境搭建1.1 开通云开发CloudBase,并设定域名云开发CloudBase是由腾讯云推出的云原生一体化多端研发平台,用户无需管理底层架构,可以直接开展业务开发。我们的第三方平台(注意不是小程序本身)使用云开发来构建。如果你想参照服务器版本,请在教程最后下载服务器版本的DEMO,代码细节一致。 开通云开发CloudBase,创建按量计费环境,进入静态网站托管页面的【基础配置】,添加自定义域名。需要注意,此处的自定义域名需要与第三方平台设置的授权发起页域名保持一致。 在本教程中,设定域名为w.cloudbase.vip [图片] 1.2 设定授权事件url域名在控制台中点击【环境-云接入】,进入云接入配置页,配置第三方平台设置的授权事件接收URL所在的域名,在本教程中,设定的授权事件url域名为wx.cloudbase.vip 云接入是使用标准的 HTTP 协议访问环境内全部资源的一种能力,包括云函数服务,通过对应的触发域名以及云函数的触发路径进行http访问,便可以使用云函数的服务。具体可以参考:云接入文档[图片] 2 创建授权事件接收URL的服务监听安装云开发vscode插件,在vscode中,创建一个云函数,命名为call。 [图片] [图片] 创建后,我们开始进行代码的编写,首先在index.js文件中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const cloud = tcb.init({ env: "" //需要填写自己的云开发环境id }); const db = cloud.database(); const _ = db.command; exports.main = async (event) => { //从event中可以获取到HTTP请求的有关信息 //event.body即为请求体 let msg_body = event.body; /*event.queryStringParameters中可以获得请求参数,取出以下三个内容 - timestamp 时间戳 - nonce 随机数 - msg_signature 消息体签名,用于验证消息体的正确 */ let { msg_signature, nonce, timestamp } = event.queryStringParameters; //判断签名是否不为空,过滤一些非开放平台的无效请求 if (msg_signature != null) { //针对信息进行base64解码 let encryptedMsg = Buffer.from(msg_body, 'base64').toString(); //取出加密的encrypt,在这里没有使用XML方式读取 let encrypt = encryptedMsg.slice(encryptedMsg.indexOf('')); //引入util.js文件,命名为WechatEncrypt,此js包含解码的所有逻辑 const WechatEncrypt = require('./util'); //引入key.json,在这里存储了第三方平台设置的key,随机码,appid等 const WXKEY = require('./key.json'); //将key.json的内容代入,创建WechatEncrypt实例 const wechatEncrypt = new WechatEncrypt(WXKEY); //将timestamp 时间戳、nonce 随机数、加密的encrypt代入gensign函数进行签名处理 let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt }); //判断签名是否和传来的参数签名一致 if (signature === msg_signature) { //将加密的encrypt直接代入decode函数进行解码,返回解码后的明文 let xml = wechatEncrypt.decode(encrypt); //判断明文中是否有ComponentVerifyTicket字段,由此来判断此为验证票据 if (xml.indexOf('ComponentVerifyTicket') != -1) { //取出相应的票据,在这里没有使用XML方式读取 let ticket = xml.slice(xml.indexOf('ticket@@@'), xml.indexOf(']]>')); try { //将票据信息保存到云开发数据库中wxid集合中,component_verify_ticket文档中,在使用前需要在控制台创建集合并设置文档 db.collection('wxid').doc('component_verify_ticket').update({ time: db.serverDate(),//更新的时间 value: ticket }); } catch (e) { console.log('save failed!', e); } } return 'success'; } else { return 'error'; } } else { return 404; } } 在index.js同级目录下,创建util.js,此为解码的主要逻辑文件,编写代码如下: const crypto = require('crypto') const ALGORITHM = 'aes-256-cbc' // 使用的加密算法 const MSG_LENGTH_SIZE = 4 // 存放消息体尺寸的空间大小。单位:字节 const RANDOM_BYTES_SIZE = 16 // 随机数据的大小。单位:字节 const BLOCK_SIZE = 32 // 分块尺寸。单位:字节 let data = { appId: '', // 微信公众号 APPID token: '', // 消息校验 token key: '', // 加密密钥 iv: '' // 初始化向量 } const Encrypt = function (params) { let { appId, encodingAESKey, token } = params let key = Buffer.from(encodingAESKey + '=', 'base64') // 解码密钥 let iv = key.slice(0, 16) // 初始化向量为密钥的前16字节 Object.assign(data, { appId, token, key, iv }) } Encrypt.prototype = { /** * 加密消息 * @param {string} msg 待加密的消息体 */ encode(msg) { let { appId, key, iv } = data let randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE) // 生成指定大小的随机数据 let msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE) // 申请指定大小的空间,存放消息体的大小 let offset = 0 // 写入的偏移值 msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset) // 按大端序(网络字节序)写入消息体的大小 let msgBuf = Buffer.from(msg) // 将消息体转成 buffer let appIdBuf = Buffer.from(appId) // 将 APPID 转成 buffer let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf]) // 将16字节的随机数据、4字节的消息体大小、若干字节的消息体、若干字节的APPID拼接起来 let cipher = crypto.createCipheriv(ALGORITHM, key, iv) // 创建加密器实例 cipher.setAutoPadding(false) // 禁用默认的数据填充方式 totalBuf = this.PKCS7Encode(totalBuf) // 使用自定义的数据填充方式 let encryptdBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()]) // 加密后的数据 return encryptdBuf.toString('base64') // 返回加密数据的 base64 编码结果 }, /** * 解密消息 * @param {string} encryptdMsg 待解密的消息体 */ decode(encryptdMsg) { let { key, iv } = data let encryptedMsgBuf = Buffer.from(encryptdMsg, 'base64') // 将 base64 编码的数据转成 buffer let decipher = crypto.createDecipheriv(ALGORITHM, key, iv) // 创建解密器实例 decipher.setAutoPadding(false) // 禁用默认的数据填充方式 let decryptdBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()]) // 解密后的数据 decryptdBuf = this.PKCS7Decode(decryptdBuf) // 去除填充的数据 let msgSize = decryptdBuf.readUInt32BE(RANDOM_BYTES_SIZE) // 根据指定偏移值,从 buffer 中读取消息体的大小,单位:字节 let msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE // 消息体的起始位置 let msgBufEndPos = msgBufStartPos + msgSize // 消息体的结束位置 let msgBuf = decryptdBuf.slice(msgBufStartPos, msgBufEndPos) // 从 buffer 中提取消息体 return msgBuf.toString() // 将消息体转成字符串,并返回数据 }, /** * 生成签名 * @param {Object} params 待签名的参数 */ genSign(params) { let { token } = data let { timestamp, nonce, encrypt } = params; let rawStr = [token,timestamp,nonce,encrypt].sort().join('') // 原始字符串 let signature = crypto.createHash('sha1').update(rawStr).digest('hex') // 计算签名 return signature }, /** * 按 PKCS#7 的方式从填充过的数据中提取原数据 * @param {Buffer} buf 待处理的数据 */ PKCS7Decode(buf) { let padSize = buf[buf.length - 1] // 最后1字节记录着填充的数据大小 return buf.slice(0, buf.length - padSize) // 提取原数据 }, /** * 按 PKCS#7 的方式填充数据结尾 * @param {Buffer} buf 待填充的数据 */ PKCS7Encode(buf) { let padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE) // 计算填充的大小。 let fillByte = padSize // 填充的字节数据为填充的大小 let padBuf = Buffer.alloc(padSize, fillByte) // 分配指定大小的空间,并填充数据 return Buffer.concat([buf, padBuf]) // 拼接原数据和填充的数据 } } module.exports = Encrypt 在index.js同级目录下创建key.json,里面保存有关设置信息 { "appId": "第三方平台详情页中展示的appid", "encodingAESKey": "在创建过程中设置的消息加解密Key", "token": "在创建过程中设置的消息校验Token" } 按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk crypto 在call文件夹中右键,点击部署并上传全部文件,云函数即可部署成功。 [图片] 前往云开发CloudBase控制台,在云函数操作页下找到部署的call云函数,点击进入函数详情页,点击右上角编辑,进入设置页,配置云接入路径为/call [图片] 于是,我们可以通过wx.cloudbase.vip/call这个地址来接收请求。微信开放平台将每隔10分钟左右就向此url发送请求(因为我们在第三方平台创建时填写的此url),此云函数便可以完成请求的解析和解密存储操作。具体效果如下: [图片] [图片] 3 使用接收到的验证票据(component_verify_ticket)获取令牌在vscode中,我们创建云函数getComToken,按照之前创建的步骤正常创建即可。在index.js编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid, component_appsecret } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(ticket) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_component_token',//请求的API地址 body: JSON.stringify({ component_appid, component_appsecret, component_verify_ticket: ticket }),//传递的所需参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { console.log(event); try { //由于令牌有一定时效性,所以我们没必要每一次都要请求,而是将令牌保存重复利用,我们将令牌保存在wxid集合中的component_access_token文档里 //首先取出文档的信息 let access_token = (await db.collection('wxid').doc('component_access_token').get()).data[0]; //以当前时间的往后一分钟来作为上限时间 let overtime = new Date((new Date()).valueOf() + 60 * 1000); //如果文档的令牌超时时间大于上限时间,则证明令牌还有效,直接返回令牌 if (access_token.time > overtime) { return access_token.value; } else { //如果小于则证明令牌过期,需要重新申请 console.log('token timeover!'); try { //取出ticket票据信息 let ticket = (await db.collection('wxid').doc('component_verify_ticket').get()).data[0]; //将票据信息传入http请求函数,等待请求结果 let result = await CallWeb(ticket.value); //结果是一个json字符串,验证是否有component_access_token字样,如果有则没有报错 if (result.indexOf('component_access_token') != -1) { //解析字符串为json let { component_access_token, expires_in } = JSON.parse(result); try { //更新令牌,并设定超时时间为当前时间的有效时效后,expires_in为有效秒数 await db.collection('wxid').doc('component_access_token').update({ time: db.serverDate({ offset: expires_in * 1000 }), value: component_access_token }); //返回新的令牌 return component_access_token; } catch (e) { console.log('access save failed!', e); return null; } } else { console.log('wxcall failed!', result); return null; } } catch (e) { console.log('ticket failed!', e); return null; } } } catch (e) { console.log('access get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid", "component_appsecret": "第三方平台 appsecret" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,进入函数的编辑设置页,按照截图进行设置并保存。 [图片] 保存后会出现固定的ip地址,如下图所示: [图片] 将此处的ip地址填写到第三方平台的白名单中,修改保存 [图片] 获取令牌的接口云函数,具体效果如下: [图片] [图片] 注意事项云开发的云函数可以设置固定IP地址,这样就可以直接将IP地址填写到第三方平台白名单中。在同一环境中所有云函数共用一个IP地址,无需重复设置。 温馨提示:以js来演示消息解密过程,其中util文件中仍然包含加密函数,有需要的同学可以自行研究使用,util无需修改直接可用,如果你想使用其他语言版本,请在官方文档中下载代码示例 三、授权流程配置1 使用令牌获取预授权码并拼接用户授权链接根据官方文档-获取预授权码的描述,我们需要component_access_token(第三方平台令牌)、component_appid(第三方平台appid),API接口为:https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=${component_access_token} 使用vscode创建一个云函数,命名为getPreAuth,按照之前正常的创建过程创建即可。在index.js中编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(token) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=' + token, body: JSON.stringify({ component_appid }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //此云函数是由后台管理页面请求的,在调用此云函数时就意味着想要创建一个授权的对象,name是对这个对象的备注 if(event.name==null||event.name=='') return null; try { //通过调用getComToken云函数获取第三方令牌 let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { //将令牌信息传入http请求函数,等待请求结果 let result = await CallWeb(access_token); //结果是一个json字符串,验证是否有pre_auth_code字样,如果有则没有报错 if (result.indexOf('pre_auth_code') != -1) { //解析字符串为json let { pre_auth_code, expires_in } = JSON.parse(result); //拼接授权链接,根据此文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Authorization_Process_Technical_Description.html //其中redirect_uri填写发起授权的url,这在之后配置 let auth_url = `https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=${component_appid}&pre_auth_code=${pre_auth_code}&redirect_uri=https://w.cloudbase.vip&auth_type=2`; //将授权链接保存到云开发数据库中的mini集合内 return await db.collection('mini').add({ status: 0, name:event.name, url: auth_url, time: db.serverDate({ offset: expires_in * 1000 }) }) } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 完成后,可以在控制台按照如下方法进行测试,具体效果如下: [图片] 我们可以保存返回结果中的id值,这将在下一步骤中用到。 2 通过授权发起页域名打开授权链接引导用户扫码由于授权链接需要从设置的发起页域名中跳转才有效,所以我们需要编写一个html文档,部署到静态网站托管服务中,由此来模拟真实业务场景下用户自助授权的整个流程。 我们创建一个html文档,编写如下代码: 客户授权 这是微信第三方平台测试——客户端接口 let msg_text = document.getElementById('text'); let app = tcb.init({ env: ''//此处填写自己的云开发环境id }); let auth = app.auth(); //加载后直接使用云开发匿名登录方式进行登录,此处需要在控制台中开启匿名登录 auth.anonymousAuthProvider().signIn().then(() => { console.log('匿名登录成功!'); //拉取url中的参数 let query = getQueryString(); //如果存在do这个参数,则执行逻辑 if (query.do != null) { msg_text.innerText = "拉取授权页面中……" //使用参数信息,其实就是上一步保存数据库返回的文档id,请求云函数authGetOne app.callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); //存在数据 if (res.result.data.length != 0) { //状态设置0为未授权,1为已授权 if(res.result.data[0].status==0){ //保存id信息,因为授权后要返回此界面,需要后续解析 window.localStorage.setItem('open_auth_id', query.do); //跳转url window.location = res.result.data[0].url; } else{ msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } }); // 获取url的请求参数 function getQueryString() { var qs = location.search.substr(1), args = {}, items = qs.length ? qs.split("&") : [], item = null, len = items.length; for (var i = 0; i < len; i++) { item = items[i].split("="); var name = decodeURIComponent(item[0]), value = decodeURIComponent(item[1]); if (name) { args[name] = value; } } return args; } 将此html命名为index.html,上传至环境的静态网站托管中。并需要在环境配置登录鉴权中开启匿名登录 [图片] 在vscode中创建云函数,命名为authGetOne,按照之前正常的创建即可。在index.js中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const cloud = tcb.init({ env: ""//填写自己云开发环境的ID }); const db = cloud.database(); exports.main = async event => { if(event.id!=null){ //field控制取出哪些变量,敏感信息不外泄 return await db.collection('mini').where({_id:event.id}).field({ url:true, status:true }).get(); } else{ return null; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk 右键上传部署云函数。 我们就可以使用w.cloudbase.vip?do=+(上一步保存的id)这个链接来实现授权页面的跳转了。(w.cloudbase.vip为本教程专用,实际实践需要改写为自己的) 3 获取第三方调用令牌3.1 根据用户授权后的授权码获取用户授权账号的信息用户授权后,会在回调的页面中回传授权码,具体表现如下: [图片] 其中auth_code参数即为授权码的信息,我们改造一下index.html,在if逻辑中添加else if逻辑 //加载后直接使用云开发匿名登录方式进行登录,此处需要在控制台中开启匿名登录 auth.anonymousAuthProvider().signIn().then(() => { console.log('匿名登录成功!'); //拉取url中的参数 let query = getQueryString(); //如果存在do这个参数,则执行逻辑 if (query.do != null) { msg_text.innerText = "拉取授权页面中……" //使用参数信息,其实就是上一步保存数据库返回的文档id,请求云函数authGetOne app.callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); //存在数据 if (res.result.data.length != 0) { //状态设置0为未授权,1为已授权 if(res.result.data[0].status==0){ //保存id信息,因为授权后要返回此界面,需要后续解析 window.localStorage.setItem('open_auth_id', query.do); //跳转url window.location = res.result.data[0].url; } else{ msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } //如果存在auth_code这个变量 else if (query.auth_code != null) { msg_text.innerText = "校验中……" //取出保存的id信息 let temp_id = window.localStorage.getItem('open_auth_id'); if (temp_id != null) { //执行authUpdateOne进行授权信息获取 app.callFunction({ name: 'authUpdateOne', data: { code: query.auth_code, id: temp_id } }).then(res => { console.log(res); if (res.result!= null) { //授权信息获取成功后,删除无用的id信息 window.localStorage.removeItem('open_auth_id'); console.log(res.result); msg_text.innerText = "校验成功!"; } else { msg_text.innerText = "校验失败,请刷新此页面重试!" } }); } else { msg_text.innerText = "路径已经刷新,请联系平台!" } } }); 将此html重新上传至静态网站托管 在vscode中创建云函数,命名为authUpdateOne,按照之前正常的创建即可。在index.js中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(token,code) { return new Promise((resolve, reject) => { request({ //获取授权信息的API url: 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=' + token, body: JSON.stringify({ component_appid, authorization_code:code }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { if(event.code==null||event.id==null) return null; try { //通过调用getComToken云函数获取第三方令牌 let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { //将令牌信息和授权码传入http请求函数,等待请求结果 let result = await CallWeb(access_token,event.code); console.log(result); //结果是一个json字符串,验证是否有authorization_info字样,如果有则没有报错 if (result.indexOf('authorization_info') != -1) { //解析字符串为json let { authorization_info } = JSON.parse(result); //取出字段 let { authorizer_access_token,authorizer_appid,authorizer_refresh_token,expires_in,func_info} = authorization_info; //存储到mini中相应的文档里,并置状态为1 return await db.collection('mini').doc(event.id).update({ status:1, time:db.serverDate(), func_info, access_token:authorizer_access_token, access_time:db.serverDate({offset: expires_in * 1000}), appid:authorizer_appid, refresh_token:authorizer_refresh_token }); } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 具体效果如下图所示: [图片] 以上是在用户的角度来看到的,在第三方平台角度看到的信息如下,我们可以获得授权账户的appid、接口令牌、刷新令牌、授权集信息。 由于授权用户可以自主修改授权的信息,所以我们需要判断用户授权的授权集是否满足我们的业务开发要求,在下图字段func_info我们可以获得权限集合的列表,我们应该有相应的判断,来进行复核确认,官方文档中已经清楚的写明了。 一切确定了之后,我们便可以根据这些来进行后续的开发操作了。 [图片] 3.2 过期的授权令牌如何重新刷新在官方文档中描述,每次提供令牌时,都附带刷新令牌,我们需要通过刷新令牌来去获取最新的令牌,由此反复。 需要注意的是,要好好保存刷新令牌,如果丢失需要用户重新授权才可以。 以下是关于重复获取令牌的云函数示例,具体部署细则参照前面的云函数 const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: "" }); const db = cloud.database(); const _ = db.command; function CallWeb(refresh_token, access_token, auth_appid) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=' + access_token, body: JSON.stringify({ component_appid: component_appid, authorizer_appid: auth_appid, authorizer_refresh_token: refresh_token }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { console.log(event); if(event.appid==null)return; try { let auth_data = (await db.collection('mini').where({ appid: event.appid }).get()).data; if (auth_data.length != 0) { let { access_token, access_time, refresh_token } = auth_data[0]; let overtime = new Date((new Date()).valueOf() + 60 * 1000); if (access_time > overtime) { return access_token; } else { console.log('token timeover!'); let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { let result = await CallWeb(refresh_token, access_token, event.appid); console.log(result); if (result.indexOf('authorizer_access_token') != -1) { let { authorizer_access_token, authorizer_refresh_token, expires_in } = JSON.parse(result); await db.collection('mini').where({appid:event.appid}).update({ access_token: authorizer_access_token, access_time: db.serverDate({ offset: expires_in * 1000 }), refresh_token: authorizer_refresh_token }); return authorizer_access_token } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } } else { console.log('can not get appid!'); return null; } } catch (e) { console.log('access get failed!', e); return null; } } 我们可以通过此云函数直接获取令牌,如果令牌过期会自动刷新令牌并保存,以备下一次使用。 四、 使用小程序云开发构建小程序模版我们开始开发编写一个小程序,为了能够较为完整的演示所有可能性,此小程序是一个使用后端服务的企业展示小程序,具体效果如下: [图片] 此小程序用到云开发,所有内容均来自云开发数据库,并且通过云函数进行更新操作。 具体代码可以点击这里下载,其中关键的逻辑信息如下: 小程序页面index.js代码如下: const app = getApp() var that = null; Page({ onShow() { //加载本地存储 let data = wx.getStorageSync('data'); //如果没有存储,直接请求云端数据 if(data==null||data==""){ this.init(); } else{ //有数据即直接部署数据 this.setData(data); wx.setNavigationBarTitle({ title: data.name, }) } }, //云端请求数据,云开发云函数 init(){ that = this; wx.cloud.callFunction({ name: "test",//云函数名称为test success(res) { //部署数据,设置标题栏内容 if(res.result.name!=null){ that.setData(res.result); wx.setStorageSync('data', res.result); wx.setNavigationBarTitle({ title: res.result.name, }) } }, fail(e){ wx.showModal({ title:"测试失败", content:JSON.stringify(e) }); } }) }, telcall(){ wx.makePhoneCall({ phoneNumber: this.data.tel, }) } }) 云函数test逻辑代码如下: const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); const _ = db.command; exports.main = async (event, context) => { try{ // 读取数据库中的INFO集合的base_info文档 let FAS = await db.collection('INFO').doc('base_info').get(); // 附加的一个计数,每次调用就加一 await db.collection('INFO').doc('base_info').update({ data:{ see:_.inc(1) } }); // 返回数据 return FAS.data; } catch(e){ return { _id:null }; } } 在开发完毕后,即可上传到第三方平台的草稿箱,参照第一章节的步骤将其转化为小程序模版,即可使用模版ID进行下面的部署步骤了。 五、小程序模版部署到小程序中(上传代码与提审)1 小程序模版部署到授权小程序在这里我们将使用第三方平台的API接口来执行部署过程。由于所有第三方API接口都需要在白名单中才可以调用,所以我们创建一个云函数来实现。 打开vscode,创建一个云函数,命名为uploadCode,按照之前的指引正常操作即可,打开index.js编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: "" //此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(access_token, data) { return new Promise((resolve, reject) => { request({ //上传小程序代码的API url: `https://api.weixin.qq.com/wxa/commit?access_token=${access_token}`, body: JSON.stringify(data), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //取出传参的appid和企业名称(业务) let { appid,name } = event; if (appid != null && name != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入令牌,以及参数 let result = await CallWeb(access_token, { template_id : "2",//模板id,要换成自己的真实的 user_version : "1.0.1",//版本号,自定义 user_desc : "第三方平台代开发",//描述 ext_json : `{ "extEnable": true, "extAppid": "${appid}", "directCommit": false, "window":{ "navigationBarTitleText": "${name}" }}`//这里是特殊配置,会覆盖小程序代码原有配置,比如在这里将标题栏名字设置为企业名字了 }); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 我们使用接口调用如下,具体效果如下图所示: [图片] 当然我们真实管理不可能以这种方式来进行,所以需要写一个管理平台,利用这个封装好的函数接口来实现业务操作。这就是学习完成之后需要自己搭建的内容了。 本教程提供了一个简易的管理平台,可以予以参考。 2 授权小程序云开发资源的部署云开发作为后端服务的小程序,第三方平台提供了丰富的API接口来部署各种资源。官方文档-云开发接入指南主要描述了云开发环境层面的一些部署能力,官方文档-云开发HTTP API主要描述了云开发业务层面的一些能力,包括数据库存储等操作。 所有的API接口都是以https://api.weixin.qq.com/tcb/开头,整体的参数都是比较固定,所以我们可以将接口通用化成一个云函数。 打开vscode,我们创建一个云函数,命名为toTCB,编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的云开发通用http请求函数 function CallWeb(access_token, api_name, data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/tcb/${api_name}?access_token=${access_token}`,//拼接相应的name,以及令牌 body: JSON.stringify(data),//请求的数据参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //获取appid,相应的云开发接口名、参数 let { appid, name, data } = event; if (appid != null && name != null && data != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token, name, data); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 我们可以在管理平台中直接请求此云函数,来进行云开发相关部署,这是客户端封装的云函数调用,需要在登录后才可以使用 function toTCB(obj) { app.callFunction({ name: 'toTcb', data: { appid: obj.appid, name: obj.name, data: obj.data } }).then(res => { obj.success ? obj.success(res.result) : null; }).catch(e => { obj.fail ? obj.fail(e) : null; }); } 以下举几个例子供参考: 1) 查询小程序的云开发环境 toTCB({ name: 'getenvinfo', appid: appid, data: {}, success(res) { console.log(res); if (res.errcode == 0 && res.info_list != 0) { console.log('检测到' + res.info_list.length + '个云开发环境,使用默认环境id:' + res.info_list[0].env); //在这里如果没有云开发环境可以使用创建环境的接口来创建,在参考示例中只有创建云开发环境的情况,需要自己改写实现 } else { alert('环境检测失败!'); } } }) 2)文件上传至云存储 toTCB({ name: 'uploadfile', appid: input.appid, data: { env: env, path: 'logo.png' }, success(res) { console.log(res); if (res.errcode == 0) { //在这里返回了一个上传链接,需要再使用http请求上传文件。 //需要注意的是,如果在前端上传文件,需要以微信公众号形式登录腾讯云控制台-添加web安全域名为操作的前端域名才可以 } else { alert('文件上传失败!'); } } }) 3)数据库的集合创建 toTCB({ name: 'databasecollectionadd', appid: appid, data: { env: env, collection_name: 'INFO' }, success(res) { console.log(res); if (res.errcode == 0 || res.errcode == -501000) { alert('创建数据库INFO成功!'); //此处注意-501000是表示数据库已存在,目的达成 } else { alert('创建数据库INFO失败!'); } } }) 4)数据库处理数据 toTCB({ name: 'databaseadd', appid: appid, data: { env: env, query: ` db.collection('INFO').add({ data:{ _id:"base_info", content:'${des}', logo:"${logourl}", name:"${name}", person:"${person}", see:0, tel:"${tel}" } }) `//拟定数据库处理语句 }, success(res) { console.log(res); if (res.errcode == 0) { alert('数据库数据部署成功!'); } else if (res.errcode == -501000) { alert('数据库有原始数据,覆盖新数据!'); toTCB({ name: 'databaseupdate', appid: appid, data: { env: env, query: ` db.collection('INFO').doc('base_info').update({ data:{ content:'${des}', logo:"${logourl}", name:"${name}", person:"${person}", see:0, tel:"${tel}" } }) ` }, success(res) { console.log(res); if (res.errcode == 0) { alert('数据库数据部署成功!'); } } }) } } }) 3 授权小程序提交审核当小程序模版上传、云开发资源部署完毕之后,就需要提交审核了。 相关代码如下: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(access_token,data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/wxa/submit_audit?access_token=${access_token}`,//拼接相应的name,以及令牌 body: JSON.stringify(data),//请求的数据参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //获取appid,参数 let { appid } = event; if (appid != null && name != null && data != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token,{ version_desc:"这是我的小程序,请审核" }); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 代码示例本教程用到的示例代码如下: 全代码示例-云开发版本
2021-09-09 - 云开发云调用快速入门
云调用和拓展能力是云开发为了让开发者能够更加方便的使用各类云服务而推出的,它们有的让开发者不再处理繁杂的鉴权,有的可以一键开通云服务所需要的权限,有的则提供了一整套完整的代码,这些都让我们更加方便的发送消息(客服消息、订阅消息、动态消息等)、使用各类云服务(内容安全、图像处理、OCR、生物认证等)以及调用小程序的开放接口。 13.1.1 云调用基础在小程序开发技术文档的服务端接口列表中罗列了所有的服务端接口,如果接口支持云调用,则在接口名称旁会带有[代码]云调用[代码]的标签。在支持云调用的接口文档中,会分别列出 HTTPS 调用的文档及云调用的文档。 也就是说,服务端接口的开发方式(方法)有两种,一种是 HTTPS 调用,一种是云调用,HTTPS 调用通用于所有的开发语言,是一种比较传统的开发方式;而云调用则是云开发提供的更加方便的开发方式。值得一提的是,我们也可以使用云函数来调用 HTTPS 接口,比如使用 axios,方法在 HTTP 处理章节有介绍。云调用是云开发提供的基于云函数使用小程序开放接口的能力,支持在云函数调用服务端开放接口。在云函数中使用云调用调用服务端接口无需换取 access_token,只要是在从小程序端触发的云函数中发起的云调用都会经过微信自动鉴权,可以在登记权限后直接调用如发送订阅消息、客服消息等开放接口。 这里需要注意的是,当我们在调试云调用的云函数时,直接使用云端测试会出现报错,这是因为云端测试是无法获取用户登录态信息的,所以建议在小程序端触发来调试。云调用大大方便了我们使用小程序开放能力的接口,以订阅消息为例,在没有云调用之前,我们想要发送订阅消息,需要以下几个步骤: 在云函数中发送 https 请求,比如前面使用到的 axios;调用小程序的 auth.getAccessToken 接口,传递 grant_type(值为 client_credential)、小程序 appid 和小程序唯一凭证密钥 secret 来获取小程序全局唯一后台接口调用凭据 AccessToken;access_token 的有效期为 2 个小时,最好要自己存储并定时刷新;调用小程序的 subscribeMessage.send 接口的 https 请求,来发送订阅消息而如果是使用云调用则无需换取 access_token,只需要进行如下步骤即可,这些步骤也是通用于其他云调用接口: 在 config.json 文件中配置相关云调用接口的权限;在云函数中无需发送 https 请求,直接使用 cloud.openapi.subscribeMessage.send 即可也就是说云开发的云调用能力都是可以通过 HTTPS 调用的方式来实现,但是使用云调用却方便很多。这里需要强调的是,要使用云调用有两个步骤,一是在云函数的配置文件里添加接口权限(方法见下,后面章节也会介绍),二是用云函数来处理。 13.1.2 云函数的配置文件使用云调用需要配置云调用权限,每个云函数需要声明其会使用到的接口,否则无法调用,声明的方法是在云函数目录下的 config.json 配置文件的[代码]permissions.openapi[代码] 字段中增加要调用的接口名(对应的接口名,可以去云调用的相关接口的文档里查找),[代码]permissions.openapi[代码] 是个字符串数组字段,值必须为所需调用的服务端接口名称。 在每次使用微信开发者工具上传云函数时均会根据配置更新权限,该配置有 10 分钟左右的缓存,如果更新后提示没有权限,稍等 10 分钟后再试。如果你是在微信开发者工具通过“新建 Nodejs 云函数”创建的云函数,云函数的目录里就都会有 config.json 的配置文件,目录结构如下,如果你是通过其他方式创建的云函数,也建议如下 3 个文件都要有(没有的话,可以自己 copy 来创建): test //云函数目录 ├── config.json //权限和定时触发器等的配置文件 ├── index.js //云函数 ├── package.json //云函数的依赖管理 config.json 文件还可以用来配置定时触发器,比如该云函数需要使用到订阅消息和内容安全两个权限,以及每 5 秒钟定时发送一次订阅消息,config.json 的写法如下: { "permissions": { "openapi": [ "subscribeMessage.send", "security.imgSecCheck" ] }, "triggers": [ { "name": "tomylove", "type": "timer", "config": "*/5 * * * * * *" } ] } config.json 配置文件的格式和前面介绍的 json 文件配置的格式一样,比如数组最后一项不能有逗号[代码],[代码],配置文件里不能有注释等,千万不要写错哦。 13.1.3 拓展能力快速入门 1、拓展能力的说明拓展能力可以让云开发更方便调用腾讯云的服务,比如图像处理、短信验证、数据库,这些除了会使用到云开发的一些资源外(有免费额度),还会使用到腾讯云的一些服务,因此会产生一些费用(也就是和云开发分开计费),云开发资源的费用可以去云开发控制台查看;而使用腾讯云服务产生的额外费用,可以去腾讯云费用中心查看。 拓展能力还可以更方便使用腾讯云的账号体系实现跨云开发资源、跨多端来调用,以及云开发团队为了方便开发者和运营人员使用而开发的 CMS 内容管理系统等等。和云调用一样,拓展能力也会不断增加一些更好用的功能。尤其是图像处理能力、CMS 内容管理,强烈建议把这两个作为云开发的核心拓展安装一下。 2、拓展能力的安装要安装拓展能力,我们需要登录到腾讯云云开发的网页控制台,登录时一定要选择其他登录方式-微信公众号,然后扫码授权选择关联的小程序账户进行登录。选择云开发环境之后,就可以点击左侧拓展能力的管理菜单,安装拓展能力到指定的云开发环境。 拓展能力的安装,其实就是根据不同的拓展能力所对应的服务执行了以下一些步骤,可以让开发者不必关注腾讯云一些过于复杂的概念或写云函数等: 在腾讯云访问管理添加了一些CAM 角色,以及为这些角色授权了一些策略;不同的拓展能力可能所需要的腾讯云的权限是一样的,所以创建的角色以及角色所需要授权的策略也就相同,建议不要随便删除;在你指定的云开发环境里新建一些云函数(名称为 tcb_extension_ci 或者以 tcb 开头)、或者在云数据库里创建一些集合(名称也是以 tcb 开头)等;由拓展能力创建的一些云函数、集合,我们是可以修改的,但是它们的名称建议不要修改;拓展能力可以根据需要安装和卸载,即使安装了只会占据云函数和集合的名额,不使用是不会产生费用的;卸载时也是可以删除这些角色和权限策略的(强烈建议不要删除角色和权限策略)以及相应的云函数和数据库。卸载了还可以再次安装。 注意,拓展能力的角色和权限策略是适用于所有云开发环境,也就是你在一个云开发环境安装拓展能力时创建了角色和指定了权限策略,那在其他云开发环境就不需要再配置了;而云函数和云数据库里的集合,还是需要你通过安装来创建或者自己创建的。
2021-09-10 - 微信开放平台定时发送的component_verify_ticket有效时间是多久?
微信开放平台定时发送的component_verify_ticket有效时间是多久?文档说10分钟会定时发送 一次,但是最近两个星期经常性的半小时到一小时或者以上都接收不到,请问是发生了什么。
2020-09-11 - 关于第三方为授权方开发的流程
这篇文章主要是记录下本人在第一次接触第三方开发的时候的一些流程,从给授权方进行授权开始,到授权方授权完成后,给授权方提供相对的服务以及开发的流程,暂时以小程序的开发为主,后续会继续完善公众号/服务号的开发流程,刚刚接触开发若有不对的,和遗失的可以私信和留言哈,欢迎各位大佬们提供建议,新手编写的,轻喷,哈哈哈哈。 [图片]
2022-03-18 - 小程序云开发?
问题描述:小程序A正常使用云开发 1.小程序A授权小程序B,授权成功。。。 2.小程序B云开发控制台可以进去,看到的云开发资源也是正常的; 3.小程序B报错:Error: invalid scope 没有权限,请先开通云服务 这是什么情况???求回复
2020-12-08 - 微信云开发方法封装
npm包minapp-fetch封装了常用的微信云开发的方法。使用起来更方便,简单。推荐给大家。 如一个简单的复杂组合查寻: minapp.find('todos', { p0: ['title', '=', 'ab'], p1: ['name', 'in', ['b', 'bb']], p2: ['location_a', 'include', [34, 22]], r: 'p0 || (p1 && p2)', }).then(res => { console.log('resssfind', res) }) 包的地址:https://www.npmjs.com/package/minapp-fetch
2020-08-25 - 使用第三方模板,该小程序帐号已授权服务商使用云开发资源,因此无法使用云开发资源 报这个错怎么处理?
使用第三方模板,该小程序帐号已授权服务商使用云开发资源,因此无法使用云开发资源 报这个错怎么处理? 如下图 [图片]
2021-01-25 - 该小程序帐号已授权服务商使用云开发资源,因此无法使用云开发资源?
如题,在开发工具中会无法开启“云开发”,但服务商绑定的小程序可以执行代码上传,在示例中也可以正确的执行图片上传至云开发的对象存储 [图片] [图片]
2020-05-14 - 使用 wx.cloud.callContainer 访问本地docker容器 的问题
先总结: 环境id要填线上环境id。 X-WX-SERVICE要填weixin cloudbase containers里的要调用的容器名称callContainer请求服务时,如果第一次请求服务报错了比如404之类的错误,接着用callContainer请求同一个接口,callContainer不会发起请求,而是给小程序前端直接返回 INVALID_PATH 的错误码和serviceName not exit的错误消息。在callContainer里加上apiVersion:2 的参数可以规避2次及以上的重复调用直接报INVALID_PATH的问题官方文档 https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/guide/debug/ 昨天callcontainer调本地的docker服务时,一直有问题调不通。最后排查出问题: 1.官方文档只提到X-WX-SERVICE要与容器名相同,一开始试了wx-cloudbase里的容器名称,发现有问题,一直报INVALID-PATH,envdbid无效,以为是X-WX-SERVICE填错了。 [图片] 后面改成docker ps 里names的名字,还是报错 [图片] 于是还想是不是环境id有问题。试了各种环境id等。 今天从官方微信群得知环境id是要填 云托管里的 线上环境id的,于是后试了下X-WX-SERVICE用wx-cloudbase的容器名称,这次发现第一次callcontainer调用本地容器日志显示是接受到请求的,但是后面又没有请求记录了。感觉这里面有问题。重新加载模拟器,又复现了问题。原来是callcontainer请求的接口,服务报了错误后,继续调用同一个接口时,callcontainer不会再向服务发送请求了,而是直接给小程序报”INVALID_PATH“的错误码,也是这个原因让我一直以为是X-WX-SERVICE填错了。 后面把参数改正确,就可以正常调用了 [图片]
2022-03-17