评论

加强版的微信支付APIv3媒体文件上传multipart/form-data协议无依赖版实现

nodejs实现的APIv3媒体上传文件封装类 Multipart.js

设计思路

没啥思路,纯体力活,就是用NodeJS来实现一把媒体文件上传的 rfc1867/rfc2388 协议。 NodeJS上可以搜罗到的 multipart 实现,大部分是解释器,装载器引用最多的是form-data这个包,接续上一版的实现,广度支持 nodejs http client,不挑食了。

node-fetch用来探测请求体是不是FormData函数片段:

//refer: https://github.com/node-fetch/node-fetch/blob/master/src/utils/is.js#L47-L67

/**
 * Check if `obj` is a spec-compliant `FormData` object
 *
 * @param {*} object
 * @return {boolean}
 */
export function isFormData(object) {
    return (
        typeof object === 'object' &&
        typeof object.append === 'function' &&
        typeof object.set === 'function' &&
        typeof object.get === 'function' &&
        typeof object.getAll === 'function' &&
        typeof object.delete === 'function' &&
        typeof object.keys === 'function' &&
        typeof object.values === 'function' &&
        typeof object.entries === 'function' &&
        typeof object.constructor === 'function' &&
        object[Symbol.toStringTag] === 'FormData'
    );
}

axios用来探测代码片段:

//refer: https://github.com/axios/axios/blob/master/lib/utils.js#L50-L58
/**
 * Determine if a value is a FormData
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an FormData, otherwise false
 */
function isFormData(val) {
  return (typeof FormData !== 'undefined') && (val instanceof FormData);
}

这个函数在浏览器环境上是可以的,但在NodeJS上是有问题的,FormData不是全局对象,探测失败,退而求其次,使用如下探测函数:

// https://github.com/axios/axios/blob/master/lib/utils.js#L161-L169
/**
 * Determine if a value is a Stream
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Stream, otherwise false
 */
function isStream(val) {
  return isObject(val) && isFunction(val.pipe);
}

实现

在coding过程中,遇到了一个比较大的挑战就是对 delete 的实现。按照MDN的文档介绍,delete是要删除同名所有值,这里啃了好久,也妥妥的搞定了。总体实现就是在上一版的同步代码模型上,增加了10几个方法,覆盖住了MDN FormData上罗列的所有方法,并且支持了 stream 流动模式。

核心代码就是以下4个函数,均是对内置的data BufferList进行编排:

应用

同步模式

(new Multipart())
  .append('a', 1)
  .append('b', '2')
  .append('c', Buffer.from('31'))
  .append('d', JSON.stringify({}), 'any.json')
  .append('e', require('fs').readFileSync('/path/your/file.jpg'), 'file.jpg')
  .getBuffer();

异步(流动)模式

(new Multipart())
  .append('f', require('fs').createReadStream('/path/your/file2.jpg'), 'file2.jpg')
  .pipe(require('fs').createWriteStream('./file3.jpg'));

示例上传代码

const {readFileSync} = require('fs')

const {Wechatpay, Multipart, Hash: { sha256 }} = require('wechatpay-axios-plugin');

const wxpay = new Wechatpay({ mchid, secret, serial, privateKey, certs });

const file = readFileSync('./hellowechatpay.png');
const meta = {filename: 'hellowechatpay.png', sha256: sha256(file)};

const form = new Multipart();
form.append('file', file, 'hellowechatpay.png');
form.append('meta', JSON.stringify(meta), 'meta.json');

wxpay
  .v3.marketing.favor.media.imageUpload(form, { meta, headers: form.getHeaders() })
  .then(({data: { media_id }}) => media_id)
  .then(console.info)
  .catch(console.error);

更高级的stream流动模式下,一条龙从浏览器客户端至微信服务端的媒体直接上传,用js来实现,就变得很有可能了,这个留给喜欢钻研的同学搞一搞了。

最后

附上 源码地址测试用例地址,如果用着还不放心,可以再装一下 form-data包,通过 new FormData 来使用。

希望本文能解决你的困惑,如果喜欢,欢迎 Star

链接

最后一次编辑于  2021-06-10  
点赞 3
收藏
评论
登录 后发表内容