16

egg获取上传文件的方法中官方给了两种处理方法,1是file直接读取,2是stream流的方式。

file读取方式

我们先看看file读取的方式,该方式需要先在config里配置下

// config.defult.js
config.multipart = {
    mode: 'file'
  };

Control层代码

const  fs = require('fs')
const path = require('path')
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
   // console.log(ctx.request.body)
     let file = ctx.request.files[0] // file包含了文件名,文件类型,大小,路径等信息,可以自己打印下看看
    
     // 读取文件
     let file = fs.readFileSync(file.filepath) //files[0]表示获取第一个文件,若前端上传多个文件则可以遍历这个数组对象
     // 将文件存到指定位置
     fs.writeFileSync(path.join('./', `uploadfile/test.png`), file)
    // ctx.cleanupRequestFiles()

   
    ctx.body = { code: 200, message: '', data: file.filename}
  }
}

前端上传文件代码(这里使用了Vue+axios)

<template>
  <div id="app">
    <img src="./assets/logo.png" @click="testClick">
    <input type="file" @change="upload" ref="fileid" multiple="multiple"/>
    <router-view/>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'App',
  methods: {
    testClick() {
      console.log('ddd')
    },
    upload() {
      let file = this.$refs.fileid.files[0]
      console.log(file)
      let formData = new FormData()
      formData.append('file', file)

      axios({
        method: 'post',
        url: 'http://127.0.0.1:7001/fileupload',
        data: formData
      }).then(res => {
        console.log(res)
      })
    }
  }
}
</script>

上面代码运行后可以在egg项目里的uploadfile目录里找到上传的文件

stream流方式

stream流的方法,单个文件可以使用getFileStream方法获取文件流 注意使用stream流方式需要把之前配置里的multipart删掉,这两种方法不能一起用,否则会报错。

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

class HomeController extends Controller {
  async index() {
    const { ctx } = this;

    let stream = await ctx.getFileStream()
    let filename = new Date().getTime() + stream.filename  // stream对象也包含了文件名,大小等基本信息
 
    // 创建文件写入路径
    let target = path.join('./', `uploadfile/${filename}`)

    const result = await new Promise((resolve, reject) => {
      // 创建文件写入流
      const remoteFileStrem = fs.createWriteStream(target)
      // 以管道方式写入流
      stream.pipe(remoteFileStrem)

      let errFlag 
      // 监听error事件
      remoteFileStrem.on('error', err => {
        errFlag = true
        // 停止写入
        sendToWormhole(stream)
        remoteFileStrem.destroy()
        console.log(err)
        reject(err)
      })
      
      // 监听写入完成事件
      remoteFileStrem.on('finish', () => {
        if (errFlag) return
        resolve({ filename, name: stream.fields.name })
      })
    })

    ctx.body = { code: 200, message: '', data: result }
  }
}

前端上传多个文件代码

upload() {
      let files = this.$refs.fileid.files
      let formData = new FormData()
      // 遍历文件
      for (let i = 0; i <files.length; i++) {
        let file = files[i]
        formData.append('file'+ i, file)
      }
     
      axios({
        method: 'post',
        url: 'http://127.0.0.1:7001/fileupload',
        data: formData
      }).then(res => {
        console.log(res)
      })
    }

getFileStream为上传一个文件时使用的方法,如果上传多个文件该方法只能拿到其中一个文件。多文件上传应该使用multipart方法。

const  fs = require('fs')
const path = require('path')
const querystring =require('querystring');
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;

    const parts = ctx.multipart();
    let part;
     while ((part = await parts()) != null) {
      if (part.length) {
        // 处理其他参数
        console.log('field: ' + part[0]);
        console.log('value: ' + part[1]);
        console.log('valueTruncated: ' + part[2]);
        console.log('fieldnameTruncated: ' + part[3]);
      } else {
        if (!part.filename) {
          
          continue;
        }
        // otherwise, it's a stream
        console.log('field: ' + part.fieldname);
        console.log('filename: ' + part.filename);
        console.log('encoding: ' + part.encoding);
        console.log('mime: ' + part.mime);
        let writePath = path.join('./', `uploadfile/${ new Date().getTime() + part.filename}`)
        let writeStrem = fs.createWriteStream(writePath)
        await part.pipe(writeStrem)     
      }
    }

    ctx.body = { code: 200, message: '', data: result }
  }
}

两种方式比较

这两种方法写下来很明显file读取的方式要简单的多,但在性能方面上这两种有什么区别呢,egg在底层是怎么实现的呢。

使用file读取的方式我们可以得到filepath这个路径,这个路径是用于缓存文件的地方,大家可以打印一下看看。在该路径中可以找到上传的文件。
图片描述

也就是说file读取方式是先在服务器里写入缓存文件,然后我们再读取缓存文件进行操作。在上面的文件操作中file读取方式的IO操作有写入缓存文件,读取缓存文件,写入文件,总共3次IO操作。而stream流的方式没有缓存文件这个操作,也就是说IO操作只有一次,若是不将写入本服务器而是上传的OSS等则没有IO操作。那么这两种方式的效应和性能就不用多说了吧。

配置选项

file读取方式由于有文件缓存所以可以配置缓存文件的位置以及自动清除时间

config.multipart = {
  mode: 'file',
  tmpdir: path.join(os.tmpdir(), 'egg-multipart-tmp', appInfo.name), // 配置文件缓存目录
  cleanSchedule: {
   
    cron: '0 30 4 * * *',  // 自动清除时间
  },
};

也可以在代码里使用cleanupRequestFiles()方法直接清除,当然如果你选择的是stream流方式就不用管这些了。

配置文件类型和大小

config.multipart = {
   whitelist: [   // 只允许上传png格式
    '.png',
  ],
  fileSize: '5mb',  // 最大5mb  
};

没落的宅男贵族
112 声望2 粉丝