Files
djangoadmin-wiki/DjangoAdmin_FastAPI_AntdVue/zh/volume3/base-classes.md
T
2026-09-12 23:12:15 +08:00

643 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 核心基类体系
本章详细描述 的核心基类体系,包括 `base_model`、`base_db`、`BaseForm`、`BaseRepository`、`BaseService` 五大基类的设计理念、继承关系和使用方式。以岗位(Position)模块为实例贯穿说明。
## 基类继承关系
:::tip 基类体系总览
五大基类各司其职,覆盖从数据模型到请求校验再到业务逻辑的完整链路。子类只需声明差异点,即可获得标准化的增删改查能力。
:::
```
┌──────────────────────────────────────────────────────────────────┐
│ SQLAlchemy Base (declarative_base) │
└──────────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ base_model (src/core/base_model.py) │
│ ─────────────────────────────────────────────────────────────── │
│ 通用字段: id / create_user / create_time / update_user / │
│ update_time / is_delete │
│ 序列化: to_dict() → 驼峰字段名 + UTC 转本地 + 排除敏感字段 │
└──────────────────────────┬───────────────────────────────────────┘
│ 多继承
▼
┌──────────────────────────────────────────────────────────────────┐
│ base_db (src/core/base_db.py) │
│ ─────────────────────────────────────────────────────────────── │
│ 持久化: save() → db.add + db.flush(不 commit) │
│ 事务: 由 db_session_middleware 统一管理 │
└──────────────────────────────────────────────────────────────────┘
│
▼
Position(base_model, base_db)
┌──────────────────────────┐
│ __tablename__ │
│ name / status / sort │
└──────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ BaseForm (src/core/base_schemas.py) │
│ ─────────────────────────────────────────────────────────────── │
│ Pydantic BaseModel 子类 │
│ 可选 id 字段: Optional[int],创建时可省略 │
│ 空字符串容错: '' / 'null' / 'undefined' → None │
└──────────────────────────────────────────────────────────────────┘
│
▼
PositionForm(BaseForm)
┌──────────────────────────┐
│ name / status / sort │
│ 继承可选 id │
└──────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ BaseRepository[M] (src/core/base_repository.py) │
│ ─────────────────────────────────────────────────────────────── │
│ 查询: get_by_id / get_by_ids / get_one / get_all / count │
│ 分页: paginate → (items, total) │
│ 写操作: create / update / batch_delete │
│ 条件: filter (原始) / filter_by (等值+软删) │
│ 软删除: 等值方法自动追加 is_delete=0 │
└──────────────────────────┬───────────────────────────────────────┘
│
▼
PositionRepository(BaseRepository[Position])
┌──────────────────────────┐
│ pass │
│ position_repo = 单例 │
└──────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ BaseService[M] (src/core/base_service.py) │
│ ─────────────────────────────────────────────────────────────── │
│ 查询: get_page / get_list / get_detail │
│ 写操作: add / update / delete / batch_delete / update_status │
│ 差异点: repo / model / page_like_fields / page_eq_fields / │
│ page_order_by / unique_fields / serialize_maps │
│ 钩子: _before_add / _before_update / _before_delete │
│ _apply_page_filters / _serialize / _serialize_detail │
│ _build_create_fields / _build_update_fields │
└──────────────────────────┬───────────────────────────────────────┘
│
▼
PositionService(BaseService[Position])
┌──────────────────────────────────┐
│ repo = position_repo │
│ model = Position │
│ page_like_fields = ('name',) │
│ page_eq_fields = ('status',) │
│ page_order_by = (('sort','asc'),)│
│ unique_fields = {'name': '...'} │
│ _before_delete() → 引用检查 │
│ position_service = 单例 │
└──────────────────────────────────┘
```
## base_model — 模型基类
**文件位置**:`src/core/base_model.py`
### 设计目标
为所有业务模型提供统一的通用字段和序列化方法,消除重复定义。
### 通用字段
```python
class base_model(Base):
__abstract__ = True # 抽象基类,不会在数据库中创建对应的表
# 主键:自增整数,所有业务表的唯一标识
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键ID")
# 创建人:记录该条记录的创建者用户名,由 Service 层自动填充
create_user = Column(String(50), nullable=True, comment="创建人用户名")
# 创建时间:记录创建时的 UTC 时间,数据库默认自动生成
create_time = Column(DateTime, default=_UTC_NOW, server_default=func.now(), comment="创建时间")
# 更新人:记录最后修改该条记录的用户名,由 Service 层自动填充
update_user = Column(String(50), nullable=True, comment="最后更新人用户名")
# 更新时间:记录最后修改时间,更新时自动刷新,数据库默认自动生成
update_time = Column(DateTime, default=_UTC_NOW, onupdate=_UTC_NOW, server_default=func.now(), comment="最后更新时间")
# 逻辑删除标记:0 表示正常记录,1 表示已删除,物理删除不使用,保证数据可恢复;添加索引以提升查询性能,Repository 层默认过滤 is_delete=0
is_delete = Column(Integer, default=0, server_default=text('0'), index=True, comment="逻辑删除标识:0-正常,1-已删除")
```
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| `id` | Integer, PK, AI | 主键自增 |
| `create_user` | String(50) | 创建人(realname) |
| `create_time` | DateTime | 创建时间(UTC,server_default) |
| `update_user` | String(50) | 更新人(realname) |
| `update_time` | DateTime | 更新时间(UTC,onupdate 自动触发) |
| `is_delete` | Integer | 软删除标识:0=正常,1=已删除 |
### 表注释钩子
```python
# src/core/base_model.py
# ============================================================
# 子类钩子:自动将 __table_comment__ 写入 __table_args__
# ============================================================
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
comment = getattr(cls, '__table_comment__', None)
if not comment:
return
# 仅对有 __tablename__ 的具体模型操作(跳过纯抽象中间层)
if not getattr(cls, '__tablename__', None):
return
existing = getattr(cls, '__table_args__', {})
if isinstance(existing, dict):
merged = {**existing}
elif isinstance(existing, tuple) and existing:
merged = dict(existing[-1]) if isinstance(existing[-1], dict) else {}
else:
merged = {}
if 'comment' not in merged:
merged['comment'] = comment
cls.__table_args__ = merged
```
子类只需声明 `__table_comment__ = "岗位表"`,基类通过 `__init_subclass__` 钩子自动将其写入 `__table_args__`,无需手动配置 `Table(comment=...)`。
### 序列化方法
```python
# ============================================================
# 子类钩子:自动将 __table_comment__ 写入 __table_args__
# ============================================================
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
comment = getattr(cls, '__table_comment__', None)
if not comment:
return
# 仅对有 __tablename__ 的具体模型操作(跳过纯抽象中间层)
if not getattr(cls, '__tablename__', None):
return
existing = getattr(cls, '__table_args__', {})
if isinstance(existing, dict):
merged = {**existing}
elif isinstance(existing, tuple) and existing:
merged = dict(existing[-1]) if isinstance(existing[-1], dict) else {}
else:
merged = {}
if 'comment' not in merged:
merged['comment'] = comment
cls.__table_args__ = merged
```
关键设计点:
1. **敏感字段排除**:`is_delete`、`password`、`salt` 不会出现在序列化结果中
2. **时区处理**:MySQL 的 `DATETIME` 读回为 naive UTC,统一转 aware UTC 后再转本地时间
3. **驼峰命名**:数据库下划线字段名自动转为前端习惯的驼峰命名
## base_db — 数据库操作基类
**文件位置**:`src/core/base_db.py`
### 设计目标
提供模型实例的持久化方法,事务边界由中间件统一管理。
```python
# src/core/base_db.py
class base_db:
# 抽象基类,不直接实例化,由业务模型继承
__abstract__ = True
def save(self):
# 从 contextvars 获取当前请求的数据库会话(由 db_session_middleware 注入)
db = get_db_session()
# 将当前模型实例添加到 SQLAlchemy 会话的持久化上下文中
db.add(self)
# 立即发送 INSERT SQL 到数据库,获取自增 ID,但不提交事务(事务由中间件统一控制)
db.flush()
# 返回当前实例,支持链式调用,便于后续操作
return self
```
:::tip 为什么 save() 不 commit?
事务提交由 `src/middleware/db_session.py` 的 `db_session_middleware` 统一管理:响应体 `code==0` 时 commit,否则 rollback。这样业务代码无需关心事务边界,也避免了部分操作成功、部分失败导致的数据不一致。审计日志等需要独立提交的场景,可使用 `core.database.commit_independently(obj)` 绕过请求事务。
:::
## BaseForm — 表单基类
**文件位置**:`src/core/base_schemas.py`
### 设计目标
所有创建/编辑表单的 Pydantic 基类,提供统一的可选 `id` 字段和前端空字符串容错处理。
### 完整代码
```python
# src/core/base_schemas.py
# +======================================================================
# | 模块: 表单基类
# | 说明: 统一处理前端传入的空字符串 id → None 等通用校验
# +======================================================================
from typing import Optional
from pydantic import BaseModel, Field, field_validator
# ============================================================
# 表单基类
# ============================================================
class BaseForm(BaseModel):
"""
表单基类
所有创建/编辑表单应继承此类,自动获得:
1. id 字段:Optional[int],前端传空字符串时自动转为 None
"""
id: Optional[int] = Field(None, description="主键ID")
@field_validator('id', mode='before')
@classmethod
def empty_str_to_none(cls, v):
"""前端 POST 空字符串 '' 时转为 None,避免 Pydantic 整数解析报错"""
if v == '' or v == 'null' or v == 'undefined':
return None
return v
```
### 代码解析
**可选 `id` 字段**:
```python
id: Optional[int] = Field(None, description="主键ID")
```
`id` 默认为 `None`。创建记录时前端不传 `id`,Pydantic 自动赋默认值;编辑记录时前端传入 `id`,Service 层据此判断是新增还是更新。
**空字符串容错**:
```python
@field_validator('id', mode='before')
@classmethod
def empty_str_to_none(cls, v):
if v == '' or v == 'null' or v == 'undefined':
return None
return v
```
前端表单在未填写 `id` 时,可能提交空字符串 `''`、字面量 `'null'` 或 `'undefined'`。Pydantic 无法将这些值解析为 `int`,会抛出校验错误。`mode='before'` 确保此验证器在 Pydantic 类型转换之前执行,将这些边界值统一转为 `None`。
### 子类示例
```python
# src/modules/system/position/schemas.py
# +======================================================================
# | 模块: 岗位表单验证
# | 说明: 岗位创建/编辑/状态更新的请求数据校验
# +======================================================================
from core.base_schemas import BaseForm
# ============================================================
# 岗位表单类
# ============================================================
class PositionForm(BaseForm):
"""岗位创建/编辑表单"""
name: str = Field(..., min_length=1, max_length=150, description="岗位名称")
status: int = Field(..., ge=1, le=2, description="岗位状态:1-在用 2-停用")
sort: int = Field(..., ge=0, le=99999, description="岗位排序")
```
`PositionForm` 继承 `BaseForm` 后自动获得可选 `id` 字段。创建时 JSON 请求体无需包含 `id`:
```json
{ "name": "测试岗位", "status": 1, "sort": 0 }
```
编辑时传入 `id`:
```json
{ "id": 1, "name": "修改后", "status": 1, "sort": 1 }
```
### 使用场景
| 场景 | 是否传 id | BaseForm 行为 |
|:----|:----|:----|
| 新增 | 不传 | `id = None`,Service 走 `add()` 分支 |
| 编辑 | 传入 | `id = 123`,Service 走 `update()` 分支 |
| 特定操作 | 不需要 id | 继承 `BaseModel` 而非 `BaseForm`(如 `PositionStatusForm`) |
:::tip 何时继承 BaseForm vs BaseModel
- **创建/编辑共用的表单**:继承 `BaseForm`,自动获得可选 `id`
- **特定操作的表单**(如状态更新、排序调整):直接继承 `BaseModel`,只定义必要字段
:::
## BaseRepository — 仓库基类
**文件位置**:`src/core/base_repository.py`
### 设计目标
封装各模型的数据访问层公共操作,提供统一的 CRUD 接口和软删除约定。
### 核心方法一览
| 方法 | 签名 | 说明 | 软删过滤 |
|:---|:---|:---|:---:|
| `get_by_id` | `(obj_id) → Optional[M]` | 按 ID 获取单条记录 | 是 |
| `get_by_ids` | `(ids) → list` | 按 ID 列表批量获取 | 是 |
| `get_one` | `(**filters) → Optional[M]` | 等值条件获取单条 | 是 |
| `get_all` | `(**filters) → List[M]` | 等值条件获取所有 | 是 |
| `count` | `(**filters) → int` | 等值条件计数 | 是 |
| `exists` | `(obj_id) → bool` | 检查记录是否存在 | 是 |
| `exists_by_field` | `(field, value, exclude_id) → bool` | 唯一性校验 | 是 |
| `paginate` | `(page_no, page_size, query) → (items, total)` | 分页查询 | 是 |
| `filter` | `(*criterion) → Query` | 原始条件查询 | **否** |
| `filter_by` | `(**kwargs) → Query` | 等值条件查询 | 是 |
| `create` | `(**fields) → M` | 创建记录 | — |
| `update` | `(obj_id, data) → int` | 按 ID 更新 | 是 |
| `batch_delete` | `(ids_str) → int` | 按 ID 软删除 | — |
### 软删除约定
:::warning 重要设计约定
- **等值便捷方法**(get_by_id / get_one / get_all / count / filter_by / exists / exists_by_field / paginate)自动追加 `is_delete=0` 过滤,调用方无需关心
- **原始出口** `filter(*criterion)` **不做**软删过滤,由调用方自行追加 `Model.is_delete == 0`,用于 `in_` / `!=` / `and_` / 区间等复杂条件
:::
### 数据清洗
```python
# src/core/base_repository.py
def _clean_model_data(self, data: dict) -> dict:
"""清洗入库数据:camelCase 键转 snake_case 并仅保留模型真实列字段
Pydantic schema 可能用 camelCase(如 parentId)而模型列是 snake_case
(parent_id),直传 Query.update / model(**fields) 会触发 CompileError /
TypeError。统一在数据访问层转换,业务模块无需各自处理。
"""
if not data:
return {}
columns = {c.name for c in self.model.__table__.columns}
return {
convert_snake_case(k): v
for k, v in data.items()
if convert_snake_case(k) in columns
}
```
Pydantic Schema 可能用 camelCase(如 `parentId`),而模型列是 snake_case(如 `parent_id`)。`_clean_model_data` 在数据访问层统一转换,同时过滤掉模型中不存在的字段,业务模块无需各自处理。`convert_snake_case` 工具函数位于 `src/utils/string.py`。
## BaseService — 服务基类
**文件位置**:`src/core/base_service.py`
### 设计目标
通过模板方法模式封装通用 CRUD 流程。子类只需声明差异点,即可获得分页、详情、添加、编辑、删除、批量删除、状态切换等标准接口。
### 差异点声明
| 差异点 | 类型 | 说明 |
|:---|:---|:---|
| `repo` | `BaseRepository[M]` | **必填**,数据仓库实例 |
| `model` | `type[M]` | **必填**,ORM 模型类 |
| `page_like_fields` | `Tuple[str, ...]` | 分页模糊查询字段(取同名查询参数,`LIKE %值%`) |
| `page_eq_fields` | `Tuple[str, ...]` | 分页等值查询字段(取同名查询参数,`=值`) |
| `page_order_by` | `Tuple[Tuple[str, str], ...]` | 分页排序规则,默认 `(('id', 'desc'),)` |
| `unique_fields` | `Dict[str, str]` | 唯一性校验字段及提示,如 `{'name': '岗位名称不能重复'}` |
| `serialize_maps` | `Dict[str, Dict]` | 枚举显示名映射。值为 `str` 时从数据字典取 `{value: label}`,值为 `dict` 时直接按值取名称 |
| `serialize_extra` | `Callable` | 列表/详情共用序列化钩子,返回需合并进字典的额外字段 |
| `detail_serialize` | `Callable` | 详情专用序列化钩子,仅在 `get_detail` 时合并 |
| `file_fields` | `Tuple[str, ...]` | 文件字段(add/update 时自动迁移临时文件,serialize 时自动补全 URL) |
| `rich_text_fields` | `Tuple[str, ...]` | 富文本字段(含嵌入图片迁移 + XSS 清洗) |
| `file_dir` | `str` | 文件存储子目录名,默认从表名去掉 `DB_PREFIX` 推导 |
### 标准接口
| 方法 | 说明 | 返回值 |
|:---|:---|:---|
| `get_page(request)` | 分页查询 | `R.page(records, total, size, current, pages)` |
| `get_list(request)` | 不分页查询 | `R.ok(data=[...])` |
| `get_detail(obj_id)` | 详情查询 | `dict` 或 `None` |
| `add(request, data)` | 新增记录 | `R.ok(msg='添加成功')` |
| `update(request, data)` | 编辑记录 | `R.ok(msg='更新成功')` |
| `delete(ids)` | 删除记录 | `R.ok(msg='本次共删除N条数据')` |
| `batch_delete(request)` | 批量删除 | `R.ok(...)` |
| `update_status(request, data)` | 状态切换 | `R.ok(msg='设置成功')` |
### 钩子方法
| 钩子 | 触发时机 | 默认行为 |
|:---|:---|:---|
| `_before_add(request, data)` | 新增前 | 空操作 |
| `_before_update(request, data)` | 编辑前 | 空操作 |
| `_before_delete(ids)` | 删除前 | 返回 None(放行) |
| `_apply_page_filters(query, request)` | 分页查询构建 | 按声明字段过滤 |
| `_serialize(item)` | 列表行序列化 | `to_dict()` + 枚举映射 + 文件 URL |
| `_serialize_detail(obj)` | 详情序列化 | 同列表 + `detail_serialize` |
| `_build_create_fields(request, data)` | 新增字段组装 | `model_dump(exclude={'id'})` + 创建人 |
| `_build_update_fields(request, data)` | 更新字段组装 | `model_dump(exclude={'id'}, exclude_none=True)` + 更新人 |
### 批量删除的 R 响应统一
```python
# src/core/base_service.py
@staticmethod
def batch_delete_with_r(repo: BaseRepository, ids) -> R:
"""按逗号分隔ID软删除并返回标准R响应(供自定义 service 复用)
数据原语(repo.batch_delete)在 repository 层;业务校验与文案收敛于此,
保证删除失败/成功的提示全仓一致。
"""
id_list = parse_id_list(str(ids))
if not id_list:
return R.failed("记录ID不存在")
count = repo.batch_delete(ids)
if count != len(id_list):
return R.failed("记录不存在")
return R.ok(msg="本次共删除{0}条数据".format(count))
```
批量删除端点为 `async def`,因为需要 `await parse_batch_ids(request)` 异步读取请求体。Service 层的 `batch_delete` 方法内部通过 `asyncio.to_thread` 将同步删除操作放入线程池:
```python
# src/core/base_service.py
async def batch_delete(self, request) -> R:
"""批量删除:请求体为 ID 数组 [1,2,3],线程池执行避免阻塞事件循环"""
ids, err = await parse_batch_ids(request)
if err:
return err
return await asyncio.to_thread(self.delete, ids)
```
:::tip 设计要点
Repository 层的 `batch_delete` 只返回删除条数(数据原语),Service 层的 `batch_delete_with_r` 负责 R 响应封装和文案统一。自定义 Service 的删除方法也应复用此静态方法,保证全仓删除提示一致。
:::
## 实例:Position 模块的基类使用
### Model(继承 base_model + base_db)
```python
# src/modules/system/position/models.py
from sqlalchemy import Column, String, Integer, text
from core.base_db import base_db
from core.base_model import base_model
from core.config import DB_PREFIX
# ============================================================
# 岗位模型
# ============================================================
class Position(base_model, base_db):
"""岗位模型类"""
# ============================================================
# 表名配置
# ============================================================
__tablename__ = DB_PREFIX + "position"
__table_comment__ = "岗位表"
# ============================================================
# 字段定义
# ============================================================
# 岗位名称
name = Column(String(255), nullable=False, index=True, comment="岗位名称")
# 岗位状态:1-在用 2-停用
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="岗位状态:1-在用 2-停用")
# 岗位排序
sort = Column(Integer, default=0, server_default=text('0'), comment="岗位排序")
# ============================================================
# 内置方法
# ============================================================
def __repr__(self):
"""返回岗位名称作为字符串表示"""
return '岗位:{}'.format(self.name)
```
获得的能力:
- 6 个通用字段(id / create_user / create_time / update_user / update_time / is_delete)
- `to_dict()` 序列化方法
- `save()` 持久化方法
- 表注释自动写入
### Repository(继承 BaseRepository)
```python
# src/modules/system/position/repository.py
# +======================================================================
# | 模块: 岗位数据访问层
# | 说明: 封装岗位模型的数据访问操作
# +======================================================================
"""modules/system/position 数据访问层。"""
from modules.system.position.models import Position
from core.base_repository import BaseRepository
# ============================================================
# 岗位仓库类
# ============================================================
class PositionRepository(BaseRepository[Position]):
"""岗位数据仓库,继承基础仓库获得通用 CRUD 能力"""
pass
# ============================================================
# 仓库实例
# ============================================================
position_repo = PositionRepository(Position)
```
获得的能力:
- 全部 12 个通用 CRUD 方法(get_by_id / get_by_ids / get_one / get_all / count / exists / exists_by_field / paginate / filter / filter_by / create / update / batch_delete)
- 软删除自动过滤(等值方法自动追加 `is_delete=0`)
- camelCase → snake_case 数据清洗(`_clean_model_data`)
### Service(继承 BaseService)
```python
# src/modules/system/position/service.py
# +======================================================================
# | 模块: 岗位业务逻辑层
# | 说明: 岗位的增删改查、唯一性校验、状态管理等业务处理
# +======================================================================
from typing import Optional
from core.base_service import BaseService
from modules.system.position.models import Position
from modules.system.position.repository import position_repo
from modules.system.user.models import User
from modules.system.user.repository import user_repo
from utils.request import parse_id_list
# ============================================================
# 岗位业务服务类
# ============================================================
class PositionService(BaseService[Position]):
"""岗位业务服务类,继承基础服务获得通用 CRUD 能力"""
# 数据访问与模型
repo = position_repo
model = Position
# 分页差异点
page_like_fields = ('name',)
page_eq_fields = ('status',)
page_order_by = (('sort', 'asc'),)
# 唯一性校验
unique_fields = {'name': '岗位名称不能重复'}
# ============================================================
# 删除前校验
# ============================================================
def _before_delete(self, ids) -> Optional[str]:
"""删除前校验:存在用户引用该岗位时禁止删除,避免用户岗位悬空"""
id_list = parse_id_list(str(ids))
if id_list and user_repo.filter(User.position_id.in_(id_list), User.is_delete == 0).first():
return "存在用户引用该岗位,请先调整用户岗位"
return None
# ============================================================
# 获取岗位数据列表
# ============================================================
def get_position_list(self, request):
"""获取岗位数据列表"""
# 与 /page 一致的筛选条件:name 模糊、status 精确;保留软删过滤与排序
query = self._apply_page_filters(self.repo.filter_by(), request)
query = query.order_by(self.model.sort.asc())
# 返回结果
return [v.to_dict() for v in query.all()]
# ============================================================
# 模块级单例
# ============================================================
position_service = PositionService()
```
获得的能力:
- 8 个标准接口(get_page / get_list / get_detail / add / update / delete / batch_delete / update_status)
- 唯一性自动校验
- 分页查询自动构建
- 序列化自动处理
:::tip 极简 CRUD
对于最简单的模块(如 Level 职级),Repository 和 Service 只需声明差异点,无需编写任何自定义方法,即可获得完整的增删改查能力。
:::
## 总结
五大基类通过模板方法模式和声明式配置,将通用 CRUD 流程固化为标准实现:
| 基类 | 职责 | 核心能力 |
|:----|:----|:----|
| `base_model` | 模型基类 | 通用字段、to_dict() 序列化 |
| `base_db` | 持久化基类 | save() 方法 |
| `BaseForm` | 表单基类 | 可选 id、空字符串容错 |
| `BaseRepository` | 仓库基类 | 12 个 CRUD 方法、软删除自动过滤 |
| `BaseService` | 服务基类 | 8 个标准接口、钩子方法、声明式配置 |
子类只需声明差异点(过滤字段、排序规则、唯一性约束等),即可获得企业级的增删改查能力。以岗位模块为例,完整实现只需约 80 行有效代码(不含版权头),其中 Repository 几乎零代码。这种设计使得新模块的开发效率提升 3-5 倍,同时保证了代码风格和行为的一致性。