三层架构
DoraCMS 采用清晰的三层架构设计:Controller → Service → Repository,每层职责明确,易于维护和扩展。
架构概览
┌─────────────────────────────────────────────┐
│ Controller 层 │
│ 职责:请求处理、参数验证、权限检查 │
│ 依赖:Service 层 │
└──────────────────┬──────────────────────────┘
│ 调用
┌──────────────────▼──────────────────────────┐
│ Service 层 │
│ 职责:业务逻辑编排、事务管理、跨模块调用 │
│ 依赖:Repository 层、其他 Service │
└──────────────────┬──────────────────────────┘
│ 调用
┌──────────────────▼──────────────────────────┐
│ Repository 层 │
│ 职责:数据库操作、查询转换、异常处理 │
│ 依赖:数据库模型 │
└─────────────────────────────────────────────┘Controller 层
Controller 层是应用的入口,负责处理 HTTP 请求和响应。
主要职责
- 接收和验证请求参数
- 调用 Service 层处理业务逻辑
- 格式化响应数据
- 权限检查
- 错误处理
标准实现
javascript
// app/controller/user.js
const { Controller } = require('egg')
class UserController extends Controller {
// 获取用户列表
async index() {
const { ctx } = this
try {
// 1. 参数验证
ctx.validate({
page: { type: 'number', required: false, min: 1 },
limit: { type: 'number', required: false, min: 1, max: 100 },
status: { type: 'string', required: false }
}, ctx.query)
// 2. 提取参数
const { page = 1, limit = 20, status } = ctx.query
// 3. 调用 Service
const result = await ctx.service.user.getUserList({
page,
limit,
filters: status ? { status } : {}
})
// 4. 返回响应
ctx.body = {
code: 0,
message: 'success',
data: result
}
} catch (error) {
// 5. 错误处理
ctx.logger.error('UserController.index error:', error)
ctx.body = {
code: 500,
message: error.message
}
}
}
// 创建用户
async create() {
const { ctx } = this
// 1. 参数验证
ctx.validate({
userName: { type: 'string', required: true, min: 3, max: 20 },
email: { type: 'email', required: true },
password: { type: 'string', required: true, min: 6 },
roleId: { type: 'string', required: true }
})
// 2. 权限检查
if (!ctx.user.hasPermission('user:create')) {
ctx.status = 403
ctx.body = { code: 403, message: '无权限' }
return
}
// 3. 调用 Service
const user = await ctx.service.user.createUser(ctx.request.body)
// 4. 返回响应
ctx.body = {
code: 0,
message: '创建成功',
data: user
}
}
// 更新用户
async update() {
const { ctx } = this
const { id } = ctx.params
// 权限检查:只能更新自己,或者有管理员权限
if (ctx.user.id !== id && !ctx.user.hasPermission('user:update')) {
ctx.status = 403
ctx.body = { code: 403, message: '无权限' }
return
}
const user = await ctx.service.user.updateUser(id, ctx.request.body)
ctx.body = {
code: 0,
message: '更新成功',
data: user
}
}
// 删除用户
async destroy() {
const { ctx } = this
const { id } = ctx.params
// 权限检查
if (!ctx.user.hasPermission('user:delete')) {
ctx.status = 403
ctx.body = { code: 403, message: '无权限' }
return
}
await ctx.service.user.deleteUser(id)
ctx.body = {
code: 0,
message: '删除成功'
}
}
}
module.exports = UserControllerController 最佳实践
✅ 应该做的
javascript
class UserController extends Controller {
async index() {
const { ctx } = this
// ✅ 参数验证
ctx.validate({ ... })
// ✅ 调用 Service
const result = await ctx.service.user.getUserList(params)
// ✅ 返回标准格式
ctx.body = { code: 0, data: result }
}
}❌ 不应该做的
javascript
class UserController extends Controller {
async index() {
const { ctx } = this
// ❌ 直接操作数据库
const users = await ctx.model.User.find()
// ❌ 业务逻辑处理
const activeUsers = users.filter(u => u.status === 'active')
// ❌ 数据转换
const result = activeUsers.map(u => ({
id: u._id.toString(),
name: u.userName
}))
ctx.body = result
}
}Service 层
Service 层是业务逻辑的核心,负责编排和协调各种操作。
主要职责
- 业务逻辑编排
- 事务管理
- 调用 Repository 进行数据操作
- 跨模块调用
- 业务验证
标准实现
javascript
// app/service/user.js
const { Service } = require('egg')
class UserService extends Service {
// 获取用户列表
async getUserList({ page, limit, filters }) {
// 直接调用 Repository
return await this.ctx.repo.user.findByPage({
filters,
populate: ['role', 'department'],
sort: { createdAt: -1 },
pagination: { page, limit }
})
}
// 创建用户(复杂业务逻辑)
async createUser(userData) {
const { ctx } = this
// 1. 业务验证:检查邮箱唯一性
const emailExists = await ctx.repo.user.findByEmail(userData.email)
if (emailExists) {
throw new Error('邮箱已被注册')
}
// 2. 业务验证:检查用户名唯一性
const userNameExists = await ctx.repo.user.findByUserName(userData.userName)
if (userNameExists) {
throw new Error('用户名已被使用')
}
// 3. 业务逻辑:加密密码
const encryptedPassword = await ctx.helper.crypto.encryptPassword(userData.password)
// 4. 业务逻辑:设置默认值
const userDataToCreate = {
...userData,
password: encryptedPassword,
status: 'active',
createdBy: ctx.user?.id
}
// 5. 调用 Repository 创建用户
const user = await ctx.repo.user.create(userDataToCreate)
// 6. 业务逻辑:发送欢迎邮件(异步)
ctx.helper.email.sendWelcomeEmail(user.email).catch(err => {
ctx.logger.error('Send welcome email failed:', err)
})
// 7. 业务逻辑:记录操作日志
await ctx.service.log.create({
action: 'user.create',
targetId: user.id,
userId: ctx.user?.id
})
return user
}
// 用户登录(涉及多个操作)
async login(userName, password) {
const { ctx } = this
// 1. 查询用户
const user = await ctx.repo.user.findByUserName(userName)
if (!user) {
throw new Error('用户不存在')
}
// 2. 验证状态
if (user.status !== 'active') {
throw new Error('账号已被禁用')
}
// 3. 验证密码
const valid = await ctx.helper.crypto.verifyPassword(password, user.password)
if (!valid) {
// 记录失败次数
await ctx.repo.user.incrementLoginFailed(user.id)
throw new Error('密码错误')
}
// 4. 更新登录信息
await ctx.repo.user.updateById(user.id, {
lastLoginAt: new Date(),
lastLoginIp: ctx.ip,
loginFailedCount: 0
})
// 5. 生成 Token
const token = ctx.helper.jwt.sign({
userId: user.id,
userName: user.userName
})
// 6. 记录登录日志
await ctx.service.log.create({
action: 'user.login',
targetId: user.id,
userId: user.id
})
return { user, token }
}
// 用户注册(涉及事务)
async register(userData) {
const { ctx } = this
const { app } = this
// 开启事务
const transaction = await app.model.transaction()
try {
// 1. 创建用户
const user = await ctx.repo.user.create(userData, { transaction })
// 2. 创建用户配置
await ctx.repo.userConfig.create({
userId: user.id,
theme: 'light',
language: 'zh-CN'
}, { transaction })
// 3. 创建用户统计
await ctx.repo.userStats.create({
userId: user.id,
articleCount: 0,
followCount: 0,
fanCount: 0
}, { transaction })
// 提交事务
await transaction.commit()
return user
} catch (error) {
// 回滚事务
await transaction.rollback()
throw error
}
}
}
module.exports = UserServiceService 最佳实践
✅ 应该做的
javascript
class UserService extends Service {
async createUser(userData) {
// ✅ 业务验证
await this.validateUser(userData)
// ✅ 业务逻辑处理
const processedData = await this.processUserData(userData)
// ✅ 调用 Repository
const user = await this.ctx.repo.user.create(processedData)
// ✅ 后续业务操作
await this.afterUserCreated(user)
return user
}
// ✅ 事务管理
async registerUser(userData) {
const transaction = await this.app.model.transaction()
try {
const user = await this.ctx.repo.user.create(userData, { transaction })
await this.ctx.repo.userProfile.create({ userId: user.id }, { transaction })
await transaction.commit()
return user
} catch (error) {
await transaction.rollback()
throw error
}
}
// ✅ 跨模块调用
async getUserWithArticles(userId) {
const user = await this.ctx.service.user.getUser(userId)
const articles = await this.ctx.service.article.getUserArticles(userId)
return { user, articles }
}
}❌ 不应该做的
javascript
class UserService extends Service {
async createUser(userData) {
// ❌ 直接操作数据库
const user = await this.ctx.model.User.create(userData)
// ❌ 在 Service 中处理请求参数
const { page, limit } = this.ctx.query
// ❌ 在 Service 中格式化响应
this.ctx.body = { code: 0, data: user }
}
}Repository 层
Repository 层是数据访问层,负责与数据库交互。
主要职责
- 数据库 CRUD 操作
- 查询转换(Adapter)
- 异常处理
- 字段映射
- 数据验证
标准实现
javascript
// app/repository/UserMongoRepository.js
const BaseMongoRepository = require('../lib/repository/BaseMongoRepository')
class UserMongoRepository extends BaseMongoRepository {
constructor(app) {
super(app, app.model.User)
}
// 配置钩子
_getDefaultPopulate() {
return ['role', 'department']
}
_getDefaultSort() {
return { createdAt: -1 }
}
// 数据处理钩子
_customProcessDataItem(user) {
// 排除密码字段
if (user.password) {
delete user.password
}
return user
}
// 业务方法
async findByEmail(email) {
return await this.findOne({
filters: { email }
})
}
async findByUserName(userName) {
return await this.findOne({
filters: { userName }
})
}
async checkEmailUnique(email, excludeId) {
const user = await this.findOne({
filters: { email, _id: { $ne: excludeId } }
})
return !user
}
async incrementLoginFailed(userId) {
return await this.updateById(userId, {
$inc: { loginFailedCount: 1 }
})
}
}
module.exports = UserMongoRepository层级交互规则
✅ 正确的调用关系
javascript
// ✅ Controller → Service → Repository
class UserController extends Controller {
async index() {
// Controller 调用 Service
const result = await this.ctx.service.user.getUserList(params)
this.ctx.body = result
}
}
class UserService extends Service {
async getUserList(params) {
// Service 调用 Repository
return await this.ctx.repo.user.findByPage(params)
}
}
// ✅ Service 可以调用多个 Repository
class UserService extends Service {
async getUserWithArticles(userId) {
const user = await this.ctx.repo.user.findById(userId)
const articles = await this.ctx.repo.article.findMany({
filters: { authorId: userId }
})
return { user, articles }
}
}
// ✅ Service 可以调用其他 Service
class UserService extends Service {
async deleteUser(userId) {
// 删除用户
await this.ctx.repo.user.deleteById(userId)
// 调用其他 Service 删除关联数据
await this.ctx.service.article.deleteUserArticles(userId)
await this.ctx.service.comment.deleteUserComments(userId)
}
}❌ 错误的调用关系
javascript
// ❌ Controller 直接调用 Repository
class UserController extends Controller {
async index() {
const result = await this.ctx.repo.user.findByPage(params)
this.ctx.body = result
}
}
// ❌ Controller 直接操作 Model
class UserController extends Controller {
async index() {
const users = await this.ctx.model.User.find()
this.ctx.body = users
}
}
// ❌ Repository 调用 Service
class UserRepository extends BaseMongoRepository {
async createUser(userData) {
// Repository 不应该调用 Service
await this.ctx.service.email.sendWelcome(userData.email)
return await this.create(userData)
}
}
// ❌ Repository 包含业务逻辑
class UserRepository extends BaseMongoRepository {
async createUser(userData) {
// 业务验证应该在 Service 层
if (userData.age < 18) {
throw new Error('年龄不能小于 18')
}
return await this.create(userData)
}
}完整示例
用户注册流程
javascript
// 1. Controller 层 - 入口
class UserController extends Controller {
async register() {
const { ctx } = this
// 参数验证
ctx.validate({
userName: { type: 'string', required: true },
email: { type: 'email', required: true },
password: { type: 'string', required: true, min: 6 }
})
// 调用 Service
const user = await ctx.service.user.register(ctx.request.body)
// 返回响应
ctx.body = {
code: 0,
message: '注册成功',
data: user
}
}
}
// 2. Service 层 - 业务逻辑
class UserService extends Service {
async register(userData) {
const { ctx } = this
// 业务验证:检查邮箱
const emailExists = await ctx.repo.user.findByEmail(userData.email)
if (emailExists) {
throw new Error('邮箱已被注册')
}
// 业务验证:检查用户名
const userNameExists = await ctx.repo.user.findByUserName(userData.userName)
if (userNameExists) {
throw new Error('用户名已被使用')
}
// 业务逻辑:密码加密
const password = await ctx.helper.crypto.encryptPassword(userData.password)
// 开启事务
const transaction = await this.app.model.transaction()
try {
// 创建用户
const user = await ctx.repo.user.create({
...userData,
password,
status: 'pending'
}, { transaction })
// 创建用户配置
await ctx.repo.userConfig.create({
userId: user.id
}, { transaction })
// 提交事务
await transaction.commit()
// 发送验证邮件
await ctx.service.email.sendVerification(user.email)
return user
} catch (error) {
await transaction.rollback()
throw error
}
}
}
// 3. Repository 层 - 数据访问
class UserRepository extends BaseMongoRepository {
async findByEmail(email) {
return await this.findOne({
filters: { email }
})
}
async findByUserName(userName) {
return await this.findOne({
filters: { userName }
})
}
}职责对比
| 维度 | Controller | Service | Repository |
|---|---|---|---|
| 职责 | 请求处理 | 业务逻辑 | 数据访问 |
| 验证 | 参数格式验证 | 业务规则验证 | 数据唯一性验证 |
| 依赖 | Service | Repository | Model |
| 事务 | 不涉及 | 管理事务 | 执行事务 |
| 调用 | 调用 Service | 调用 Repository | 不调用上层 |
| 复杂度 | 简单 | 复杂 | 中等 |
最佳实践总结
Controller 层
- ✅ 只负责请求和响应
- ✅ 参数验证和权限检查
- ✅ 错误处理和日志记录
- ❌ 不包含业务逻辑
- ❌ 不直接操作数据库
Service 层
- ✅ 业务逻辑编排
- ✅ 事务管理
- ✅ 调用 Repository 和其他 Service
- ❌ 不直接操作 Model
- ❌ 不处理 HTTP 请求和响应
Repository 层
- ✅ 数据库操作
- ✅ 查询转换和优化
- ✅ 数据验证和异常处理
- ❌ 不包含业务逻辑
- ❌ 不调用 Service
下一步
- 📖 Repository 模式 - 深入理解 Repository
- 🗄️ 双数据库支持 - 学习数据库切换
- 💡 最佳实践 - 新模块开发指南
- 🛠️ 架构详解 - 深入架构设计