Skip to content

4.6 核心基类体系 ​

概述

项目有四个核心基类,构成"控制器 → 逻辑层 → 模型"三层架构的骨架。所有业务模块都必须继承对应的基类,通过属性配置 + 生命周期钩子实现声明式 CRUD,子类只需声明"做什么"而不用写"怎么做"。

基类继承关系 ​

继承规则

  • 系统共享模块(Menu、Dict、Config 等)继承 BaseLogic
  • 租户隔离模块(User、Role、Article、Dept、Level 等)继承 BaseTenantLogic
  • 所有模型继承 BaseModel,所有控制器继承 BaseController
text
BaseController(控制器基类)
    └── 所有 Controller

BaseLogic(逻辑基类 - 系统共享模块)
    ├── MenuLogic
    ├── DictLogic
    ├── ConfigLogic
    └── ...
    │
    └── BaseTenantLogic(租户逻辑基类,继承 BaseLogic)
        ├── UserLogic
        ├── RoleLogic
        ├── ArticleLogic
        ├── DeptLogic
        ├── LevelLogic
        └── ...

BaseModel(模型基类,继承 ThinkPHP Model)
    └── 所有 Model

请求处理全链路 ​

理解基类体系的关键是理解一次请求如何流经各层:

text
HTTP 请求
    │
    ▼
BaseController(参数获取、响应封装)
    │  getJsonBody() / getParams()
    │  success() / fail() / _page()
    │
    ▼
BaseLogic(业务编排、属性驱动)
    │  validateData()  → 自动参数校验
    │  beforeAdd()     → 生命周期钩子
    │  add()           → 文件处理、唯一性校验、写入
    │  afterAdd()      → 关联表保存
    │
    ▼
BaseModel(数据持久化)
    │  onBeforeInsert  → 审计字段自动填充
    │  soft_delete     → 全局过滤已删除数据
    │
    ▼
数据库

BaseController ​

文件:app/BaseController.php

设计要点 ​

  • 通用 CRUD 方法以 _ 前缀命名,避免与子类路由方法冲突
  • 子类只需实现 initialize() 并实例化自己的 Logic 层
  • 登录用户信息来自 AuthMiddleware 注入的 $request->userInfo

方法分类 ​

text
┌─────────────────────────────────────────────────────────────┐
│                    BaseController                           │
├─────────────────────────────────────────────────────────────┤
│ 统一响应                                                    │
│   success($data, $msg, $raw)    成功响应                    │
│   _page($result)                分页响应                    │
│   fail($msg, $code, $data)      失败响应                    │
├─────────────────────────────────────────────────────────────┤
│ 参数获取                                                    │
│   getParam($name, $default)     获取单个参数                │
│   getParams()                   获取所有参数                │
│   getPageParams()               获取分页参数(含边界限制)  │
│   getJsonBody()                 获取 JSON 请求体            │
├─────────────────────────────────────────────────────────────┤
│ 登录用户                                                    │
│   getLoginUser()                获取用户信息对象            │
│   getLoginUserId()              获取用户 ID(未登录返回 0) │
│   getLoginUsername()            获取用户名(未登录返回 '') │
├─────────────────────────────────────────────────────────────┤
│ 通用 CRUD(子类通过 parent:: 调用)                          │
│   _index($logic, $where, $with) 分页查询                    │
│   _list($logic, $where)         全量列表                    │
│   _treeList($logic, $where)     树形列表                    │
│   _detail($logic, $id)          详情查询                    │
│   _add($logic, $data)           新增                        │
│   _edit($logic, $id, $data)     修改                        │
│   _remove($logic, $id)          删除                        │
│   _batchRemove($logic, $ids)    批量删除                    │
├─────────────────────────────────────────────────────────────┤
│ 手动验证(特殊场景)                                        │
│   validate($data, $validate)    数据验证                    │
└─────────────────────────────────────────────────────────────┘

通用 CRUD 方法与 Logic 层的对应关系 ​

Controller 方法Logic 方法说明
_index($logic)$logic->pageList()分页查询
_list($logic)$logic->allList()全量列表
_treeList($logic)$logic->treeList()树形列表
_detail($logic, $id)$logic->detail($id)详情查询
_add($logic, $data)$logic->add($data)新增
_edit($logic, $id, $data)$logic->update($id, $data)修改
_remove($logic, $id)$logic->delete($id)删除
_batchRemove($logic, $ids)$logic->batchDelete($ids)批量删除

