文章目录
正文
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();
// 生成 JWT
const 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: '用户名或密码错误' });
}
// 生成 JWT
const 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());
// 配置 CORS
app.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, // 10MB
maxFiles: 5,
})
);
logger.add(
new transports.File({
filename: path.join(__dirname, '../logs/combined.log'),
maxsize: 10485760, // 10MB
maxFiles: 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: always
ports:
- "3000:3000"
depends_on:
- mongodb
- redis
environment:
- 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.com
volumes:
- ./logs:/usr/src/app/logs
networks:
- app-network
mongodb:
image: mongo:6
restart: always
ports:
- "27017:27017"
volumes:
- mongodb-data:/data/db
networks:
- app-network
redis:
image: redis:7-alpine
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mongodb-data:
redis-data:
9.2 CI/CD 流水线
# .github/workflows/main.yml
name: Node.js CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
build-and-deploy:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: yourusername/yourapp:latest
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /path/to/your/app
docker-compose pull
docker-compose up -d
9.3 部署流程
结语
感谢您的阅读!期待您的一键三连!欢迎指正!