微信公眾號之臨時素材管理

前言:公眾號在發送消息的時候可能會使用本地的多媒體文件,例如圖片、視頻等,而這些素材微信公眾號是不允許我們直接發送給用戶的,只能上傳到微信服務器上,得到 media_id 通過 media_id 去微信服務器查找素材發送給用戶,這些上傳到微信服務器的素材又分為臨時素材和永久素材,本次我們來介紹如何上傳臨時素材和獲取臨時素材。

書接前文:

先大致過一下官網:新增臨時素材

一、media_id 的特點:
  • 可復用
  • 媒體文件在微信后臺保存時間為 3 天,即 3 天后 media_id 失效。
  • 需使用 https 調用接口
  • 上傳臨時素材的格式、大小需要和公眾平臺官網要求一致
二、接口調用說明:
  • 請求方式:使用 https 進行 POST 請求,提交方式為 FORM 表單方式。
  • 請求接口
https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE

需要三個參數:

  • access_token 調用接口憑證
  • type 媒體文件類型例如圖片(image)
  • media_id form 表單提交時候攜帶的參數
三、返回說明

正確情況下的返回 JSON數 據包結果如下:

{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}
  • type 媒體文件類型
  • media_id 媒體文件上傳后,獲取標識(有用)
  • created_at 媒體文件上傳時間戳
四、封裝上傳臨時素材

首先我們請求的接口里面有用到 access_token 那么我們最好把這個函數放到之前封裝的 WeChat 這個類里面,方便得到 access_token。

觀察請求 URL,我們發現大多數請求的 URL 前綴都是相同的,為了便于管理,比如萬一微信改域名了,我們方便改 URL ,需要把前綴單獨提出來,在 utils 文件下的 api.js 接口文件新增上傳素材接口和得到素材接口代碼如下:

//地址前綴
const prefix = 'https://api.weixin.qq.com/cgi-bin/';

module.exports = {
    accessToken: `${prefix}token?grant_type=client_credential`,
    ticket: `${prefix}ticket/getticket?type=jsapi`,
    temporary: {
        upload:`${prefix}media/upload?`,
        get:`${prefix}media/get?`
    }
}

在 WeChat.js 文件新增上傳接口:

//引入路徑
const { resolve } = require("path");
//引入fs模塊
const { createReadStream , createWriteStream } = require("fs");
//引入request模塊
const request = require("request");
//以上幾個是多引用的模塊
//上傳臨時素材
uploadTemporaryMaterial(type, fileName) {
    //獲取文件的絕對路徑
    const filePath = resolve(__dirname, '../media', fileName);

    return new Promise(async (resolve, reject) => {

        try { //放置可能出錯的代碼
            //獲取access_token
            const data = await this.fetchAccessToken();
            //定義請求地址
            const url = `${api.temporary.upload}access_token=${data.access_token}&type=${type}`;

            const formData = {
                media: createReadStream(filePath)
            }
            //以form表單的方式發送請求
            const result = rp({ method: 'POST', url, json: true, formData })
            //將數據返回給用戶
            resolve(result);
        } catch (e) {
            //一旦try中的代碼出了問題,就會走catch邏輯,處理錯誤
            reject('uploadTemporaryMaterial方法出了問題:' + e);
        }

    })
}

uploadTemporaryMaterial 函數講解:

  • 函數接收兩個參數,type 上傳文件類型,fileName 上傳文件名。
  • 根據 fileName 去 media 文件夾找到上傳文件的絕對路徑。
  • 難點之一就是使用 request-promise-native 攜帶 media_id 發送 form 請求,一起研究下。
    首先我們去 npm 查看官方文檔:

    翻譯紅框的內容:

這個包和另一個 npm 包 request-promise 很像,不同的是 request-promise-native 使用的是原生 ES6 語法構建, request-promise 使用的是 ES6 的 polyfill 來構建的。使用請去參考 request-promise 的說明文檔, request-promise 的使用方法一切適用于 request-promise-native。

很明顯 request-promise 和 request-promise-native 功能都是一樣的,就是構建各自的語法稍有不同,我們就聽官方的去看 request-promise 的文檔去找 form 表單提交方式。

來到 request-promise 的官網,首先我們的知道能夠上傳文件的只有表單。無論是法form 表單還是虛擬表單都可以。接下來看官網這句話:

If you want to include a file upload then use options.formData
如果你想上傳文件請使用 formData,其實就是表單上傳。


我們只要看下 formData 如何配置的,主要字段的 name 和 file 其實對應的是 input 標簽里面的 name 的屬性值。第一個是上傳文本的格式,第二個才是表單上傳文件。文件的值比較特殊使用的是 NodeJS 的創建文件可讀流 createReadStream。options 里面配置文件名和文件格式,可省略,上傳多媒體文件,很明顯我們用第二個。

formData: {
    // Like <input type="text" name="name">
    name: 'Jenn',
    // Like <input type="file" name="file">
    file: {
        value: fs.createReadStream('test/test.jpg'),
        options: {
            filename: 'test.jpg',
            contentType: 'image/jpg'
        }
    }
}

