You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于NodeJS/Express实现Form Data上传的全控制与进度追踪

手动处理Form Data上传(不依赖第三方模块)

核心逻辑

Form Data基于multipart/form-data编码传输,需要手动解析该编码格式以拆分文本字段和文件内容;同时监听数据流实时计算上传进度,通过WebSocket推送至客户端,最后对文件做尺寸、画质调整后多位置保存。

1. 从请求头提取边界符

multipart/form-data的请求头中会携带boundary参数,这是分隔各个字段的标记,先将其提取出来:

const contentType = req.headers['content-type'];
const boundary = contentType.split('boundary=')[1];
if (!boundary) {
  res.writeHead(400);
  res.end('无效的Form Data格式');
  return;
}

2. 实时计算并推送上传进度

从Content-Length请求头获取总数据大小,每次接收数据时累加已接收量,计算进度百分比后通过WebSocket发送给客户端:

let receivedSize = 0;
const totalSize = parseInt(req.headers['content-length'], 10);

// 假设已通过ws模块建立WebSocket连接(client为连接实例)
req.on('data', (chunk) => {
  receivedSize += chunk.length;
  const progress = Math.round((receivedSize / totalSize) * 100);
  ws.send(JSON.stringify({ type: 'uploadProgress', progress }));
});

3. 手动解析multipart数据流

将接收的所有数据块拼接后,用边界符拆分各个字段,分别提取文本内容和文件Buffer:

let chunks = [];
req.on('data', (chunk) => chunks.push(chunk))
  .on('end', () => {
    const fullBuffer = Buffer.concat(chunks);
    // 按边界符拆分数据流,过滤空的分隔块
    const parts = fullBuffer.toString().split(`--${boundary}`).filter(part => part.trim());
    
    const formData = { fields: {}, files: {} };
    
    parts.forEach(part => {
      // 拆分字段头部和内容(以两个换行符为分隔)
      const [headerSection, content] = part.split('\r\n\r\n');
      if (!headerSection || !content) return;
      
      // 从头部提取字段名和文件名
      const nameMatch = headerSection.match(/name="([^"]+)"/);
      const filenameMatch = headerSection.match(/filename="([^"]+)"/);
      
      if (nameMatch) {
        const fieldName = nameMatch[1];
        if (filenameMatch) {
          // 处理文件字段:移除内容末尾换行符并转为Buffer
          const filename = filenameMatch[1];
          const fileBuffer = Buffer.from(content.trimEnd(), 'binary');
          formData.files[fieldName] = { filename, buffer: fileBuffer };
        } else {
          // 处理文本字段
          formData.fields[fieldName] = content.trimEnd();
        }
      }
    });
    
    // 拿到解析后的Form Data,开始处理文件
    handleProcessedFile(formData);
  });

4. 处理文件并多位置保存

使用sharp库调整图片尺寸和画质,并行保存至多个路径:

const sharp = require('sharp');
const fs = require('fs/promises');

async function handleProcessedFile(formData) {
  // 假设客户端上传的文件字段名为avatar,文本字段名为username
  const { avatar } = formData.files;
  const { username } = formData.fields;
  
  // 调整图片参数:裁剪为300x300,JPG画质80%
  const processedBuffer = await sharp(avatar.buffer)
    .resize(300, 300, { fit: 'cover' })
    .jpeg({ quality: 80 })
    .toBuffer();
  
  // 并行保存到多个位置
  await Promise.all([
    fs.writeFile(`./uploads/origin/${avatar.filename}`, avatar.buffer), // 原图
    fs.writeFile(`./uploads/thumb/${username}_avatar.jpg`, processedBuffer), // 缩略图
    fs.writeFile(`./uploads/backup/${avatar.filename}`, processedBuffer) // 备份
  ]);
  
  // 通知客户端上传完成
  ws.send(JSON.stringify({ type: 'uploadComplete', msg: '文件处理并保存完成' }));
  
  res.writeHead(200);
  res.end('上传成功');
}

注意事项

  • 大文件上传时不要一次性将所有数据块存入内存,需改为流式解析,避免内存溢出
  • 文本字段包含非ASCII字符时,确保编码为UTF-8
  • 增加错误捕获:解析失败、文件处理出错时,及时向客户端返回错误响应并通过WebSocket同步错误信息

内容的提问来源于stack exchange,提问作者Theme Farmer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:17:40