larksuite / node-sdk

larksuite open sdk for nodejs
MIT License
136 stars 27 forks source link

图片上传报错 `TypeError: source.on is not a function` #39

Closed tzwm closed 1 year ago

tzwm commented 1 year ago

写了个简单的图片批量上传脚本, 有比较奇怪的错误,想要求助一下。 client 其他的消息发送指令都正常使用没问题,就是图片上传这个有点异常。

@larksuiteoapi/node-sdk 版本 1.12.0

[error]: [
  [
    TypeError: source.on is not a function
        at Function.DelayedStream.create (/Users/tzwm/projects/MTRPG-AI/node_modules/delayed-stream/lib/delayed_stream.js:33:10)
        at FormData.CombinedStream.append (/Users/tzwm/projects/MTRPG-AI/node_modules/combined-stream/lib/combined_stream.js:45:37)
        at FormData.append (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/form-data/lib/form_data.js:75:3)
        at build (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:63:16)
        at each (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:58:9)
        at Object.forEach (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/utils.js:276:12)
        at build (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:40:13)
        at toFormData (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:67:3)
        at Object.transformRequest (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/defaults/index.js:80:14)
        at transform (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/core/transformData.js:18:15)
  ]
]
fs.readdir(directory, (err, files) => {
  // 过滤出所有图片文件
  const imageFiles = files.filter(file => {
    const extension = file.split('.').pop()?.toLowerCase();
    return extension === 'jpg' || extension === 'jpeg' || extension === 'png' || extension === 'gif';
  });

  // 输出所有图片文件的文件名
  console.log('Image files in directory:');
  imageFiles.forEach(file => {
    console.log(file);
    const buffer = fs.readFileSync(directory + "/" + file);
    //console.log(buffer);
    client.im.image.create({
      "data": {
        "image_type": "message",
        "image": buffer,
      }
    }).then(res => {
      console.log(res);
    }).catch(err => console.log("errorrrrrrrr", err))
  });
});
tzwm commented 1 year ago

呃另外我不太会 JS,这个上传脚本是 ChatGPT 帮忙写的,但我大致看了下没啥毛病,出错的地方在 larkclient API 调用上,其他逻辑看起来没问题。

mazhe-nerd commented 1 year ago

厉害呀同学,ChatGPT可以的。 我试了一下正常的调用链路是可以的: image

可以先试试发送单个图片看看?排除下文件本身的原因

DevinDon commented 1 year ago

报错详情

LTS 版本18.15.0 的 Node.js 调用 im.image.create 方法会有问题,报错基本一致;切到版本 16.20.0 就没有问题。

[
  [
    TypeError: source.on is not a function
        at DelayedStream.create (/xxx/node_modules/.pnpm/delayed-stream@1.0.0/node_modules/delayed-stream/lib/delayed_stream.js:33:10)
        at CombinedStream.append (/xxx/node_modules/.pnpm/combined-stream@1.0.8/node_modules/combined-stream/lib/combined_stream.js:45:37)
        at FormData.append (/xxx/node_modules/.pnpm/form-data@4.0.0/node_modules/form-data/lib/form_data.js:75:3)
        at build (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/helpers/toFormData.js:63:16)
        at each (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/helpers/toFormData.js:58:9)
        at Object.forEach (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/utils.js:276:12)
        at build (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/helpers/toFormData.js:40:13)
        at toFormData (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/helpers/toFormData.js:67:3)
        at Object.transformRequest (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/defaults/index.js:80:14)
        at transform (/xxx/node_modules/.pnpm/axios@0.27.2/node_modules/axios/lib/core/transformData.js:18:15)
  ]
]

溯源 - 直接原因

在 Node 16 里,CombinedStream.append 调用传进来的数据是一个 Buffer

神奇的判断逻辑

但是在 Node 18 里,CombinedStream.append 调用传进来的数据是一个 Blob

image

CombinedStream.isStreamLike 这个方法显然没有考虑 Blob 这种情况,显而易见的就把 Blob 当作 Stream 交给了 DelayedStream.create 处理,结果调用了不存在的 on 方法,报错。

image

接下来找找根本原因。

溯源 - 根本原因

在 Nodejs 16 中,追踪整个调用栈上的参数类型,在 build 之前都是 Uint8Arraybuild 之后都是 Buffer,看起来没什么问题。

image

那问题应该与 build 方法有关,我们检查下 build 的逻辑。

image

convertValue 中有转换类型的逻辑,在 formData.append 入参中,类型已经从 Uint8Array 转为了 Buffer,我们检查下逻辑:

image

哦吼,看到问题了,Nodejs 16 中没有 Blob 这种类型,所以用的是 Buffer 来承载数据;但是 Nodejs 18 中有了 Blob 就会优先使用 Blob 类型;刚好碰到一个类型校验不严谨的(笑),就搞了个 bug。

image

解决方案

在聊解决方案之前,让我先吐槽下这个类型断言逻辑,这种逻辑是哪个天才想出来的,不是 Buffer 就是 Stream?这不是妥妥的瞧不起 null 和 undefined 一大家子吗?

image

我扔个 global 进去都能搞崩(笑)。

正确的处理逻辑应当使用 instanceof ReadStream 或者 instanceof Stream 方式,起码逻辑应该是:我认出来你是 Stream,你才是 Stream,否则我就不支持。

有两个方案解决:

  1. 降级 Nodejs 到不支持 Blob 特性的低版本,或者在全局干掉 Blob 对象(有风险)
  2. 私有仓库发个 combined-stream@1.0.8 版本的同名包给替换掉,这玩意已经好几年没更新了,别指望作者能修问题

上述建议仅供参考,如有问题欢迎指出。

mazhe-nerd commented 1 year ago

感谢同学提供思路,我这边看下尽快修复

mazhe-nerd commented 1 year ago

1.17.1版本以上可以使用stream代替buffer,1.17.1以下版本需要对image字段ts-ignore一下:

 const file = fs.createReadStream('your url');
  client.im.image.create({
    data: {
      image_type: 'message',
      image: file
    }
  }).then(res => {
    console.log(res);
  }).catch(e => {
    console.log(e);
  });