egg.js接收前端文件处理

最近在做一些项目,开始频繁的处理一些上传文件和导出excel的需求,现在就 egg.js 接收文件处理做一点简单的总结。

File 模式

  • 需要在 config 中启用 file 模式
1
2
3
exports.multipart = {
mode: 'file',
};
  • 上传/接收文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const Controller = require('egg').Controller;
const fs = require('mz/fs');

module.exports = class 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,
};
}
};
  • 上传 / 接收多个文件

对于多文件, 通过 ctx.request.files 属性进行遍历,分别处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const Controller = require('egg').Controller;
const fs = require('mz/fs');

module.exports = class extends Controller {
async upload() {
const { ctx } = this;
console.log(ctx.request.body);
for (const file of ctx.request.files) {
console.log('field: ' + file.fieldname);
console.log('filename: ' + file.filename);
console.log('encoding:' + file.encoding);
console.log('mime:' + file.mime);
console.log('tmp filepath:' + file.filepath);
let result;
try {
result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
}
finally {
await fs.unlink(file.filepath);
}
}
}
}

Stream模式

  • 上传接收单个文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const path = require('path');
const sendToWormhole = require('stream-wormhole');
const Controller = require('egg').controller;

class UploaderController extends Controller {
async upload() {
const ctx = this.ctx;
const stream = await ctx.getFileStream();
const name = 'egg-multipart-test/' + path.basename(stream.filename);
// 文件处理,上传到云存储等等
let result;
try {
result = await ctx.oss.put(name, stream);
} catch (err) {
// 必须将上传的文件流消费掉,要不然浏览器响应会卡死
await sendToWormhole(stream);
throw err;
}
ctx.body = {
url: result.url,
// 所有表单字段都能通过 `stream.fields` 获取到
fields: stream.fields,
};
}
}

module.exports = UploaderController;

// 补充
stream = await ctx.getFileStream({
requireFile: false, // 修复了,当没有文件上传时的报错
});
const buffers = [];
let buffer;
let res;
const { status, userName } = stream.fields;

通过 ctx.getFileStream 获取用户上传文件,需要满足两个条件:

  • 只支持上传一个文件

  • 上传文件必须在所有其他的fileds 后面,否则拿到文件流时可能还获取不到fields。

  • 上传/接收多个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;

实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const sendToWormhole = require('stream-wormhole');
const XLSX = require('xlsx');

class UploaderController extends Controller {
async uploadFile() {
const { ctx } = this;
try {
const stream = await ctx.getFileStream();
const buffers = [];
const { type,userName } = stream.fileds;
let res;
await new Promise(resolve => {
stream.on('data', chunk => {
buffers.push(chunk);
}).on('end', () => {
buffer = Buffer.concat(buffers);
const workbook = XLSX.read(buffer, { type: 'buffer' });
const sheetName = workbook.SheetNames;
res = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], {
raw: true,
header: 1,
});
resolve();
}).on('error', err => {
throw err;
})
});
// 读取的文件数据
}
catch (er) {
throw err;
}
}
}