微信公衆號圖片上傳至阿里雲OSS

最近在作微信公衆號,須要將圖片上傳至阿里雲OSS。在作這個功能的過程當中,我走了很多彎路,嘗試過不少種方法,最後終於研究出一種便捷優美的方式。如今把這些方法和思路記錄下來,避免遺忘。javascript

1、經過瀏覽器直接傳給OSS

這種方式最簡單。由於微信公衆號的跳轉頁面是基於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

1)構造DOM節點,表單提交上傳

像下面代碼這樣構造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>

2)利用Html5的FormData對象上傳

像下面這樣構造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]

2、服務端下載微信圖片再轉存至OSS

上面的方法雖然簡單直接,但只能從相冊中選擇圖片,而想要拍攝圖片並上傳,則必須經過微信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次的演變才完善:

1)利用fs將圖片寫到本地

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('上傳文件出錯');
  });
});

這種方式須要頻繁地寫文件和刪文件,感受一點都不極客。

2)利用memory-streams模塊將圖片寫到內存

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('上傳文件出錯');
  });
});

這種方式將圖片暫存在內存裏面,那若是併發量很大,是否是內存要爆炸了都?感受仍是不可取。

3)將下載圖片的流直接寫入OSS文件

折騰了很久,發現原來能夠這麼簡單和優雅:

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('上傳文件出錯');
  });
});

這種方式省去了前面兩種方式的中間步驟,更加簡練直接,我的認爲是最好的。

相關文章
相關標籤/搜索