个人案例
微商店助手
一机在手,生意到手。
微商店助手扫码体验
- 云函数(nodejs)使用上传图片api获取MediaID
接口说明 适用对象:服务商/电商平台 请求URL:https://api.mch.weixin.qq.com/v3/merchant/media/upload 逻辑流程: 从云储存或小程序端获取图片二进制数据 使用crypto生成文件摘要 使用crypto生成签名和授权信息(需要先有文件名和文件摘要) 自定义分割字符串boundary和HTTP头Content-Type 创建传输body的头部和尾部,并与文件拼接 发送请求 1. 获取图片二进制数据 可以从云储存获取,或小程序端传参 小程序获取的是ArrayBuffer,在后续使用中需要转型成Buffer 场景1: 从云储存获取图片buffer 云函数代码 [代码]// [云储存文件id] 可以从小程序端传入 const fileRes = await cloud.downloadFile({fileID: '云储存文件id'}) const imgBuffer = fileRes.fileContent [代码] 场景2:从小程序端获取图片buffer 小程序代码 [代码]const fileSystemManager = wx.getFileSystemManager() const buffer = fileSystemManager.readFileSync('图片路径') wx.cloud.callFunction({ name: '云函数名称', data: { buffer, filename: '图片名.jpg' } }) [代码] 云函数代码 [代码]let { buffer, filename } = event const imgBuffer = Buffer.from(buffer) [代码] 2. 生成摘要、创建meta [代码]const hash = crypto.createHash('sha256'); hash.update(imgBuffer); let sha256 = hash.digest('hex') let meta = { filename, sha256 } [代码] 3. 生成签名、创建授权信息 [代码]let getAuthorization = async function(meta) { // 获取商户私钥、证书序列号、商户号 // (可以储存在云数据中,从云数据库获取) const priKey = '商户私钥' const serialNo = '商户证书序列号' const mchid= '商户号' // 生成随机序列 let nonceStr = Math.random().toString() // 获取当前时间戳(这里需要的是秒数不是毫秒,要除以1000) let timestamp = Math.floor(Date.now() / 1000) // 创建待签名文本 let signatureText = "POST\n" + "/v3/merchant/media/upload\n" + timestamp + "\n" + nonceStr + "\n" + JSON.stringify(meta)+"\n" // 生成签名 let sign = crypto.createSign('RSA-SHA256') sign.update(signatureText) let signature = sign.sign(priKey, 'base64') // 合成授权信息 let authorization = 'WECHATPAY2-SHA256-RSA2048' + ` mchid="${mchid}"` + `,nonce_str="${nonceStr}"` + `,timestamp="${timestamp}"` + `,serial_no="${serialNo}"` + `,signature="${signature}"` return authorization } [代码] 4. 自定义分割字符串boundary和HTTP头Content-Type [代码]// 自定义分割字符串(可以自行定义,和发送的内容不重复即可) let boundary = 'miwoo-boundary-' + Math.random() let contentType = 'multipart/form-data; boundary=' + boundary [代码] 5. 创建传输body的前半部分和尾部,并与文件拼接 [代码]// 创建body的前半部分并转换为buffer // 注意反引号`与单引号'的区别 // 分割符要在boundary前加-- let beginBuffer = Buffer.from( `--${boundary}\r\n` + 'Content-Disposition: form-data; name="meta";\r\n' + 'Content-Type: application/json\r\n' + '\r\n' + `${JSON.stringify(meta)}\r\n` + `--${boundary}\r\n` + `Content-Disposition: form-data; name="file"; filename="${filename}";\r\n` + 'Content-Type: image/jpg\r\n' + '\r\n' ) // 创建body尾部(第一个\r\n是接在imgBuffer后面的换行) // 结束分割符要在boundary两边加-- let endBuffer = Buffer.from(`\r\n--${boundary}--\r\n`) // 将imgBuffer加入头部与尾部,拼接成完整body(不能直接使用+号连接) let body = Buffer.concat([beginBuffer,imgBuffer,endBuffer]) [代码] 6. 发送请求 [代码]axios({ method: 'POST', url: 'https://api.mch.weixin.qq.com/v3/merchant/media/upload', headers: { 'Authorization': authorization, 'Content-Type': contentType }, data: body }) .then(res => { console.log(res.data) }) [代码] 完整流程 [代码]const cloud = require('wx-server-sdk') cloud.init() const crypto = require('crypto') //使用crypto生成文件摘要以及签名 const axios = require('axios') //使用axios发送请求(npm install axios) exports.main =async (event, context) => { // 1. 获取图片buffer const imgBuffer = '从云储存或小程序获取' // 获取文件名(请自行获取) const filename = '图片名.jpg' // 2. 生成文件摘要 const hash = crypto.createHash('sha256'); hash.update(imgBuffer); let sha256 = hash.digest('hex') let meta = { filename, sha256 } // 3. 获取签名(使用上面的签名函数) let authorization = await getAuthorization(meta) // 4. 自定义分割字符串和Content-Type let boundary = 'miwoo-boundary-' + Math.random() let contentType = 'multipart/form-data; boundary=' + boundary // 5. 创建(拼接)body let beginBuffer = Buffer.from( `--${boundary}\r\n` + 'Content-Disposition: form-data; name="meta";\r\n' + 'Content-Type: application/json\r\n' + '\r\n' + `${JSON.stringify(meta)}\r\n` + `--${boundary}\r\n` + `Content-Disposition: form-data; name="file"; filename="${filename}";\r\n` + 'Content-Type: image/jpg\r\n' + '\r\n' ) let endBuffer = Buffer.from(`\r\n--${boundary}--\r\n`) let body = Buffer.concat([beginBuffer,imgBuffer,endBuffer]) // 6. 发送请求 return await axios({ method: 'POST', url: 'https://api.mch.weixin.qq.com/v3/merchant/media/upload', headers: { 'Authorization': authorization, 'Content-Type': contentType }, data: body }) .then(res => { console.log(res.data) return res.data.media_id }) } [代码] 欢迎留言 本文为**电商平台(云开发)**的填坑之作,欢迎提出不足之处、分享云开发的经验和坑。
2021-06-02 - 小程序云开发:data exceed max size 解决方案
说现象 前两天有客服消息说我的小程序无法进行正常功能的使用了,后来定位到问题了,在 update 的时候出现错误信息:data exceed max size。 [图片] 并且这个错误还开发工具上不出现,只有在手机上才出现。原因是 update 的字段内容太大了导致无法正常修改,那么我们要做的就是让参数变小。 分析问题 [图片] 这个小程序是实现的是投票功能,有多层嵌套,之前的更新方案是直接修改 optionData 里面的所有内容。 数据结构解释: optionData 是一个数组里面存储选项数据 optionData 下面还有一个joiner数据组,这个是存放用户投票信息的。 optionData内容过大导致报错,解决方案是通过只更新局部来解决。 上代码 没改之前的代码 [代码] let vote = await db.collection('activity').doc(event.id).update({ data:{ optionData:event.optionData } }) [代码] 优化后的代码 [代码] let vote = await db.collection('activity').doc(event.id).update({ data:{ ['optionData.'+ joiner.index +'.joiner']:_.push(event.userInfo) } }) [代码] 总结 很多bug当数据量小的时候问题并不会暴露出来,数据一旦大起来问题就来了。本质上反应出一个问题就是从数据结构的设计上这个就是不合理的,不应该所有数据都存放在一个大字段里面,应该做数据拆分,然后通过联查来展示,这样更加便于后续的数据分析与统计。 最后,感谢 @老张 的指点。 后记 还有一种情况也会出现 data exceed max size ,查询数据过大。 有两种解决方案: 查询通过过滤一些无关字段。 1.1 聚合查询使用「project」进行过滤 1.2 普通查询使用「field」进行过滤 分页查询。 详细查看「云开发:实现分页功能」 以上两种方式本质上都是减少一次性的查询数据量。
2020-12-19 - 小程序分享图生成指南:告别 Canvas 踩坑,轻松实现朋友圈分享
由于微信小程序无法直接分享到朋友圈,业界普遍采用 Canvas 生成带小程序码的图片,引导用户保存后分享,相信不少开发者在绘制分享图时都曾遇到过各种 Canvas 的“彩蛋”(坑)。 今天推荐一个开源组件:Painter,它能够帮助我们轻松实现分享图绘制,并且已在支付宝小程序中验证可用。 快速开始 第一步:创建自定义组件 在组件的 JSON 文件中引入 Painter: [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 第二步:编写组件 WXML 将 Painter 组件定位在屏幕外,避免影响用户界面: [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 第三步:实现组件逻辑 [代码]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') } } }) [代码] 如何使用 1. 引入组件 在页面的 JSON 文件中引入封装好的分享组件: [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 2. 页面布局 [代码]<!-- 触发按钮 --> <button class="intro" bindtap="getUserInfo">点我生成分享图</button> <!-- 分享组件 --> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 3. 页面逻辑 [代码]Page({ data: { isCanDraw: false, userInfo: {} }, // 重置绘制状态 handleClose() { this.setData({ isCanDraw: false }) }, // 获取用户信息并开始绘制 getUserInfo() { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: (res) => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 触发绘制 }) }, fail: (err) => { console.log('获取用户信息失败:', err) } }) } }) [代码] 效果展示 完成上述步骤后,即可生成如下的分享图片: [图片] 实用技巧 文字居中:通过设置 [代码]align: 'center'[代码] 和合适的[代码]left[代码]值实现 文字换行:设置[代码]width[代码]和 [代码]maxLines[代码] 属性,当 [代码]maxLines: 1[代码] 时,超出一行会显示为省略号 资源链接 代码片段:点击查看 Painter 可视化工具:点击查看 常见问题 Q:为什么图片无法加载? A:需要在小程序后台配置 downloadFile 合法域名: 进入「开发」→「开发设置」→「服务器域名」 配置图片的域名前缀,如 https://qiniu-image.qtshe.com Q:真机调试注意事项? A:开发者工具中可在「详情」→「本地设置」中开启「不校验合法域名」,真机调试时需开启调试模式。 通过以上步骤,你就可以轻松实现小程序分享图生成功能了。如有任何问题,欢迎在评论区留言讨论!
11-13