个人主页:Guiat
归属专栏:node.js
文章目录
- 1. Node.js 全栈开发概述
- 1.1 全栈开发的优势
- 1.2 Node.js 全栈开发技术栈
- 2. 开发环境搭建
- 2.1 Node.js 和 npm 安装
- 2.2 开发工具安装
- 2.3 版本控制设置
- 2.4 项目初始化流程
- 3. 后端开发 (Node.js)
- 3.1 Express 框架基础
- 3.2 RESTful API 设计
- 3.3 中间件开发
- 3.4 数据库集成
- 3.5 身份验证和授权
- 3.6 后端架构
- 4. 前端开发
- 4.1 React 基础
- 4.2 状态管理 (Redux)
- 4.3 API 请求处理
- 4.4 前端路由 (React Router)
- 4.5 前端架构
- 5. 全栈集成
- 5.1 前后端通信
- 5.2 身份验证流程
- 5.3 完整的全栈应用架构
- 6. 数据建模与持久化
- 6.1 MongoDB 数据模型
- 6.2 MySQL 数据建模
- 6.3 数据关系建模
- 7. API 开发与设计模式
- 7.1 模块化路由
- 7.2 控制器模式
- 7.3 服务层模式
- 7.4 错误处理模式
- 8. 安全性与性能优化
- 8.1 安全最佳实践
- 8.2 性能优化技术
- 8.3 日志系统
- 9. 部署与 DevOps
- 9.1 Docker 容器化
- 9.2 CI/CD 流水线
- 9.3 部署流程
正文
1. Node.js 全栈开发概述
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,使开发者能够使用 JavaScript 进行服务器端编程。全栈开发指的是同时处理前端和后端的开发工作。
1.1 全栈开发的优势
- 使用统一的语言(JavaScript)开发前后端
- 减少技术栈上下文切换成本
- 提高开发效率和代码复用性
- 便于小团队快速构建完整应用
- 简化部署和维护流程
1.2 Node.js 全栈开发技术栈
2. 开发环境搭建
2.1 Node.js 和 npm 安装
Node.js 是全栈开发的基础环境,npm 是其包管理工具。
# 安装 Node.js 和 npm (macOS)
brew install node# 安装 Node.js 和 npm (Ubuntu)
sudo apt update
sudo apt install nodejs npm# 安装 Node.js 和 npm (Windows)
# 从 https://nodejs.org 下载安装程序# 验证安装
node --version
npm --version
2.2 开发工具安装
良好的开发工具可以提高开发效率:
# 安装常用全局工具
npm install -g nodemon # 自动重启服务器
npm install -g ts-node # TypeScript 执行环境
npm install -g create-react-app # React 项目生成器
npm install -g @vue/cli # Vue 项目生成器
npm install -g prettier # 代码格式化工具
2.3 版本控制设置
Git 是最流行的版本控制系统:
# 初始化 Git 仓库
git init# 创建 .gitignore 文件
echo "node_modules/\n.env\n.DS_Store\ndist/\nbuild/" > .gitignore# 配置用户信息
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
2.4 项目初始化流程
3. 后端开发 (Node.js)
3.1 Express 框架基础
Express 是 Node.js 最流行的 Web 应用框架之一。
// 基本 Express 服务器
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));// 路由
app.get('/', (req, res) => {res.send('Hello World!');
});// 启动服务器
app.listen(PORT, () => {console.log(`Server running on port ${PORT}`);
});
3.2 RESTful API 设计
RESTful API 是一种软件架构风格,用于设计网络应用。
// 用户资源的 RESTful API
const express = require('express');
const router = express.Router();// 获取所有用户
router.get('/users', (req, res) => {// 实现获取所有用户的逻辑res.json({ users: [] });
});// 获取单个用户
router.get('/users/:id', (req, res) => {const userId = req.params.id;// 实现获取单个用户的逻辑res.json({ user: { id: userId } });
});// 创建用户
router.post('/users', (req, res) => {const userData = req.body;// 实现创建用户的逻辑res.status(201).json({ user: userData });
});// 更新用户
router.put('/users/:id', (req, res) => {const userId = req.params.id;const userData = req.body;// 实现更新用户的逻辑res.json({ user: { id: userId, ...userData } });
});// 删除用户
router.delete('/users/:id', (req, res) => {const userId = req.params.id;// 实现删除用户的逻辑res.status(204).end();
});module.exports = router;
3.3 中间件开发
中间件是 Express 中处理请求和响应的功能模块。
// 认证中间件
const jwt = require('jsonwebtoken');const authMiddleware = (req, res, next) => {const token = req.header('Authorization')?.replace('Bearer ', '');if (!token) {return res.status(401).json({ message: '未提供访问令牌' });}try {const decoded = jwt.verify(token, process.env.JWT_SECRET);req.user = decoded;next();} catch (error) {return res.status(401).json({ message: '无效的访问令牌' });}
};// 错误处理中间件
const errorHandlerMiddleware = (err, req, res, next) => {console.error(err.stack);// 自定义错误响应if (err.name === 'ValidationError') {return res.status(400).json({ message: err.message });}res.status(500).json({ message: '服务器内部错误' });
};module.exports = { authMiddleware, errorHandlerMiddleware };
3.4 数据库集成
Node.js 可以与各种数据库集成。
// MongoDB 集成 (使用 Mongoose)
const mongoose = require('mongoose');// 连接 MongoDB
mongoose.connect(process.env.MONGODB_URI, {useNewUrlParser: true,useUnifiedTopology: true,
})
.then(() => console.log('MongoDB 连接成功'))
.catch((err) => console.error('MongoDB 连接失败:', err));// 定义用户模型
const userSchema = new mongoose.Schema({username: {type: String,required: true,unique: true,trim: true,},email: {type: String,required: true,unique: true,trim: true,lowercase: true,},password: {type: String,required: true,},createdAt: {type: Date,default: Date.now,},
});const User = mongoose.model('User', userSchema);module.exports = User;
// MySQL 集成 (使用 mysql2)
const mysql = require('mysql2/promise');// 创建连接池
const pool = mysql.createPool({host: process.env.DB_HOST,user: process.env.DB_USER,password: process.env.DB_PASSWORD,database: process.env.DB_NAME,waitForConnections: true,connectionLimit: 10,queueLimit: 0,
});// 用户数据访问对象
const UserDAO = {// 获取所有用户async getAllUsers() {const [rows] = await pool.query('SELECT * FROM users');return rows;},// 通过 ID 获取用户async getUserById(id) {const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);return rows[0];},// 创建用户async createUser(user) {const { username, email, password } = user;const [result] = await pool.query('INSERT INTO users (username, email, password) VALUES (?, ?, ?)',[username, email, password]);return { id: result.insertId, ...user };},
};module.exports = UserDAO;
3.5 身份验证和授权
// JWT 身份验证实现
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const User = require('../models/User');const authController = {// 用户注册async register(req, res) {try {const { username, email, password } = req.body;// 检查是否已存在用户const existingUser = await User.findOne({ $or: [{ username }, { email }] });if (existingUser) {return res.status(400).json({ message: '用户名或邮箱已被使用' });}// 哈希密码const hashedPassword = await bcrypt.hash(password, 10);// 创建新用户const newUser = new User({username,email,password: hashedPassword,});await newUser.save();// 生成 JWTconst token = jwt.sign({ userId: newUser._id, username: newUser.username },process.env.JWT_SECRET,{ expiresIn: '1h' });res.status(201).json({ user: newUser, token });} catch (error) {res.status(500).json({ message: '注册失败', error: error.message });}},// 用户登录async login(req, res) {try {const { username, password } = req.body;// 查找用户const user = await User.findOne({ username });if (!user) {return res.status(401).json({ message: '用户名或密码错误' });}// 验证密码const isPasswordValid = await bcrypt.compare(password, user.password);if (!isPasswordValid) {return res.status(401).json({ message: '用户名或密码错误' });}// 生成 JWTconst token = jwt.sign({ userId: user._id, username: user.username },process.env.JWT_SECRET,{ expiresIn: '1h' });res.json({ user, token });} catch (error) {res.status(500).json({ message: '登录失败', error: error.message });}},
};module.exports = authController;
3.6 后端架构
4. 前端开发
4.1 React 基础
React 是一个用于构建用户界面的 JavaScript 库。
// 创建 React 应用
npx create-react-app my-app
cd my-app
npm start// 简单的 React 组件
import React from 'react';function Welcome(props) {return <h1>Hello, {props.name}</h1>;
}export default Welcome;
4.2 状态管理 (Redux)
Redux 是一个 JavaScript 状态容器,提供可预测的状态管理。
// Redux store 配置
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { userReducer } from './reducers/userReducer';
import { productReducer } from './reducers/productReducer';// 合并 reducers
const rootReducer = combineReducers({user: userReducer,products: productReducer,
});// 创建 store
const store = createStore(rootReducer,applyMiddleware(thunk)
);export default store;// 用户 reducer
const initialState = {currentUser: null,isLoading: false,error: null,
};export const userReducer = (state = initialState, action) => {switch (action.type) {case 'LOGIN_REQUEST':return { ...state, isLoading: true, error: null };case 'LOGIN_SUCCESS':return { ...state, isLoading: false, currentUser: action.payload };case 'LOGIN_FAILURE':return { ...state, isLoading: false, error: action.payload };case 'LOGOUT':return { ...state, currentUser: null };default:return state;}
};// 用户 actions
export const loginUser = (credentials) => {return async (dispatch) => {try {dispatch({ type: 'LOGIN_REQUEST' });const response = await fetch('/api/auth/login', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(credentials),});if (!response.ok) {const error = await response.json();throw new Error(error.message);}const data = await response.json();localStorage.setItem('token', data.token);dispatch({ type: 'LOGIN_SUCCESS', payload: data.user });} catch (error) {dispatch({ type: 'LOGIN_FAILURE', payload: error.message });}};
};
4.3 API 请求处理
使用 Axios 或 Fetch API 处理 HTTP 请求。
// 使用 Axios 的 API 服务
import axios from 'axios';// 创建 axios 实例
const apiClient = axios.create({baseURL: process.env.REACT_APP_API_URL,headers: {'Content-Type': 'application/json',},
});// 请求拦截器添加认证 token
apiClient.interceptors.request.use((config) => {const token = localStorage.getItem('token');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;},(error) => {return Promise.reject(error);}
);// 响应拦截器处理错误
apiClient.interceptors.response.use((response) => response,(error) => {// 处理 401 错误if (error.response && error.response.status === 401) {// 清除本地存储并重定向到登录页localStorage.removeItem('token');window.location.href = '/login';}return Promise.reject(error);}
);// 用户相关 API 调用
export const userService = {login: (credentials) => apiClient.post('/auth/login', credentials),register: (userData) => apiClient.post('/auth/register', userData),getCurrentUser: () => apiClient.get('/users/me'),updateProfile: (userData) => apiClient.put('/users/me', userData),
};// 产品相关 API 调用
export const productService = {getProducts: (params) => apiClient.get('/products', { params }),getProductById: (id) => apiClient.get(`/products/${id}`),createProduct: (productData) => apiClient.post('/products', productData),updateProduct: (id, productData) => apiClient.put(`/products/${id}`, productData),deleteProduct: (id) => apiClient.delete(`/products/${id}`),
};export default apiClient;
4.4 前端路由 (React Router)
React Router 是 React 应用程序的声明式路由。
// React Router 配置
import React from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';// 组件导入
import Home from './pages/Home';
import Login from './pages/Login';
import Register from './pages/Register';
import Dashboard from './pages/Dashboard';
import Profile from './pages/Profile';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import NotFound from './pages/NotFound';// 私有路由组件
const PrivateRoute = ({ children }) => {const { currentUser } = useSelector((state) => state.user);return currentUser ? children : <Navigate to="/login" />;
};const AppRouter = () => {return (<BrowserRouter><Routes><Route path="/" element={<Home />} /><Route path="/login" element={<Login />} /><Route path="/register" element={<Register />} />{/* 受保护的路由 */}<Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} /><Route path="/profile" element={<PrivateRoute><Profile /></PrivateRoute>} /><Route path="/products" element={<Products />} /><Route path="/products/:id" element={<ProductDetail />} />{/* 404 页面 */}<Route path="*" element={<NotFound />} /></Routes></BrowserRouter>);
};export default AppRouter;
4.5 前端架构
5. 全栈集成
5.1 前后端通信
5.2 身份验证流程
5.3 完整的全栈应用架构
6. 数据建模与持久化
6.1 MongoDB 数据模型
// 用户模型
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');const userSchema = new mongoose.Schema({username: {type: String,required: true,unique: true,trim: true,minlength: 3,maxlength: 30,},email: {type: String,required: true,unique: true,trim: true,lowercase: true,match: [/^\S+@\S+\.\S+$/, '请提供有效的电子邮件地址'],},password: {type: String,required: true,minlength: 6,},role: {type: String,enum: ['user', 'admin'],default: 'user',},profilePicture: String,bio: {type: String,maxlength: 500,},isActive: {type: Boolean,default: true,},lastLogin: Date,
}, {timestamps: true,
});// 密码哈希中间件
userSchema.pre('save', async function(next) {if (!this.isModified('password')) return next();try {const salt = await bcrypt.genSalt(10);this.password = await bcrypt.hash(this.password, salt);next();} catch (error) {next(error);}
});// 验证密码方法
userSchema.methods.comparePassword = async function(candidatePassword) {return bcrypt.compare(candidatePassword, this.password);
};const User = mongoose.model('User', userSchema);module.exports = User;
6.2 MySQL 数据建模
-- 用户表
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(30) NOT NULL UNIQUE,email VARCHAR(100) NOT NULL UNIQUE,password VARCHAR(255) NOT NULL,role ENUM('user', 'admin') DEFAULT 'user',profile_picture VARCHAR(255),bio TEXT,is_active BOOLEAN DEFAULT TRUE,last_login DATETIME,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);-- 产品表
CREATE TABLE products (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100) NOT NULL,description TEXT,price DECIMAL(10, 2) NOT NULL,stock INT NOT NULL DEFAULT 0,category_id INT,image_url VARCHAR(255),is_featured BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);-- 类别表
CREATE TABLE categories (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50) NOT NULL UNIQUE,description TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);-- 订单表
CREATE TABLE orders (id INT AUTO_INCREMENT PRIMARY KEY,user_id INT NOT NULL,status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',total_amount DECIMAL(10, 2) NOT NULL,shipping_address TEXT NOT NULL,payment_method VARCHAR(50) NOT NULL,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);-- 订单项表
CREATE TABLE order_items (id INT AUTO_INCREMENT PRIMARY KEY,order_id INT NOT NULL,product_id INT NOT NULL,quantity INT NOT NULL,price DECIMAL(10, 2) NOT NULL,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT
);-- 用户地址表
CREATE TABLE user_addresses (id INT AUTO_INCREMENT PRIMARY KEY,user_id INT NOT NULL,address_line1 VARCHAR(100) NOT NULL,address_line2 VARCHAR(100),city VARCHAR(50) NOT NULL,state VARCHAR(50) NOT NULL,postal_code VARCHAR(20) NOT NULL,country VARCHAR(50) NOT NULL,is_default BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);-- 产品评论表
CREATE TABLE product_reviews (id INT AUTO_INCREMENT PRIMARY KEY,product_id INT NOT NULL,user_id INT NOT NULL,rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),comment TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,UNIQUE (product_id, user_id)
);
6.3 数据关系建模
7. API 开发与设计模式
7.1 模块化路由
// /routes/index.js - 路由主入口
const express = require('express');
const userRoutes = require('./userRoutes');
const productRoutes = require('./productRoutes');
const orderRoutes = require('./orderRoutes');
const authRoutes = require('./authRoutes');const router = express.Router();// API 版本控制
router.use('/v1/users', userRoutes);
router.use('/v1/products', productRoutes);
router.use('/v1/orders', orderRoutes);
router.use('/v1/auth', authRoutes);module.exports = router;// /routes/userRoutes.js - 用户相关路由
const express = require('express');
const userController = require('../controllers/userController');
const { authMiddleware, adminMiddleware } = require('../middleware/authMiddleware');const router = express.Router();// 公共路由
router.get('/:id/profile', userController.getUserProfile);// 需要认证的路由
router.get('/me', authMiddleware, userController.getCurrentUser);
router.put('/me', authMiddleware, userController.updateProfile);
router.get('/me/orders', authMiddleware, userController.getUserOrders);// 管理员路由
router.get('/', authMiddleware, adminMiddleware, userController.getAllUsers);
router.delete('/:id', authMiddleware, adminMiddleware, userController.deleteUser);module.exports = router;
7.2 控制器模式
// /controllers/productController.js
const Product = require('../models/Product');
const { createError } = require('../utils/errorHandler');// 控制器对象
const productController = {// 获取所有产品async getAllProducts(req, res, next) {try {const { category, search, minPrice, maxPrice, sort, limit = 10, page = 1 } = req.query;// 构建查询条件const query = {};if (category) {query.category_id = category;}if (search) {query.name = { $regex: search, $options: 'i' };}if (minPrice || maxPrice) {query.price = {};if (minPrice) query.price.$gte = parseFloat(minPrice);if (maxPrice) query.price.$lte = parseFloat(maxPrice);}// 构建排序条件let sortOptions = { createdAt: -1 }; // 默认按创建时间降序if (sort) {const [field, order] = sort.split(':');sortOptions = { [field]: order === 'desc' ? -1 : 1 };}// 计算分页const skip = (parseInt(page) - 1) * parseInt(limit);// 执行查询const products = await Product.find(query).sort(sortOptions).limit(parseInt(limit)).skip(skip);// 获取总数const total = await Product.countDocuments(query);res.json({products,pagination: {total,page: parseInt(page),limit: parseInt(limit),pages: Math.ceil(total / parseInt(limit)),},});} catch (error) {next(error);}},// 获取单个产品async getProductById(req, res, next) {try {const { id } = req.params;const product = await Product.findById(id);if (!product) {return next(createError(404, '找不到该产品'));}res.json(product);} catch (error) {next(error);}},// 创建产品async createProduct(req, res, next) {try {const productData = req.body;const newProduct = new Product(productData);await newProduct.save();res.status(201).json(newProduct);} catch (error) {next(error);}},// 更新产品async updateProduct(req, res, next) {try {const { id } = req.params;const updateData = req.body;const product = await Product.findByIdAndUpdate(id,updateData,{ new: true, runValidators: true });if (!product) {return next(createError(404, '找不到该产品'));}res.json(product);} catch (error) {next(error);}},// 删除产品async deleteProduct(req, res, next) {try {const { id } = req.params;const product = await Product.findByIdAndDelete(id);if (!product) {return next(createError(404, '找不到该产品'));}res.status(204).end();} catch (error) {next(error);}},
};module.exports = productController;
7.3 服务层模式
// /services/orderService.js
const Order = require('../models/Order');
const Product = require('../models/Product');
const { createError } = require('../utils/errorHandler');// 订单服务
const orderService = {// 创建订单async createOrder(orderData, userId) {// 验证产品库存const orderItems = orderData.items;let totalAmount = 0;// 验证所有产品是否有足够库存for (const item of orderItems) {const product = await Product.findById(item.productId);if (!product) {throw createError(404, `商品ID ${item.productId} 不存在`);}if (product.stock < item.quantity) {throw createError(400, `商品 ${product.name} 库存不足`);}// 计算项目价格item.price = product.price;totalAmount += product.price * item.quantity;}// 创建订单事务const session = await Order.startSession();session.startTransaction();try {// 创建订单const newOrder = new Order({user: userId,items: orderItems.map(item => ({product: item.productId,quantity: item.quantity,price: item.price})),totalAmount,shippingAddress: orderData.shippingAddress,paymentMethod: orderData.paymentMethod,status: 'pending'});await newOrder.save({ session });// 更新产品库存for (const item of orderItems) {await Product.findByIdAndUpdate(item.productId,{ $inc: { stock: -item.quantity } },{ session });}// 提交事务await session.commitTransaction();session.endSession();return newOrder;} catch (error) {// 回滚事务await session.abortTransaction();session.endSession();throw error;}},// 获取用户订单async getUserOrders(userId) {return Order.find({ user: userId }).sort({ createdAt: -1 }).populate('items.product', 'name image_url');},// 获取订单详情async getOrderById(orderId, userId, isAdmin = false) {const query = { _id: orderId };// 如果不是管理员,只能查看自己的订单if (!isAdmin) {query.user = userId;}const order = await Order.findOne(query).populate('user', 'username email').populate('items.product');if (!order) {throw createError(404, '订单不存在');}return order;},// 更新订单状态async updateOrderStatus(orderId, status, userId, isAdmin = false) {const query = { _id: orderId };// 如果不是管理员,只能更新自己的订单if (!isAdmin) {query.user = userId;// 非管理员只能取消订单if (status !== 'cancelled') {throw createError(403, '没有权限执行此操作');}// 只能取消待处理或处理中的订单query.status = { $in: ['pending', 'processing'] };}const order = await Order.findOneAndUpdate(query,{ status },{ new: true, runValidators: true });if (!order) {throw createError(404, '订单不存在或无法更新状态');}return order;},
};module.exports = orderService;
7.4 错误处理模式
// /utils/errorHandler.js
// 创建自定义错误
const createError = (statusCode, message, details = null) => {const error = new Error(message);error.statusCode = statusCode;error.details = details;return error;
};// 全局错误处理中间件
const errorHandler = (err, req, res, next) => {console.error('Error:', err);// 获取错误状态码和消息const statusCode = err.statusCode || 500;const message = err.message || '服务器内部错误';// 区分不同环境下的错误响应const response = {error: {message,status: statusCode,},};// 在开发环境中提供更详细的错误信息if (process.env.NODE_ENV === 'development') {response.error.stack = err.stack;if (err.details) {response.error.details = err.details;}}// MongoDB 验证错误处理if (err.name === 'ValidationError') {response.error.status = 400;response.error.message = '数据验证失败';response.error.details = Object.values(err.errors).map(e => e.message);}// MongoDB 重复键错误处理if (err.code === 11000) {response.error.status = 409;response.error.message = '数据已存在';const field = Object.keys(err.keyValue)[0];response.error.details = `${field} 已被使用`;}// JWT 错误处理if (err.name === 'JsonWebTokenError') {response.error.status = 401;response.error.message = '无效的认证令牌';}if (err.name === 'TokenExpiredError') {response.error.status = 401;response.error.message = '认证令牌已过期';}res.status(response.error.status).json(response);
};module.exports = { createError, errorHandler };
8. 安全性与性能优化
8.1 安全最佳实践
// /config/security.js
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
const cors = require('cors');// 配置安全中间件
const configSecurity = (app) => {// 设置安全 HTTP 头app.use(helmet());// 防止 XSS 攻击app.use(xss());// 防止 NoSQL 注入app.use(mongoSanitize());// 防止参数污染app.use(hpp());// 配置 CORSapp.use(cors({origin: process.env.CORS_ORIGIN || '*',methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],allowedHeaders: ['Content-Type', 'Authorization'],credentials: true,}));// 配置速率限制const limiter = rateLimit({windowMs: 15 * 60 * 1000, // 15分钟max: 100, // 每个IP在windowMs内最多100个请求message: {error: {message: '请求过多,请稍后再试',status: 429,},},});// 应用速率限制到所有请求app.use('/api', limiter);// 严格的认证路由限制const authLimiter = rateLimit({windowMs: 60 * 60 * 1000, // 1小时max: 10, // 每个IP每小时最多10次尝试message: {error: {message: '尝试次数过多,请稍后再试',status: 429,},},});// 应用认证限制到登录和注册路由app.use('/api/v1/auth/login', authLimiter);app.use('/api/v1/auth/register', authLimiter);
};module.exports = configSecurity;
8.2 性能优化技术
// /config/performance.js
const compression = require('compression');
const { createClient } = require('redis');// Redis 客户端
let redisClient;// 配置性能优化
const configPerformance = async (app) => {// 启用 gzip 压缩app.use(compression());// 设置 Redis 缓存(如果配置了)if (process.env.REDIS_URL) {redisClient = createClient({url: process.env.REDIS_URL,});await redisClient.connect().catch(err => {console.error('Redis 连接失败:', err);});redisClient.on('error', (err) => {console.error('Redis 错误:', err);});console.log('Redis 缓存已启用');}
};// 缓存中间件
const cacheMiddleware = (duration) => {return async (req, res, next) => {// 如果 Redis 未连接,跳过缓存if (!redisClient || !redisClient.isReady) {return next();}// 跳过非 GET 请求的缓存if (req.method !== 'GET') {return next();}// 创建缓存键const cacheKey = `api:${req.originalUrl}`;try {// 尝试从缓存获取数据const cachedData = await redisClient.get(cacheKey);if (cachedData) {// 返回缓存数据const data = JSON.parse(cachedData);return res.json(data);}// 修改 res.json 方法以缓存响应const originalJson = res.json;res.json = function(data) {// 将数据保存到缓存redisClient.setEx(cacheKey, duration, JSON.stringify(data)).catch(err => console.error('Redis 缓存错误:', err));// 调用原始 json 方法return originalJson.call(this, data);};next();} catch (error) {console.error('缓存错误:', error);next();}};
};// 清除缓存模式
const clearCache = async (pattern) => {if (!redisClient || !redisClient.isReady) {return;}try {// 查找匹配的键const keys = await redisClient.keys(pattern);// 如果有匹配的键,删除它们if (keys.length > 0) {await redisClient.del(keys);console.log(`已清除 ${keys.length} 个缓存键`);}} catch (error) {console.error('清除缓存错误:', error);}
};module.exports = {configPerformance,cacheMiddleware,clearCache,
};
8.3 日志系统
// /utils/logger.js
const winston = require('winston');
const { format, transports, createLogger } = winston;
const path = require('path');// 定义日志格式
const logFormat = format.combine(format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),format.errors({ stack: true }),format.splat(),format.json()
);// 创建 logger 实例
const logger = createLogger({level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',format: logFormat,defaultMeta: { service: 'api-service' },transports: [// 控制台输出new transports.Console({format: format.combine(format.colorize(),format.printf(info => `${info.timestamp} ${info.level}: ${info.message}${info.stack ? '\n' + info.stack : ''}`)),}),],
});// 在生产环境中添加文件传输
if (process.env.NODE_ENV === 'production') {logger.add(new transports.File({filename: path.join(__dirname, '../logs/error.log'),level: 'error',maxsize: 10485760, // 10MBmaxFiles: 5,}));logger.add(new transports.File({filename: path.join(__dirname, '../logs/combined.log'),maxsize: 10485760, // 10MBmaxFiles: 5,}));
}// 捕获未处理的异常和拒绝
logger.exceptions.handle(new transports.File({ filename: path.join(__dirname, '../logs/exceptions.log') })
);// 中间件:请求日志
const requestLogger = (req, res, next) => {const startTime = new Date();// 请求完成时的回调res.on('finish', () => {const duration = new Date() - startTime;logger.info({type: 'request',method: req.method,path: req.path,query: req.query,statusCode: res.statusCode,duration: `${duration}ms`,userAgent: req.get('User-Agent'),ip: req.ip,});});next();
};module.exports = { logger, requestLogger };
9. 部署与 DevOps
9.1 Docker 容器化
# Dockerfile
FROM node:18-alpine# 创建应用目录
WORKDIR /usr/src/app# 安装应用依赖
COPY package*.json ./
RUN npm ci --only=production# 复制应用代码
COPY . .# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000# 暴露端口
EXPOSE 3000# 启动应用
CMD ["node", "server.js"]
# docker-compose.yml
version: '3'services:app:build: .restart: alwaysports:- "3000:3000"depends_on:- mongodb- redisenvironment:- NODE_ENV=production- PORT=3000- MONGODB_URI=mongodb://mongodb:27017/myapp- REDIS_URL=redis://redis:6379- JWT_SECRET=your_jwt_secret- CORS_ORIGIN=https://yourdomain.comvolumes:- ./logs:/usr/src/app/logsnetworks:- app-networkmongodb:image: mongo:6restart: alwaysports:- "27017:27017"volumes:- mongodb-data:/data/dbnetworks:- app-networkredis:image: redis:7-alpinerestart: alwaysports:- "6379:6379"volumes:- redis-data:/datanetworks:- app-networknetworks:app-network:driver: bridgevolumes:mongodb-data:redis-data:
9.2 CI/CD 流水线
# .github/workflows/main.yml
name: Node.js CI/CDon:push:branches: [ main ]pull_request:branches: [ main ]jobs:test:runs-on: ubuntu-lateststrategy:matrix:node-version: [16.x, 18.x]steps:- uses: actions/checkout@v3- name: Use Node.js ${{ matrix.node-version }}uses: actions/setup-node@v3with:node-version: ${{ matrix.node-version }}cache: 'npm'- name: Install dependenciesrun: npm ci- name: Run lintingrun: npm run lint- name: Run testsrun: npm testbuild-and-deploy:needs: testruns-on: ubuntu-latestif: github.event_name == 'push' && github.ref == 'refs/heads/main'steps:- uses: actions/checkout@v3- name: Use Node.js 18.xuses: actions/setup-node@v3with:node-version: 18.xcache: 'npm'- name: Install dependenciesrun: npm ci- name: Buildrun: npm run build- name: Set up Docker Buildxuses: docker/setup-buildx-action@v2- name: Login to DockerHubuses: docker/login-action@v2with:username: ${{ secrets.DOCKERHUB_USERNAME }}password: ${{ secrets.DOCKERHUB_TOKEN }}- name: Build and push Docker imageuses: docker/build-push-action@v4with:context: .push: truetags: yourusername/yourapp:latest- name: Deploy to serveruses: appleboy/ssh-action@masterwith:host: ${{ secrets.SSH_HOST }}username: ${{ secrets.SSH_USERNAME }}key: ${{ secrets.SSH_PRIVATE_KEY }}script: |cd /path/to/your/appdocker-compose pulldocker-compose up -d
9.3 部署流程
结语
感谢您的阅读!期待您的一键三连!欢迎指正!