WEB-24:网站文件上传处理

王尘宇 网站建设 3

网站文件上传处理 是通过安全的文件验证、合理的存储方案、高效的上传体验、完善的文件管理,使用户能够安全便捷地上传和管理文件的技术开发方法。


文件上传场景

常见场景 ⭐⭐⭐⭐⭐

用户头像:

- 图片上传
- 裁剪调整
- 多尺寸生成
- CDN 存储

文档资料:

- PDF/Word 上传
- 文件大小限制
- 病毒扫描
- 分类存储

图片画廊:

- 多图上传
- 拖拽排序
- 批量处理
- 缩略图生成

大文件传输:

- 分片上传
- 断点续传
- 进度显示
- 高速传输

安全设计

文件验证 ⭐⭐⭐⭐⭐

类型验证:

// 前端验证
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];

function validateFileType(file) {
  return allowedTypes.includes(file.type);
}

// 后端验证(必须)
const mime = require('mime-types');
const ext = mime.extension(file.mimetype);

const allowedExts = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'];
if (!allowedExts.includes(ext)) {
  throw new Error('不支持的文件类型');
}

大小限制:

// 前端验证
const maxSize = 5 * 1024 * 1024; // 5MB

if (file.size > maxSize) {
  throw new Error('文件大小不能超过 5MB');
}

// 后端验证(必须)
app.use(upload({
  limits: {
    fileSize: 5 * 1024 * 1024
  }
}));

文件名安全:

// 文件名处理
const path = require('path');
const crypto = require('crypto');

function sanitizeFilename(filename) {
  const ext = path.extname(filename);
  const safeName = crypto.randomBytes(16).toString('hex');
  return safeName + ext;
}

// 防止目录遍历
const basename = path.basename(filename);

病毒扫描 ⭐⭐⭐⭐

扫描方案:

// 使用 ClamAV
const NodeClam = require('clamscan');

const clamscan = new NodeClam().init({
  remove_infected: true,
  quarantine_infected: false,
  scan_log: '/var/log/clamav.log'
});

async function scanFile(filePath) {
  const { is_infected, viruses } = await clamscan.is_infected(filePath);

  if (is_infected) {
    throw new Error(`发现病毒:${viruses.join(', ')}`);
  }

  return true;
}

上传限制:

✅ 限制文件类型
✅ 限制文件大小
✅ 限制上传频率
✅ 登录用户才能上传

存储方案

本地存储 ⭐⭐⭐

适用场景:

✅ 小网站
✅ 预算有限
✅ 文件量少
✅ 单服务器

目录结构:

uploads/
├── avatars/
│   ├── 2026/
│   │   ├── 03/
│   │   │   └── abc123.jpg
├── documents/
│   └── 2026/
│       └── 03/
│           └── def456.pdf
└── temp/

代码实现:

const multer = require('multer');
const path = require('path');

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const date = new Date();
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const dir = `uploads/${file.fieldname}/${year}/${month}`;

    // 创建目录
    require('fs').mkdirSync(dir, { recursive: true });
    cb(null, dir);
  },
  filename: (req, file, cb) => {
    const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1E9);
    const ext = path.extname(file.originalname);
    cb(null, uniqueName + ext);
  }
});

const upload = multer({ storage });

云存储 ⭐⭐⭐⭐⭐

适用场景:

✅ 中大型网站
✅ 文件量大
✅ 多服务器
✅ 需要 CDN

阿里云 OSS:

const OSS = require('ali-oss');

const client = new OSS({
  region: 'oss-cn-beijing',
  accessKeyId: 'YOUR_ACCESS_KEY',
  accessKeySecret: 'YOUR_ACCESS_SECRET',
  bucket: 'your-bucket'
});

async function uploadToOSS(file, filename) {
  const result = await client.put(filename, file.path);
  return result.url;
}

腾讯云 COS:

const COS = require('cos-nodejs-sdk-v5');

const cos = new COS({
  SecretId: 'YOUR_SECRET_ID',
  SecretKey: 'YOUR_SECRET_KEY',
  Bucket: 'your-bucket',
  Region: 'ap-beijing'
});

async function uploadToCOS(file, filename) {
  return new Promise((resolve, reject) => {
    cos.putObject({
      Key: filename,
      Body: file
    }, (err, data) => {
      if (err) reject(err);
      else resolve(`https://${data.Location}`);
    });
  });
}

七牛云:

const qiniu = require('qiniu');

// 配置上传策略
const mac = new qiniu.auth.digest.Mac('ACCESS_KEY', 'SECRET_KEY');
const options = {
  scope: 'your-bucket',
  expires: 3600
};
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);

上传体验优化

进度显示 ⭐⭐⭐⭐⭐

前端实现:

0%

拖拽上传 ⭐⭐⭐⭐

拖拽实现:

拖拽文件到此处,或点击选择文件

断点续传 ⭐⭐⭐⭐

分片上传:

