Files
easygoadmin-wiki/EasyGoAdmin_Iris_EleVue/zh/architecture/backend-layer.md
T
2026-10-20 21:19:44 +08:00

16 KiB
Raw Blame History

4.3 后端分层架构

:::tip 概述 后端采用"三层主干 + 三层支撑"架构:Controller → Logic → Model 构成主干链路,Validate、Service、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。 :::

架构全景

┌─────────────────────────────────────────────────────────────────────┐
│                         支撑层                                      │
│                                                                     │
│  Validate(参数校验)   Service(通用能力)   Middleware(请求拦截)  │
│  app/validate/*.php    app/service/*.php     app/middleware/*.php   │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ 被主干层调用
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         主干链路                                     │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ Controller(控制器层)                                        │   │
│  │ 接收请求 → 获取参数 → 调用 Logic → 封装响应                   │   │
│  │ 基类:BaseController | 文件:app/controller/*.php             │   │
│  └──────────────────────────┬──────────────────────────────────┘   │
│                             │ 调用 Logic 方法                       │
│                             ▼                                       │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ Logic(逻辑层)                                               │   │
│  │ 属性配置 → 参数校验 → 生命周期钩子 → 横切处理 → 调用 Model    │   │
│  │ 基类:BaseLogic / BaseTenantLogic | 文件:app/logic/*.php     │   │
│  └──────────────────────────┬──────────────────────────────────┘   │
│                             │ 调用 Model ORM                        │
│                             ▼                                       │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ Model(模型层)                                               │   │
│  │ 表映射 → 软删除 → 审计字段 → ORM → SQL                        │   │
│  │ 基类:BaseModel | 文件:app/model/*.php                       │   │
│  └──────────────────────────┬──────────────────────────────────┘   │
│                             │                                       │
│                             ▼                                       │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ Database(数据库)                                            │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

各层职责详解

Controller 层

文件:app/controller/{Module}Controller.php 基类:BaseController 职责:HTTP 请求入口,只做"搬运"不做"判断"

职责 说明 示例
参数获取 从请求中提取参数 $this->getJsonBody()、$this->getParams()
调用 Logic 委托业务逻辑 $this->logic->add($data)
响应封装 统一 JSON 格式返回 $this->success()、$this->fail()
注解标注 声明权限和日志 #[Permission]、#[Log]

Controller 不应该做: 业务判断、数据库操作、文件处理、参数校验(特殊场景除外)。

class ArticleController extends BaseController
{
    protected ArticleLogic $logic;

    protected function initialize(): void
    {
        $this->logic = new ArticleLogic();
    }

    // 标准 CRUD — 一行调用
    #[Permission('sys:article:page', '文章分页')]
    public function page(): Json
    {
        return parent::_index($this->logic);
    }

    #[Permission('sys:article:add', '添加文章')]
    public function add(): Json
    {
        return parent::_add($this->logic, $this->getJsonBody());
    }
}

Logic 层

文件:app/logic/{Module}Logic.php 基类:BaseLogic(系统共享)/ BaseTenantLogic(租户隔离) 职责:业务核心,通过"属性配置 + 生命周期钩子"声明式 CRUD

职责 方式 说明
行为定义 属性配置 $pageLikeFields、$uniqueFields、$validateClass 等
业务定制 生命周期钩子 beforeAdd、afterUpdate、beforeDelete 等
自动校验 $validateClass add/update 时自动调用验证器
横切处理 属性驱动 文件字段、富文本、枚举映射、数据权限、租户隔离

Logic 不应该做: 直接 HTTP 交互、直接返回 JSON 响应。

class ArticleLogic extends BaseTenantLogic
{
    /**
     * 关联的模型类
     *
     * @var string
     */
    protected string $modelClass = Article::class;

    /**
     * 参数验证器类
     *
     * @var string
     */
    protected string $validateClass = ArticleValidate::class;

    /**
     * 需要自动处理的文件上传字段
     *
     * @var array
     */
    protected array $fileFields = ['cover'];

    /**
     * LIKE 模糊匹配字段
     *
     * @var array
     */
    protected array $pageLikeFields = ['title'];

    /**
     * 精确匹配字段
     *
     * @var array
     */
    protected array $pageEqFields = ['status', 'category_id'];

    /**
     * 唯一性校验字段
     *
     * @var array
     */
    protected array $uniqueFields = ['title'];

    /**
     * 枚举显示名映射
     *
     * @var array
     */
    protected array $serializeMaps = ['status' => 'article_status'];

    /**
     * 新增前处理:设置默认排序值
     *
     * @param array $data 待新增的数据
     * @return array 处理后的数据
     */
    protected function beforeAdd(array $data): array
    {
        $data['sort'] = $data['sort'] ?? 0;
        return $data;
    }
}

