最近在作微信公众号,须要将图片上传至阿里云OSS。在作这个功能的过程当中,我走了很多弯路,尝试过不少种方法,最后终于研究出一种便捷优美的方式。如今把这些方法和思路记录下来,避免遗忘。javascript
这种方式最简单。由于微信公众号的跳转页面是基于QQ浏览器的,因此能够直接使用HTML的input
元素选择图片。html
<input type="file", name="pic", accept="image/jpeg" />
OSS有一个Post Object的接口容许HTML表单上传文件,除了文件(file)以外,还有一些其余的字段如保存到OSS的路径(key)、策略(policy)、本身的OSS应用的accessKeyId、签名(signature)等。java
因此须要构造表单。通常有两种方式:ajax
像下面代码这样构造form元素,而后利用$('form').submit()
提交。json
<!-- 请求地址由bucket名和oss的区域构成 --> <form method="POST" action="http://bucketname.oss-cn-shenzhen.aliyuncs.com"> <input type="hidden" name="key" /> <input type="hidden" name="policy" /> <input type="hidden" name="OSSAccessKeyId" /> <input type="hidden" name="success_action_status" /> <input type="hidden" name="signature" /> <input type="file" name="file" accept="image/jpeg" /> </form>
像下面这样构造FormData对象,再经过ajax或fetch post表单数据。api
const formData = new FormData(); formData.append('key', filePath); // OSS的保存路径 formData.append('policy', policy); // 策略 formData.append('OSSAccessKeyId', accessKeyId); // OSS对象的标识 formData.append('success_action_status', '200'); // 成功返回码 formData.append('signature', signature); // 签名 formData.append('file', file); // 图片文件,$('input[name="pic"]').files[0]
上面的方法虽然简单直接,但只能从相册中选择图片,而想要拍摄图片并上传,则必须经过微信JS-SDK来调用相机。浏览器
wx.chooseImage({ count: 1, // 默认9 sizeType: ['original', 'compressed'], // 能够指定是原图仍是压缩图,默认两者都有 sourceType: ['album', 'camera'], // 能够指定来源是相册仍是相机,默认两者都有 success: function (res) { // 返回选定照片的本地ID列表,localId能够做为img标签的src属性显示图片 var localIds = res.localIds; } });
这里有个问题,微信JS-SDK选择图片以后返回的是图片的标识id,而不是实际的图片文件,因此不能构造form表单上传OSS。服务器
那该怎么办呢?思路:将图片先上传至微信的服务器(最多保存3天),再经过微信的下载多媒体文件接口(http://file.api.weixin.qq.com...)将图片下载到服务器,再上传至OSS(虽然有点绕,但可行)。微信
客户端代码:并发
wx.chooseImage({ count: 1, // 默认9 sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success: function (res) { var localIds = res.localIds; wx.uploadImage({ localId: localIds[0], // 须要上传的图片的本地ID,由chooseImage接口得到 isShowProgressTips: 1, // 默认为1,显示进度提示 success: function (res) { var serverId = res.serverId; // 返回图片的服务器端ID // do something ... // 调用本身搭建的服务端的api,传入serverId,作获取微信图片上传OSS的相关操做 doSomething(); } }); } });
小tips:选择图片时只要选择了
compressed
,微信就会自动帮咱们压缩图片,官方文档也说明上传的多媒体文件会控制格式和大小,其中图片控制在jpg格式和1M如下的大小。因此,基本不用考虑图片过大的问题。实测中,8M的图片压缩后只有120KB左右。
服务端的代码通过了3次的演变才完善:
const fs = require('fs'); const request = require('require'); const OSS = require('ali-oss').Wrapper; const ossClient = new OSS({ accessKeyId: 'your access key', accessKeySecret: 'your access secret', bucket: 'your bucket name', region: 'oss-cn-hangzhou' }); // 须要获取微信accessToken,这里不细说 const accessToken = 'access token'; const mediaId = 'xxxxxxx'; // 微信多媒体文件id const destPath = `weixin/images/201702/${mediaId}.jpg`; // OSS文件路径,按本身喜欢构造咯 const wxReq = request(`http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${accessToken }&media_id=${mediaId}`); // 将文件流pipe到本地文件 wxReq.pipe(fs.createWriteStream(`${mediaId}.jpg`)); wxReq.on('end', () => { co(function* () { const result = yield ossClient.putStream(destPath, fs.createReadStream(`${mediaId}.jpg`), {timeout: 30 * 60 * 1000}); console.log('图片上传阿里云结果', result); fs.unlink(`${mediaId}.jpg`); // res.status(200).json(result); }).catch(err => { console.warn(err); //res.status(500).send('上传文件出错'); }); });
这种方式须要频繁地写文件和删文件,感受一点都不极客。
const request = require('require'); const OSS = require('ali-oss').Wrapper; const streams = require('memory-streams'); const ossClient = new OSS({ accessKeyId: 'your access key', accessKeySecret: 'your access secret', bucket: 'your bucket name', region: 'oss-cn-hangzhou' }); const accessToken = 'access token'; const mediaId = 'xxxxxxx'; // 微信多媒体文件id const destPath = `weixin/images/201702/${mediaId}.jpg`; // OSS文件路径 const writer = new streams.WritableStream(); const wxReq = request(`http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${accessToken }&media_id=${mediaId}`); wxReq.pipe(writer); wxReq.on('end', () => { co(function* () { const result = yield ossClient.put(destPath, writer.toBuffer(), {timeout: 30 * 60 * 1000}); console.log('图片上传阿里云结果', result); // res.status(200).json(result); }).catch(err => { console.warn(err); //res.status(500).send('上传文件出错'); }); });
这种方式将图片暂存在内存里面,那若是并发量很大,是否是内存要爆炸了都?感受仍是不可取。
折腾了很久,发现原来能够这么简单和优雅:
const request = require('require'); const OSS = require('ali-oss').Wrapper; const ossClient = new OSS({ accessKeyId: 'your access key', accessKeySecret: 'your access secret', bucket: 'your bucket name', region: 'oss-cn-hangzhou' }); const accessToken = 'access token'; const mediaId = 'xxxxxxx'; // 微信多媒体文件id const destPath = `weixin/images/201702/${mediaId}.jpg`; // OSS文件路径 const wxReq = request(`http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${accessToken }&media_id=${mediaId}`); wxReq.on('response', (response) => { // request的响应结果response能够做为读取流传给ossClient co(function* () { const result = yield ossClient.putStream(destPath, response, {timeout: 30 * 60 * 1000}); console.log('图片上传阿里云结果', result); // res.status(200).json(result); }).catch(err => { console.warn(err); //res.status(500).send('上传文件出错'); }); });
这种方式省去了前面两种方式的中间步骤,更加简练直接,我的认为是最好的。