阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 文件上传与下载处理

文件上传与下载处理

作者:陈川 阅读数:41244人阅读 分类: Node.js

文件上传处理

文件上传是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 => {
  // 处理每个文件
});

文件上传安全考虑

文件上传功能需要特别注意安全性问题:

  1. 文件类型验证:
const ALLOWED_TYPES = ['image/jpeg', 'image/png'];
if (!ALLOWED_TYPES.includes(file.type)) {
  ctx.throw(400, '不支持的文件类型');
}
  1. 文件重命名防止目录遍历攻击:
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);
  1. 文件内容检查:
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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