分页参数 ​

getPageParams() 返回 ['pageNo' => int, 'pageSize' => int],带边界限制:

  • pageNo 最小为 1
  • pageSize 限制在 1~100,默认 20

前端传参示例:GET /api/article/page?pageNo=1&pageSize=10&title=测试&status=1

子类实现示例 ​

php
class ArticleController extends BaseController
{
    protected ArticleLogic $logic;

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

    // 分页查询 — 一行搞定
    #[Permission('sys:article:list', '文章列表')]
    public function page(): Json
    {
        return parent::_index($this->logic);
    }

    // 详情
    #[Permission('sys:article:detail', '文章详情')]
    public function detail(int $id): Json
    {
        return parent::_detail($this->logic, $id);
    }

    // 新增(使用 _add 快捷方法)
    #[Log('文章管理-新增', Log::TYPE_ADD, '新增文章:{title}')]
    #[Permission('sys:article:add', '添加文章')]
    public function add(): Json
    {
        return parent::_add($this->logic, $this->getJsonBody());
    }

    // 删除 — 一行搞定
    #[Permission('sys:article:delete', '删除文章')]
    public function delete(int $id): Json
    {
        return parent::_remove($this->logic, $id);
    }
}

BaseLogic ​

文件:app/BaseLogic.php

BaseLogic 是整个项目的核心,通过"属性配置 + 生命周期钩子"实现声明式 CRUD。子类只需配置属性和按需重写钩子,即可获得完整的增删改查能力。

属性配置清单 ​

属性类型说明示例
modelClassstring关联模型类名(必须)Article::class
validateClassstring验证器类名(设置后 add/update 自动校验)ArticleValidate::class
fileFieldsarray单文件字段列表['cover', 'avatar']
multiFileFieldsarray多文件字段列表(逗号分隔的多个URL)['images']
fileSaveDirstring文件保存子目录'article'
pageLikeFieldsarrayLIKE 模糊匹配字段['title', 'author']
pageEqFieldsarray精确匹配字段['status', 'category_id']
pageOrderByarray默认排序规则['field'=>'sort','type'=>'asc']
uniqueFieldsarray唯一性校验字段['username', ['code','pid']]
serializeMapsarray枚举显示名映射['status'=>'article_status']
contentFieldsarray富文本字段['content']
dataScopeUserFieldstring数据权限:归属字段'create_user'
dataScopeDeptFieldstring数据权限:部门字段'dept_id'
tenantScopeFieldstring租户隔离字段'tenant_id'
treeParentFieldstring树形结构父级字段'parent_id'
treeLikeFieldstring树形列表模糊搜索字段'name'

生命周期钩子 ​

钩子时机用途可拦截
beforeAdd($data)新增前修改数据、预处理,返回处理后的 data✅ 抛异常
afterAdd($id, $data)新增后保存关联表、发送通知❌
beforeUpdate($id, $data)修改前修改数据、预处理,返回处理后的 data✅ 抛异常
afterUpdate($id, $data)修改后更新关联表❌
beforeDelete($id)删除前检查子级、业务约束✅ 抛异常
afterDelete($id)删除后清理关联数据❌
beforeBatchDelete($ids)批量删除前整体校验✅ 抛异常
afterBatchDelete($ids)批量删除后批量清理❌
afterDetail($id, &$data)详情查询后补充关联数据、格式化输出(引用传递)❌
afterPageList(&$records, $params)列表查询后批量补充关联数据,分页 pageList 与全量 allList 均触发(引用传递)❌

新增流程(add) ​

text
beforeAdd($data)              ← 钩子:可修改数据,抛异常可拦截
    │
    ▼
validateData($data, 'add')    ← 自动参数校验(配置了 validateClass 时)
    │
    ▼
array_camel_to_snake($data)   ← 键名 camelCase → snake_case
    │
    ▼
processTenantId($data)        ← 自动填充 tenant_id
    │
    ▼
processFileFieldsOnSave       ← 文件字段:临时文件 → 正式目录
    │
    ▼
processContentFieldsOnSave    ← 富文本:迁移媒体文件
    │
    ▼
