下面使用实现文件上传功能,先新建文件夹,结构如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>koa 实现文件上传功能</title>
</head>
<body>
<form action="/login" method="POST" enctype="multipart/form-data">
用户名:
<input type="text" name="username" />
<br />
密码:
<input type="password" name="password" />
<br />
头像:
<input type="file" name="avatar" />
<br />
<button>提交</button>
</form>
</body>
</html>
test.txt
凯小默
的
博客
这里主要使用上次开发的 kaimo-koa-bodyparser
中间件,然后进行类型区分开发
const querystring = require("querystring");
const path = require("path");
const fs = require("fs");
const uuid = require("uuid");
console.log("使用的是 kaimo-koa-bodyparser 中间件", uuid.v4());
/**
* @description 实现根据一个字符串来分割 buffer
* @param {String} sep 分割字符串
* @return {Array} 返回 buffer 数组
* */
Buffer.prototype.split = function (sep) {
let sepLen = Buffer.from(sep).length;
let arr = [];
let offset = 0;
let currentIndex = 0;
// 先赋值完在对比
while ((currentIndex = this.indexOf(sep, offset)) !== -1) {
arr.push(this.slice(offset, currentIndex));
offset = currentIndex + sepLen;
}
// 剩余的也 push 到数组
arr.push(this.slice(offset));
return arr;
};
// 测试
const buffer = Buffer.from("凯小默1--凯小默2--凯小默3");
console.log(buffer.split("--"));
// 中间件的功能可以扩展属性、方法
module.exports = function (uploadDir) {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
const arr = [];
ctx.req.on("data", function (chunk) {
arr.push(chunk);
});
ctx.req.on("end", function () {
if (ctx.get("content-type") === "application/x-www-form-urlencoded") {
const result = Buffer.concat(arr).toString();
console.log("kaimo-koa-bodyparser-result--x-www-form-urlencoded-->", result);
ctx.request.body = querystring.parse(result);
}
if (ctx.get("content-type").includes("multipart/form-data")) {
const result = Buffer.concat(arr);
console.log("result.toString----->", result.toString());
console.log("result----->", result);
let boundary = "--" + ctx.get("content-type").split("=")[1];
console.log("分隔符 boundary----->", boundary);
// 需要去掉无用的头尾
let lines = result.split(boundary).slice(1, -1);
console.log("lines----->", lines);
// 服务器收取到的结果全部放在这个对象中
let obj = {};
lines.forEach((line) => {
// 通过两个回车截取
let [head, body] = line.split("\r\n\r\n");
head = head.toString();
console.log("head----->", head);
// 获取到头部的
let key = head.match(/name="(.+?)"/)[1];
console.log("key----->", key);
// 根据 head 里是否有 filename 去区分是否是文件
if (!head.includes("filename")) {
console.log("body----->", body);
console.log("body.toString----->", body.toString());
// 去掉尾部无用字符
obj[key] = body.toString().slice(0, -2);
} else {
// 是文件,文件上传名字需要的是随机的,这里使用 uuid 库生成
// 拿到内容,去头尾
let content = line.slice(head.length + 4, -2);
console.log("uploadDir----->", uploadDir);
let filePath = path.join(uploadDir, uuid.v4());
console.log("filePath----->", filePath);
obj[key] = {
filePath,
size: content.length
};
fs.writeFileSync(filePath, content);
}
});
ctx.request.body = obj;
}
resolve();
});
});
await next(); // 完成后需要继续向下执行
};
};
添加 fileParser.js
测试代码,upload 文件夹为上传文件的放置的地方
const Koa = require("koa");
const path = require("path");
const static = require("koa-static");
// 使用自己实现的 koa-bodyparser
const bodyParser = require("./kaimo-koa-bodyparser");
const app = new Koa();
app.use(static(path.resolve(__dirname, "public")));
// 传入需要保存上传文件的文件夹
app.use(bodyParser(path.resolve(__dirname, "upload")));
app.use(async (ctx, next) => {
console.log(ctx.path, ctx.method);
if (ctx.path == "/login" && ctx.method === "POST") {
ctx.body = ctx.request.body;
console.log("ctx.body-------->", ctx.body);
} else {
await next();
}
});
app.on("error", function (err) {
console.log("error----->", err);
});
app.listen(3000);
启动服务,访问 http://localhost:3000/login
nodemon fileParser.js
填写数据,上传 test.txt
文件
最后可以看到文件已经上传到了 upload 文件夹