Model 层

文件:app/model/{TableName}.php 基类:BaseModel 职责:数据持久化,只做表映射和自动维护

职责 说明
表映射 protected $name = 'article'
软删除 全局查询范围自动过滤 is_delete=1
审计字段 onBeforeInsert/onBeforeUpdate 自动填充
关联关系 hasMany、belongsTo 等

Model 不应该做: 业务判断、复杂查询逻辑(交给 Logic)。

class Article extends BaseModel
{
    protected $name = 'article';

    // 关联关系(按需定义)
    public function category()
    {
        return $this->belongsTo(Category::class, 'category_id');
    }
}

Validate 层

文件:app/validate/{Module}Validate.php 基类:think\Validate 职责:参数格式校验,定义规则 + 场景

class ArticleValidate extends Validate
{
    protected $rule = [
        'title' => 'require|max:200',
    ];
    protected $message = [
        'title.require' => '文章标题不能为空',
    ];
    protected $scene = [
        'add'    => ['title'],
        'update' => ['title'],
    ];
}

触发方式: Logic 配置 $validateClass 后,add/update 自动调用。

Service 层

文件:app/service/{Name}Service.php 职责:跨模块通用能力,无状态、无副作用

服务 能力
DictService 字典查询(两级缓存)
PasswordService 密码加密(bcrypt 双重加盐)
JwtService Token 生成/刷新/解析
ExcelService Excel 导入导出
CaptchaService 图片验证码

Service 不应该做: 持有状态、直接操作数据库(通过 Model 静态方法例外)。

Middleware 层

文件:app/middleware/{Name}Middleware.php 职责:请求拦截,在 Controller 前后执行

CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → LogMiddleware → Controller
中间件 作用 注入数据
AuthMiddleware JWT 认证 + 权限校验 $request->userInfo
TenantMiddleware 租户上下文 $request->tenantId
DemoMiddleware 演示环境拦截 —
LogMiddleware 操作日志记录 —

各层方法对照表

分页查询链路

层 方法 输入 输出
Controller _index($logic) HTTP 请求参数 Json 响应
Logic pageList($params, $where, $with) 参数数组 ['records'=>[], 'total'=>int, ...]
Model Model::page($current, $size)->select() 分页条件 Collection

新增链路

层 方法 输入 输出
Controller _add($logic, $data) 或 $this->logic->add($data) Json 请求体 Json 响应
Logic add($data) 关联数组 int(新记录 ID)
Model Model::create($data) 关联数组 Model 实例

Logic::add 内部流程: beforeAdd → validateData → camel_to_snake → processTenantId → processFile → processContent → filterTableFields → checkUnique → Model::create → afterAdd

修改链路

层 方法 输入 输出
Controller _edit($logic, $id, $data) id + Json 请求体 Json 响应
Logic update($id, $data) id + 关联数组 bool
Model Model::find($id)->save($data) 模型实例 + 关联数组 bool

删除链路

层 方法 输入 输出
Controller _remove($logic, $id) id Json 响应
Logic delete($id) id bool
Model $model->save(['is_delete' => 1]) 软删除标记 bool

数据流向

请求方向(前端 → 后端 → 数据库)

前端请求(camelCase JSON)
    │
    ▼
BaseController::getJsonBody()       ← 获取原始参数
    │
    ▼
BaseLogic::beforeAdd($data)         ← 钩子:预处理
    │
    ▼
BaseLogic::validateData($data)      ← 自动参数校验
    │
    ▼
