上传方式

  1. file 直接读取
  2. stream 流的方式:创建文件写入流,以管道方式写入流

file 读取

  1. config 配置
// config.defult.js
config.multipart = {
mode: 'file',
tmpdir: // 配置文件缓存目录
cleanSchedule: {
cron: '0 30 4 * * *', // 自动清除时间 文件缓存
},
};

  1. 读取并存储

    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}
    }
    }

stream 流方式

无需对 config 配置

注意使用 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 };
}
}

多个文件

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 读取的方式要简单的多,但在性能上,Stream 更优

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

image

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

path.join 和 resolve

1 path.join([path1][, path2][, …]) 用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix 系统是”/“,Windows 系统是”"。
2 path.resolve([from …], to)to参数解析为绝对路径,给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve(‘/foo’, ‘/bar’, ‘baz’) 会返回 /bar/baz。

path.resolve 返回绝对路径

path.join 返回相对路径,并且会正确使用当前系统的路径分隔符;

参考:javascript - egg 文件上传接收总结_个人文章 - SegmentFault 思否