Appearance
Node.js 实战案例
本章通过实际案例展示Node.js在不同场景下的应用,包括完整的项目架构、实现细节和最佳实践。
案例一:RESTful API服务
项目概述
构建一个完整的RESTful API服务,包含用户管理、认证和数据操作功能。
项目结构
user-api/
├── src/
│ ├── controllers/
│ │ ├── user.controller.js
│ │ └── auth.controller.js
│ ├── models/
│ │ ├── user.model.js
│ │ └── index.js
│ ├── routes/
│ │ ├── users.route.js
│ │ └── auth.route.js
│ ├── middleware/
│ │ ├── auth.middleware.js
│ │ ├── validation.middleware.js
│ │ └── error.middleware.js
│ ├── services/
│ │ ├── user.service.js
│ │ └── auth.service.js
│ ├── utils/
│ │ ├── database.js
│ │ └── logger.js
│ └── app.js
├── tests/
├── config/
├── .env
├── package.json
└── README.md
核心代码实现
javascript
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const userRoutes = require('./routes/users.route');
const authRoutes = require('./routes/auth.route');
const errorMiddleware = require('./middleware/error.middleware');
const logger = require('./utils/logger');
const app = express();
// 安全中间件
app.use(helmet());
app.use(cors());
// 速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 限制每个IP 15分钟内最多100个请求
});
app.use(limiter);
// 解析JSON
app.use(express.json({ limit: '10mb' }));
// 请求日志
app.use(logger.requestLogger);
// 路由
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
// 健康检查端点
app.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});
// 404处理
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// 全局错误处理
app.use(errorMiddleware);
module.exports = app;
javascript
// src/models/user.model.js
const { DataTypes } = require('sequelize');
const sequelize = require('../utils/database');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
len: [1, 100]
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [6, 100]
}
},
role: {
type: DataTypes.ENUM('user', 'admin', 'moderator'),
defaultValue: 'user'
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'users',
timestamps: true,
indexes: [
{
unique: true,
fields: ['email']
}
]
});
module.exports = User;
javascript
// src/services/user.service.js
const User = require('../models/user.model');
const bcrypt = require('bcryptjs');
class UserService {
async createUser(userData) {
// 检查用户是否已存在
const existingUser = await User.findOne({
where: { email: userData.email }
});
if (existingUser) {
throw new Error('用户已存在');
}
// 哈希密码
const hashedPassword = await bcrypt.hash(userData.password, 12);
// 创建用户
const user = await User.create({
...userData,
password: hashedPassword
});
// 返回不包含密码的用户信息
const { password, ...userWithoutPassword } = user.toJSON();
return userWithoutPassword;
}
async getUserById(id) {
const user = await User.findByPk(id);
if (!user) {
throw new Error('用户不存在');
}
const { password, ...userWithoutPassword } = user.toJSON();
return userWithoutPassword;
}
async updateUser(id, updateData) {
const user = await User.findByPk(id);
if (!user) {
throw new Error('用户不存在');
}
if (updateData.password) {
updateData.password = await bcrypt.hash(updateData.password, 12);
}
await user.update(updateData);
const { password, ...userWithoutPassword } = user.toJSON();
return userWithoutPassword;
}
async deleteUser(id) {
const user = await User.findByPk(id);
if (!user) {
throw new Error('用户不存在');
}
await user.destroy();
return { message: '用户删除成功' };
}
async getAllUsers(page = 1, limit = 10) {
const offset = (page - 1) * limit;
const { count, rows } = await User.findAndCountAll({
attributes: { exclude: ['password'] },
limit: parseInt(limit),
offset: parseInt(offset),
order: [['createdAt', 'DESC']]
});
return {
users: rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
};
}
}
module.exports = new UserService();
案例二:实时聊天应用
项目概述
使用Node.js和Socket.IO构建实时聊天应用,支持多房间聊天、用户状态显示等功能。
核心实现
javascript
// chat-server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: process.env.CLIENT_URL || "http://localhost:3000",
methods: ["GET", "POST"]
}
});
// 在线用户管理
const users = new Map();
io.use((socket, next) => {
const user = socket.handshake.auth;
if (user && user.id && user.name) {
socket.userId = user.id;
socket.userName = user.name;
next();
} else {
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
console.log(`用户 ${socket.userName} 已连接`);
// 添加用户到在线列表
users.set(socket.userId, {
id: socket.userId,
name: socket.userName,
socketId: socket.id,
joinedAt: new Date()
});
// 通知其他用户
socket.broadcast.emit('user_joined', {
id: socket.userId,
name: socket.userName
});
// 更新在线用户列表
io.emit('users_list', Array.from(users.values()));
// 加入房间
socket.on('join_room', (room) => {
socket.join(room);
socket.to(room).emit('user_joined_room', {
userId: socket.userId,
userName: socket.userName,
room: room
});
});
// 处理消息
socket.on('send_message', (data) => {
const message = {
id: Date.now().toString(),
userId: socket.userId,
userName: socket.userName,
message: data.message,
timestamp: new Date(),
room: data.room || null
};
if (data.room) {
// 房间消息
io.to(data.room).emit('receive_message', message);
} else {
// 全局消息
io.emit('receive_message', message);
}
});
// 用户离开
socket.on('disconnect', () => {
console.log(`用户 ${socket.userName} 已断开连接`);
users.delete(socket.userId);
io.emit('user_left', {
id: socket.userId,
name: socket.userName
});
io.emit('users_list', Array.from(users.values()));
});
// 用户状态更新
socket.on('user_typing', (data) => {
socket.to(data.room).emit('user_typing', {
userId: socket.userId,
userName: socket.userName,
room: data.room
});
});
});
const PORT = process.env.PORT || 4000;
server.listen(PORT, () => {
console.log(`聊天服务器运行在端口 ${PORT}`);
});
案例三:微服务架构
项目概述
构建基于Node.js的微服务架构,包含用户服务、订单服务和API网关。
API网关实现
javascript
// gateway.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const app = express();
// 速率限制
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100,
message: '请求过于频繁'
});
app.use(apiLimiter);
// 服务发现和路由
const userProxy = createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true,
pathRewrite: {
'^/api/users': '/users'
}
});
const orderProxy = createProxyMiddleware({
target: 'http://order-service:3002',
changeOrigin: true,
pathRewrite: {
'^/api/orders': '/orders'
}
});
// 路由
app.use('/api/users', userProxy);
app.use('/api/orders', orderProxy);
// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'OK',
services: {
user: 'running',
order: 'running',
gateway: 'running'
}
});
});
app.listen(3000, () => {
console.log('API网关运行在端口 3000');
});
案例四:文件上传服务
项目概述
构建安全的文件上传服务,支持多格式验证、病毒扫描和云存储集成。
核心实现
javascript
// file-upload-service.js
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs').promises;
const { v4: uuidv4 } = require('uuid');
const sharp = require('sharp'); // 图片处理
const app = express();
// 配置multer存储
const storage = multer.diskStorage({
destination: async (req, file, cb) => {
// 创建按日期组织的目录结构
const date = new Date();
const dir = path.join('uploads',
date.getFullYear().toString(),
(date.getMonth() + 1).toString().padStart(2, '0'),
date.getDate().toString().padStart(2, '0')
);
try {
await fs.mkdir(dir, { recursive: true });
cb(null, dir);
} catch (err) {
cb(err);
}
},
filename: (req, file, cb) => {
// 生成安全的文件名
const uniqueName = `${uuidv4()}-${Date.now()}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
// 文件过滤器
const fileFilter = (req, file, cb) => {
// 允许的MIME类型
const allowedMimeTypes = [
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
'application/pdf',
'text/plain',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
if (allowedMimeTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('不支持的文件类型'), false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5 // 最多5个文件
},
fileFilter: fileFilter
});
// 图片优化中间件
const optimizeImage = async (req, res, next) => {
if (!req.file) return next();
const file = req.file;
// 只对图片文件进行优化
if (file.mimetype.startsWith('image/')) {
try {
await sharp(file.path)
.resize(1920, 1080, {
fit: 'inside',
withoutEnlargement: true
})
.jpeg({ quality: 80 })
.toFile(file.path + '.optimized');
// 替换原文件
await fs.unlink(file.path);
await fs.rename(file.path + '.optimized', file.path);
req.optimizedFile = file;
} catch (err) {
console.error('图片优化失败:', err);
}
}
next();
};
// 上传端点
app.post('/upload',
upload.array('files', 5),
optimizeImage,
(req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: '未选择文件' });
}
const uploadedFiles = req.files.map(file => ({
originalName: file.originalname,
filename: file.filename,
path: file.path,
size: file.size,
mimetype: file.mimetype
}));
res.json({
message: '文件上传成功',
files: uploadedFiles
});
}
);
// 文件下载
app.get('/download/:filename', async (req, res) => {
try {
const filename = req.params.filename;
const filePath = path.join('uploads', filename);
// 验证文件路径(防止路径遍历)
const resolvedPath = path.resolve(filePath);
const baseDir = path.resolve('uploads');
if (!resolvedPath.startsWith(baseDir)) {
return res.status(403).json({ error: '访问被拒绝' });
}
await fs.access(filePath);
res.download(filePath);
} catch (err) {
res.status(404).json({ error: '文件未找到' });
}
});
app.listen(3000, () => {
console.log('文件上传服务运行在端口 3000');
});
案例五:定时任务服务
项目概述
构建基于Node.js的定时任务服务,支持任务调度、监控和管理。
核心实现
javascript
// task-scheduler.js
const cron = require('node-cron');
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const fs = require('fs').promises;
class TaskScheduler {
constructor() {
this.tasks = new Map();
this.taskLogs = [];
this.maxLogEntries = 1000;
}
// 添加定时任务
addTask(name, cronExpression, taskFunction, options = {}) {
const task = cron.schedule(cronExpression, async () => {
const logEntry = {
id: Date.now().toString(),
name: name,
timestamp: new Date().toISOString(),
status: 'running'
};
try {
console.log(`执行任务: ${name}`);
await taskFunction();
logEntry.status = 'completed';
logEntry.duration = Date.now() - parseInt(logEntry.id);
} catch (error) {
logEntry.status = 'failed';
logEntry.error = error.message;
console.error(`任务 ${name} 执行失败:`, error);
}
// 记录日志
this.taskLogs.push(logEntry);
if (this.taskLogs.length > this.maxLogEntries) {
this.taskLogs.shift();
}
// 保存日志到文件
this.saveTaskLogs();
}, {
scheduled: options.scheduled !== false,
timezone: options.timezone || 'Asia/Shanghai'
});
this.tasks.set(name, {
cron: task,
expression: cronExpression,
function: taskFunction,
options: options
});
console.log(`任务 ${name} 已添加: ${cronExpression}`);
}
// 启动任务
startTask(name) {
const task = this.tasks.get(name);
if (task) {
task.cron.start();
console.log(`任务 ${name} 已启动`);
}
}
// 停止任务
stopTask(name) {
const task = this.tasks.get(name);
if (task) {
task.cron.stop();
console.log(`任务 ${name} 已停止`);
}
}
// 获取任务状态
getTaskStatus(name) {
const task = this.tasks.get(name);
if (!task) return null;
return {
name: name,
expression: task.expression,
running: task.cron.running,
options: task.options
};
}
// 获取所有任务状态
getAllTasks() {
const tasks = [];
for (const [name, task] of this.tasks) {
tasks.push(this.getTaskStatus(name));
}
return tasks;
}
// 获取任务日志
getTaskLogs(limit = 50) {
return this.taskLogs.slice(-limit).reverse();
}
// 保存日志到文件
async saveTaskLogs() {
try {
await fs.writeFile('task-logs.json', JSON.stringify(this.taskLogs, null, 2));
} catch (error) {
console.error('保存任务日志失败:', error);
}
}
// 加载日志
async loadTaskLogs() {
try {
const data = await fs.readFile('task-logs.json', 'utf8');
this.taskLogs = JSON.parse(data);
} catch (error) {
// 文件不存在或解析失败,使用空数组
this.taskLogs = [];
}
}
}
// 初始化任务调度器
const scheduler = new TaskScheduler();
// 示例任务
scheduler.addTask(
'daily-report',
'0 9 * * *', // 每天上午9点执行
async () => {
console.log('生成每日报告...');
// 这里放置实际的任务逻辑
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('每日报告生成完成');
},
{ timezone: 'Asia/Shanghai' }
);
scheduler.addTask(
'cleanup-temp-files',
'0 */6 * * *', // 每6小时执行
async () => {
console.log('清理临时文件...');
// 清理逻辑
await new Promise(resolve => setTimeout(resolve, 500));
console.log('临时文件清理完成');
}
);
// 启动调度器
async function startScheduler() {
await scheduler.loadTaskLogs();
console.log('任务调度器已启动');
}
startScheduler();
// API端点(如果需要)
const express = require('express');
const api = express();
api.get('/tasks', (req, res) => {
res.json(scheduler.getAllTasks());
});
api.get('/tasks/:name', (req, res) => {
const status = scheduler.getTaskStatus(req.params.name);
if (!status) {
return res.status(404).json({ error: '任务未找到' });
}
res.json(status);
});
api.get('/logs', (req, res) => {
const limit = parseInt(req.query.limit) || 50;
res.json(scheduler.getTaskLogs(limit));
});
api.listen(3001, () => {
console.log('任务调度API运行在端口 3001');
});
这些实战案例展示了Node.js在不同场景下的应用,涵盖了从基础API服务到复杂微服务架构的完整实现方案。