个人案例
- 纸牌测运势
一块还原小时候大人通过摆扑克牌测试运气的玩法的小程序
纸牌测运势扫码体验
- 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 [图片] 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() } }, // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' } } }, data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 }, methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' }) this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' }, }, { type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' } }, { type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' } }, { type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' } }, { type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' } }, { type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' } } ] } }) }, onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') }, onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) } }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') } } }) [代码] 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false }, // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw }) }, getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 }) }, fail: err => { console.log(err) } }) } }) [代码] 最后绘制分享图的自定义组件就完成啦~效果图如下: [图片] tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
2022-01-20 - ColorUI-高颜值,高效率的小程序组件库
[图片] 简介 hi!开发者!ColorUI迎来了2.0的升级,相比之前的版本,2.0版本重构了基础代码,增加了更多的配色,这是一个全新的小程序UI解决方案。 ColorUI是一个Css类的UI组件库!不是一个Js框架。相比于同类小程序组件库,ColorUI更注重于视觉交互! 项目为个人开源项目,如果项目有帮到你,希望能支持下开发者。 [图片] 截图 [图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片] 使用 浏览GitHub:https://github.com/weilanwl/ColorUI/
2018-12-25 - 平台型服务商代实现企业官网小程序(云开发版)
一、帐号准备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 - 云开发“分账功能”踩坑记
云开发的分账功能还在公测中,需要去申请开通。如果等了几天还没开通,可以在社区提问,会有官方人员来跟进。 我等了 5 天才开通。 开始之前,首先要搞清楚一个概念:云开发就是一个服务商,所以开发者不需要注册成为支付服务商,就可以使用服务商分账。 以下是我开发过程中遇到的一些坑。可能你会遇到其他坑,记得把调用接口的结果打印出来,看看报的是什么错。 1、文档不全,需要传 profit_sharing 或者 profitSharing 云开发统一下单接口的文档缺少 profit_sharing 参数,需要结合原支付接口文档来看,在分账对接步骤里也有说明。 如果没传这个参数,会报错:“非分账订单不支持分账”。 经过测试发现,这个参数是必需的,可以是 profit_sharing,也可以是 profitSharing,两者都兼容。由于其他参数都是驼峰写法,所以我用了 profitSharing . 2、不需要每次分账都“添加分账接收方” 添加分账接收方,只需要调一次接口即可,添加成功后,不需要每次分账都添加接收方。 有个小提醒,receiver 这个参数不是 JSON,而是JSON 序列化后的字符串,记得用 JSON.stringify 处理。序列化前是对象,不是数组,如果有多个接收方就调用多次接口。 (注意区分一下,在分账接口中,分账接收方的参数是 receivers,不是 receiver。不管单次分账还是多次分账,receivers 参数都是序列化后的字符串,序列化前是对象数组,不是对象) 这里还有个 bug,根据通知指引,我并没有找到这个入口:“商户平台 - 交易中心 - 管理分账接收方”。后面发了个提问帖,找到了这个暗门。登录商户平台,然后访问这个地址:https://pay.weixin.qq.com/index.php/xphp/ccmn_sharing/split_relation_manage 这可能也不是 bug,我猜估计是担心商户会有意或者无意的删除分账接收方。 3、sub_appid 是必填 文档有误,这个参数是必填。 4、PERSONAL_OPENID 和 PERSONAL_SUB_OPENID receivers 的 type 参数,PERSONAL_OPENID 和 PERSONAL_SUB_OPENID,刚开始会有点迷糊。我填的是 PERSONAL_SUB_OPENID,account 填小程序数据库里的用户 openid . 5、支付成功后,需要延时 1 分钟处理 支付成功后如果立即处理分账会报错:“订单处理中,暂时无法分账,请稍后再试”。 分账产品介绍的文档是这么写:“在交易完成后,准实时(建议1分钟后)或30天内调分账接口。” 我单独用了一个云函数来处理分账,setTimeout 设置 59 秒后调用分账接口。 在开发者工具里,进入该云函数的配置设置,把超时时间设置为 60 秒,否则会返回超时。 支付成功后调用该云函数。 2021-9-23 更新: 不能用 setTimeout 处理分账,这样云函数会一直占用内存,超成资源浪费(而且是巨大的浪费)。应改成定时触发,例如每小时触发一次,把已支付的并且支付时间已经过了一分钟的订单找出来,调用分账接口。
2021-09-23 - 该死的颜色选择器!!
目标 看到这个很酷的网站 所以也想看看怎么弄? 先来挑战入门版… 颜色坐标系 首先要解决一个 误解 [图片] 我们所看到的颜色面板, 其实就是一个固定的样式, 而我们获取的颜色其实是从 坐标模型中 计算出来的。 坐标模型有很多, 在此使用的是 HSV颜色模型 Q: 为什么使用 HSV ? A: HSV色系对用户来说是一种直观的颜色模型, 主要由 色调(Hue, 简H)、饱和度(Saturation, 简S)、色明度(Value, 简V) 将 HSV六角锥体模型 转为 直观的数学坐标系 [图片] 需要注意, document中元素节点 坐标原点是右上角, 而数学坐标原点为右下角 数学坐标系: y、x、h HSV坐标系: v、s、h 确认坐标系的范围 色调H: 取值范围为0°~360° 饱和度S:取值范围为0.0~1.0 亮度V:取值范围为0.0(黑色)~1.0(白色) 通过document节点上元素的宽高, 计算步长, 达到取值范围为 0~100(转为百分制) 颜色转换 通过 触摸 坐标系 获取 y(v)、x(s)、h 的值, 然后利用算法公式转换成 rgb 颜色 hsv转rgb公式 rgb转hsv公式 还有 rgb转hex、 rgb转hsl; 都在这里 实例用法 详细注释在 代码中… 代码片段: https://developers.weixin.qq.com/s/rRHvfdmx79mR github: https://github.com/angxuejian/moto.wxui/tree/main/UI/colorPicker 1. 将 colorPicker 组件 引入到项目中。 [代码]// index.json { "usingComponents": { "color-picker": "../../components/colorPicker/colorPicker" } } // index.html <view> <color-picker></color-picker> </view> [代码] 2. Attributes 属性 类型 默认值 必填 说明 width number 35 否 宽度; 单位px height number 35 否 高度; 单位px predefined string #409EFF 否 预览颜色; 支持HEX和RGB; 只支持英文字符 3. Events 事件名称 回调参数 说明 change 当前颜色 当修改绑定值时触发 4. 示例 [图片] 参考文献 MakerGYT 看了MakerGYT写的mini-color-picker源码, 非常强🤙🤙🤙 颜色公式转换 在线测试工具,校验计算是否正确 hsv百度百科 Element的color-picker
2020-11-29