- 第三方服务商提审限额机制优化说明
第三方服务商按月按提审表现分配提审限额(quota)的机制上线以来,服务商对提审质量的重视度不断上升,大盘提审合格率也不断提升。为了保障服务商提审效率,携手与服务商共同发展,平台近期将完善quota机制,更科学合理地分配服务商提审额度。 一、提审限额分配机制优化 服务商表现排名,会综合服务商的以下表现进行评价。同时平台考察服务商表现维度更全面(11月在服务商助手小程序的“表现与权益”专区上线) 月quota分配优化前:月quota基于近3月提审单数、服务商表现排名分配月quota分配优化后:月quota基于近6月提审单数、服务商表现排名分配新服务商支持:新第三方服务商每月最少可获得50个quota。*新第三方服务商定义:自上线之日起一年内称之为新第三方服务商 安全:提审质量、线上违规情况、低风险小程序数量、高风险小程序数量等 规模:授权小程序个数、主体个数、月新增小程序个数等 活跃:高活跃小程序个数及占比、不活跃小程序个数及占比 支付:支付金额、笔数、支付活跃小程序个数及占比 体验:体验差小程序个数及占比 [图片] 二、quota自助调额范围优化: 优化前:服务商每次申请临时额度为月初分配提审额度的15%优化后:月初分配的quota作为调额范围的划分标准,具体调整范围如下图:[图片] 特别注意: 服务商在可选范围内按需求自助调额,自助调整次数以及调额范围将基于提审合格率、quota消耗率等服务商表现分配,分配规则如下: 自然年内若存在超过三个月(不含)提审合格率低于80%,将无法申请临时调额quota消耗率作为第三方服务商画像分值的一个重要参考项,请按需进行quota调额申请,若quota消耗率存在超过三个月(不含)低于60%,服务商的画像分值排序将会降低,影响后续审核的优先级以及quota等权益分配。quota消耗率指当月quota使用值/(当月quota分配值+临时申请quota总值)*100% [图片] 平台将持续为服务商提供高效快捷审核服务,若因业务发展等情况存在超额提审需求的服务商还可以通过“小程序服务商助手-我的-咨询反馈”联系人工客服进行申请更多临时额度,客服工作时间:工作日:9:00-12:00,14:00-18:00,特殊时期以平台公告为准。 临时额度人工申请周期为1-3天,请服务商提前规划好提审额度的需求。 微信开放平台运营团队 2020.11.4
2022-06-01 - wx.login() 获取openid的方法
官方文档中给出了示例代码: wx.login({ success (res) { if (res.code) { //发起网络请求 wx.request({ url: 'https://example.com/onLogin', data: { code: res.code } }) } else { console.log('登录失败!' + res.errMsg) } } }) 能用吗?经反复摸索,终于得到下述用法: wx.login({ success: res => { if (res.code) { let URL='https://api.weixin.qq.com/sns/jscode2session?appid=##################&secret=***************** ***************&js_code='+res.code+'&grant_type=authorization_code' wx.request({ url: URL , success:function(res){ console.log(res.data)//res.data中有openid } }) } } }) 注:###......#为小程序ID;***……*为小程序密钥。res.code为临时获得的码。还得注意编译程序时本地设置中勾选“不校验合法域名” 小程序中的坑太多了!官方文档多单不实用!!!
2022-10-29 - 名片小程序跨终端升级
本节课程告诉你如何围绕名片小程序进行跨终端升级,提升开发效率。 iframe class="embed-responsive-item vqq-player" type="text/html" width="640" height="390" src="https://v.qq.com/txp/iframe/player.html?vid=i3552pbfzd3&disableplugin=IframeBottomOpenClientBar&&auto=0" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>
06-12 - 使用试用小程序提高业务转化率
[视频] 试用小程序介绍 试用小程序,是微信官方推出的一种免认证即可快速创建,14天有效期的体验版小程序,可降低商家体验小程序门槛,实现真正的所见即所得。试用小程序的推出将原有的先购买、再认证、最后体验的流程变为先体验、再购买、最后认证,从而提高了该过程的转化率。 [图片] 试用小程序适用场景 那么什么场景适合接入试用小程序能力呢?哪些服务商可以接入这个能力呢?主要包括如下类别:有线上官网流量,但门槛高,转化率不理想的;计划建设线上拓客渠道的;有大量存量H5客户,有新增开通小程序需求的。具体的渠道包括服务商自己的PC官网、服务号、企业微信、小程序、APP、H5 等。对于通过上述注册渠道创建的试用小程序,可以通过法人扫脸和复用公众号资质等方式进行转正。 试用小程序接入和应用 试用小程序的接入主要包括两个接口,创建试用小程序接口和试用小程序转正接口,具体接入指引可以查看创建试用小程序文档。 在接入试用小程序能力后,可以入驻微信服务平台的先试后付专区。微信服务平台是微信官方的撮合平台,入驻先试后付专区后,用户可以先试后付、一键开店,降低用户转化门槛。目前先试后付已上线场景包括微信服务平台全站和视频号开店(搜一搜”视频号开店“),入驻专区后,服务商可获得潜在客资与销售线索,获得品牌露出与增强用户认知的机会。同时,通过让用户在一分钟内获得自己的C端店铺并可以体验到B端店铺的管理能力,用户的付费率和转化效果将大幅提升。 [图片] 关于试用小程序在PC、公众号、小程序的对接以及案例查看,请关注后续相关文章。 [图片]
2021-09-30 - 如何实现扫服务号带参二维码打开小程序
为了给广大用户在“扫码点餐”的场景提供更好的用户体验,不再需要“扫码关注后点餐”;同时,也为了兼容线下已有的二维码,微信公众平台开放扫描服务号带参链接二维码打开小程序能力。 能力介绍 服务号带参链接二维码,是指开发者通过官方开放的“生成服务号带参数的二维码”接口生成的带参链接,形如“http://weixin.qq.com/q/xxx”的服务号带参链接。 扫服务号带参二维码打开小程序,通过该能力,线下商户不需要更换线下带参二维码,通过开放的接口完成配置后,即可实现在用户扫描服务号带参链接二维码时打开小程序,使用小程序的功能。 开放范围 服务号:已认证的服务号小程序:企业、媒体、政府及其他组织类型小程序,不支持个人主体。 相关规则 1个小程序最多支持配置10w个服务号带参链接;即单个小程序,“创建二维码规则”的接口调用总次数为10w次。接口的调用限制为:200次/分钟,请注意使用频率。 对接说明 获取服务号带参链接,如果是存量的链接,需开发者自行获取,平台不存储已经生成的服务号带参链接;如果是新增的链接,开发者可通过生成带参数的二维码接口进行生成。检查是否完成授权。若步骤1,也是服务商代创建的,那么该服务号是已经将“账号服务权限集”(权限集id为3)的权限授权给该服务商。“扫服务号带参二维码打开小程序”涉及的接口也是同在“账号服务权限集”中。如果尚未完成授权,则参考第三方授权的文档完成授权。调用接口添加二维码规则,在步骤1中获取到的服务号带参链接作为该接口的prefix的入参进行配置;规则添加后,需要调用接口发布已设置的二维码规则进行发布方可生效。如果规则配置错误了,则可以调用接口修改二维码规则,注意,已经发布的规则,不支持修改。如果想撤销已经发布生效的规则,则可以调用接口删除已设置的二维码规则,此时无需重新发布即可生效。 其他相关能力 扫码关注组件:该能力用于在用户扫小程序码打开小程序时,开发者可在小程序内配置公众号关注组件,方便用户快捷关注公众号。相关详细的规则可查看official-account。 [图片] 扫码关注组件对接指南: 设置展示的公众号调接口开启商家小程序的扫码关注组件,接口详情可点击查看设置展示的公众号信息。设置之前可以调接口以获取可以用来设置的公众号列表,当前仅支持设置同主体的公众号,即将支持关联主体。设置完成之后,可以调用接口以获取展示的公众号信息。补充说明,上述步骤涉及的接口属于开发管理与数据分析权限集(权限集id为18),若服务商需要代商家调用该接口应当获得商家小程序相关授权。 [图片]
2021-09-09 - 平台型服务商代实现企业官网小程序(云开发版)
一、帐号准备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 - 平台型服务商代实现企业官网小程序(服务器版)
一、帐号准备1 第三方平台类型选择:平台型服务商 2 权限选择:开发管理与数据分析权限 3 开发配置1)登录授权的发起页域名:w.cloudbase.vip 2)授权事件接收URL:w.cloudbase.vip/call 注意:在这里只是教程演示的URL,请根据自己的域名设置 请根据以上内容在微信开放平台注册一个第三方平台帐号,具体流程可以参照第一章节的步骤。 二、基础信息设置1 开发环境搭建1.1 准备服务器环境,设定域名请准备一个线上服务器环境,并可以在公网访问到。而且服务器的域名必须为授权事件接收URL的域名。 在本教程中,以NodeJS服务器环境为例子,设定域名为w.cloudbase.vip 同时将服务器的IP地址填写到第三方平台的白名单中,修改保存 [图片] 1.2 下载部署服务器DEMO示例代码为了能够更加清楚的理解之后的步骤,请下载服务器node示例,并按照示例中的部署步骤部署到准备的服务器环境中。 打开项目的work/key.json,将以下第三方平台基础的开发信息写入文件: { "appId": "第三方平台APPID", "component_appid": "第三方平台APPID(同上)", "component_appsecret": "第三方平台appsecret", "encodingAESKey": "加解密Key", "token": "随机字符", "template_id":"小程序模板id,先空,后续步骤添加", "redirect_uri":"授权发起页url,需为此项目部署的域名,而且在开放平台设置,如此项目可以通过www.example.com访问到index页,则填写http://www.example.com" } 2 创建授权事件接收URL的服务监听请定位到项目work/call,在这里执行了一个事件接收监听逻辑,此目录的代码模块在router中以/call的形式外发,这样接口URL地址为w.cloudbase.vip/call,正是在微信平台中设置的授权事件接收URL。 我们提取出关键代码,此模块的入口文件代码如下: const db = require('../database/lowdb');//使用文件数据库LowDB封装的数据存取接口 function main(msg_body,query){ //从event中可以获取到HTTP请求的有关信息 //event.body即为请求体 console.log(msg_body,query); /*event.queryStringParameters中可以获得请求参数,取出以下三个内容 - timestamp 时间戳 - nonce 随机数 - msg_signature 消息体签名,用于验证消息体的正确 */ let { msg_signature, nonce, timestamp } = query; //判断签名是否不为空,过滤一些非开放平台的无效请求 if (msg_signature != null) { //针对信息进行base64解码 let encryptedMsg = Buffer.from(msg_body, 'base64').toString(); //取出加密的encrypt,在这里没有使用XML方式读取 let encrypt = encryptedMsg.slice(encryptedMsg.indexOf(') + 18, 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文档中 console.log(ticket,0); db.update_cvt(ticket,0); } catch (e) { console.log('save failed!', e); } } return 'success'; } else { return 'error'; } } else { return 404; } } module.exports = main; 在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 我们通过router将此模块进行接口外发,代码如下: router.post("/call", function (req, res, next) { res.send(require('../work/call/index')(req.body,req.query)); }); 于是,我们可以通过wx.cloudbase.vip/call这个地址来接收请求。微信开放平台将每隔10分钟左右就向此url发送请求(因为我们在第三方平台创建时填写的此url),此接口便可以完成请求的解析和解密存储操作。 温馨提示:以js来演示消息解密过程,其中util文件中仍然包含加密函数,有需要的同学可以自行研究使用,util无需修改直接可用,如果你想使用其他语言版本,请在官方文档中下载代码示例 3 使用接收到的验证票据(component_verify_ticket)获取令牌请定位到项目work/getComToken文件夹,在这里封装了通过ticket获取令牌的相关逻辑,入口代码如下: const request = require('request'); const db = require('../database/lowdb');//使用文件数据库LowDB封装的数据存取接口 //获取相关的第三方平台信息 const { component_appid, component_appsecret } = require('../key.json'); //封装的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); }); }); } async function main(event){ try { //由于令牌有一定时效性,所以我们没必要每一次都要请求,而是将令牌保存重复利用,我们将令牌保存在wxid数据库中的component_access_token文档里 //首先取出文档的信息 let access_token = db.get_cat(); //以当前时间的往后一分钟来作为上限时间 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 = db.get_cvt(); //将票据信息传入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为有效秒数 db.update_cat(component_access_token,expires_in); //返回新的令牌 return component_access_token; } catch (e) { console.log('access save failed!', e); return null; } } else { console.log('wxcall failed!', result); return result; } } catch (e) { console.log('ticket failed!', e); return null; } } } catch (e) { console.log('access get failed!', e); return null; } } module.exports = main; 如此,当其他模块需要使用第三方平台的access_token,就可以直接用以下方式来调用了,将会返回最新的access_token: //通过调用getComToken接口获取第三方令牌 let access_token = await require('../getComToken/index')(); 三、授权流程配置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} 在示例项目中定位work/getPreAuth目录,此包含获取预授权码并封装授权链接的逻辑,代码如下: const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('../key.json'); const db = require('../database/lowdb');//使用文件数据库LowDB封装的数据存取接口 //封装的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); }); }); } async function main(event){ //此接口是由后台管理页面请求的,在调用此接口时就意味着想要创建一个授权的对象,name是对这个对象的备注 if(event.name==null||event.name=='') return null; try { //通过调用getComToken接口获取第三方令牌 let access_token = await require('../getComToken/index')(); 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 let auth_url = `https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=${component_appid}&pre_auth_code=${pre_auth_code}&redirect_uri=https://wx.cloudbase.vip/cloud&auth_type=2`; //将授权链接保存到mini数据库内 return db.update_mini(null,{ status: 0, name:event.name, url: auth_url, time: new Date(new Date().getTime() + 1000 * expires_in) }) } else { console.log('wxcall failed!', result); return result; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } module.exports = main; 由于所有的模块都遵循work/模块名称/index的路径逻辑,所以在router中可以进行统一的中转外发,如下代码所示: router.post("/webcall", async function (req, res, next) { let {name,data} = req.body; if(name){ res.send(await require(`../work/${name}/index`)(data)); } else{ res.send(404); } }); 我们定位到views/admin.ejs,这是示例项目中简单的管理平台,主要实现了整个的授权小程序管理和部署操作。 其中,在admin.ejs中,我们可以调用getPreAuth模块获取授权路径,如下代码所示: function getPreAuth() { let name = prompt("请输入授权备注名", ""); if (name) { callFunction({ name: 'getPreAuth', data: { name: name } }).then(res => { if (res != null) { console.log('获取成功', res.id); init(); } else { alert('获取授权链接失败!') } }); } } function deletepre(id) { callFunction({ name: 'admindelete', data: { id: id } }).then(res => { init(); }); } function callFunction(obj){ return new Promise((resolve, reject) => { let xml=new XMLHttpRequest(); xml.open("POST","webcall",true) xml.setRequestHeader("Content-type","application/json"); xml.send(JSON.stringify({ name:obj.name, data:obj.data==null?{}:obj.data })); xml.responseType='text'; xml.onreadystatechange=function() { if (xml.readyState === 4 && xml.status === 200) { let e = JSON.parse(xml.responseText); resolve(e); } } }); } admin页面的router外发配置如下: router.get("/admin", function (req, res, next) { res.render("admin", {}); }); 我们可以在页面中通过新增授权来获取一个授权链接了。 2 通过授权发起页域名打开授权链接引导用户扫码当授权链接创建成功后,我们就能够在管理端admin中得到授权的列表了,列表中每一个项都由数据库文档id来唯一标示,当我们在页面中点击链接时,就可以跳转到域名跟路径了。 域名根路径下是index页面,相关路由代码如下: router.get("/", function (req, res, next) { res.render("index", {}); }); 在项目中定位到views/index.ejs文件,在这里是模拟客户的页面。 代码如下: 客户授权title> head> 这是微信第三方平台测试——客户端接口div> let msg_text = document.getElementById('text'); let query = getQueryString(); if (query.do != null) { msg_text.innerText = "拉取授权页面中……" callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); if (res) { if (res.status == 0) { window.localStorage.setItem('open_auth_id', query.do); window.location = res.url; } else { msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } 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; } function callFunction(obj) { return new Promise((resolve, reject) => { let xml = new XMLHttpRequest(); xml.open("POST", "webcall", true) xml.setRequestHeader("Content-type", "application/json"); xml.send(JSON.stringify({ name: obj.name, data: obj.data == null ? {} : obj.data })); xml.responseType = 'text'; xml.onreadystatechange = function () { if (xml.readyState === 4 && xml.status === 200) { let e = JSON.parse(xml.responseText); resolve(e); } } }); } script> body> html> 这个页面在打开的时候,会根据参数想authGetOne接口去获取授权链接,而参数其实就是创建授权链接时保存在数据库中的文档id。 我们定位到work/authGetOne目录,此处实现了通过文档id获取整个授权信息的逻辑。 const db = require('../database/lowdb');//使用LOWDB实现的简易数据库接口。 function main(event){ if(event.id!=null){ //我们在前面步骤已经存入了一个id return db.get_lowmini(event.id); } else{ return null; } } module.exports = main; 我们就可以使用w.cloudbase.vip?do=+(上一步保存的id)这个链接来实现授权页面的跳转了。(w.cloudbase.vip为本教程专用,实际实践需要改写为自己的) 3 获取用户授权信息3.1 根据用户授权后的授权码获取用户授权账号的信息用户授权后,会在回调的页面中回传授权码,具体表现如下: [图片] 其中auth_code参数即为授权码的信息,我们改造一下上一步的index.ejs,在if逻辑下添加else if逻辑 else if (query.auth_code != null) { msg_text.innerText = "校验中……" let temp_id = window.localStorage.getItem('open_auth_id'); if (temp_id != null) { callFunction({ name: 'authUpdateOne', data: { code: query.auth_code, id: temp_id } }).then(res => { console.log(res); if (res != null) { window.localStorage.removeItem('open_auth_id'); console.log(res); msg_text.innerText = "校验成功!"; } else { msg_text.innerText = "校验失败,请刷新此页面重试!" } }); } else { msg_text.innerText = "路径已经刷新,请联系平台!" } } 当用户授权成功后,回调回此网址,并附带参数,我们便可以根据参数来进行令牌的获取了。在上面代码中调用了authUpdateOne接口。 我们定位到work/authUpdateOne目录,此实现了获取用户授权的开发相关信息。代码如下 const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('../key.json'); const db = require('../database/lowdb'); //封装的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); }); }); } async function main(event){ if(event.code==null||event.id==null) return null; try { //通过调用getComToken接口获取第三方令牌 let access_token = await require('../getComToken/index')(); 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 db.update_mini(event.id,{ status:1, time:new Date(), func_info, access_token:authorizer_access_token, access_time:new Date(new Date().getTime() + 1000 * expires_in), appid:authorizer_appid, refresh_token:authorizer_refresh_token }); } else { console.log('wxcall failed!', result); return result; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } module.exports = main; 当获取成功后,就会在页面中提示授权成功的信息,用户授权步骤完成。 以上是在用户的角度来看到的,在第三方平台角度看到的信息如下,我们可以获得授权账户的appid、接口令牌、刷新令牌、授权集信息。 由于授权用户可以自主修改授权的信息,所以我们需要判断用户授权的授权集是否满足我们的业务开发要求,在下图字段func_info我们可以获得权限数据的列表,我们应该有相应的判断,来进行复核确认,官方文档中已经清楚的写明了。 一切确定了之后,我们便可以根据这些来进行后续的开发操作了。 [图片] 3.2 过期的授权令牌如何重新刷新在官方文档中描述,每次提供令牌时,都附带刷新令牌,我们需要通过刷新令牌来去获取最新的令牌,由此反复。 需要注意的是,要好好保存刷新令牌,如果丢失需要用户重新授权才可以。 我们定位到work/getAuthToken目录,此实现了获取刷新令牌的逻辑,代码如下: const request = require('request'); const { component_appid } = require('../key.json'); const db = require('../database/lowdb'); 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); }); }); } async function main (event){ console.log(event); if(event.appid==null)return; try { let auth_data = db.query_mini(event.appid); console.log(auth_data); if (auth_data) { let { access_token, access_time, refresh_token , uuid} = 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 require('../getComToken/index')(); 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); db.update_mini(uuid,{ access_token: authorizer_access_token, access_time: new Date(new Date().getTime() + 1000 * expires_in), 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; } } module.exports = main; 我们可以通过此接口直接获取令牌,如果令牌过期会自动刷新令牌并保存,以备下一次使用。 四、 构建小程序模版 我们在了解完模板小程序的创建过程以及部署流程之后,我们就开始开发编写一个小程序,为了能够较为完整的演示所有可能性,此小程序是一个使用后端服务的企业展示小程序,具体效果如下: [图片] 界面数据直接使用wx.request来获取业务服务API的信息(此处需要自行开发) 具体代码可以点击这里下载,其中关键的逻辑信息如下: 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.request({ url: 'https://ex.com/get', //仅为示例,请更换为自己的api链接 header: { 'content-type': 'application/json' }, success (res) { //部署数据,设置标题栏内容 if(res.data.name!=null){ that.setData(res.data); wx.setStorageSync('data', res.data); wx.setNavigationBarTitle({ title: res.data.name, }) } }, fail(e){ wx.showModal({ title:"测试失败", content:JSON.stringify(e) }); } }); }, telcall(){ wx.makePhoneCall({ phoneNumber: this.data.tel, }) } }) 服务器API返回的数据格式如下(需自行在自己服务器实现返回): data:{ content:'企业的描述', logo:"企业的logo照片地址", name:"企业的名称", person:"企业的代表人", tel:"企业的电话" } 在这里特别说明一下,使用传统服务器形式通信的小程序,直接进行request请求调用自定义的url接口(如上边代码的https://ex.com/get),需要注意此处调用的url域名需要在设置的白名单中。 在开发完毕后,即可上传到第三方平台的草稿箱,参照第一章节的步骤将其转化为小程序模版,即可使用模版ID进行下面的部署步骤了。 五、小程序模版部署到小程序中(上传代码与提审)1 小程序模版部署到授权小程序在这里我们将使用第三方平台的API接口来执行部署过程。由于所有第三方API接口都需要在白名单中才可以调用。 我们定位到work/uploadcode目录,这里实现了整个小程序部署上传的逻辑: const request = require('request'); function CallWeb(access_token, data) { return new Promise((resolve, reject) => { request({ 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); }); }); } async function main(event){ let { appid,name } = event; if (appid != null && name != null) { let access_token = await require('../getAuthToken/index')({ appid }); if (access_token != null) { let result = await CallWeb(access_token, { template_id : "2", 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; } } module.exports = main; 在admin页面中,有调用此接口进行小程序模版上传部署,相关代码如下: function uploadCode(){ showdesw('开始上传小程序代码……'); callFunction({ name: 'uploadCode', data: { appid: input.appid, name:input.name } }).then(res => { console.log(res); showdesw('小程序代码上传成功!'); }).catch(e => { console.log(e); }); } 2 授权小程序服务器域名配置我们需要通过官方文档-设置服务器域名来进行域名的设置,与小程序代码的请求地址保持对应,不要设置错。 关于服务器域名设置这里,需要特别注意的是需要先将域名登记到第三方平台的小程序服务器域名中,才可以调用接口进行配置。而且配置时会自动删除原先配置的域名,需要特别注意 具体代码如下: const request = require('request'); //封装的http请求函数 function CallWeb(access_token,data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/wxa/modify_domain?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 require('../getAuthToken/index')({ appid }); if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token,{ action:"add", requestdomain: ["https://www.qq.com", "https://www.qq.com"] }); 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; } } 3 授权小程序提交审核当小程序模版上传、服务器配置完毕之后,就需要提交审核了。 相关代码如下: const request = require('request'); //封装的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 require('../getAuthToken/index')({ appid }); 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; } } 代码示例本教程用到的示例代码如下: 全代码示例-服务器node版本
2021-09-09 - 一文了解第三方授权
为了保障商家小程序账号安全性,可进行更细粒度的控制,微信第三方平台向合作的小程序服务商开放安全可靠的授权方式,商家可免向服务商提供密钥以及把服务商添加到开发者成员,更安全也更方便。 [图片] 小程序服务商权限 [图片] 关于小程序权限集详细说明可查看小程序权限集文档。 第三方授权实现流程 █ 服务商 1、创建第三方平台账号 登录微信开放平台(https://open.weixin.qq.com/)注册开放平台账号并且完成认证后,进入开放平台管理中心,创建第三方平台账号,详情可查看创建第三方平台账号。 2、配置开发资料 第三方平台账号创建完成之后,进入账号详情配置开发资料,主要配置授权事件接收URL用于接收平台推送的component_verify_ticket以及其他必要开发资料,详情可查看第三方平台开发资料详细说明。 3、接收平台推送的component_verify_ticket 在完成开发资料配置后,可进行全网发布检测以验证相关配置是否正确。确认配置无误后等待10分钟即授权事件接收UR即可接收到平台推送的component_verify_ticket,详情可查看component_verify_ticket说明。 4、用component_verify_ticket换取component_access_token 在正确获取component_verify_ticket后可参考接口文档生成component_access_token。component_access_token有效期为2小时,请注意及时刷新。 5、用component_access_token换取pre_auth_code 在正确获取component_access_token后可参考接口文档生成pre_auth_code。主要用于构建授权页面链接,请注意pre_auth_code也是有有效期,请及时刷新。 6、构建授权页面链接 在完成上述步骤后,可依据授权流程技术说明文档进行构建授权页面的URL,可构建用于PC的授权页面URL,也可以构建用于移动端的H5页面的URL,还可以通过引用授权组件方式生成授权页面。 7、将授权页面放置服务商官网 构建的PC版的授权页面,可放置于服务商官网,商家扫码确认即可完成授权; 若是H5版授权页面,可放置于服务商公众号等形态官网中,商家点击确认即可完成授权; 若是小程序插件版授权页面,可放置于服务商小程序形态的官网中,商家点击确认即可完成授。 █ 商家 1、进入授权页面 商家进入服务商授权页面的方式取决于服务商提供该页面的方式,若服务商提供的是pc版授权页面,则商家可通过扫码的方式进入; 若服务商提供的是H5版的授权页面,则商家可直接点击链接即可进入授权页面; 若服务商提供的是插件版的授权页面,则商家可在服务商小程序中完成授权。 2、选择要授权的账号 如果当前微信用户是多个小程序账号的管理员,则进入授权页面时需选择要进行授权的小程序账号。 如果当前微信用户仅为一个小程序管理员,则直接进入步骤3;如果当前用户不是任何小程序的管理员,则会出现相关错误提示。 补充,商家可通过“公众平台安全助手”--->“绑定查询”--->"微信号绑定账号"进行查询其名下的小程序账号信息。 3、阅读即将授权的权限集说明 商家在该页面可详细看到即将授予哪些权限给服务商,如果即将授予的权限范围过大,可点击“自定义权限”进入权限集编辑页面,将部分权限集清除后再点击“授权”。相关操作可点击查看详情。 4、确认授权 再确认无谓后点击授权即可完成将小程序部分权限授予指定服务商。后续商家可通过重新扫码进入授权页面的方式更新权限集,可以通过登录微信公众平台-设置-第三方设置,进行解除授权。后续平台放将提供更加便捷的方式进行更新以及解除授权,敬请期待。 [图片]
2021-09-09 - 利用第三方授权 更新并发布多套小程序
开发一个小程序管理平台,其他小程序管理员只需一次授权给第三方,第三方平台即可帮助他发布小程序,不同管理员的配置参数不同,其他功能都基本相同 开发步骤: 一、注册开放平台: 到微信开放平台注册账号 :https://open.weixin.qq.com/cgi-bin/readtemplate?t=regist/regist_tmpl&lang=zh_CN 二、申请第三方平台开发 申请第三方平台必须拥有一定的开发者资质,必须先通过开发者资质认证,才可以开始第三方平台开发,在开发平台账号管理中可进行资质认证 三、创建第三方平台 申请完成后,在开发平台的管理中心,点击第三方平台,在下方可看到创建第三方按钮 [图片] 点击创建第三方平台,进入下方页面,选择平台型服务商, [图片] 1.填写基本信息,与定制化服务商一致 2.选择权限,只能选择业务必须的权限集,否则无法通过审核,公众号或小程序也可能会拒绝授权给你。(权限集是公众号或小程序的权限集合,用于实现业务) 3.填写开发资料 4.开发资料 ①授权发起也域名(即用户打开我们自己的授权页域名) ②授权事件接收URL(我们接收所有授权小程序或公众号取消授权通知、授权成功通知、授权更新通知事件的url地址 , 包括接收微信平台推送的ticket) ③消息与事件接收URL (我们接收所有授权小程序或公众号的消息和事件推送,例如客服消息 微信就会推送到这个地址上) 这里要注意一点:该参数按规则填写(需包含/$APPID$,如www.abc.com/$APPID$/callback) 填写的地址需要包含/$APPID$ 我们后续可以用nginx 重写地址 把访问指向同一个地址就可以了 例如:填写的地址是 www.abc.com/msg/$APPID$/msgEventPath.php nginx重写地址: rewrite ^/msg/(.)/(.).php /msgEventPath.php last; ④其它按照提示填写就可以了,添加上白名单ip 然后提交审核就可以了,如果信息没有问题是马上就能审核成功的,然后再管理中心的第三方平台即可看到改第三方服务商,详情里面即有改第三方平台相关的配置信息 四、小程序管理员授权给第三方平台 只有小程序管理员授权给第三方,第三方才能为该小程序发布,更新部署代码。 授权开发步骤: 1.保存component_verify_ticket, 微信端会定时推送消息到配置好的授权事件接收URL(创建三方平台时填写的,可在该三方详情中查看) 上,我们需要保存这个component_verify_ticket和 不断更新,component_verify_ticket必须保持是微信端推送的最新一个 2.用component_verify_ticket去换取第三方平台的token(第三方平台指的就是我们自己在开发的平台)token是有有效期的,所以我们要保存它的过期时间,并将token做缓存,当token没过期时就不用再去换取,反之我们要利用最新的component_verify_ticket去重新获取token 3.换取预授权码pre_auth_code,pre_auth_code是用来换取微信端的授权二维码的 4.跳转到授权页面(两种方式),建议第二种,方便 用户授权的时候会先打开我们自己的一个页面 (例如 http://www.abc.com/authorization.php ),这个页面里需要做一个按钮或者用js去跳转到微信的授权页面 ①扫码授权 :跳转后得到授权码,注意这个页面只能用网页访问,小程序访问不了,因为不能将微信域名配置为业务域名用户扫码后 就可以授权给第三方平台了 https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx&auth_type=xxx。 ②点击移动端链接快速授权https://mp.weixin.qq.com/safe/bindcomponent?action=bindcomponent&auth_type=3&no_scan=1&component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx&auth_type=xxx&biz_appid=xxxx#wechat_redirect 请求参数(两种方式一样) component_appid 第三方平台方appid pre_auth_code 预授权码 redirect_uri 回调URI 必须和授权地址同一个域名 auth_type 要授权的帐号类型:1则商户点击链接后,手机端仅展示公众号、2表示仅展示小程序,3表示公众号和小程序都展示。如果为未指定,则默认小程序和公众号都展示。第三方平台开发者可以使用本字段来控制授权的帐号类型。 前四步总结(移动端快速授权流程): 用户自己获取授权连接: 需要后台配合,给出一个接口,请求该接口则直接返回最新的预授权码(pre_auth_code),拿到授权码之后,再通过拼接返回一个授权地址,跳转到改地址,即为授权页面下方图二 ,用户点击授权即可授权给第三方。用户点击授权后,授权页会自动跳转进入回调URI,并在URL参数中返回授权码和过期时间(redirect_url?auth_code=xxx&expires_in=600),我们可以通过 $GET[‘authcode’] 去获取授权用户的小程序或二维码 调用接口的accesstoken(有效期两小时) 并将其保存/更新,然后我们就可以获取授权用户小程序或公众号的信息 [图片] 5.使用授权码换取公众号或小程序的接口调用凭据和授权信息 接口调用请求说明 http请求方式: POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=xxxx(component_access_token在第二步可获取) POST请求参数示例: { “component_appid”:“appid_value” ,//第三方平台appid “authorization_code”: “auth_code_value”//授权code,会在授权成功时返回给第三方平台 } 请求成功后拿到 authorizer_access_token:授权方接口调用凭据(在授权的公众号或小程序具备API权限时,才有此返回值),也简称为令牌,后面调用小程序待开发的api中使用, authorizer_refresh_token:接口调用凭据刷新令牌(在授权的公众号具备API权限时,才有此返回值),刷新令牌主要用于第三方平台获取和刷新已授权用户的access_token,只会在授权时刻提供,请妥善保存。 一旦丢失,只能让用户重新授权,才能再次拿到新的刷新令牌 五、小程序模板开发 第三方平台帮助旗下已授权的小程序进行代码管理时,需先开发完成小程序模版,再将小程序模版部署到旗下小程序帐号中,具体流程如下: 第一步:绑定开发小程序 (1)第三方平台的开发人员需先到微信公众平台(mp.weixin.qq.com)申请一个普通的小程序并完善小程序的头像、昵称、简介、服务类目等信息。 (2)进入微信开放平台,在第三方平台详情中,将该小程序添加为开发小程序。 注意:绑定为开发小程序后,该小程序的在开发工具中上传,代码会直接上传到开放平台,不会上传到公众平台。 第二步:小程序模版的开发和上传 使用开发小程序的开发者微信号登录微信web开发者工具(IDE),开发者工具中按照正常的小程序开发流程进行代码开发和调试。开发完成后,在开发工具中点击上传。更新模板后需要更部署到旗下小程序之前必须上传到模板库。注意:上传时版本号要求不一样,一样的版本号会被默认为同一版本,判断为管理员没有更新 第三步:添加到小程序模版库,获得模版ID 从开发者工具中上传的代码,会先存在草稿箱中,每个开发小程序只保留最新一份上传记录。开发者可将草稿箱中的代码添加到小程序模版库中,小程序模版库中的模版不会被覆盖。最多可以有五十个代码模版,添加后可以获得模版ID(TemplateID) 拿到模板ID后,再加上之前获取到的authorizer_access_token(令牌),就能为授权过给该第三方平台的小程序部署代码了。 六、为旗下小程序进行代码管理 举个例子:为授权的小程序帐号上传小程序代码 1、为授权的小程序帐号上传小程序代码 请求方式: POST(请使用https协议) https://api.weixin.qq.com/wxa/commit?access_token=TOKEN POST数据示例 { “template_id”:0, “ext_json”:“JSON_STRING”, //ext_json需为string类型,请参考下面的格式 “user_version”:“V1.0”, “user_desc”:“test”, } 参数说明: access_token 请使用第三方平台获取到的该小程序授权的authorizer_access_token template_id 代码库中的代码模版ID ext_json 第三方自定义的配置 user_version 代码版本号,开发者可自定义(长度不要超过64个字符) user_desc 代码描述,开发者可自定义 通过此请求,第三方平台会自动将模板中的代码自动部署到授权给该第三方的小程序上 更多代码管理查看文档 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=1a70ae891ca6e0339cf56bd1b3c322b0ec86eec9&lang= 持续更新中… 相关文章:https://developers.weixin.qq.com/community/develop/doc/0000ee097e0f00dcd55b8e40856800?jumpto=reply&parent_commentid=00004e9efb84388cd95ba8023514&commentid=000c0623e98068f0e85be2b97564
2020-12-09 - 微信开放平台第三方代实现小程序业务简述
一、微信公众平台-第三方平台开发(什么是第三方平台) 微信公众平台-第三方平台(简称第三方平台)开放给所有通过开发者资质认证后的开发者使用。在得到公众号或小程序运营者(简称运营者)授权后,第三方平台开发者可以通过调用微信开放平台的接口能力,为公众号或小程序的运营者提供账号申请、小程序创建、技术开发、行业方案、活动营销、插件能力等全方位服务。同一个账号的运营者可以选择多家适合自己的第三方为其提供产品能力或委托运营。 从具体的业务场景上说,第三方平台包括以下场景: 1、提供行业解决方案,整体打包公众号或小程序的产品开发等; 2、行业:提供更加专业的运营能力,精细化运营用户公众号或小程序; 3、功能:对公众平台功能的优化,如专门优化图文消息视觉样式和排版的工具,或专门定制的 CRM 用户管理功能,或功能强大的小程序插件等。 二、第三方平台代实现小程序业务 第三方平台代实现小程序业务,即第三方平台帮助旗下已授权的小程序进行代码管理。和普通的小程序开发流程相比,第三方代实现小程序业务,需要先开发完成小程序模板,再将小程序模板部署到旗下小程序帐号中 三、从0到1代实现小程序业务 3.1 微信开放平台注册与认证 在微信开放平台https://open.weixin.qq.com/ 注册帐号,并在账号中心中,完成认证。 [图片] 3.2 创建第三方平台 1)在“微信开放平台-管理中心-第三方平台”创建第三方平台。 [图片] 2)选择对应的服务商类型进行第三方平台创建。 *定制化开发服务商:指具备完整开发独立小程序或插件,并可以提供后续运营的服务商。定制化服务商,可将自己已经开发出的定制化小程序关联到服务商平台中,生成凭证(票据)填充到小程序代码包中进行关联。平台获取开发关系。 *平台型服务商:平台型服务商可以通过一键授权,获得为商户开发部署小程序的权限,代商户完成小程序的开发部署及上线。 [图片] Step 1 : 基本信息填写 平台名称: 名称尽可能能够和自己的业务方向相似,且不要包含测试等字样。 业务标签:有如下几类,可以按照行业划分,也可以按照功能划分,标签选择自己业务范畴之内的即可。 [图片] 平台简介: 这里将显示在详情页中,一般用户从这里获得对平台的业务认知,建议要合理填写。 官方网站: 此处填写你的官网域名,需要注意的是,此处写的地址必须要和此第三方平台的业务描述相一致。否则在审核的时候会被驳回的。 平台图标: 需要自己制作核实的图标,必须保证是108*108像素,仅支持PNG格式,大小不超过300KB。 Step 2 :选择权限 微信开放平台为第三方平台型服务商提供丰富的权限勾选,服务商自由选择需要的权限,所勾选的权限,将在用户授权页面展示。最终服务商可获得的权限,以授权用户选择的权限为准。关于权限详情,可查询第三方平台权限说明 特别提醒,请勿随意全选所有权限,当选择一些没有用到的权限集时,会在全网发布的自动化测试时触发某些检测,导致不能通过。具体可以细看全网发布接入检测说明 [图片] Step 3 :开发资料填写 [图片] 授权发起页域名: 此处填写的是跳转授权页时所在的域名,比如此处填写 w.example.com ,那么授权页必须从这个域名中打开才能正常显示,否则会出错。这在一定程度上防止了授权信息的盗用和滥授权,一般这里填用户授权可以访问的业务域名。这里不校验https,但是需要保证域名是实名且可以公共访问的。 授权测试公众号列表: 由于在创建后并没有全网发布,需要先行做开发测试,这里就是用于测试的公众号、小程序的原始ID。我们需要找到一个任意主体的小程序用来测试,推荐选择自己的,好在接下来的环节中实践。需要注意的是,在全网发布之前,扫描授权页二维码授权的账号只能在这个列表中选择,列表外的没有办法成功授权! 授权事件接收URL: 此处需要填写一个API地址,开放平台向你发送的所有消息均通过请求此URL来完成。在这个地址请求后,需要程序判定消息的类型以及相应的处理方法。在此实践中,此URL主要用来接收开放平台的验证票据(component_verify_ticket) 信息校验Token和消息加解密Key: 这两个主要用来解密开放平台向接收URL发送的消息,或者加密信息向开放平台发送。token填写任意一个字符串即可,key必须保证长度为43位的字符串,只能是字母和数字。 小程序服务器域名: 在授权过来的小程序中配置的合法域名必须在此设置的列表中,本教程小程序使用云开发作为后端服务,所以此处设置的域名没有用到,在使用传统后端服务模式时,建议填写支持小程序后台服务的域名。 小程序业务域名: 在授权过来的小程序中配置的合法域名必须在此设置的列表中,本教程小程序使用云开发作为后端服务,所以此处设置的域名没有用到,在使用传统后端服务模式时,建议填写支持小程序后台服务的域名。 白名单IP地址: 这两个主要用来解密开放平台向接收URL发送的消息,或者加密信息向开放平台发送。token填写任意一个字符串即可,key必须保证长第三方平台所有的API接口必须在白名单列表中的IP才可以成功响应,一般填写自己服务器的公网IP。 更多信息可查询文档申请资料说明 完成上述【基本信息】、【选择权限】和【开发资料】信息填写后,提交审核即可,审核时间1小时以内。审核成功后,就可以使用相关接口进行对应开发。 3.3 平台型服务商代实现小程序基本流程 3.3.1开发准备 1)配置域名并设定授权事件url 2)创建授权事件接收URL的服务监听 微信开放平台向授权事件url发送相关的开发或通知信息,如验证票据等。开放平台向此URL发送的数据是加密过的,具体遵循消息加解密方案。 3)使用接收到的验证票据(component_verify_ticket)获取令牌 令牌是第三方平台接口的调用凭据,根据官方文档-获取令牌可知,我们需要请求API接口:https://api.weixin.qq.com/cgi-bin/component/api_component_token 在请求时需要提供三个参数:component_appid(第三方平台 appid)、component_appsecret(第三方平台 appsecret)、component_verify_ticket(第二步获取的ticket),其中前两个我们可以在开放平台直接获取。 [图片] 4)使用令牌获取预授权码并拼接用户授权链接 预授权码(pre_auth_code)是第三方平台方实现授权托管的必备信息,每个预授权码有效期为 10 分钟。一般我们获取它用来拼接授权链接并发给用户使用。 5)获取第三方调用令牌 3.3.2 小程序模版开发 平台型服务商需要将现成小程序模板通过接口直接上传部署到用户的小程序账号中。具体操作为我们在第三方平台的控制台中可以添加开发小程序。 [图片]选择自己为管理者的小程序账号。在添加流程中,也是需要验证小程序账号密码并通过管理员微信扫码验证的。(这里是输入谁的小程序账号密码?) [图片] 当添加成功之后,我们便可以在公众平台的设置页中看到如下信息的变化,开发小程序所属账号为我们的第三方平台的账号,此时小程序的代码上传便由第三方平台接管。 [图片] 使用微信开发者工具使用此小程序appid创建或导入项目,效果如下,跟普通开发相比,多了一个平台名 [图片] 当我们上传代码时,会提示上传到第三方平台的草稿箱。 [图片] 当上传之后,我们便可以在第三方平台详情页中看到我们上传的代码。 [图片]我们可以点击右边的按钮,直接将代码直接添加到模板库中,也可以删除。 [图片]添加后在模板库中就会出现模板代码,并附带有模板编号,我们便可以通过对应的API接口直接将此模板小程序上传至指定的授权小程序中了,无需再用开发者工具上传提交。 具体关于模板小程序的细则,可以查看官方文档。其中关于extAppid的开发调试属于小程序开发的范畴知识附加,在这里不详细阐述。 3.3.3 小程序模板部署与发布(提审) 小程序模版开发完成后,可以点击全网发布按钮。 [图片] 在发布前会进行自动化测试检查,在本教程推荐的两个权限集勾选,无其他权限集的状态下,只需要验证票据的推送状态。具体可以细看全网发布接入检测说明。 检测通过后,会进行资质审核。主要检查你的官网描述是否和设置的描述相仿。审核通过后,第三方平台业务即可上线,可向所有符合要求的公众号、小程序进行登录授权。
2021-09-09