引言
在现代Web应用开发中,微服务架构已经成为构建可扩展、可维护应用的重要模式。Node.js作为高性能的JavaScript运行时环境,Express作为轻量级的Web应用框架,以及MongoDB作为流行的NoSQL数据库,三者的结合为构建现代化的微服务架构提供了完美的技术栈组合。
本文将深入探讨如何使用Node.js + Express + MongoDB构建完整的微服务架构解决方案,从基础概念到实际代码实现,涵盖服务拆分、数据模型设计、RESTful API开发等核心内容,为全栈开发者提供一份实用的技术指南。
微服务架构概述
什么是微服务架构
微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件架构模式。每个服务都运行在自己的进程中,通过轻量级通信机制(通常是HTTP API)进行交互。每个服务围绕特定的业务功能构建,并可以独立部署、扩展和维护。
微服务的核心优势
- 独立开发与部署:每个服务可以独立开发、测试和部署
- 技术多样性:不同服务可以使用不同的技术栈
- 可扩展性:可以根据需求单独扩展特定服务
- 容错性:单个服务的故障不会影响整个系统
- 团队协作:小团队可以负责特定服务
微服务与传统架构对比
传统的单体应用架构将所有功能集成在一个单一的应用程序中,而微服务架构将应用分解为多个小型服务。这种分解使得系统更加灵活,但也带来了服务间通信、数据一致性等新的挑战。
Node.js + Express + MongoDB 技术栈选择
Node.js 的优势
Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,具有以下特点:
- 非阻塞I/O:基于事件驱动和非阻塞I/O模型,适合处理高并发场景
- 单线程:使用单线程模型处理大量并发连接
- 丰富的生态系统:npm包管理器提供了海量的第三方模块
- 快速开发:JavaScript语言特性使得开发效率高
Express 框架特点
Express是Node.js最流行的Web应用框架,具有以下优势:
- 简洁轻量:核心功能简单,易于学习和使用
- 中间件支持:丰富的中间件生态系统
- 灵活路由:支持复杂的路由规则
- 高性能:基于原生HTTP模块,性能优秀
MongoDB 的适用场景
MongoDB作为文档型数据库,在微服务架构中表现出色:
- 灵活的数据模型:JSON格式存储,适应性强
- 水平扩展能力:支持分片和复制集
- 丰富的查询语言:支持复杂的查询操作
- 良好的Node.js集成:提供官方的Node.js驱动
项目结构设计
整体架构图
┌─────────────────────────────────────────────────────────┐
│ 微服务架构总览 │
├─────────────────────────────────────────────────────────┤
│ 用户服务 (User Service) │
│ ┌─────────────────────────────┐ │
│ │ User API Controller │ │
│ │ User Service Layer │ │
│ │ User Repository │ │
│ └─────────────────────────────┘ │
│ 订单服务 (Order Service) │
│ ┌─────────────────────────────┐ │
│ │ Order API Controller │ │
│ │ Order Service Layer │ │
│ │ Order Repository │ │
│ └─────────────────────────────┘ │
│ 支付服务 (Payment Service) │
│ ┌─────────────────────────────┐ │
│ │ Payment API Controller │ │
│ │ Payment Service Layer │ │
│ │ Payment Repository │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
项目目录结构
microservice-project/
├── package.json
├── README.md
├── config/
│ ├── database.js
│ └── server.js
├── src/
│ ├── services/
│ │ ├── user/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── model/
│ │ │ └── routes/
│ │ ├── order/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── model/
│ │ │ └── routes/
│ │ └── payment/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── repository/
│ │ ├── model/
│ │ └── routes/
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── error.js
│ │ └── validation.js
│ ├── utils/
│ │ ├── logger.js
│ │ └── helpers.js
│ └── app.js
├── tests/
└── docker-compose.yml
数据模型设计
用户服务数据模型
// src/services/user/model/user.model.js
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,
lowercase: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, '请输入有效的邮箱地址']
},
password: {
type: String,
required: true,
minlength: 6
},
firstName: {
type: String,
required: true,
trim: true,
maxlength: 50
},
lastName: {
type: String,
required: true,
trim: true,
maxlength: 50
},
phone: {
type: String,
trim: true,
maxlength: 20
},
avatar: {
type: String,
default: null
},
isActive: {
type: Boolean,
default: true
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
}
}, {
timestamps: true,
toJSON: {
transform: function(doc, ret) {
delete ret.password;
delete ret.__v;
return ret;
}
}
});
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(12);
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);
};
module.exports = mongoose.model('User', userSchema);
订单服务数据模型
// src/services/order/model/order.model.js
const mongoose = require('mongoose');
const orderItemSchema = new mongoose.Schema({
productId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
productName: {
type: String,
required: true
},
quantity: {
type: Number,
required: true,
min: 1
},
price: {
type: Number,
required: true,
min: 0
}
});
const orderSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
items: [orderItemSchema],
totalAmount: {
type: Number,
required: true,
min: 0
},
status: {
type: String,
enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'],
default: 'pending'
},
shippingAddress: {
street: String,
city: String,
state: String,
zipCode: String,
country: String
},
paymentMethod: {
type: String,
enum: ['credit_card', 'debit_card', 'paypal', 'bank_transfer'],
required: true
},
paymentStatus: {
type: String,
enum: ['pending', 'completed', 'failed', 'refunded'],
default: 'pending'
}
}, {
timestamps: true,
toJSON: {
transform: function(doc, ret) {
delete ret.__v;
return ret;
}
}
});
module.exports = mongoose.model('Order', orderSchema);
数据库连接配置
数据库配置文件
// config/database.js
const mongoose = require('mongoose');
class Database {
constructor() {
this.connect();
}
connect() {
const dbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/microservice_db';
mongoose.connect(dbUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
})
.then(() => {
console.log('MongoDB connected successfully');
})
.catch((error) => {
console.error('MongoDB connection error:', error);
process.exit(1);
});
// 连接事件监听
mongoose.connection.on('connected', () => {
console.log('Mongoose connected to MongoDB');
});
mongoose.connection.on('error', (err) => {
console.error('Mongoose connection error:', err);
});
mongoose.connection.on('disconnected', () => {
console.log('Mongoose disconnected from MongoDB');
});
// 应用退出时关闭数据库连接
process.on('SIGINT', async () => {
await mongoose.connection.close();
console.log('Mongoose connection closed due to app termination');
process.exit(0);
});
}
}
module.exports = new Database();
RESTful API 设计规范
用户服务API设计
// src/services/user/routes/user.routes.js
const express = require('express');
const router = express.Router();
const userController = require('../controller/user.controller');
// GET /api/users - 获取所有用户
router.get('/', userController.getAllUsers);
// GET /api/users/:id - 根据ID获取用户
router.get('/:id', userController.getUserById);
// POST /api/users - 创建新用户
router.post('/', userController.createUser);
// PUT /api/users/:id - 更新用户信息
router.put('/:id', userController.updateUser);
// DELETE /api/users/:id - 删除用户
router.delete('/:id', userController.deleteUser);
// POST /api/users/login - 用户登录
router.post('/login', userController.loginUser);
// GET /api/users/profile - 获取当前用户信息
router.get('/profile', userController.getProfile);
module.exports = router;
用户控制器实现
// src/services/user/controller/user.controller.js
const User = require('../model/user.model');
const { validationResult } = require('express-validator');
const jwt = require('jsonwebtoken');
class UserController {
// 获取所有用户
async getAllUsers(req, res) {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const users = await User.find()
.select('-password')
.skip(skip)
.limit(limit)
.sort({ createdAt: -1 });
const total = await User.countDocuments();
res.json({
success: true,
data: users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
console.error('Error fetching users:', error);
res.status(500).json({
success: false,
message: '获取用户列表失败',
error: error.message
});
}
}
// 根据ID获取用户
async getUserById(req, res) {
try {
const user = await User.findById(req.params.id)
.select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
console.error('Error fetching user:', error);
res.status(500).json({
success: false,
message: '获取用户信息失败',
error: error.message
});
}
}
// 创建新用户
async createUser(req, res) {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '验证失败',
errors: errors.array()
});
}
const { username, email, password, firstName, lastName } = req.body;
// 检查用户是否已存在
const existingUser = await User.findOne({
$or: [{ email }, { username }]
});
if (existingUser) {
return res.status(409).json({
success: false,
message: '用户名或邮箱已存在'
});
}
// 创建新用户
const user = new User({
username,
email,
password,
firstName,
lastName
});
await user.save();
// 生成JWT token
const token = jwt.sign(
{ userId: user._id, username: user.username },
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: '24h' }
);
res.status(201).json({
success: true,
message: '用户创建成功',
data: {
user: {
id: user._id,
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName
},
token
}
});
} catch (error) {
console.error('Error creating user:', error);
res.status(500).json({
success: false,
message: '创建用户失败',
error: error.message
});
}
}
// 更新用户信息
async updateUser(req, res) {
try {
const { username, email, firstName, lastName, phone } = req.body;
const user = await User.findByIdAndUpdate(
req.params.id,
{
username,
email,
firstName,
lastName,
phone
},
{ new: true, runValidators: true }
).select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
res.json({
success: true,
message: '用户信息更新成功',
data: user
});
} catch (error) {
console.error('Error updating user:', error);
res.status(500).json({
success: false,
message: '更新用户信息失败',
error: error.message
});
}
}
// 删除用户
async deleteUser(req, res) {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
res.json({
success: true,
message: '用户删除成功'
});
} catch (error) {
console.error('Error deleting user:', error);
res.status(500).json({
success: false,
message: '删除用户失败',
error: error.message
});
}
}
// 用户登录
async loginUser(req, res) {
try {
const { email, password } = req.body;
// 查找用户
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({
success: false,
message: '邮箱或密码错误'
});
}
// 验证密码
const isPasswordValid = await user.comparePassword(password);
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: '邮箱或密码错误'
});
}
// 生成JWT token
const token = jwt.sign(
{ userId: user._id, username: user.username, role: user.role },
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: '24h' }
);
res.json({
success: true,
message: '登录成功',
data: {
user: {
id: user._id,
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
role: user.role
},
token
}
});
} catch (error) {
console.error('Error logging in:', error);
res.status(500).json({
success: false,
message: '登录失败',
error: error.message
});
}
}
// 获取当前用户信息
async getProfile(req, res) {
try {
const user = await User.findById(req.user.userId)
.select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
console.error('Error fetching profile:', error);
res.status(500).json({
success: false,
message: '获取用户信息失败',
error: error.message
});
}
}
}
module.exports = new UserController();
中间件开发
验证中间件
// src/middleware/validation.js
const { body, validationResult } = require('express-validator');
const validateUserCreate = [
body('username')
.isLength({ min: 3, max: 30 })
.withMessage('用户名长度必须在3-30个字符之间')
.matches(/^[a-zA-Z0-9_]+$/)
.withMessage('用户名只能包含字母、数字和下划线'),
body('email')
.isEmail()
.normalizeEmail()
.withMessage('请输入有效的邮箱地址'),
body('password')
.isLength({ min: 6 })
.withMessage('密码长度至少6个字符')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('密码必须包含大小写字母和数字'),
body('firstName')
.isLength({ min: 1, max: 50 })
.withMessage('名字长度必须在1-50个字符之间'),
body('lastName')
.isLength({ min: 1, max: 50 })
.withMessage('姓氏长度必须在1-50个字符之间')
];
const validateUserUpdate = [
body('username')
.optional()
.isLength({ min: 3, max: 30 })
.withMessage('用户名长度必须在3-30个字符之间')
.matches(/^[a-zA-Z0-9_]+$/)
.withMessage('用户名只能包含字母、数字和下划线'),
body('email')
.optional()
.isEmail()
.normalizeEmail()
.withMessage('请输入有效的邮箱地址'),
body('firstName')
.optional()
.isLength({ min: 1, max: 50 })
.withMessage('名字长度必须在1-50个字符之间'),
body('lastName')
.optional()
.isLength({ min: 1, max: 50 })
.withMessage('姓氏长度必须在1-50个字符之间')
];
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '验证失败',
errors: errors.array()
});
}
next();
};
module.exports = {
validateUserCreate,
validateUserUpdate,
handleValidationErrors
};
认证中间件
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
message: '访问令牌缺失'
});
}
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
if (err) {
return res.status(403).json({
success: false,
message: '令牌无效或已过期'
});
}
req.user = user;
next();
});
};
const authorizeRole = (...roles) => {
return (req, res, next) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: '权限不足'
});
}
next();
};
};
module.exports = {
authenticateToken,
authorizeRole
};
错误处理机制
全局错误处理中间件
// src/middleware/error.js
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
// 自定义错误处理
if (err.name === 'ValidationError') {
return res.status(400).json({
success: false,
message: '数据验证失败',
errors: Object.values(err.errors).map(e => e.message)
});
}
if (err.name === 'CastError') {
return res.status(400).json({
success: false,
message: '无效的ID格式'
});
}
if (err.code === 11000) {
// MongoDB重复键错误
const field = Object.keys(err.keyValue)[0];
return res.status(409).json({
success: false,
message: `${field} 已存在`
});
}
// 默认错误处理
res.status(err.statusCode || 500).json({
success: false,
message: err.message || '服务器内部错误',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
module.exports = errorHandler;
应用启动配置
主应用文件
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const database = require('../config/database');
const errorHandler = require('./middleware/error');
class App {
constructor() {
this.app = express();
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
}
setupMiddleware() {
// 安全中间件
this.app.use(helmet());
// CORS配置
this.app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
}));
// 日志中间件
this.app.use(morgan('combined'));
// 解析JSON请求体
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 静态文件服务
this.app.use(express.static('public'));
}
setupRoutes() {
// 健康检查端点
this.app.get('/health', (req, res) => {
res.json({
success: true,
message: 'Service is running',
timestamp: new Date().toISOString()
});
});
// API路由
const userRoutes = require('./services/user/routes/user.routes');
const orderRoutes = require('./services/order/routes/order.routes');
const paymentRoutes = require('./services/payment/routes/payment.routes');
this.app.use('/api/users', userRoutes);
this.app.use('/api/orders', orderRoutes);
this.app.use('/api/payments', paymentRoutes);
// 404处理
this.app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: '请求的资源不存在'
});
});
}
setupErrorHandling() {
// 全局错误处理
this.app.use(errorHandler);
}
listen(port = process.env.PORT || 3000) {
return this.app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}
}
module.exports = App;
启动文件
// src/server.js
const App = require('./app');
const app = new App();
const PORT = process.env.PORT || 3000;
app.listen(PORT);
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
测试策略
单元测试示例
// tests/user.service.test.js
const request = require('supertest');
const app = require('../src/app');
const User = require('../src/services/user/model/user.model');
describe('User Service', () => {
beforeEach(async () => {
await User.deleteMany({});
});
afterEach(async () => {
await User.deleteMany({});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'Password123',
firstName: 'Test',
lastName: 'User'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data.user.username).toBe(userData.username);
expect(response.body.data.user.email).toBe(userData.email);
expect(response.body.data.token).toBeDefined();
});
it('should return validation error for invalid data', async () => {
const userData = {
username: 'ab',
email: 'invalid-email',
password: '123'
};
const response
评论 (0)