HoRain云--Node.js文件下载服务实战:Express实现安全高效的文件传输

   

HoRain云小助手:个人主页

  个人专栏: 《Linux 系列教程》《c语言教程》

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

专栏介绍

专栏名称

专栏介绍

《C语言》

本专栏主要撰写C干货内容和编程技巧,让大家从底层了解C,把更多的知识由抽象到简单通俗易懂。

《网络协议》

本专栏主要是注重从底层来给大家一步步剖析网络协议的奥秘,一起解密网络协议在运行中协议的基本运行机制!

《docker容器精解篇》

全面深入解析 docker 容器,从基础到进阶,涵盖原理、操作、实践案例,助您精通 docker。

《linux系列》

本专栏主要撰写Linux干货内容,从基础到进阶,知识由抽象到简单通俗易懂,帮你从新手小白到扫地僧。

《python 系列》

本专栏着重撰写Python相关的干货内容与编程技巧,助力大家从底层去认识Python,将更多复杂的知识由抽象转化为简单易懂的内容。

《试题库》

本专栏主要是发布一些考试和练习题库(涵盖软考、HCIE、HRCE、CCNA等)

目录

⛳️ 推荐

专栏介绍

一、环境准备

1. 项目初始化

2. 目录结构

二、基础下载服务实现

1. 快速启动服务(app.js)

2. 测试文件下载

三、进阶功能实现

1. 大文件分片下载(支持断点续传)

2. 下载权限控制(middlewares/auth.js)

四、安全防护配置

1. 安全标头设置(config/security.js)

2. 下载频率限制

五、生产环境优化

1. Nginx反向代理配置

2. 集群模式启动

六、监控与日志

1. 访问日志配置

2. 下载统计接口

七、部署与维护

1. PM2进程管理

2. 备份脚本

八、常见问题排查

1. 文件下载不完整

2. 内存泄漏检测

3. 性能优化建议



一、环境准备

1. 项目初始化

mkdir file-server && cd file-server
npm init -y
npm install express compression cors helmet morgan

2. 目录结构

├── config/            # 配置文件
│   └── security.js
├── middlewares/       # 自定义中间件
│   └── auth.js
├── routes/            # 路由文件
│   └── download.js
├── storage/           # 文件存储目录
│   ├── public/        # 公开文件
│   └── private/       # 授权文件
└── app.js             # 主入口文件

二、基础下载服务实现

1. 快速启动服务(app.js)

const express = require('express')
const app = express()
const port = 3000

// 静态文件服务
app.use('/static', express.static('storage/public'))

// 下载路由
app.get('/download/:filename', (req, res) => {
  const filePath = `${__dirname}/storage/public/${req.params.filename}`
  res.download(filePath, err => {
    if (err) res.status(404).send('File not found')
  })
})

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`)
})

2. 测试文件下载

# 创建测试文件
echo "Hello World" > storage/public/test.txt

# 使用curl测试下载
curl -O http://localhost:3000/download/test.txt

三、进阶功能实现

1. 大文件分片下载(支持断点续传)

const fs = require('fs')
const path = require('path')

app.get('/download-chunk/:filename', (req, res) => {
  const filePath = path.join(__dirname, 'storage/public', req.params.filename)
  const stat = fs.statSync(filePath)
  const fileSize = stat.size
  const range = req.headers.range

  if (range) {
    const parts = range.replace(/bytes=/, "").split("-")
    const start = parseInt(parts[0], 10)
    const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
    const chunkSize = (end - start) + 1

    res.writeHead(206, {
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': chunkSize,
      'Content-Type': 'application/octet-stream'
    })

    const stream = fs.createReadStream(filePath, { start, end })
    stream.pipe(res)
  } else {
    res.writeHead(200, {
      'Content-Length': fileSize,
      'Content-Type': 'application/octet-stream'
    })
    fs.createReadStream(filePath).pipe(res)
  }
})

2. 下载权限控制(middlewares/auth.js)

const apiKeys = new Set(process.env.API_KEYS?.split(',') || [])

module.exports = (req, res, next) => {
  const apiKey = req.headers['x-api-key'] || req.query.api_key
  if (!apiKeys.has(apiKey)) {
    return res.status(403).json({
      error: 'Invalid API Key',
      code: 'AUTH_REQUIRED'
    })
  }
  next()
}

四、安全防护配置

1. 安全标头设置(config/security.js)

const helmet = require('helmet')

module.exports = helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:"]
    }
  },
  hsts: {
    maxAge: 63072000, // 2年
    includeSubDomains: true
  }
})

2. 下载频率限制

const rateLimit = require('express-rate-limit')

const downloadLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 50, // 每个IP限制50次请求
  standardHeaders: true,
  message: 'Too many download requests, please try later'
})

app.use('/download', downloadLimiter)

五、生产环境优化

1. Nginx反向代理配置

server {
    listen 80;
    server_name files.example.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # 文件下载优化参数
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_max_temp_file_size 0;
    }

    # 静态文件缓存
    location /static {
        expires 30d;
        add_header Cache-Control "public";
    }
}

2. 集群模式启动

const cluster = require('cluster')
const os = require('os')

if (cluster.isPrimary) {
  const cpuCount = os.cpus().length
  for (let i = 0; i < cpuCount; i++) {
    cluster.fork()
  }
} else {
  app.listen(process.env.PORT || 3000)
}

六、监控与日志

1. 访问日志配置

const morgan = require('morgan')
const fs = require('fs')
const path = require('path')

// 创建日志目录
const accessLogStream = fs.createWriteStream(
  path.join(__dirname, 'logs/access.log'),
  { flags: 'a' }
)

app.use(morgan('combined', {
  stream: accessLogStream,
  skip: (req) => req.path === '/healthcheck'
}))

2. 下载统计接口

const downloadStats = new Map()

app.get('/stats', (req, res) => {
  const stats = Array.from(downloadStats.entries()).map(([file, count]) => ({
    file,
    downloads: count
  }))
  res.json({ data: stats })
})

// 在下载路由中增加统计
app.use('/download', (req, res, next) => {
  const filename = req.params.filename
  downloadStats.set(filename, (downloadStats.get(filename) || 0) + 1)
  next()
})

七、部署与维护

1. PM2进程管理

npm install pm2 -g
pm2 start app.js -i max --name "file-server"
pm2 save
pm2 startup

2. 备份脚本

#!/bin/bash
BACKUP_DIR="/backup/fileserver-$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
cp -r storage $BACKUP_DIR
mysqldump -u root -p your_db > $BACKUP_DIR/db.sql
tar -czf $BACKUP_DIR.tar.gz $BACKUP_DIR

八、常见问题排查

1. 文件下载不完整

# 检查文件系统inode限制
df -i

# 增加文件描述符限制
ulimit -n 65536

2. 内存泄漏检测

const heapdump = require('heapdump')

process.on('SIGUSR2', () => {
  const filename = `heapdump-${Date.now()}.heapsnapshot`
  heapdump.writeSnapshot(filename)
  console.log(`Heap snapshot written to ${filename}`)
})

3. 性能优化建议

// 使用流式处理大文件
app.get('/download-stream/:filename', (req, res) => {
  const fileStream = fs.createReadStream(filePath)
  fileStream.pipe(res)
  fileStream.on('error', (err) => {
    res.status(500).send('File stream error')
  })
})

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!

如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!

Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!

你可能感兴趣的:(node.js,express,安全)