您好,登录后才能下订单哦!
# 怎么用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
# 全局安装nodemon(开发热更新)
npm install -g nodemon
mkdir image-upload-website
cd image-upload-website
npm init -y
npm install express multer mongoose bcryptjs ejs dotenv
/public
/uploads
/css
/js
/views
index.ejs
gallery.ejs
login.ejs
/routes
upload.js
auth.js
app.js
.env
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}`);
});
创建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;
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;
<!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>
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('上传过程中发生错误');
}
});
});
// 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;
<!-- 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>
// 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);
// 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;
npm install pm2 -g
pm2 start app.js --name "image-upload"
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;
}
}
# .env
PORT=3000
MONGODB_URI=mongodb://localhost:27017/image_upload
JWT_SECRET=your_jwt_secret_here
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());
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. 根据实际需求调整功能细节
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。