微信公众号之临时素材管理

前言:公众号在发送消息的时候可能会使用本地的多媒体文件,例如图片、视频等,而这些素材微信公众号是不允许我们直接发送给用户的,只能上传到微信服务器上,得到 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 文件夹里面看看图片是否已经下载下来:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容