基础功能:上传文件
演示如下:
概括流程:
- 前端:把文件数据获取并
append
到 FormData
对象中
- 后端:通过
ctx.request.files
对象拿到二进制数据,获得 node
暂存的文件路径
前端
前端的工作就是把页面写好,ajax
和 FormData
组装好,发送给后端。
前端这边代码如下:
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <input type="file" name="file" id="file" /> <button id="btn">点我上传</button> </body> <script> const btn = document.getElementById("btn"); btn.onclick = function () { let file = document.querySelector("#file").files[0]; console.log(file); let formData = new FormData(); formData.append("data", file); formData.append("name", "文件"); formData.append("年龄", 20);
let xhr = new XMLHttpRequest(); xhr.open("post", "/upload"); xhr.onload = function () { console.log(xhr.responseText); }; xhr.send(formData); }; </script> </html>
|
基础:xhr.upload 上传钩子函数
大概有如下几个钩子(比较常用的)
1 2 3 4 5 6 7 8 9 10 11 12
| xhr.upload.onprogress = (event) => { console.log("上传过程"); }; xhr.upload.onload = () => { console.log("上传成功"); }; xhr.upload.onloadend = () => { console.log("上传完成"); }; xhr.upload.onabort = () => { console.log("取消上传"); };
|
onprogress
这个函数是在上传过程中不断循环被执行的,其中有事件因子 event
,里面会有上传中的信息
如果想要监控速度和进度的话,可以在上传的过程中计算出来
如果想要取消上传,就把 xhr.abort()
即可。
1 2 3 4
| document.getElementById("cancelBtn").addEventListener("click", function () { xhr.abort(); });
|
基础:利用钩子函数计算下载速度和进度
速度:思路就是求出一段时间的下载量(byte
)和一段时间(s
),然后做除法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| let oldDataSize; let oldTime; xhr.onload = function () { let responseText = xhr.responseText; console.log("上传成功", responseText); }; xhr.upload.onloadstart = () => { console.log("上传开始!"); oldLoaded = 0; oldTime = new Date().getTime(); }; xhr.upload.onprogress = (enent) => { let duringLoaded = event.loaded - oldLoaded; let duringTime = (new Date().getTime() - oldTime) / 1000;
oldTime = new Date().getTime(); oldLoaded = event.loaded;
console.log("上传中:>>", event); };
|
进度:已经上传的数据loaded
与总数据 total
的比值
完善:添加进度条以及速度标识
整体代码如下:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <input type="file" name="file" id="file" /> <div>进度: <progress value="0" max="100" id="progress"></progress></div> <div>速度: <span id="speed"></span> <span id="unit"></span></div> <button id="btn">上传</button> <button id="cancelBtn">取消上传</button> </body> <script> const btn = document.getElementById("btn"); let xhr = new XMLHttpRequest(); let oldDataSize; let oldTime; btn.onclick = function () { let file = document.querySelector("#file").files[0]; console.log(file); let formData = new FormData(); formData.append("data", file); formData.append("name", "文件"); formData.append("年龄", 20);
xhr.open("post", "/upload"); xhr.onload = function () { console.log(xhr.responseText); }; xhr.upload.onloadstart = (event) => { console.log("开始上传"); oldLoaded = 0; oldTime = new Date(); }; xhr.upload.onprogress = (event) => { console.log("正在上传:>>", event);
let duringLoaded = (event.loaded - oldLoaded) / 1024; let duringTime = (new Date() - oldTime) / 1000;
oldTime = new Date(); oldLoaded = event.loaded; let speed = duringLoaded / duringTime; let unit = "b/s"; if (speed > 1024) { speed = speed / 1024; unit = "kb/s"; } if (speed > 1024) { speed = speed / 1024; unit = "mb/s"; } if (speed > 1024) { speed = speed / 1024; unit = "gb/s"; } if (speed > 1024) { speed = speed / 1024; unit = "tb/s"; } document.getElementById("speed").innerHTML = `${speed}`; document.getElementById("unit").innerHTML = `${unit}`;
const { total, loaded } = event; let progress = ((loaded / total) * 100).toFixed(0); document.getElementById("progress").value = progress; }; xhr.upload.onload = () => { console.log("上传成功"); }; xhr.upload.onloadend = () => { console.log("上传完成"); }; xhr.upload.onabort = () => { console.log("取消上传"); };
xhr.send(formData); };
document.getElementById("cancelBtn").addEventListener("click", function () { xhr.abort(); }); </script> </html>
|
后端
后端获取相应数据的方式如下:
1 2 3 4
| router.post("/upload", (ctx) => { console.log(ctx.request.body); console.log(ctx.request.files); });
|
node
会帮我们把二进制文件存储到临时地址,我们可以通过 fs
模块拿到文件,然后写到自己想要的位置
基本功能:拿到二进制数据并转存文件
后端接收注意要在 KoaBody
这里允许上传文件,具体的知识点可以阅读一下这篇博文:理解 HTTP 中的 multipart/form-data
1 2 3 4 5
| app.use( KoaBody({ multipart: true, }) );
|
后端代码如下:
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
| const Koa = require("koa"); const View = require("koa-views"); const Router = require("koa-router"); const { koaBody } = require("koa-body"); const Static = require("koa-static"); const fs = require("fs"); const app = new Koa(); const router = new Router(); app.use(View(__dirname)); app.use(Static(__dirname)); app.use(koaBody({ multipart: true }));
router.get("/", async (ctx, next) => { await ctx.render("index.html"); });
router.post("/upload", async (ctx, next) => { console.log("ctx.request.files:>>", ctx.request.files); console.log("ctx.request.body:>>", ctx.request.body); const filePath = ctx.request.files.data.filepath; const readFile = fs.readFileSync(filePath); fs.writeFileSync( "static/" + ctx.request.files.data.originalFilename, readFile ); ctx.body = "请求成功"; });
app.use(router.routes()); app.listen(3000, () => { console.log("server start:>>", "http://localhost:3000"); });
|
优化:文件夹的判断以及错误处理
可以检测文件夹是否存在,如果文件夹不存在的话自然会报错,完善后的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
router.post("/upload", async (ctx, next) => { try { const data = ctx.request.files.data; const { filepath, originalFilename } = data; if (!fs.existsSync(`static`)) { fs.mkdirSync("static"); } const readFile = fs.readFileSync(filepath); fs.writeFileSync(`static/${originalFilename}`, readFile); ctx.body = "请求成功"; } catch (err) { console.log(err); } });
|
断点续传
Q & A
Error: options.maxFileSize (209715200 bytes) exceeded, received 209731427 bytes of file data
这是后端有上传文件大小限制的问题,在 koa-body
配置中把文件改的大一些,默认是 200mb
,点我查看源文档
1 2 3 4 5 6 7 8 9 10 11 12
|
app.use( koaBody({ multipart: true, formidable: { maxFileSize: 1000 * 1024 * 1024, }, }) );
|