- 云开发云函数中使用Redis的最佳实践,包括五种常用数据结构和分布式全局锁
Redis因其拥有丰富的数据结构、基于单线程模型可以实现简易的分布式锁、单分片5w+ ops的超强性能等等特点,成为了大家处理高并发问题的最常用的缓存中间件。 那么云开发能不能使用Redis呢?答案是肯定的。 下面我介绍下云开发中Redis使用的最佳实践: 第一步、购买Redis,安装Redis扩展 参见官方文档:https://developers.weixin.qq.com/community/develop/article/doc/000a4446518488b6002c9fa3651813 吐槽一下,写这篇文章的原因之一就是上面的官方文档中的示例代码是在不堪入目,希望这篇文章能让小伙伴少踩些坑。 第二步、创建并部署测试云函数,配置云函数的网络环境 [图片] 第三步、编写代码 cache.js const Redis = require('ioredis') const redis = new Redis({ port: 6379, host: '1.1.1.1', family: 4, password: 'password', db: 0 }) exports.redis = redis /** * 加redis全局锁 * @param {锁的key} lockKey * @param {锁的值} lockValue * @param {持续时间,单位s} duration */ exports.lock = async function(lockKey, lockValue, duration) { const lockSuccess = await redis.set(lockKey, lockValue, 'EX', duration, 'NX') if (lockSuccess) { return true } else { return false } } /** * 解redis全局锁 * @param {锁的key} lockKey * @param {锁的值} lockValue */ exports.unlock = async function (lockKey, lockValue) { const existValue = await redis.get(lockKey) if (existValue == lockValue) { await redis.del(lockKey) } } 上面是操作redis的工具方法,可以打包放到云函数的层管理中,方便其他云函数引用。层管理使用方式参见官方文档:https://cloud.tencent.com/document/product/876/50940 index.js const cloud = require("wx-server-sdk") const cache = require('/opt/utils/cache.js') // 使用到了云函数的层管理 cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate exports.main = async (event, context) => { context.callbackWaitsForEmptyEventLoop = false const wxContext = cloud.getWXContext() let appId = wxContext.APPID if (wxContext.FROM_APPID) { appId = wxContext.FROM_APPID } let unionId = wxContext.UNIONID if (wxContext.FROM_UNIONID) { unionId = wxContext.FROM_UNIONID } let openId = wxContext.OPENID if (wxContext.FROM_OPENID) { openId = wxContext.FROM_OPENID } // redis五种常用数据结构 // 字符串 await cache.redis.set('hello', 'world') // 无过期时间 await cache.redis.set('hello', 'world', 'EX', 60) // 过期时间60s let stringValue = await cache.redis.get('hello') console.log('string: ', stringValue) // hash await cache.redis.hset('hash', 'hello', 'world') let hashValue = await cache.redis.hget('hash', 'hello') console.log('hash: ', hashValue) // list await cache.redis.lpush('list', 'hello', 'world') let listList = await cache.redis.lrange('list', 0, -1) // 读取队列所有元素 await cache.redis.ltrim('list', 1, 0) // 清空队列 console.log('listList: ', listList) // set await cache.redis.sadd('set', 'hello', 'world') let setExist = await cache.redis.sismember('set', 'hello') // 检查元素是否在集合中 console.log('set: ', setExist) // zset await cache.redis.zadd('zset', 1, 'hello', 2, 'world') let zsetList = await cache.redis.zrange('zset', 0, -1, 'WITHSCORES') console.log('zsetList: ', zsetList) // redis实现分布式全局锁 // 加全局锁,锁的过期时间应根据实际业务调整 const createOrderLock = `createOrderLock:${unionId}` const ts = Date.now() if (!(await cache.lock(createOrderLock, ts, 3))) { return { code: 4, msg: '操作太频繁了' } } // 这边写全局互斥的业务逻辑代码 // 比如创建订单,一个用户同时只能并发创建一个订单 // 解全局锁 await cache.unlock(createOrderLock, ts) return { code: 0, data: {} } } 上面是测试云函数的入口文件,演示了redis五种常用数据结构和redis全局锁的使用方法。 最后还有个小tips,所有引用到cache.js的云函数需要安装ioredis的依赖,进入云函数目录,使用如下命令: npm install ioredis
2021-06-17 - 如何提升你的云函数性能
在使用云开发一段时间后,你一定会遇见一个问题:虽然云函数非常的方便,但我的云函数似乎性能不够好,为什么我的云函数每次加载都需 2 ~ 3 秒种,时间太长了!。 这篇文章,就来告诉你,应该如何提升你的云函数性能。 如何了解云函数运行情况? 在了解如何优化云函数的运行情况之前, 我们需要先了解,如何查看当前的云函数运行情况,这样才能有个对比。 [图片] 打开小程序开发者工具,并打开你的项目 进入到你要调试的页面,打开调试器 调用云函数,并在调试器中切换到 Network 页面,找到你的请求。 点击你的请求,然后切换到 Timing 页面,查看具体的情况。 在这个页面中,你可以理解其中的 Waiting(TTFB) 是你发起请求到你接收到返回结果的第一个字节的时间,简单的来说,就是服务器计算结果需要花费的时间。而下方的 Content Download 则是下载内容所需的时间,你可以理解是表现出网络速度快慢的数据。 总结来说,就是如果 Waiting TTFB 的值比较大,你就去优化云函数性能。如果 Content Download 的数值毕竟大,你就需要优化网络情况 优化 Waiting TTFB 云函数的运行机制 Waiting TTFB 的优化是云函数性能端的优化,那么在优化之前,我们就需要先来了解一下云函数的运行机制,以便帮助我们了解应该如何去进行性能优化。 [图片] 在蕴含运行时,具体的顺序是这样的 用户发起请求,请求发送到云开发的后台 云开发后台的调度器将请求分发给下方的执行的 worker 、容器 容器创建环境、下载代码 执行代码 在这个过程中,发起请求到云开发、调度器调度速度、调度器传递信息到容器、函数调用等,都是可以优化的,但是我们在具体的使用过程中。这些大都需要由云开发的工作人员来完成,对于我们自己来说,只能去尽可能的优化容器内部到代码层面的东西。 接下来,我们可以看看更细致的调用逻辑。 [图片] 在云开发中,我们可以将调用分为三种类型: 冷启动:图中的红色阶段,需要重新创建容器、下载代码,耗时最长 温启动:图中的黄色阶段,需要下载代码,耗时较长 热启动:图中的蓝色阶段,不需要下载代码,耗时最短 我们可以看到,最快的,是热启动,函数不需要创建容器,不需要启动函数就可以完成执行,显然比要创建容器或要下载代码的温启动和冷启动速度更快。这样,我们就得到了优化云函数性能的第一个方法 1. 让你的云函数每次调用都走热启动 当我们可以让我们的云函数的每一次调用都走热启动,少了容器的创建和函数的部署,请求的速度理所当然的要比冷启动和温启动更快。 我们可以测试一下,我设置每秒调用一次云函数,看看 TTFB 的变化。 [代码]setInterval(()=>{wx.cloud.callFunction({name:'profile'})},1000) [代码] 函数内代码是默认创建的云函数代码。 则对应的执行效果如下 [图片] 可以看到,函数的执行时间从第一次的 1.2s 降低到了 200ms左右,性能提升了 80%,我们仅仅是简单的提升了函数的调用频次,就可以实现提升函数的调用性能,这就是热启动带给我们的价值。 实施方案 如果你需要足够高的性能,不妨借助云开发的定时器,定期唤起你的容器,从而为你的容器保活,确保你的函数时刻被热启动。 2. 缩小你的函数大小 在前面我们曾介绍过,云函数在启动过程中,会创建容器和下载代码。创建容器的过程对于开发者来说不可控,不过我们可以使用一些方法,缩小我们的代码,提升代码的下载速度,比如说,缩小我们的函数代码。 这里我们可以做个测试,这里我创建了两个函数,两个函数的代码完全一致,不同的是,在实验组的函数中,我加入了一个 temp 变量的声明,这个变量的值是一个非常长的字符串,从而使得两个函数的大小分别是 68K 和 4K。 接下来,我们看看二者的执行时间。 [图片] 我们会发现,几乎没有差距的代码,因为加入了变量声明的因素,在性能上会略慢几毫秒,后续随着容器的不断复用,函数的之间的差距也越来越小,几乎可以忽视。 实施方案 对于你的代码,要尽可能的精炼,减少无用的代码,减少代码下载所需时间。 3. 削减不需要的 Package 除了下载代码以外,还需要下载 Node 环境运行所需的依赖包,虽然云开发可能针对 Node Modules 已经做了缓存,但依然存在下载的时间差区别,这里我也做了一个实验。 空包:什么都没装,把 wx-server-sdk 都卸载掉了。 复杂包:装了 Mongoose、sequelize、sails 等依赖的包。 函数逻辑上也相差无几,都是返回 Event ,则结果如下 [图片] 我们发现,前三次可能是因为涉及到依赖包的下载问题,所以前三次的时长大小对比特别的明显,而从第四次开始,二者的区别就不大了,可能是因为依赖已经完成了缓存,所以可以直接使用缓存来完成函数的执行。 实施方案 你可以选择看看你的 package.json ,看看其中是否有你不需要的依赖,将其删除,仅保留有需要的依赖,可以有效提升你的代码执行速度。 优化 Content Download 如果你想要优化 Content Download ,核心需要优化的是两个点: 手机到服务端的节点的距离和速度 内容的大小 前者一般来说,你可以通过切换不同的网络环境来实现优化,比如从 3G 切换到 4G ,从 4G 升级到 5G,这些都可以提升你的手机到服务端节点之间的速度。 此外,还可以借助内容分发网络 CDN 能力来完成缩小你到服务端节点之间的距离,不过对于云函数来说,因为你不可控,无法控制,所以这一点不再谈。 这里补充一句,云开发的文件存储都是有 CDN 的,因此,你通过云存储下载的文件才会比别人更快。 后者则一般通过调整代码来完成,比如只返回必须的资源,对于不需要的内容,不再返回,或压缩返回。 总结 最后,我们回顾一下这篇文章中介绍的优化云函数的方法: 函数下载性能优化 保持函数容器的热启动,提升函数启动性能 缩小函数大小,提升代码下载速度 削减不必要的包,减少依赖大小 网络优化 使用更好的网络,比如 Wi-Fi 云函数中仅返回所需要的内容,减少下载时间。 以上这些方法,你都在你的函数中试过么?有没有其他的优化方法?欢迎你与我分享。
2019-12-08 - 用【库存】看懂云开发数据库事务
在正常使用数据库(CRUD)的情况下,这些操作都会顺利进行所有数据都会被成功更新,由于某些特定的业务场景,需要进行一系列的操作,在这过程中必须保证每一步的操作都正常执行,如果任何一个环节出了差错,比如更新库存信息发生异常,这终将会导致数据库的信息混乱而不可预测,数据库事务正是用来保证这种一系列操作的稳定性技术。 什么是事务? 数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。 事务的特性 ACID,指数据库事务正确执行的四个主要特性的缩写,一个事务,必需要具有这四种基本特性,否则在事务过程当中无法保证数据的正确性。 1:原子性(Atomicity) 指的是一个事务内所有操作共同组成一个原子包,要么全部成功,要么全部失败。 假如在数据库中对一个属性进行了更新,但是执行到一半的时候出现了异常,这样就可能使得操作后的数据与我们预期的数据不同,所以原子性要求你这个方法要么全部执行成功,要么全部失败 2:一致性(Consistency) 指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。 在原子性中规定方法中的操作都执行或者都不执行,但并没有说要所有操作一起执行,所以操作的执行也是有先后顺序的,那我们要是在执行一半时查询数据库,那我们会得到中间的更新的属性?一致性规定提交前后只存在两个状态,提交前的状态和提交后的状态 3:隔离性(Isolation) 指的是数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。 多个事务可能操作同一数据库资源,不同的事务为了保证隔离性,如果没有隔离会造成几种问题 事务A读到事务B修改却未提交的数据,事务B回滚数据修改操作,导致了事务A获得数据是脏数据 事务A先读取数据,事务B对数据进行修改,事务B再一次读取该行数据时就会造成前后两次读取结果不一致 事务A读取数据,事务B对其进行操作时,当事务A重新读取该段数据时会造成前后两次查询的数据不一致的现象 目前云开发数据库使用的是快照隔离,具体将在下面进行介绍 4:持久性(Durability) 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失 如果没有持久性的特性,一旦数据库出现异常,数据将会丢失 拥有持久性事务一旦提交后,数据库中的数据必须被永久的保存下来,即使服务器系统崩溃或服务器宕机等故障,只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。 ####云开发数据库事务 介绍 云开发数据库本身有提供(如 inc、mul、addToSet)等原子性操作符号和嵌套记录的数据结构设计,如跨多个记录或跨多集合的原子操作时,可以使用云数据库事务能力。 隔离性 云开发数据库事务过程中采用的快照隔离级别(snapshot),在事务期间,读操作返回的是对象的快照,而非实际数据,事务期间写操作执行时: 改变快照,保证接下来的读的一致性; 给对象加上事务锁 事务锁 数据对象存在事务锁对数据写入的影响: 其它事务的写入会直接失败; 普通的更新操作会被阻塞,直到事务锁释放或者超时事务提交后,操作完毕的快照会被原子性地写入数据库中 单记录操作 云开发数据库事务中不支持批量操作,只支持单记录操作比如(collection.doc, collection.add),单记录操作可避免大量锁冲突、保证运行效率,并且大多数情况下单记录操作足够满足需求,因为在事务中是可以对多个单个记录进行操作的,也就是可以在一个事务中同时对集合 A 的记录 x 和 y 两个记录操作、又对集合 B 的记录 z 操作,接下来会通过小示例来进行演示。 事务 API 云开发数据库事务提供两种操作风格的接口,一个是简易的、带有冲突自动重试的runTransaction接口,一个是流程自定义控制的startTransaction接口。 使用小示例 假设有以下场景: 某仓库有1000箱医用口罩,A医院需要800箱、B医院需要300箱并提交申请,仓库的管理模式是先收到提交申请在进行库存商品确认完毕后,进行领用。 在无事务的情况下伪代码自上而下执行 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command exports.main = async (event, context) => { // 医院 await db.collection('resource').doc('A'||'B') .update({ data: { resource: _.inc(-800||-300) }, }) // 仓库 await db.collection('store').doc('store') .update({ data: { resource: _.inc(+800||+300), }, }) } // 判断是否满足要求 if('仓库库存' >'领用数量' ){ await db.collection('store').doc('store') .update({ data: { count:_inc(-800||-300), }, }) }eles{ '回退的业务逻辑' } } [代码] 根据以上的代码执行结果来看: A/B医院提交了领用口罩的申请; 仓库接收了B医院提交的申请; 判断是否符合数量要求 执行到3时候发现仓库库存,并不能满足医院的领取要求时,需要将提交申请退还给医院,并处理一些退回的逻辑。 该情况下需要处理操作量大、复杂度高、在高并发的执行情况下会导致一些具体的操作没有完成比如: 医院提交了申请,仓库并没有收到; 医院提交了申请,仓库收到申请,并没有执行发放,也没有退还给医院; 事务的情况下伪代码自上而下执行 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database({ throwOnNotFound: false, }) const _ = db.command exports.main = async (event) => { try { const result = await db.runTransaction(async transaction => { const resource = await transaction.collection('resource').doc('A'||'B').get() const store = await transaction.collection('store').doc('store').get() const updateResource = await transaction.collection('resource').doc('A'||'B').update({ data: { resource: _.inc(-800||-300) } }) const updateStoreResource = await transaction.collection('store').doc('store').update({ data: { resource: _.inc(+800||+300), } }) if(store.data.count > 800||300){ const updateStoreCount = await transaction.collection('store').doc('store').update({ data: { count:_inc(-800||-300), } }) // 会作为 runTransaction resolve 的结果返回 return { resourceAccount: resource.data.count + 800||300, } }else{ // 会作为 runTransaction reject 的结果出去 await transaction.rollback('领取失败') } }) return { success: true, resourceAccount: result.resourceAccount, } } catch (e) { console.error(`transaction error`, e) return { success: false, error: e } } } [代码] 根据以上的代码执行结果来看: 1.首先读取了A/B医院与仓库的记录快照; 2.医院提交申请,减少对应的数量; 3.仓库接收到医院的提交申请; 4.判断仓库中的数量是否满足本次领取的数量; 执行到4时候发现仓库库存,并不能满足医院的领取要求时,事务会将所有更改的记录还原到读取记录快照时的数据,也就是说这些执行步骤[代码]要不就都成功,要不就都失败,数据回滚,不需要过多的回退逻辑[代码] 未使用事务 VS 使用事务 未使用事务 由于操作量大,复杂度高,在加上出现高并发的情况就会有数据不一致的情况出现; 回退逻辑复杂; 使用事务 事务由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,保证了数据一致性; 在执行事务之后保留了数据对象的快照,执行中出现任何问题可直接回滚; 总结 在使用云开发数据库中,如果仅仅是涉及单记录的修改,完全可以使用如 inc、mul、addToSet)等原子性操作符号,涉及到跨集合以及多个记录同时修改并需要保证一致性的情况,那事务功能将是最好的选择。
2020-09-14 - 云开发能否处理秒杀或者电商中超卖的问题?
- 需求的场景描述(希望解决的问题) 比如在秒杀的场景,怎么处理超卖的问题? 是否能给某次读写加锁,或者其他解决方案? - 希望提供的能力 云函数读写加锁或者其他能够保证读写过程中没有其他写入操作。
2019-01-28 - 云调用能力—图像处理和OCR
云调用有些接口属于 AI 服务的范畴,比如借助于人工智能来进行智能裁剪、扫描条码/二维码、图片的高清化等图像处理和识别银行卡、营业执照、驾驶证、身份证、印刷体、驾驶证等 OCR,有了这些接口我们也能在小程序里使用人工智能了。接下来我们以小程序的条码/二维码识别和识别印刷体为例来介绍一下云调用。 13.3.1 图像处理使用开发者工具新建一个云函数,如 scancode,然后在 config.json 里添加 img.scanQRCode 云调用的权限,使用 npm install 安装依赖之后,上传并部署所有文件(此时也会更新权限)。 { "permissions": { "openapi": [ "img.scanQRCode" ] } } 然后再在 index.js 里输入以下代码,注意[代码]cloud.openapi.img.scanQRCode[代码]方法和[代码]img.scanQRCode[代码]权限的对应写法,不然会报 604100 的错误。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { try { const result = await cloud.openapi.img.scanQRCode({ imgUrl: "https://tcb-1251009918.cos.ap-guangzhou.myqcloud.com/demo/qrcodetest.png", //注意二维码必须是条码/二维码,不能是小程序码 }); return result; } catch (err) { console.log(err); return err; } }; 调用该云函数之后,返回的 result 对象里包含 result 对象,在 codeResults 的 data 里可以得到二维码里包含的内容。 codeResults: [{ data: "使用云开发来开发微信小程序可以免费。。。", pos: {leftTop: {…}, rightTop: {…}, rightBottom: {…}, leftBottom: {…}},typeName: "QR_CODE"}] errCode: 0 errMsg: "openapi.img.scanQRCode:ok" imgSize: {w: 260, h: 260} 13.3.2 OCR 人工智能识别使用开发者工具新建一个云函数,如 ocrprint,然后在 config.json 里添加 ocr.printedText 云调用的权限,使用 npm install 安装依赖之后,上传并部署所有文件(此时也会更新权限)。 { "permissions": { "openapi": [ "ocr.printedText" ] } } 调用该云函数之后,返回的 result 对象里包含 result 对象,在 codeResults 的 data 里可以得到二维码里包含的内容。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { try { const result = await cloud.openapi.ocr.printedText({ imgUrl: "https://tcb-1251009918.cos.ap-guangzhou.myqcloud.com/demo/ocrprint.png", }); console.log(result); return result; } catch (err) { console.log(err); return err; } }; 调用该云函数之后,返回的 result 对象里包含 result 对象,在的 items 里可以返回图片包含的文字内容。 items: Array(4) 0: {text: "JavaScript入门", pos: {…}} 1: {text: "JavaScript是目前世界上最流行的编程语言之一,它也是小程序开发最重要的基础语言。要做出一个功能复杂的小程序,除了需要掌握JavaScript的基本语", pos: {…}} 2: {text: "法,还要了解如何使用JavaScript来操作小程序(通过API接口)", pos: {…}} 3: {text: "过API接口)。", pos: {…}} 13.3.3 图像处理拓展能力图片是小程序非常重要的元素,尤其是旅游照片、社交图片、电商产品图片、相册类小程序、媒体图文等,图片的加载速度、清晰度、图片的交互、图片效果的处理以及图片加载的 CDN 消耗都是一个不得不需要去关注的问题。而云开发图像处理拓展能力结合云存储则可以非常有效的解决很多问题。 强烈建议所有有图片处理需求的用户都应该安装图像处理拓展能力,这个能力大大弥补和增强了云存储在图片处理能力,尤其是图片按照需求的规格进行缩放可以大大减少 CDN 的消耗以及图片的加载速度以及我们可以按照不同的业务场景使用快速缩略模板,而这一切的操作和云存储的结合都是非常实用且易用的。 1、图像处理能力介绍云开发图像处理能力结合的是腾讯云数据万象的图片解决方案,图像处理提供多种图像处理功能,包含智能裁剪、无损压缩、水印、格式转换等,图像处理拓展能力所包含的功能非常丰富,使用如下图片处理的费用是按量计费的,计费周期为月,10TB 以内免费,超出 10TB,按 0.025 元/GB 来计费,省事而便宜: 缩放:等比缩放、设定目标宽高缩放等多种方式;裁剪:普通裁剪、缩放裁剪、内切圆、人脸智能裁剪;旋转:普通旋转、自适应旋转;格式转换:jpg、bmp、gif、png、webp、yjpeg 格式转换,gif 格式优化,渐进显示功能;质量变换:针对 JPG 和 WEBP 图片进行质量变换;高斯模糊:对图片进行模糊处理;锐化:对图片进行锐化处理;图片水印:提供图片水印处理功能;文字水印:提供实时文字水印处理功能;获取图片基本信息:查询图片基本信息,包括格式、长、宽等;获取图片 EXIF:查询图片 EXIF 信息,如照片的拍摄参数、缩略图等;获取图片主色调:获取图片主色调信息;去除元信息:去除图片元信息,减小图像体积;快速缩略模板:快速实现图片格式转换、缩略、剪裁等功能,生成缩略图;管道操作符:对图片按顺序进行多种处理当我们在腾讯云云开发网页控制台(注意要使用微信公众号的方式登录)添加完图像处理的拓展能力之后,我们可以在腾讯云的数据万象存储桶里看到云开发的云存储,而关于图像处理能力的深入使用,也可以参考腾讯云数据万象的技术文档。在小程序云开发里使用图像处理能力的方法有三种: 图像地址的拼接,只需要在图片的下载地址 url 里拼接一些简单的参数(API 管道操作符),就能够使用到图像处理的能力,非常方便易用,这个不会把图片处理的结果存储到云存储,不会占用云存储的空间;在获取图片基本信息、获取图片 EXIF、获取图片主色调等方面非常方便;在前端(小程序端)做持久化图像处理,支持有结果图输出的处理操作,也就是我们可以把缩放、裁剪、格式转换、质量变换等处理之后的图片存储到云存储方便以后使用;在云函数端做持久化图像处理,支持有结果图输出的处理操作 01图像地址的拼接在了解图像处理能力之前,我们需要先了解一下云存储文件的 fileID、下载地址以及下载地址携带的权限参数 sign(图像处理能力的参数拼接就是基于下载地址的),如下图所示: [图片] 在安装了图像处理拓展能力的情况下,我们可以直接拿云存储的下载地址进行拼接,拼接之后的链接我们既可以在小程序里使用,也可以用于图床,比如原始图片下载地址为: https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049 而相关的图像处理能力的拼接案例如下,具体的操作可以看技术文档,实际的效果,可以复制粘贴链接到浏览器或小程序里体验(换成自己的地址),注意拼接方式就是在下载地址后面加了一个[代码]&imageMogr2/thumbnail/!20p[代码](注意这里由于已经有了一个 sign 参数,所以拼接时用的是[代码]$[代码],不能写成[代码]?[代码],否则不会生效),直接就可以啦,非常易用: //将图片等比例缩小到原来的20% https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049&imageMogr2/thumbnail/!20p 后面为了方便,我们将[代码]https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049[代码]简写为 download_url: //缩放宽度,高度不变,下面案例为宽度为原图50%,高度不变 download_url&imageMogr2/thumbnail/!50px //缩放高度,宽度不变,下面案例为高度为原图50%,宽度不变 download_url&imageMogr2/thumbnail/!x50p //指定目标图片的宽度(单位为px),高度等比压缩,注意下面的是x,不是px,p与x在拼接里代表着不同的意思 download_url&imageMogr2/thumbnail/640x //指定目标图片的高度(单位为px),宽度等比压缩: download_url&imageMogr2/thumbnail/x355 //限定缩略图的宽度和高度的最大值分别为 Width 和 Height,进行等比缩放 download_url&imageMogr2/thumbnail/640x355 //限定缩略图的宽度和高度的最小值分别为 Width 和 Height,进行等比缩放 download_url&imageMogr2/thumbnail/640x355r //忽略原图宽高比例,指定图片宽度为 Width,高度为 Height ,强行缩放图片,可能导致目标图片变形 download_url&imageMogr2/thumbnail/640x355! //等比缩放图片,缩放后的图像,总像素数量不超过 Area download_url&imageMogr2/thumbnail/150000@ //取半径为300,进行内切圆裁剪 download_url&imageMogr2/iradius/300 //取半径为100px,进行圆角裁剪 download_url&imageMogr2/rradius/100 //顺时针旋转90度 download_url&imageMogr2/rotate/90 //将jpg格式的原图片转换为 png 格式 download_url&imageMogr2/format/png //模糊半径取8,sigma 值取5,进行高斯模糊处理 download_url&imageMogr2/blur/8x5 //获取图片的基础信息,返回的是json格式,我们可以使用https请求来查看图片的format格式,width宽度、height高度,size大小,photo_rgb主色调 download_url&imageInfo 2、小程序端持久化图像处理当我们希望把缩放、裁剪、旋转、格式变换等图像处理的结果(也就是处理之后的图片)存储到云存储,这个就叫做持久化图像处理,在安装了图像处理能力之后,我们也可以在小程序端做图像处理。 当用户把原始图片上传到小程序端时,我们需要对该图片进行一定的处理,比如图片过大就对图片进行裁剪缩小;比如图片需要进行一定的高斯模糊、旋转等处理,这些虽然在图像处理之前,也是可以使用 js 来做的,但是小程序端图像处理的效果并没有那么好或者过于复杂,使用图像处理的拓展能力就非常实用了。在小程序端构建图像拓展依赖 首先在开发者工具小程序根目录(一般为 miniprogram),右键“在终端中打开”,然后在终端里输入以下代码,也就是在小程序端安装图像拓展依赖,安装完时,我们就可以在 miniprogram 文件夹下看到 node_modules: npm install --save @cloudbase/extension-ci-wxmp@latest 然后点击开发者工具工具栏里的工具-构建 npm,构建成功之后,就可以在 miniprogram 文件夹下看到 minprogram_npm 里有@cloubase 文件夹,里面有 extension-ci-wxmp,说明图像拓展依赖就构建完成。 在小程序端进行图像处理 使用开发者工具新建一个 imgprocess 的页面,然后在 imgprocess.wmxl 里输入如下代码,我们新建一个 button 按钮: 处理图片button> 然后再在 imgprocess.js 的 Page()函数的上面(外面)引入图像处理依赖,代码如下: const extCi = require("./../../miniprogram_npm/@cloudbase/extension-ci-wxmp"); 然后再在 imgprocess.js 的 Page()函数的里面写一个 imgprocess 的事件处理函数,点击 button 之后会先执行 readFile()函数,也就是获取图片上传到小程序临时文件的结果(是一个对象),然后再调用 imageProcess()函数,这个函数会对图片进行处理,图片会保存为[代码]tcbdemo.jpg[代码],而处理之后的图片会保存为 image_process 文件夹下的 tcbdemo.png,相当于保存了两张图片: async imgprocess(){ const readFile = async function() { let res = await new Promise(resolve=>{ wx.chooseImage({ success: function(res) { let filePath = res.tempFilePaths[0] let fm = wx.getFileSystemManager() fm.readFile({ filePath, success(res){ resolve(res) } }) } }) }) return res } let fileResult = await readFile(); //获取图像的临时文件上传结果 const fileContent = fileResult.data //获取上传到临时文件的图像,为Uint8Array或Buffer格式 async function imageProcess() { extCi.invoke({ action: "ImageProcess", cloudPath: "tcbdemo.jpg", // 图像在云存储中的路径,有点类似于wx.cloud.uploadFile接口里的cloudPath,上传的文件会保存为云存储根目录下的hehe.jpg operations: { rules: [ { fileid: "/image_process/tcbdemo.png", //将图片存储到云存储目录下的image_process文件夹里,也就是我们用image_process存储处理之后的图片 rule: "imageMogr2/format/png", // 处理样式参数,我们可以在这里写图片处理的参数拼接 } ] }, fileContent }).then(res => { console.log(res); }).catch(err => { console.log(err); }) } await imageProcess() } 可能你的开发者工具会报以下错误:[代码]https://786c-xly-xrlur-1300446086.pic.ap-shanghai.myqcloud.com 不在以下 request 合法域名列表中,请参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html[代码],这个要按照参考文档将链接加入到合法域名当中,不然不会生成图片;[代码]action[代码]是操作类型,它的值可以为:ImageProcess 图像处理,DetectType 图片安全审核(后面会介绍),WaterMark 图片忙水印、DetectLabel 图像标签等。[代码]operations[代码]是图像处理参数,尤其是 rule 和我们之前 url 的拼接是一致的,比如[代码]imageMogr2/blur/8x5[代码]、[代码]imageMogr2/rradius/100[代码]等参数仍然有效。上面函数里的 fileContent 不是必要的,也就是说我们可以不在小程序端上传图片,而是直接修改云存储里面已有的图片,并将图片处理后的照片保存,这种情况代码可以写成如下: async imgprocess(){ extCi.invoke({ action: "ImageProcess", cloudPath: "tcbdemo.jpg", // 会直接处理这张图片 operations: { rules: [ { fileid: "/image_process/tcbdemo.png", rule: "imageMogr2/format/png", // 处理样式参数,与下载时处理图像在url拼接的参数一致 } ] }, }).then(res => { console.log(res); }).catch(err => { console.log(err); }) } 3、云函数端持久化图像处理在云函数端的处理和小程序端的处理,使用的方法大体上是一致的,不过云函数的处理图片的场景和小程序端处理图片的场景会有所不同,小程序端主要用于当用于上传图片时就对图片进行处理,云函数则主要用于从第三方下载图片之后进行处理或者对云存储里面的图片进行处理(比如使用定时触发器对云存储里指定文件夹的图片进行处理)。不建议把图片传输到云函数端再来对图片进行处理。 使用开发者工具新建一个 imgprocess 的云函数,然后在 package.json 里添加 latest 最新版的[代码]@cloudbase/extension-ci[代码],并右键云函数目录选择在终端中打开输入命令 npm install 安装依赖: "dependencies": { "wx-server-sdk": "latest", "@cloudbase/extension-ci": "latest" } 然后再在 index.js 里输入以下代码,代码的具体含义可以参考小程序端的内容讲解: const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const extCi = require("@cloudbase/extension-ci"); cloud.registerExtension(extCi); async function process() { try { const opts = { rules: [ { fileid: "/image_process/tcbdemo.jpeg", rule: "imageMogr2/format/png", }, ], }; const res = await app.invokeExtension("CloudInfinite", { action: "ImageProcess", cloudPath: "tcbdemo.jpg", fileContent, operations: opts, }); console.log(res); return res; } catch (err) { console.log(err); } }
2021-09-10 - 小白零基础入门,教你制作微信小程序!【第二十九课】砍价
手把手教你砍价活动怎么做!怎样的砍价活动才能提高用户活跃度和裂变传播推广?跟着今天的课程,一起来学习吧~ [视频] [图片] 最低价,能砍到的最低价格活动时间:该商品参与砍价活动的时间 时间到了就自动下架砍价时间:用户发起砍价的参与时间,在该时间内可以让别人帮忙砍价,也可以下单付款砍价方式:1.若不填或者填0,代表不限制人数,按下面设置砍价,砍完即止;2.若设置人数,人数必须大于1,并且根据下面的砍价范围进行计算:例如:砍价从50砍到0元,砍价人数设置10人,前5人设置砍价10-20元 则前5个人就砍到0,不触发后面的人数 ,若设置前5人砍1-5元,则触发后续5人剩余砍价,第10个人直接砍到0而不限制于剩余砍价
2021-04-13