filterTableFields($data)      ← 过滤非数据库字段
    │
    ▼
checkUnique($data)            ← 唯一性校验
    │
    ▼
Model::create($data)          ← 写入数据库
    │
    ▼
afterAdd($id, $data)          ← 钩子:保存关联表等副作用

修改流程(update) ​

text
beforeUpdate($id, $data)                ← 钩子
    │
    ▼
validateData($data, 'update', $id)      ← 自动参数校验
    │
    ▼
array_camel_to_snake → 文件 → 富文本 → 过滤 → 唯一性(排除自身)
    │
    ▼
Model::find($id)->save($data)
    │
    ▼
afterUpdate($id, $data)

删除流程(delete) ​

text
beforeDelete($id)      ← 钩子:抛异常可拦截
    │
    ▼
is_delete = 1          ← 软删除
    │
    ▼
afterDelete($id)

查询流程(pageList / allList) ​

text
buildQuery($model)               ← 构建查询条件(LIKE + 精确匹配 + 数据权限 + 租户隔离)
    │
    ▼
applyOrderBy($model)             ← 排序(前端传参优先,否则使用配置)
    │
    ▼
$page / $select                  ← 分页 / 全量查询
    │
    ▼
processFileFieldsOnReadList      ← 文件字段补全域名
    │
    ▼
processSerializeMapsList         ← 枚举显示名补全
    │
    ▼
filterFields                     ← 字段过滤
    │
    ▼
afterPageList(&$records)         ← 钩子:批量补充关联数据(引用传递)

自动参数校验(validateClass) ​

配置 $validateClass 属性后,add/update 时自动调用对应验证器校验参数,无需在 Controller 中手动调用。

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

    /**
     * 参数验证器类
     *
     * @var string
     */
    protected string $validateClass = \app\validate\ArticleValidate::class;
    // ...
}

工作原理:

  1. add() 中:beforeAdd → validateData($data, 'add') → 后续入库
  2. update() 中:beforeUpdate → validateData($data, 'update', $id) → 后续入库
  3. 验证器按场景(add/update)校验,场景不存在时使用默认规则
  4. update 场景自动注入 id 到 data,确保 unique 规则排除当前记录
  5. 校验失败抛出 ValidateException,由 ExceptionHandle 统一返回

覆盖范围: 无论是 Controller 通过 _add/_edit 调用,还是 import 等内部调用都会生效。

向后兼容: $validateClass 默认为空字符串,不设置则不验证。

唯一性校验(uniqueFields) ​

php
// 全局唯一
protected array $uniqueFields = ['username', 'mobile'];

// 分组内唯一:code 在同一 pid 下唯一
protected array $uniqueFields = [
    'username',           // 全局唯一
    ['code', 'pid'],      // code 在同一 pid 下唯一
];

校验时机:add/update 时自动校验(在 checkUnique() 中执行)。错误提示格式:"字段中文名 + 已存在",可通过重写 getFieldLabel() 自定义。

查询条件构建 ​

buildQuery() 按以下顺序叠加条件:

  1. 额外固定条件(代码层面通过 $where 传入)
  2. LIKE 模糊匹配(pageLikeFields 中配置的字段)
  3. 精确匹配(pageEqFields 中配置的字段)
  4. 数据权限过滤(applyDataScope)
  5. 租户隔离过滤(applyTenantScope)

子类实现示例 ​

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

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

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

    /**
     * 文件保存的子目录
     *
     * @var string
     */
    protected string $fileSaveDir = 'article';

    /**
     * 富文本字段
     *
     * @var array
     */
    protected array $contentFields = ['content'];

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

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

    /**
     * 默认排序规则
     *
     * @var array
     */
    protected array $pageOrderBy = ['field' => 'sort', 'type' => 'asc'];

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

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

    /**
     * 数据权限:归属字段名(用于按创建人过滤)
     *
     * @var string
     */
    protected string $dataScopeUserField = 'create_user';

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

BaseTenantLogic ​

文件:app/BaseTenantLogic.php

设计说明 ​

BaseTenantLogic 继承 BaseLogic,仅做一件事:将 tenantScopeField 设为 'tenant_id',从而让所有子类自动获得租户隔离能力。

