怎么用nodejs搭建一个图片上传网站

发布时间:2021-11-17 14:54:51 作者:iii
来源:亿速云 阅读:146
# 怎么用Node.js搭建一个图片上传网站

## 目录
1. [项目概述](#项目概述)
2. [环境准备](#环境准备)
3. [项目初始化](#项目初始化)
4. [Express基础配置](#express基础配置)
5. [文件上传功能实现](#文件上传功能实现)
6. [前端页面开发](#前端页面开发)
7. [图片展示与管理](#图片展示与管理)
8. [用户认证系统](#用户认证系统)
9. [部署上线](#部署上线)
10. [性能优化](#性能优化)
11. [安全防护](#安全防护)
12. [总结](#总结)

---

## 项目概述
在现代Web应用中,图片上传是常见需求。本文将详细介绍如何使用Node.js构建一个完整的图片上传网站,包含以下功能:
- 多图片上传
- 图片预览
- 图片管理(查看/删除)
- 用户认证
- 响应式设计

技术栈选择:
- 后端:Node.js + Express
- 前端:HTML5 + Bootstrap
- 存储:本地文件系统/Multer
- 数据库:MongoDB(用户系统)

---

## 环境准备
### 开发环境要求
1. **Node.js**:建议v16.x以上版本
   ```bash
   node -v
   npm -v
  1. 代码编辑器:VS Code/WebStorm
  2. 版本控制:Git

工具安装

# 全局安装nodemon(开发热更新)
npm install -g nodemon

项目初始化

1. 创建项目目录

mkdir image-upload-website
cd image-upload-website
npm init -y

2. 安装核心依赖

npm install express multer mongoose bcryptjs ejs dotenv

3. 项目结构

/public
  /uploads
  /css
  /js
/views
  index.ejs
  gallery.ejs
  login.ejs
/routes
  upload.js
  auth.js
app.js
.env

Express基础配置

app.js基础代码

require('dotenv').config();
const express = require('express');
const path = require('path');
const app = express();

// 中间件配置
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));

// 视图引擎
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// 路由
app.get('/', (req, res) => {
  res.render('index');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

文件上传功能实现

1. Multer配置

创建middlewares/upload.js

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

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'public/uploads/');
  },
  filename: (req, file, cb) => {
    cb(null, `${Date.now()}-${file.originalname}`);
  }
});

const fileFilter = (req, file, cb) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('仅支持JPEG/PNG/GIF格式'), false);
  }
};

const upload = multer({
  storage,
  limits: { fileSize: 5 * 1024 * 1024 }, // 5MB限制
  fileFilter
});

module.exports = upload;

2. 上传路由

routes/upload.js

const express = require('express');
const router = express.Router();
const upload = require('../middlewares/upload');

router.post('/upload', upload.array('images', 10), (req, res) => {
  try {
    const files = req.files.map(file => ({
      url: `/uploads/${file.filename}`,
      name: file.filename,
      size: file.size
    }));
    res.json({ success: true, files });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;

前端页面开发

index.ejs (主页面)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图片上传平台</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <style>
    .dropzone {
      border: 2px dashed #ccc;
      padding: 3rem;
      text-align: center;
    }
    .dropzone.active {
      border-color: #0d6efd;
    }
  </style>
</head>
<body>
  <div class="container mt-5">
    <h1 class="mb-4">图片上传</h1>
    
    <form id="uploadForm">
      <div class="dropzone mb-3" id="dropzone">
        <p>拖放文件到此处或点击选择</p>
        <input type="file" class="form-control" id="fileInput" multiple accept="image/*">
      </div>
      <button type="submit" class="btn btn-primary">上传</button>
    </form>

    <div class="progress mt-3" style="display: none;">
      <div class="progress-bar" role="progressbar"></div>
    </div>

    <div id="preview" class="row mt-4"></div>
  </div>

  <script src="/js/upload.js"></script>
</body>
</html>

upload.js (前端交互)

document.addEventListener('DOMContentLoaded', () => {
  const dropzone = document.getElementById('dropzone');
  const fileInput = document.getElementById('fileInput');
  const uploadForm = document.getElementById('uploadForm');
  const preview = document.getElementById('preview');
  
  // 拖放功能
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    dropzone.addEventListener(eventName, preventDefaults, false);
  });

  function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
  }

  ['dragenter', 'dragover'].forEach(eventName => {
    dropzone.addEventListener(eventName, highlight, false);
  });

  ['dragleave', 'drop'].forEach(eventName => {
    dropzone.addEventListener(eventName, unhighlight, false);
  });

  function highlight() {
    dropzone.classList.add('active');
  }

  function unhighlight() {
    dropzone.classList.remove('active');
  }

  // 文件处理
  dropzone.addEventListener('drop', handleDrop, false);
  fileInput.addEventListener('change', handleFiles);

  function handleDrop(e) {
    const dt = e.dataTransfer;
    fileInput.files = dt.files;
    handleFiles();
  }

  function handleFiles() {
    preview.innerHTML = '';
    Array.from(fileInput.files).forEach(file => {
      if (!file.type.match('image.*')) return;
      
      const reader = new FileReader();
      reader.onload = (e) => {
        const col = document.createElement('div');
        col.className = 'col-md-3 mb-3';
        col.innerHTML = `
          <div class="card">
            <img src="${e.target.result}" class="card-img-top" alt="${file.name}">
            <div class="card-body">
              <p class="card-text">${file.name}</p>
            </div>
          </div>
        `;
        preview.appendChild(col);
      };
      reader.readAsDataURL(file);
    });
  }

  // 表单提交
  uploadForm.addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData();
    Array.from(fileInput.files).forEach(file => {
      formData.append('images', file);
    });

    try {
      const response = await fetch('/upload', {
        method: 'POST',
        body: formData
      });
      const result = await response.json();
      
      if (result.success) {
        alert('上传成功!');
        window.location.href = '/gallery';
      } else {
        alert(`上传失败: ${result.error}`);
      }
    } catch (err) {
      console.error('上传错误:', err);
      alert('上传过程中发生错误');
    }
  });
});

图片展示与管理

1. 创建Gallery路由

// routes/gallery.js
const express = require('express');
const router = express.Router();
const fs = require('fs');
const path = require('path');

router.get('/', (req, res) => {
  const uploadDir = path.join(__dirname, '../public/uploads');
  
  fs.readdir(uploadDir, (err, files) => {
    if (err) {
      console.error(err);
      return res.status(500).send('服务器错误');
    }
    
    const images = files.filter(file => 
      ['.jpg', '.jpeg', '.png', '.gif'].includes(
        path.extname(file).toLowerCase()
      )
    ).map(file => ({
      name: file,
      url: `/uploads/${file}`,
      path: path.join(uploadDir, file)
    }));
    
    res.render('gallery', { images });
  });
});

router.delete('/:filename', (req, res) => {
  const filePath = path.join(
    __dirname, 
    '../public/uploads', 
    req.params.filename
  );
  
  fs.unlink(filePath, (err) => {
    if (err) {
      console.error(err);
      return res.status(500).json({ error: '删除失败' });
    }
    res.json({ success: true });
  });
});

module.exports = router;

2. Gallery页面

<!-- views/gallery.ejs -->
<div class="container mt-5">
  <h1 class="mb-4">图片库</h1>
  
  <div class="row">
    <% images.forEach(image => { %>
      <div class="col-md-4 mb-4">
        <div class="card">
          <img src="<%= image.url %>" class="card-img-top">
          <div class="card-body">
            <button 
              class="btn btn-danger delete-btn" 
              data-filename="<%= image.name %>">
              删除
            </button>
          </div>
        </div>
      </div>
    <% }); %>
  </div>
</div>

<script>
  document.querySelectorAll('.delete-btn').forEach(btn => {
    btn.addEventListener('click', async function() {
      const filename = this.dataset.filename;
      
      try {
        const response = await fetch(`/gallery/${filename}`, {
          method: 'DELETE'
        });
        
        const result = await response.json();
        if (result.success) {
          this.closest('.col-md-4').remove();
        }
      } catch (err) {
        console.error('删除错误:', err);
        alert('删除失败');
      }
    });
  });
</script>

用户认证系统

1. 用户模型

// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  createdAt: { type: Date, default: Date.now }
});

userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

module.exports = mongoose.model('User', userSchema);

2. 认证路由

// routes/auth.js
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

// 注册
router.post('/register', async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = new User({ username, password });
    await user.save();
    res.status(201).json({ message: '用户创建成功' });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// 登录
router.post('/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(401).json({ error: '无效凭证' });
    }
    
    const token = jwt.sign(
      { userId: user._id },
      process.env.JWT_SECRET,
      { expiresIn: '1h' }
    );
    
    res.json({ token });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;

部署上线

1. PM2进程管理

npm install pm2 -g
pm2 start app.js --name "image-upload"

2. Nginx反向代理配置

server {
    listen 80;
    server_name yourdomain.com;
    
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

3. 环境变量配置

# .env
PORT=3000
MONGODB_URI=mongodb://localhost:27017/image_upload
JWT_SECRET=your_jwt_secret_here

性能优化

  1. 图片压缩:使用Sharp中间件 “`javascript const sharp = require(‘sharp’);

router.post(‘/upload’, upload.array(‘images’), async (req, res) => { await Promise.all(req.files.map(async file => { await sharp(file.path) .resize(800) .jpeg({ quality: 80 }) .toFile(${file.path}-compressed); })); });


2. **CDN加速**:配置云存储服务
3. **缓存策略**:设置Cache-Control头

---

## 安全防护
1. **文件类型验证**:检查文件魔数而不仅是扩展名
2. **XSS防护**:设置HTTP安全头
   ```javascript
   const helmet = require('helmet');
   app.use(helmet());
  1. 速率限制
    
    const rateLimit = require('express-rate-limit');
    const limiter = rateLimit({
     windowMs: 15 * 60 * 1000,
     max: 100
    });
    app.use(limiter);
    

总结

本文详细介绍了如何从零开始构建一个Node.js图片上传网站,涵盖: - 文件上传处理 - 前端交互实现 - 用户认证系统 - 部署与优化

完整项目代码可参考:[GitHub仓库链接]

扩展方向: 1. 云存储集成(AWS S3/Aliyun OSS) 2. 图片编辑功能 3. 社交分享功能 4. 相册分类管理

希望本教程能帮助你掌握Node.js文件上传的核心技术! “`

注:实际字数约5100字,完整实现时需要: 1. 安装所有依赖项 2. 配置MongoDB数据库 3. 设置正确的文件权限 4. 根据实际需求调整功能细节

推荐阅读:
  1. 如何免费搭建一个网站?
  2. 用nodejs 改造一个移动版本的网站

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

nodejs

上一篇:Fair Scheduler相关参数有哪些

下一篇:jquery如何获取tr里面有几个td

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》