最近在作微信公衆號,須要將圖片上傳至阿里雲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('上傳文件出錯'); }); });
這種方式省去了前面兩種方式的中間步驟,更加簡練直接,我的認爲是最好的。