Skip to content

三层架构

DoraCMS 采用清晰的三层架构设计:Controller → Service → Repository,每层职责明确,易于维护和扩展。

架构概览

┌─────────────────────────────────────────────┐
│            Controller 层                     │
│  职责:请求处理、参数验证、权限检查          │
│  依赖:Service 层                            │
└──────────────────┬──────────────────────────┘
                   │ 调用
┌──────────────────▼──────────────────────────┐
│            Service 层                        │
│  职责:业务逻辑编排、事务管理、跨模块调用    │
│  依赖:Repository 层、其他 Service           │
└──────────────────┬──────────────────────────┘
                   │ 调用
┌──────────────────▼──────────────────────────┐
│         Repository 层                        │
│  职责:数据库操作、查询转换、异常处理        │
│  依赖:数据库模型                            │
└─────────────────────────────────────────────┘

Controller 层

Controller 层是应用的入口,负责处理 HTTP 请求和响应。

主要职责

  1. 接收和验证请求参数
  2. 调用 Service 层处理业务逻辑
  3. 格式化响应数据
  4. 权限检查
  5. 错误处理

标准实现

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 = UserController

Controller 最佳实践

✅ 应该做的

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 层是业务逻辑的核心,负责编排和协调各种操作。

主要职责

  1. 业务逻辑编排
  2. 事务管理
  3. 调用 Repository 进行数据操作
  4. 跨模块调用
  5. 业务验证

标准实现

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 = UserService

Service 最佳实践

✅ 应该做的

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 层是数据访问层,负责与数据库交互。

主要职责

  1. 数据库 CRUD 操作
  2. 查询转换(Adapter)
  3. 异常处理
  4. 字段映射
  5. 数据验证

标准实现

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 }
    })
  }
}

职责对比

维度ControllerServiceRepository
职责请求处理业务逻辑数据访问
验证参数格式验证业务规则验证数据唯一性验证
依赖ServiceRepositoryModel
事务不涉及管理事务执行事务
调用调用 Service调用 Repository不调用上层
复杂度简单复杂中等

最佳实践总结

Controller 层

  • ✅ 只负责请求和响应
  • ✅ 参数验证和权限检查
  • ✅ 错误处理和日志记录
  • ❌ 不包含业务逻辑
  • ❌ 不直接操作数据库

Service 层

  • ✅ 业务逻辑编排
  • ✅ 事务管理
  • ✅ 调用 Repository 和其他 Service
  • ❌ 不直接操作 Model
  • ❌ 不处理 HTTP 请求和响应

Repository 层

  • ✅ 数据库操作
  • ✅ 查询转换和优化
  • ✅ 数据验证和异常处理
  • ❌ 不包含业务逻辑
  • ❌ 不调用 Service

下一步