微信公眾號上傳接口參數其中 media 官方是這么描述的:


微信公眾號上傳接口參數其中 media_id 官方是這么描述的

我們就可以理解為上傳的 input 標簽的 name 值為 media。所以上傳代碼封裝如上面匯總案例區。

然后進行測試,在 media 文件夾里放入需要上傳的文件,我們測試上傳一個 test.png 圖片,接下來去運行程序,在 Wechat.js 里面:

//先實例化
const wx = new Wechat();
//調用臨時素材上傳函數
wx.uploadTemporaryMaterial('image',"test.png").then(res=>console.log(res));

node 運行程序:

C:\Users\lenovo\Desktop\微信公眾號開發\WeChat\wechat>node Wechat.js
文件讀取成功~
文件保存成功~
{
  type: 'image',
  media_id: 'm6pUnsPg6aIMmlRNSuWvWa_Gtthg0fl2oiU1B5Eqp3amPA2fe21iGWluwU0Tu0o_',
  created_at: 1578315227,
  item: []
}

看到 media_id 了表示成功上傳了。如何把這個上傳的文件保存下來就是下來我們要做的事情。

五、封裝獲取臨時素材接口

這個接口需要注意:視頻文件不支持 https 下載,調用該接口需 http 協議。

接口請求方式:GET,https 調用。
請求接口:

https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID

請求接口攜帶的兩個參數:

  • access_token 調用接口憑證。
  • media_id 上傳素材時得到的媒體文件 ID。

在 Wxchat.js 里面新增:

//獲取臨時素材
getTemporaryMaterial(type, mediaId, fileName) {
    //獲取文件的絕對路徑
    const filePath = resolve(__dirname, '../media', fileName);

    return new Promise(async (resolve, reject) => {
        //獲取access_token
        const data = await this.fetchAccessToken();
        //定義請求地址
        let url = `${api.temporary.get}access_token=${data.access_token}&media_id=${mediaId}`;
        //判斷是否是視頻文件
        if (type === 'video') {
            //視頻文件只支持http協議
            url = url.replace('https://', 'http://');
            //發送請求
            const data = await rp({ method: 'GET', url, json: true });
            //返回出去
            resolve(data);
        } else {
            //其他類型文件
            request(url)
                .pipe(createWriteStream(filePath))
                .once('close', resolve) //當文件讀取完畢時,可讀流會自動關閉,一旦關閉觸發close事件,從而調用resolve方法通知外部文件讀取完畢了
        }
    });
}

這個函數需要注意兩點:

  • 保存文件,視頻文件使用的是 http 請求,所以條件判斷,再利用正則替換下 URL 就可以了。
  • 第一個難點是使用 request-promise-native 發送 formData ,現在第二個難點就是使用 request-promise-native 來接收 stream 流式文件。接收流式文件我們在 request-promise-native 官網查到下面這段話:


    request-promise-native接收流式文件

大致意思就是講:request-promise-native 和 request API 是一樣的,只不過 request-promise-native 實現了使用 ES6 的 promise 來調取接口。另外,不建議使用流式響應(例如.pipe(...)),因為對于大請求,請求承諾會增加不必要的內存占用。為此使用原始的請求庫來接收流式文件。

然后為了接收流式文件,同時為了不增加不必要的內存占用,我們去 request 官網去查看流式文件如何接收的,走起。

官網上找到的使用示例為:

request('http://google.com/doodle.png').pipe(fs.createWriteStream('ddle.png'))

直接請求鏈接創建可寫流放到本地,還有一句話引起了我的注意:

And since pipe() returns the destination stream in ≥ Node 0.5.x you can do one line proxying. 

pipe() 方法返回的目標流符合 NodeJs(符合大于 0.5.x 的版本)的 pipe()函數所以,可以使用 NodeJS 里面 pipe 的方法。接下來就去 NodeJS 官網查看 stream 如何進行異步流的寫入了,找到官網異步寫入流示例:

如何從異步迭代器中利用pipe管道傳入可寫流

下面有段說明:

To ensure completion of the write stream without errors, it is safer to use the finished() method as above, instead of using the once() listener for the 'finish' event.
為了確保流完全無錯誤寫入,請使用 finished() 方法來終止程序,而不是使用 once 來替代 finish 事件。

pipe() 方法執行完可以返回。

接下來去試著運行程序,首先復制我們上面得到的 media_id 在下面函數里面使用用來得到素材:

wx.getTemporaryMaterial('image',"m6pUnsPg6aIMmlRNSuWvWa_Gtthg0fl2oiU1B5Eqp3amPA2fe21iGWluwU0Tu0o_","gettest.png").then(()=>console.log("成功下載圖片"));

最后 node 運行程序:

C:\Users\lenovo\Desktop\微信公眾號開發\WeChat\wechat>node Wechat.js
文件讀取成功~
成功下載圖片

這時候去 media 文件夾里面看看圖片是否已經下載下來:


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容