使用getFileStream的注意事项

在使用nodejs获取上传的文件的时候,有两种方式可获取上传文件:

1.  File 模式
2.  Stream 模式

今天的主角是Stream 模式的上传/接受单个文件的getFileStream方法,我们优先讲这个
  1. getFileStream方法如何使?
    话不多说直接上代码
const ctx = this.ctx;
// 至此就获取到了浏览器通过 Multipart/form-data 格式发送的文件,
const stream = await ctx.getFileStream();
// 那通过FormData发送过来的其他参数怎么获取呢?他的所有参数就放在fields里面
const { xx1, xx2, xx3 } = stream.fields;

重点来了

要通过 ctx.getFileStream 便捷地获取到用户上传的文件,需要满足两个条件:
- 只支持上传一个文件。
- 上传文件必须在所有其他的 fields 后面,否则在拿到文件流时可能还获取不到 fields。
  • 解释一下第二个条件
    小二来一碗代码饭~ – 好嘞!
function uploadFile(file, { topCategory, subCategory, fileSize }) {
  if (!(file instanceof File)) {
    throw new Error("无效的文件对象");
  }
  const forms = new FormData();
  forms.append("xx1", xx1);
  forms.append("xx2", xx2);
  forms.append("xx3", xx3);
  // 这个必须在最后
  forms.append("file", file);
  const url ="<https://example.com/upload>";
  return axios.post(url, forms);
}

浅谈基于egg的文件上传

Stream 模式

单文件上传

  • 前端上传
function uploadFile(file, { xx1, xx2, xx3 }) {
  if (!(file instanceof File)) {
    throw new Error("无效的文件对象");
  }
  const forms = new FormData();
  // 参数
  forms.append("xx1", xx1);
  forms.append("xx2", xx2);
  forms.append("xx3", xx3);
  // 文件
  forms.append("file", file);
  const url ="<https://example.com/upload>";
  return axios.post(url, forms);
}
  • 后端接口处理
async create() {
  const { ctx } = this;
  const stream = await ctx.getFileStream();
  const { xx1, xx2, xx3 } = stream.fields;
  
  // 生成文件目录
  const targetPath = 'xx/xx/upload';
  promiseDirSync(targetPath);
  const target = path.join(targetPath, 'fileName.mp4');
  try {
    // 创建写入流
    const writeStream = fs.createWriteStream(target);
    // 创建管道
    await pump(stream, writeStream);
  } catch (error) {
    // 此过程中出现任何错误都得将上传的文件流消费掉,要不然浏览器响应会卡死
    await sendToWormhole(stream);
    throw err;
  }
  
  await this.returnCreate(this.models.video.create(Object.assign(
    { xx1, xx2,... },
    extraInfos
    )));
  }

多文件上传

const sendToWormhole = require('stream-wormhole');
const Controller = require('egg').Controller;

class UploaderController extends Controller {
  async upload() {
    const ctx = this.ctx;
    const parts = ctx.multipart();
    let part;
    // parts() 返回 promise 对象
    while ((part = await parts()) != null) {
      if (part.length) {
        // 这是 busboy 的字段
        console.log('field:' + part[0]);
        console.log('value:' + part[1]);
        console.log('valueTruncated:' + part[2]);
        console.log('fieldnameTruncated:' + part[3]);
      } else {
        if (!part.filename) {
          // 这时是用户没有选择文件就点击了上传(part 是 file stream,但是 part.filename 为空)
          // 需要做出处理,例如给出错误提示消息
          return;
        }
        // part 是上传的文件流
        console.log('field:' + part.fieldname);
        console.log('filename:' + part.filename);
        console.log('encoding:' + part.encoding);
        console.log('mime:' + part.mime);
        // 文件处理,上传到云存储等等
        let result;
        try {
          result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);
        } catch (err) {
          // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
          await sendToWormhole(part);
          throw err;
        }
        console.log(result);
      }
    }
    console.log('and we are done parsing the form!');
  }
}

module.exports = UploaderController;

File 模式

通过File 模式上传先配置config文件

exports.multipart = {
  fileSize: '200mb', // 大小
  whitelist: [ '.tar', '.mp4' ], // 格式
  mode: 'file',
};

单文件上传

  • 前端实现
function uploadFile(file, { xx1, xx2, xx3 }) {
  if (!(file instanceof File)) {
    throw new Error("无效的文件对象");
  }
  const forms = new FormData();
  // 参数
  forms.append("xx1", xx1);
  forms.append("xx2", xx2);
  forms.append("xx3", xx3);
  // 文件
  forms.append("file", file);
  const url ="<https://example.com/upload>";
  return axios.post(url, forms);
}
  • 后端实现
// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('fs/promises');
const path = require('path'); // 补上缺失的 path 模块
class UploadController extends Controller {
  async upload() {
    const { ctx } = this;
    const file = ctx.request.files\[0];
    const name = 'egg-multipart-test/' + path.basename(file.filename);
    let result;
    try {
    // 处理文件,例如上传到云采存储
    result = await ctx.oss.put(name, file.filepath);
    } finally {
    // 注意删除临时文件
    await fs.unlink(file.filepath);
    }
      ctx.body = {
        url: result.url,
        // 获取全部字段值
        requestBody: ctx.request.body,
      };
    }
}
module.exports = UploadController;

多文件上传

  • 前端实现
const uploadFile = async (ctx) => {
  const formData = new FormData();
  formData.append('title', ctx.form.title);
  for (const key in ctx.form) {
    formData.append(key, ctx.form\[key])
  }
  try {
    const response = await axios.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
        '\_csrf': ctx.csrf,
      },
    });
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
};
  • 后端实现
    对于多个文件,可以使用 ctx.request.files 数组进行遍历,然后分别处理每个文件。以下是你的前端静态页面的代码:
// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('fs/promises');
const path = require('path'); // 补上缺失的 path 模块

class UploadController extends Controller {
  async upload() {
    const { ctx } = this;
    console.log(ctx.request.body);
    console.log(`共收到 ${ctx.request.files.length} 个文件`);
    for (const file of ctx.request.files) {
      console.log(`字段名: ${file.fieldname}`);
      console.log(`文件名: ${file.filename}`);
      console.log(`编码: ${file.encoding}`);
      console.log(`MIME 类型: ${file.mime}`);
      console.log(`临时文件路径: ${file.filepath}`);
      let result;
      try {
        // 处理文件,例如上传到云采存储
        result = await ctx.oss.put(
          'egg-multipart-test/' + file.filename,
          file.filepath,
        );
      } finally {
        // 注意删除临时文件
        await fs.unlink(file.filepath);
      }
      console.log(result);
    }
  }
}

module.exports = UploadController;

为了保证文件上传的安全,egg框架限制了支持的文件格式。框架默认支持的白名单如下:

// images
'.jpg', '.jpeg', // image/jpeg
'.png', // image/png,image/x-png
'.gif', // image/gif
'.bmp', // image/bmp
'.wbmp', // image/vnd.wap.wbmp
'.webp',
'.tif',
'.psd',
// text
'.svg',
'.js', '.jsx',
'.json',
'.css', '.less',
'.html', '.htm',
'.xml',
// tar
'.zip',
'.gz', '.tgz', '.gzip',
// video
'.mp3',
'.mp4',
'.avi'
  • 34
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值