Egg如何上传文件?

1,355 阅读3分钟

相关往期文章:
文章一:Node框架之Egg —— EggJS+MySQL(mysql2+egg-sequelize+egg-cors)实现简单的增删改查

文章二:Node框架之Egg的中间件 —— 登录鉴权

本实例源码:

一、简介

1. 本例实现效果

上传文件到本地,按照创建日期文件夹去管理文件;即将同一天的上传文件放入到当天日期的文件夹下;如下图所示:
image.png

2. 所用到的第三方插件:

moment:用于创建以日期划分的文件夹;
uuid:用于给上传的文件重新命名。
安装:npm install moment uuid --save

3. 实现方法

上传文件总共有两种实现方法:

  • file形式上传
  • stream形式上传

二、代码实现

1. file形式上传

  • 配置
    // config/config.default.js
    
    'use strict';
    
    module.exports = appInfo => {
      const config = exports = {};
      config.keys = appInfo.name + '_1637216035577_8221';
      config.middleware = [];
      /* 取消安全证书验证 */
      config.security = {
        csrf: {
          enable: false,
        },
        domainWhiteList: ['*'], // 白名单
      };
      config.multipart = {
        mode: 'file',
        fileSize: '50mb',
        fileExtensions: ['.png', '.jpg'], // 扩展几种上传的文件格式
      };
      return {
        ...config
      };
    };
    
  • 上传代码
    // app/controller/uploadForFile.js 
    
    'use strict';
    const fs = require('fs')
    const path = require('path')
    
    const Controller = require('egg').Controller;
    
    class UploadForFileController extends Controller {
      async upload() {
        const { ctx } = this
        const file = ctx.request.files[0]
        const fileData = fs.readFileSync(file.filepath);
        const base64Str = Buffer.from(fileData, 'binary').toString('base64');
        const bufferData = Buffer.from(base64Str, 'base64');
        // 获取当前日期,用于文件夹创建
        const dirName = ctx.helper.formatTime(new Date())
        // 指定上传路径
        const uploadBasePath = '../public/uploadForFile'
        // 文件重命名
        const filename = `${ctx.helper.getUUID()}${path.extname(file.filename)}`
        const dir = path.join(__dirname, uploadBasePath, dirName);
        const src = path.join(__dirname, uploadBasePath, dirName, filename);
        // 判断是否存在该文件夹,不存在则创建。
        if (!fs.existsSync(dir)) fs.mkdirSync(dir);
        try {
          await fs.writeFileSync(src, bufferData);
          ctx.status = 200;
          ctx.body = {
            filename,
            url: `/public/uploadForFile/${dirName}/${filename}`
          };
        } catch (e) {
          ctx.status = 500;
          ctx.body = { msg: '上传文件失败' };
        }
      }
    }
    
    module.exports = UploadForFileController;
    

2. stream形式上传

  • 配置:
    // config/config.default.js
    
    'use strict';
    
    module.exports = appInfo => {
      const config = exports = {};
      config.keys = appInfo.name + '_1637216035577_8221';
      config.middleware = [];
      /* 取消安全证书验证 */
      config.security = {
        csrf: {
          enable: false,
        },
        domainWhiteList: ['*'], // 白名单
      };
      config.multipart = {
        mode: 'stream',
        fileSize: '50mb',
        fileExtensions: ['.png', '.jpg'], // 扩展几种上传的文件格式(白名单)
      };
      return {
        ...config
      };
    };
    
  • 上传代码
    // app/controller/uploadForStream.js 
    
    'use strict';
    const fs = require('fs');
    const path = require('path');
    const sendToWormhole = require('stream-wormhole');
    
    const Controller = require('egg').Controller;
    
    class UploadForStreamController extends Controller {
      async upload() {
        const { ctx } = this;
        const stream = await ctx.getFileStream();
        // 获取当前日期,用于文件夹创建
        const dirName = ctx.helper.formatTime(new Date())
        // 指定上传路径
        const uploadBasePath = '../public/uploadForStream'
        // 文件重命名
        const filename = `${ctx.helper.getUUID()}${path.extname(stream.filename)}`
        const dir = path.join(__dirname, uploadBasePath, dirName);
        const src = path.join(__dirname, uploadBasePath, dirName, filename);
    
        // 判断是否存在该文件夹,不存在则创建。
        if (!fs.existsSync(dir)) fs.mkdirSync(dir);
    
        const result = await new Promise((resolve, reject) => {
          const remoteFileStream = fs.createWriteStream(src);
          stream.pipe(remoteFileStream);
    
          let errFlag;
          remoteFileStream.on('error', err => {
            errFlag = true;
            sendToWormhole(stream);
            remoteFileStream.destroy();
            reject(err);
          });
    
          remoteFileStream.on('finish', async () => {
            if (errFlag) return;
            resolve(filename);
          });
        });
        ctx.body = {
          filename,
          url: `/public/uploadForStream/${dirName}/${result}`
        };
      }
    }
    
    module.exports = UploadForStreamController;
    

三、 总结

本实例中,通过两种方法上传文件到本地:file形式上传stream形式上传。 值得注意的是:

1. 配置与获取方式

两种方式配置和获取前端传过来的文件参数的方式不一样。

  • file形式上传
    // 配置
    config.multipart = {
        mode: 'file',
        fileSize: '50mb',
        whitelist: [ '.xlsx' ],
    };
    // 获取
    const files = ctx.request.files;
    
  • stream形式上传
    // 配置
    config.multipart = {
        mode: 'stream',
        fileSize: '50mb',
        fileExtensions: ['.png', '.jpg'], // 扩展几种上传的文件格式(白名单)
    };
    // 获取
    const stream = await ctx.getFileStream();
    

2. 扩展上传的文件格式(白名单)

在配置中,还得指定好上传文件的类型,你才会上传成功,不然你就会报错: image.png 但是,在上传图片(png / jpg / jpeg / gif)的时候,是不用添加白名单的。

3. moment、uuid 插件的使用

在本示例中,我将这两个插件封装在helper中的,这两个插件取决于你的项目是否使用,我自己喜欢用这种方式去管理文件和重命名文件名。

// app/extend/helper.js

const moment = require('moment')
const { v4: uuidv4 } = require('uuid');

module.exports = {
  formatTime(date, format = 'YYYY-MM-DD') {
    return moment(date).format(format)
  },
  getUUID() {
    return uuidv4();
  },
};

ltjs.gif