// 前端分片
async function uploadLargeFile(file) {
  const chunkSize = 5 * 1024 * 1024; // 5MB 每片
  const totalChunks = Math.ceil(file.size / chunkSize);

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('chunkIndex', i);
    formData.append('totalChunks', totalChunks);
    formData.append('fileId', file.name);

    await fetch('/upload/chunk', {
      method: 'POST',
      body: formData
    });
  }

  // 通知合并
  await fetch('/upload/merge', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ fileId: file.name, chunks: totalChunks })
  });
}

图片处理

缩略图生成 ⭐⭐⭐⭐⭐

使用 Sharp:

const sharp = require('sharp');

async function generateThumbnails(inputPath, outputPath) {
  // 生成多种尺寸
  await Promise.all([
    sharp(inputPath)
      .resize(100, 100, { fit: 'cover' })
      .toFile(`${outputPath}_thumb_100.jpg`),

    sharp(inputPath)
      .resize(300, 300, { fit: 'cover' })
      .toFile(`${outputPath}_thumb_300.jpg`),

    sharp(inputPath)
      .resize(800, 800, { fit: 'max' })
      .toFile(`${outputPath}_medium.jpg`)
  ]);
}

图片压缩:

async function compressImage(inputPath, outputPath, quality = 80) {
  await sharp(inputPath)
    .jpeg({ quality })
    .toFile(outputPath);
}

图片裁剪 ⭐⭐⭐⭐

前端裁剪:




WEB-24:网站文件上传处理-第1张图片-王尘宇



文件管理

数据库设计 ⭐⭐⭐⭐

CREATE TABLE files (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    filename_original VARCHAR(255) NOT NULL,
    filename_stored VARCHAR(255) NOT NULL,
    file_path VARCHAR(500) NOT NULL,
    file_url VARCHAR(500) NOT NULL,
    file_type VARCHAR(50),
    file_size INT NOT NULL,
    mime_type VARCHAR(100),

    -- 分类
    category VARCHAR(50),
    tags JSON,

    -- 状态
    status ENUM('pending', 'scanned', 'approved', 'rejected') DEFAULT 'pending',
    is_public BOOLEAN DEFAULT FALSE,

    -- 统计
    download_count INT DEFAULT 0,
    view_count INT DEFAULT 0,

    -- 时间
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_user (user_id),
    INDEX idx_category (category),
    INDEX idx_status (status)
);

文件列表 ⭐⭐⭐⭐

API 实现:

// 获取文件列表
app.get('/api/files', async (req, res) => {
  const { page = 1, limit = 20, category, search } = req.query;

  const where = {};
  if (category) where.category = category;
  if (search) {
    where.filename_original = { [Op.like]: `%${search}%` };
  }

  const files = await File.findAndCountAll({
    where,
    order: [['created_at', 'DESC']],
    limit: parseInt(limit),
    offset: (page - 1) * parseInt(limit)
  });

  res.json({
    success: true,
    data: files.rows,
    pagination: {
      total: files.count,
      page: parseInt(page),
      limit: parseInt(limit)
    }
  });
});

王尘宇实战建议

18 年经验总结

  1. 安全第一
  2. 严格验证
  3. 病毒扫描
  4. 权限控制

  5. 体验重要

  6. 进度显示
  7. 拖拽上传
  8. 错误友好

  9. 存储选择

  10. 小站本地
  11. 大站云端
  12. CDN 加速

  13. 图片优化

  14. 缩略图
  15. 压缩
  16. 裁剪

  17. 管理规范

  18. 数据库记录
  19. 分类管理
  20. 权限控制

西安企业建议

  • 根据业务选择方案
  • 重视安全验证
  • 优化上传体验
  • 合规存储

常见问题解答

Q1:文件存储在哪里好?

答:
- 小网站:本地存储
- 大网站:云存储 +CDN
- 图片多:对象存储
- 根据预算

Q2:如何防止恶意上传?

答:
- 文件类型验证
- 大小限制
- 频率限制
- 登录验证
- 病毒扫描

Q3:大文件如何上传?

答:
- 分片上传
- 断点续传
- 进度显示
- 超时处理

Q4:图片需要处理吗?

答:
需要:
- 缩略图
- 压缩
- 裁剪
- 多尺寸

Q5:如何管理上传的文件?

答:
- 数据库记录
- 分类标签
- 搜索功能
- 权限控制


总结

网站文件上传处理核心要点:

  • 🔒 安全验证 — 类型、大小、病毒
  • ☁️ 存储方案 — 本地、云端、CDN
  • 📤 上传体验 — 进度、拖拽、断点
  • 🖼️ 图片处理 — 缩略、压缩、裁剪
  • 📁 文件管理 — 数据库、分类、权限

王尘宇建议: 文件上传是常见功能。做好安全验证和用户体验,选择合适的存储方案。


关于作者

王尘宇
西安蓝蜻蜓网络科技有限公司创始人

联系方式:
- 🌐 网站:wangchenyu.com
- 💬 微信:wangshifucn
- 📱 QQ:314111741
- 📍 地址:陕西西安


本文最后更新:2026 年 3 月 18 日
版权声明:本文为王尘宇原创,属于"网站建设系列"第 24 篇,转载请联系作者并注明出处。
下一篇:WEB-25:网站邮件系统配置

标签: 网站建设

发布评论 0条评论)

  • Refresh code

还木有评论哦,快来抢沙发吧~