BaseLogic::convertQueryParams()     ← camelCase → snake_case
    │
    ▼
BaseLogic::processFileFieldsOnSave  ← 文件字段处理
    │
    ▼
BaseLogic::filterTableFields()      ← 过滤非数据库字段
    │
    ▼
BaseLogic::checkUnique()            ← 唯一性校验
    │
    ▼
BaseModel::create()                 ← 审计字段自动填充
    │
    ▼
Database(snake_case)

响应方向(数据库 → 后端 → 前端)

Database(snake_case 数据)
    │
    ▼
BaseModel                           ← ORM 查询
    │
    ▼
BaseLogic 处理
    │ processFileFieldsOnRead()     ← 文件字段补全域名
    │ processContentFieldsOnRead()  ← 富文本占位符替换
    │ processSerializeMaps()        ← 枚举显示名补全
    │ afterDetail()                 ← 钩子:补充关联数据
    ▼
Result::convertData()               ← snake_case → camelCase
    │
    ▼
JSON 响应 → 前端

异常处理传播

Model 层异常(如数据库连接失败)
    │
    ▼ 向上抛出
Logic 层异常(如 beforeDelete 抛出业务异常、validateData 抛出 ValidateException)
    │
    ▼ 向上抛出
Controller 层
    │ 方式一:try-catch 捕获,返回 fail()
    │ 方式二:不捕获,由 ExceptionHandle 统一处理
    ▼
ExceptionHandle::render()
    │ ValidateException   → {"code":1, "msg":"参数验证失败", "data":"..."}
    │ ModelNotFoundException → {"code":1, "msg":"数据不存在"}
    │ RuntimeException(401)  → {"code":1, "msg":"未授权"}
    │ 其他异常              → {"code":1, "msg":"操作失败"}
    ▼
JSON 响应

推荐做法:

  • Controller 通过 _add/_edit/_remove 调用时,不捕获异常,交给 ExceptionHandle
  • Controller 直接调用 $this->logic->add() 时,try-catch 捕获并返回 $this->fail()
  • Logic 层的业务异常(如 beforeDelete 校验)直接 throw new \Exception

完整请求示例

以"新增文章"为例,展示一个请求从进入到返回的完整链路:

POST /api/article/add
Content-Type: application/json
Authorization: Bearer <token>

{"title": "新文章", "categoryId": 1, "status": 0}
1. CorsMiddleware        → 放行(设置跨域头)
2. AuthMiddleware        → 校验 Token → 注入 $request->userInfo
3. TenantMiddleware      → 解析租户 → 注入 $request->tenantId
4. LogMiddleware         → 记录日志(#[Log] 注解)

5. ArticleController::add()
   │ getJsonBody() → ["title"=>"新文章", "categoryId"=>1, "status"=>0]
   │
   ▼
6. ArticleLogic::add($data)
   │ beforeAdd($data)           → 设置默认 sort=0
   │ validateData($data, 'add') → 校验 title 必填 ✓
   │ camel_to_snake($data)      → ["title"=>"新文章", "category_id"=>1, "status"=>0]
   │ processTenantId($data)     → 加入 tenant_id
   │ processFileFieldsOnSave    → cover 无值,跳过
   │ filterTableFields($data)   → 过滤非表字段
   │ checkUnique($data)         → title 唯一 ✓
   │
   ▼
7. Article::create($data)
   │ onBeforeInsert            → 自动填充 create_user, create_time
   │
   ▼
8. Database INSERT

9. ArticleLogic::afterAdd($id, $data)  → 无额外操作

10. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}}

层间通信规范

规则 说明
Controller → Logic ✅ 允许,标准调用
Controller → Model ❌ 禁止,越层
Logic → Model ✅ 允许,标准调用
Logic → Service ✅ 允许,调用通用能力
Logic → Controller ❌ 禁止,反向依赖
Model → Logic ❌ 禁止,反向依赖
Model → Service ⚠️ 慎用,仅静态工具方法
Middleware → Controller ✅ 通过 $request 注入数据
Validate → Logic ❌ 禁止,只做格式校验