溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么用nodejs搭建一個圖片上傳網站

發布時間:2021-11-17 14:54:51 來源:億速云 閱讀:165 作者:iii 欄目:web開發
# 怎么用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. 根據實際需求調整功能細節

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女