乐趣区

egg文件上传接收总结

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  
};
退出移动版