美文网首页基础前端
微信公众号之临时素材管理

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

作者: CondorHero | 来源:发表于2020-01-07 00:44 被阅读0次

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


    相关文章

      网友评论

        本文标题:微信公众号之临时素材管理

        本文链接:https://www.haomeiwen.com/subject/zqaqactx.html