php
abstract class BaseTenantLogic extends BaseLogic
{
    /**
     * 租户隔离字段名
     *
     * @var string
     */
    protected string $tenantScopeField = 'tenant_id';
}

隔离机制(由 BaseLogic 实现) ​

场景行为
查询时applyTenantScope 自动叠加 WHERE tenant_id = ? 条件
新增时processTenantId 自动填充 tenant_id 字段
超级管理员tenantId = 0 时不过滤,可跨租户查看数据

租户上下文来源 ​

TenantMiddleware 解析并注入 $request->tenantId。

使用约定 ​

  • 需要租户隔离的模块继承此基类(如 User、Article、Dept、Level 等)
  • 系统共享表不继承此基类,直接继承 BaseLogic(如 Menu、Role、Dict、Config 等)

子类示例 ​

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

    /**
     * 参数验证器类
     *
     * @var string
     */
    protected string $validateClass = \app\validate\ArticleValidate::class;
    // tenantScopeField 已由基类设为 'tenant_id',无需再声明
    // ...
}

BaseModel ​

文件:app/BaseModel.php

设计说明 ​

BaseModel 继承 ThinkPHP 的 Model,提供两项横切能力:

  1. 软删除:基于 is_delete 字段,不使用 ThinkPHP 内置的 deleteTime 机制
  2. 审计字段自动写入:create_user / create_time / update_user / update_time

软删除机制 ​

text
┌─────────────────────────────────────────────────────────┐
│                   软删除机制                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  全局查询范围 soft_delete:                               │
│    所有查询自动叠加 WHERE is_delete = 0                  │
│    → 已删除数据默认不可见                                │
│                                                         │
│  软删除操作:                                             │
│    $model->softDelete()   → UPDATE SET is_delete = 1    │
│    BaseModel::batchSoftDelete($ids)  → 批量软删除        │
│                                                         │
│  查询已删除数据:                                         │
│    Model::withoutGlobalScope(['soft_delete'])->...       │
│                                                         │
└─────────────────────────────────────────────────────────┘

方法列表:

方法类型说明
softDelete()实例方法设置 is_delete = 1
batchSoftDelete($ids)静态方法批量软删除,先关闭全局范围再 whereIn 更新
scopeSoftDelete($query)全局范围自动过滤 is_delete = 1 的数据

审计字段自动写入 ​

通过模型事件自动填充,无需手动处理:

事件自动填充说明
onBeforeInsert(创建前)create_user、create_time若调用方已显式传入,则不覆盖
onBeforeUpdate(更新前)update_user、update_timeupdate_time 始终覆盖

用户来源:AuthMiddleware 注入的 $request->userInfo->username。若无登录用户(如控制台任务),则跳过用户字段,时间字段仍写入。

数据库字段规范 ​

所有表建议包含以下公共字段:

字段名类型说明
idint主键,自增
is_deletetinyint(1)软删除标记,0=正常 1=已删除
create_uservarchar(50)创建人
create_timedatetime创建时间
update_uservarchar(50)更新人
update_timedatetime更新时间

使用示例 ​

php
// 模型定义 — 最简形式
class Article extends BaseModel
{
    /**
     * 对应数据库表名(不含前缀)
     *
     * @var string
     */
    protected $name = 'article';
}

// 软删除
$article = Article::find(1);
$article->softDelete();  // UPDATE article SET is_delete = 1 WHERE id = 1

// 批量软删除
Article::batchSoftDelete([1, 2, 3]);

// 查询已删除数据(临时关闭全局范围)
$all = Article::withoutGlobalScope(['soft_delete'])->select();

// 审计字段自动写入(无需手动处理)
Article::create(['title' => '测试']);
// 自动填充: create_user='admin', create_time='2026-09-22 12:00:00'

基类选择指南 ​

场景继承原因
系统级共享数据(菜单、字典、配置)BaseLogic不需要租户隔离
租户业务数据(用户、角色、文章、部门、职级)BaseTenantLogic需要按租户隔离数据
所有数据模型BaseModel获得软删除 + 审计字段
所有控制器BaseController获得统一响应 + 通用 CRUD

快速判断

问自己:这个模块的数据是否需要按租户隔离?

  • 是 → 继承 BaseTenantLogic
  • 否 → 继承 BaseLogic

小蚂蚁云团队 · 提供技术支持