# 4.6 核心基座体系 :::tip 概述 本项目没有传统"基类继承"——用**组合**替代继承:ModuleSpec 泛型基座(module_spec.go)承担 CRUD 流水线,base.go 共享能力层提供横切函数。本节解释两者的分工与设计纪律。 ::: ## 组合替代继承的架构 ```text ┌────────────────────────────────────────────────────┐ │ 模块 logic 文件(example.go) │ │ var exampleSpec = ModuleSpec[...]{ 声明差异 } │──▶ 委托 │ func 方法() { exampleSpec.Page(...) } │ │ func 扩展() { 手写(合法逃生舱)} │ └───────────────────────┬────────────────────────────┘ │ 复用 ┌───────────────────────▼────────────────────────────┐ │ module_spec.go:ModuleSpec 泛型基座 │ │ applyFilters / readTransform / saveTransform │ │ checkUniqueRules / FillTextFields / buildTree │ └───────────────────────┬────────────────────────────┘ │ 调用 ┌───────────────────────▼────────────────────────────┐ │ base.go 共享能力层(导出上限 15 个) │ │ 文件转正 / 富文本 / 字典文本 / 唯一校验 / │ │ 事务 / 分块查询 / 泛型树 / 临时目录清理 │ └────────────────────────────────────────────────────┘ ``` ## base.go 能力清单(internal/logic/base.go) | 能力 | 函数 | 一句话职责 | |------|------|-----------| | 文件字段 | SaveFileFields / SaveMultiFileFields | 批量转正(原地更新 map) | | | SaveFileField / SaveMultiFileField | 单值便捷版 | | 富文本 | LocalizeContentFields | 存库本地化([IMG_URL] 占位符化) | | | ExpandContentFields | 读取还原(**唯一还原出口**) | | 枚举文本 | DictGetText | 字典值 → 显示名(进程内缓存 300s) | | | SerializeMapOnRead | map 行补 {field}Text | | | LogTypeText | 操作日志类型 → 中文(静态表) | | 唯一校验 | CheckUnique | UX 快速报错(TOCTOU,真值靠 uk_* 索引) | | 事务 | WithTrans | 事务包裹(错误自动回滚) | | 分块 | ChunkQuery | 分块遍历(导出防 OOM,默认 500/页) | | 树 | BuildTree[T] | 泛型平铺转树 | | 清理 | CleanTemp | 清理上传临时日期目录 | 架构纪律(文件头约束,真实注释): ```text 1) 按能力分区:文件字段 / 富文本 / 枚举文本 / 唯一校验 / 事务 / 分块查询 / 树 2) 依赖方向单向:只允许依赖 dao / utility / consts,禁止引用模块文件 3) 导出函数上限 15 个,超出按能力拆 internal/logic/base/ 子包 4) CheckUnique 仅为 UX 快速报错(TOCTOU),并发唯一性靠 uk_* 索引 5) [IMG_URL] 占位符是持久化契约,还原只能走 ExpandContentFields ``` ## ModuleSpec 基座流水线(module_spec.go) ```text Page applyFilters → Count → applyOrder.Page → structRows → readTransform → AfterPageList List applyFilters → 排序 → req.limit 上限 → structRows → readTransform → IsTree 时 buildTree(叶子 Children 预置 [] 而非 null) Detail Model.Where(id).One → Struct → readTransform → AfterDetail Add ToData → BeforeAdd → saveTransform(文件/富文本转正)→ checkUniqueRules → Insert → LastInsertId → AfterAdd → 返回 id Update ToData → BeforeUpdate → saveTransform → checkUniqueRules(排除自身) → Update(仅更新 data 中出现的键)→ AfterUpdate Delete BeforeDelete → Delete(钩子改写软删)→ AfterDelete BatchDelete BeforeBatchDelete → 逐条 BeforeDelete+软删 → AfterBatchDelete ``` readTransform 顺序:FileFields 补 URL → MultiFileFields 补 URL → ContentFields 展开 [IMG_URL] → FillTextFields({field}Text)。 ## 设计纪律(为什么基座不会腐化) ::: warning 基座三铁律 1. **基座禁止"按模块分支"**——差异只进声明字段与钩子;写不进钩子 → 手写(合法逃生舱)。这条纪律保证基座永远不出现 `if module == "user"` 式代码。 2. **钩子用具体函数类型**,req any 经 mustAs[T] 断言(失败=编程错误,panic 暴露而非带病运行)。 3. **钩子内禁止 append、禁止 HTTP/文件 IO**——文件迁移统一在 saveTransform 完成。 ::: applyFilters 固定顺序:`LikeFields → EqFields → KeywordFields → Filter → DataScope`;租户/软删隔离由 dao.Ctx **先于本链**注入(任何模块不可绕过)。 ## ModuleSpec 声明字段全景 ```text Model func(ctx) *gdb.Model 数据源(通常 dao.Xxx.Ctx) LikeFields []string 逐列 LIKE(值取 req 同名驼峰字段,空跳过) EqFields []string 等值(零值跳过) KeywordFields []string req.keyword 多列 OR(基座保留能力) Filter func(ctx, m, req) *gdb.Model 复杂条件逃生舱 OrderBy / ListOrderBy / OrderableFields 排序(白名单控制前端排序) IsTree / ParentOf / ChildSetter 树形 SerializeMaps map[字段]字典编码 {field}Text 填充 FileFields / MultiFileFields / ContentFields / FileSaveDir 文件与富文本 UniqueFields []UniqueRule{Field, Group} 唯一性预检(Group 分组唯一) DataScope DataScopeFunc 行级数据权限(ApplyDataScope 接线) ToData req → map[string]any 新增/修改共用转换 钩子 Before/After × Add/Update/Delete/Batch + AfterPageList/AfterDetail ``` ## 与"基类方案"的对比 | 维度 | 本项目(泛型基座+组合) | 传统基类继承 | |------|----------------------|-------------| | 差异表达 | 声明字段 + 钩子 | 重写虚方法 | | 类型安全 | 泛型三类型参数编译期约束 | 常退化 any/map | | 复用粒度 | 函数级(base.go 自由组合) | 类级(受继承链束缚) | | 基座污染风险 | 三铁律禁止按模块分支 | 基类易堆积 if-else | | 逃生舱 | 手写方法(完全自由) | 多重继承/桥接方法 |