【Node.js】全栈开发实践

个人主页: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 部署流程
结语
感谢您的阅读!期待您的一键三连!欢迎指正!

相关文章:
【Node.js】全栈开发实践
个人主页: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 框架…...
自定义类型-联合体
概念 联合体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型 联合体的所有成员共享同一块内存空间,大小由最大的成员决定 用于在同一块内存单元内存放不同类型的变量 语法结构 结构与结构体类似, union 共用体名 {成员列…...
Qt项目开发中所遇
讲述下面代码所表示的含义: QWidget widget_19 new QWidget(); QVBoxLayout *touchAreaLayout new QVBoxLayout(widget_19);QWidget *buttonArea new QWidget(widget_19); 1、新建一个名为widget_19的QWidget,将给其应用垂直管路布局。 2、新建一个…...
ubuntu sh安装包的安装方式
ubuntu sh安装包的安装方式以Miniconda2为例 https://repo.anaconda.com/miniconda/ 如果需要python2.7版本可下载以下版本 Miniconda2-latest-Linux-x86_64.sh 打开终端输入安装命令 sudo sh Miniconda2-latest-Linux-x86_64.sh 然后按提示安装,注意安装位置 …...
Redis语法大全
一、String(字符串) 特点:单键值存储,值可为字符串、数字,支持原子操作。 常用命令 SET 语法:SET key value [EX seconds] [PX milliseconds] [NX|XX]说明:设置键值对,可指定过期时…...
OpenAI宣布:核心API支持MCP,助力智能体开发
今天凌晨,OpenAI全资收购io的消息成为头条。同时,OpenAI还宣布其核心API——Responses API支持MCP服务。过去,开发智能体需通过函数调用与外部服务交互,过程复杂且延迟高。而今,Responses API支持MCP后,开发…...
我的爬虫夜未眠:一场与IP限流的攻防战
深夜的办公室里,键盘声此起彼伏,屏幕的蓝光映在程序员的脸上。我揉了揉酸胀的眼睛,第8次刷新日志页面——依旧是刺眼的“429 Too Many Requests”(请求过多)。这是本月第三次因为IP被目标网站封禁而被迫中断爬虫任务了…...
git:The following paths are ignored by one of your
遇到错误: The following paths are ignored by one of your .gitignore files: www hint: Use -f if you really want to add them. 说明:Git 拒绝添加 www/html/index.php,因为你的 .gitignore 中忽略了整个 www/ 目录(即 ww…...
算法--js--组合总和
题:给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复…...
微服务中的 AKF 拆分原则:构建可扩展系统的核心方法论
在数字化浪潮的推动下,互联网应用规模呈指数级增长,传统单体架构逐渐暴露出难以扩展、维护成本高等问题,微服务架构应运而生并成为企业应对复杂业务场景的主流选择。然而,随着业务的不断扩张和用户量的持续增加,如何确…...
vue element-plus 集成多语言
main.js中 // 引入i18n import i18n from /i18n/index 使用i18 app.use(i18n) 在App.vue中 <template><el-config-provider :locale"locale" namespace"el" size"small"><router-view /></el-config-provider> </tem…...
如何测试JWT的安全性:全面防御JSON Web Token的安全漏洞
在当今的Web应用安全领域,JSON Web Token(JWT)已成为身份认证的主流方案,但OWASP统计显示,错误配置的JWT导致的安全事件占比高达42%。本文将系统性地介绍JWT安全测试的方法论,通过真实案例剖析典型漏洞,帮助我们构建全…...
车载网关策略 --- 车载网关重置前的请求转发机制
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
EtpBot:安卓自动化脚本开发神器
EtpBot 是什么? EtpBot是一款专为安卓设备设计的自动化脚本开发工具,支持用户通过编写脚本实现自动化操作。该模块提供了丰富的API接口,涵盖点击、滑动、输入、截图等常见操作,帮助开发者快速构建自动化任务。ETPBot支持多设备并行…...
连锁企业管理系统对门店运营的促进作用
连锁企业管理系统通过整合数字化工具与流程优化,能从多维度提升门店运营效率与竞争力,以下是其对门店运营的具体促进作用: 一、数据化管理:精准决策与运营监控 实时数据同步与分析 系统可整合各门店销售数据、库存信息、客流统计…...
现代生活健康养生新策略
在充满挑战的现代生活中,各种健康问题悄然来袭,亚健康状态困扰着不少人。摒弃中医概念,运用现代科学理念,也能找到行之有效的养生之道。 饮食上,遵循 “彩虹饮食法” 能让营养摄入更全面。不同颜色的蔬果富含不同的…...
车载以太网网络测试-27【SOME/IP-SD简述】
文章目录 1 摘要2 SOME/IP-SD协议介绍2.1 定义与作用2.2 SOMEIP/SD协议通俗易懂的理解2.2.1 SOMEIP/SD协议是什么?2.2.2 通信流程(简化)2.2.3 车载功能示例2.2.4 类比理解 2.3 SOME/IP-SD报文结构2.3.1 Flags2.3.1.1 REBOOT (Bit 7)2.3.1.2 U…...
云南安全员考试报名需要具备哪些条件?
云南安全员考试分为 A、B、C 三类,不同类别报名条件有所不同,具体如下: 安全员 A 证 年龄 18 周岁以上。具有中专及以上的文化程度、中级及以上专业技术职称(法定代表人除外)。其中分管安全生产的副总经理(…...
Android Binder线程池饥饿与TransactionException:从零到企业级解决方案(含实战代码+调试技巧)
简介 在Android系统中,Binder作为进程间通信(IPC)的核心机制,承载着大量跨进程调用任务。然而,当Binder线程池资源耗尽时,可能导致严重的线程饥饿问题,最终引发TransactionException异常,甚至导致应用崩溃或系统卡顿。本文将从零开始,系统讲解Binder线程池的工作原理…...
FFmpeg 超级详细安装与配置教程(Windows 系统)
FFmpeg 是一款功能强大的开源多媒体处理工具集,能够进行音视频的编码、解码、转码、混流、推流、滤镜、格式转换等多种操作。本教程将详细介绍如何在 Windows 系统上安装和配置 FFmpeg,并提供一些常用的使用示例,助你从入门到基本掌握。 目录…...
【Redis8】最新安装版与手动运行版
1. 下载 Redis 百度网盘 2. 解压后直接运行 redis-server.exe 3. 使用安装版 双击 install_redis_service.bat 输入安装路径(请提前创建好安装路径)后直接回车下一步直接回车即可,因为是使用配置模板文件为默认解压出来的,然后…...
PyQt 探索QMainWindow:打造专业的PyQt5主窗
在PyQt5的世界里,窗口的创建和管理是构建图形用户界面(GUI)的基础。QMainWindow作为主窗口类,为开发者提供了强大而灵活的应用程序框架。今天,就让我们一起深入了解QMainWindow的奥秘。 QMainWindow简介 QMainWindow…...
Spring Boot 集成 Elasticsearch【实战】
前言: 上一篇我们简单分享了 Elasticsearch 的一些概念性的知识,本篇我们来分享 Elasticsearch 的实际运用,也就是在 Spring Booot 项目中使用 Elasticsearch。 Elasticsearch 系列文章传送门 Elasticsearch 基础篇【ES】 Elasticsearch …...
06算法学习_58. 区间和
58. 区间和 06算法学习_58. 区间和题目描述:个人代码:学习思路:第一种写法:题解关键点: 个人学习时疑惑点解答: 06算法学习_58. 区间和 卡码网题目链接: 59. 螺旋矩阵 II 题目描述: 58. 区间…...
如何在Java中进行PDF合并
引言 Java 开发者在处理 PDF 文档时,常常需要增强文档工作流的功能。市场上有多种 Java PDF SDK 库可供选择,其中一项关键功能就是 PDF 合并。 PDF 合并在许多场景中都非常重要,例如: 1 优化用户下载流程 2 合并多份报告 3…...
Python爬虫之路(14)--playwright浏览器自动化
playwright 前言 你有没有在用 Selenium 抓网页的时候,体验过那种「明明点了按钮,它却装死不动」的痛苦?或者那种「刚加载完页面,它又刷新了」的抓狂?别担心,你不是一个人——那是 Selenium 在和现代前…...
Python开启智能之眼:OpenCV+深度学习实战
开篇导言 场景痛点 "某汽车零部件厂每月因人工质检遗漏损失300万,直到部署了基于Python的视觉检测系统..." 传统质检效率低下、成本高昂 深度学习技术带来的产业变革 Python在视觉识别领域的独特优势 一、技术架构解析 1.1 系统组成模块 图表 代码 下载 检测结…...
华为模拟器练习简单的拓扑图(3台路由器和2台pc)
1、题目要求 根据下图,pc1连通pc2,实现不同网段直接的互通 2、思路整理 2.1 根据图上的要求,为主机和路由器相连接的端口设置对应IP地址(子网掩码都是24位),路由器连接pc的那个端口,是主机pc的网关 2.2 …...
uniapp生成的app,关于跟其他设备通信的支持和限制
以下内容通过AI生成,这里做一下记录。 蓝牙 移动应用(App)通过蓝牙与其他设备通信,是通过分层协作实现的。 一、通信架构分层 应用层(App) 调用操作系统提供的蓝牙API(如Android的BluetoothA…...
如何提高独立服务器的安全性?
独立服务器相对于其它服务器来说,整体的硬件设备都是独立的同时还有着强大的服务器性能,其中CPU设备能够决定着服务器的运算能力,所以独立服务器的安全性受到企业格外的重视,严重的话会给企业造成巨大的资金损失。 那么࿰…...
