文件上传与下载处理
文件上传处理
文件上传是Web开发中常见的功能需求。Koa2通过中间件机制简化了文件上传的处理流程。使用koa-body
中间件可以轻松实现文件上传功能。首先需要安装依赖:
npm install koa-body
基本配置示例:
const Koa = require('koa');
const koaBody = require('koa-body');
const app = new Koa();
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 200*1024*1024, // 设置上传文件大小限制,默认2M
keepExtensions: true // 保持文件扩展名
}
}));
app.use(async ctx => {
if (ctx.method === 'POST' && ctx.url === '/upload') {
const file = ctx.request.files.file; // 获取上传文件
// 处理文件逻辑...
ctx.body = '文件上传成功';
}
});
app.listen(3000);
前端HTML表单示例:
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
处理多个文件上传时,前端使用multiple
属性:
<input type="file" name="files" multiple>
后端处理代码:
const files = ctx.request.files.files; // 获取文件数组
files.forEach(file => {
// 处理每个文件
});
文件上传安全考虑
文件上传功能需要特别注意安全性问题:
- 文件类型验证:
const ALLOWED_TYPES = ['image/jpeg', 'image/png'];
if (!ALLOWED_TYPES.includes(file.type)) {
ctx.throw(400, '不支持的文件类型');
}
- 文件重命名防止目录遍历攻击:
const path = require('path');
const fs = require('fs');
const ext = path.extname(file.name);
const newFilename = `${Date.now()}${ext}`;
const filePath = path.join(__dirname, 'uploads', newFilename);
fs.renameSync(file.path, filePath);
- 文件内容检查:
const fileBuffer = fs.readFileSync(file.path);
if (fileBuffer.toString('utf8', 0, 2) === '#!') {
ctx.throw(400, '潜在的可执行文件');
}
文件下载处理
Koa2提供了多种文件下载方式。最简单的方式是直接设置响应头:
app.use(async ctx => {
if (ctx.path === '/download') {
const filePath = path.join(__dirname, 'files', 'example.pdf');
ctx.set('Content-Disposition', 'attachment; filename="example.pdf"');
ctx.body = fs.createReadStream(filePath);
}
});
处理大文件下载时,应该使用流式传输:
const fileStream = fs.createReadStream(filePath);
fileStream.on('error', err => {
ctx.throw(404, '文件不存在');
});
ctx.set('Content-Length', fs.statSync(filePath).size);
ctx.body = fileStream;
断点续传实现
对于大文件下载,实现断点续传可以提升用户体验:
app.use(async ctx => {
if (ctx.path === '/download') {
const filePath = path.join(__dirname, 'files', 'large.zip');
const stat = fs.statSync(filePath);
const range = ctx.headers.range;
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1;
const chunksize = (end - start) + 1;
ctx.set('Content-Range', `bytes ${start}-${end}/${stat.size}`);
ctx.set('Accept-Ranges', 'bytes');
ctx.set('Content-Length', chunksize);
ctx.status = 206;
const file = fs.createReadStream(filePath, {start, end});
ctx.body = file;
} else {
ctx.set('Content-Length', stat.size);
ctx.body = fs.createReadStream(filePath);
}
}
});
文件下载进度显示
前端可以通过XMLHttpRequest实现下载进度显示:
const xhr = new XMLHttpRequest();
xhr.open('GET', '/download/large.zip', true);
xhr.responseType = 'blob';
xhr.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(`${percentComplete}% downloaded`);
}
};
xhr.onload = function() {
if (this.status === 200) {
const blob = this.response;
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = 'large.zip';
a.click();
}
};
xhr.send();
文件压缩下载
使用archiver库可以实现多个文件的压缩下载:
const archiver = require('archiver');
app.use(async ctx => {
if (ctx.path === '/download-zip') {
const archive = archiver('zip', {
zlib: { level: 9 } // 设置压缩级别
});
ctx.set('Content-Type', 'application/zip');
ctx.set('Content-Disposition', 'attachment; filename="files.zip"');
archive.pipe(ctx.res);
archive.directory('public/files/', false);
archive.finalize();
}
});
文件上传进度监控
前端可以通过FormData和XMLHttpRequest实现上传进度监控:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(`${percentComplete}% uploaded`);
}
};
xhr.onload = function() {
if (xhr.status === 200) {
console.log('上传成功');
}
};
xhr.send(formData);
云存储集成
集成AWS S3存储的示例:
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
accessKeyId: 'YOUR_ACCESS_KEY',
secretAccessKey: 'YOUR_SECRET_KEY'
});
app.use(async ctx => {
if (ctx.method === 'POST' && ctx.url === '/upload-s3') {
const file = ctx.request.files.file;
const params = {
Bucket: 'your-bucket-name',
Key: `uploads/${Date.now()}_${file.name}`,
Body: fs.createReadStream(file.path)
};
try {
const data = await s3.upload(params).promise();
ctx.body = { url: data.Location };
} catch (err) {
ctx.throw(500, '上传到S3失败');
}
}
});
文件管理API设计
一个完整的文件管理API示例:
const router = require('koa-router')();
const File = require('../models/file'); // 假设有File模型
router.post('/files', async ctx => {
const file = ctx.request.files.file;
const newFile = await File.create({
name: file.name,
size: file.size,
type: file.type,
path: `/uploads/${file.name}`
});
ctx.body = newFile;
});
router.get('/files', async ctx => {
const files = await File.find();
ctx.body = files;
});
router.get('/files/:id', async ctx => {
const file = await File.findById(ctx.params.id);
if (!file) ctx.throw(404);
ctx.set('Content-Disposition', `attachment; filename="${file.name}"`);
ctx.body = fs.createReadStream(path.join(__dirname, file.path));
});
router.delete('/files/:id', async ctx => {
const file = await File.findByIdAndRemove(ctx.params.id);
if (!file) ctx.throw(404);
fs.unlinkSync(path.join(__dirname, file.path));
ctx.status = 204;
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Session 管理的实现方案
下一篇:数据流式传输优化