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

Node.js服务器文件上传的优化与安全问题咨询

解决分块上传引发的服务器内存耗尽风险

这确实是分块上传场景里很典型的恶意攻击风险,咱们一步步拆解可行的解决思路:

核心问题分析

你当前把所有未完成的分块都存在堆内存的Uint8数组里,一旦恶意客户端发起大量半截上传请求,这些占内存的分块会持续堆积,直接把服务器内存撑爆。下面是几个针对性的解决方案,按落地优先级排序:


1. 把临时分块从内存迁移到磁盘存储

这是最直接有效的办法——别再把分块存在堆内存里了,改成存在服务器本地的临时目录。给每个上传任务分配单独的子文件夹,分块按序号命名(比如chunk_1.dat、chunk_2.dat),等所有分块上传完成后,再从磁盘读取合并成完整文件,最后清理临时文件。

示例代码思路:

const fs = require('fs').promises;
const path = require('path');

// 为每个上传任务创建专属临时目录
async function createTempUploadDir(uploadId) {
  const tempDir = path.join(__dirname, 'temp_uploads', uploadId);
  await fs.mkdir(tempDir, { recursive: true });
  return tempDir;
}

// 将接收的分块写入磁盘
async function saveChunkToDisk(tempDir, chunkIndex, chunkBuffer) {
  const chunkPath = path.join(tempDir, `chunk_${chunkIndex}`);
  await fs.writeFile(chunkPath, chunkBuffer);
}

这样内存里只会留存当前正在处理的单个分块,不会堆积大量未完成任务的数据。

2. 给未完成上传任务设置超时清理机制

前端会在2小时内重试,那咱们给每个上传任务设置2.5小时的超时阈值(留一点缓冲时间)。用定时器或定时任务,定期扫描所有未完成的上传任务,超过超时时间的直接删除对应临时文件/清理残留数据。

示例实现:

const activeUploads = new Map(); // key: uploadId, value: { lastActiveTime: Date, tempDir: string }

// 每次收到分块时更新任务活动时间
function refreshUploadActivity(uploadId) {
  const uploadInfo = activeUploads.get(uploadId);
  if (uploadInfo) {
    uploadInfo.lastActiveTime = new Date();
  }
}

// 每30分钟清理一次超时任务
setInterval(async () => {
  const now = new Date();
  const timeoutMs = 2.5 * 60 * 60 * 1000; // 2.5小时
  for (const [uploadId, info] of activeUploads.entries()) {
    if (now - info.lastActiveTime > timeoutMs) {
      // 删除临时目录及分块文件
      await fs.rm(info.tempDir, { recursive: true, force: true });
      activeUploads.delete(uploadId);
    }
  }
}, 30 * 60 * 1000);

3. 限制单个客户端的并发上传任务数

恶意客户端可能同时发起上百个上传任务来消耗内存,所以要限制每个IP(或已登录用户ID)的并发上传数量。比如用一个IP计数器:

const ipUploadCounts = new Map(); // key: clientIp, value: number

// 处理上传请求前检查并发数
function checkUploadLimit(clientIp) {
  const currentCount = ipUploadCounts.get(clientIp) || 0;
  if (currentCount >= 5) { // 限制每个IP最多5个并发上传
    return false;
  }
  ipUploadCounts.set(clientIp, currentCount + 1);
  return true;
}

// 上传完成/超时后减少计数
function decreaseUploadCount(clientIp) {
  const currentCount = ipUploadCounts.get(clientIp) || 0;
  if (currentCount > 0) {
    ipUploadCounts.set(clientIp, currentCount - 1);
  } else {
    ipUploadCounts.delete(clientIp);
  }
}

如果有用户系统,用用户ID代替IP会更准确,避免IP代理绕过限制。

4. 启用内存监控与告警

在服务器中加入内存监控逻辑,当堆内存使用率超过阈值(比如70%)时,暂时拒绝新的上传请求,同时触发告警通知运维人员。

示例监控代码:

let isAcceptingUploads = true;

function checkMemoryStatus() {
  const memUsage = process.memoryUsage();
  const heapUsedPercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
  if (heapUsedPercent > 70 && isAcceptingUploads) {
    isAcceptingUploads = false;
    console.warn(`内存使用率过高:${heapUsedPercent.toFixed(2)}%,暂停接收新上传`);
    // 这里可以添加告警逻辑,比如发邮件/企业微信通知
    // 5分钟后自动恢复接收
    setTimeout(() => {
      isAcceptingUploads = true;
      console.log("恢复接收新上传请求");
    }, 5 * 60 * 1000);
  }
}

// 每分钟检查一次内存状态
setInterval(checkMemoryStatus, 60 * 1000);

总结一下,核心思路就是减少内存中留存的临时数据+主动清理过期资源+限制恶意请求规模,这几个方案组合起来就能有效抵御这类内存耗尽攻击。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:37:28