背景:A4 开发完成,需要回写实施报告、技术架构与开发计划状态。\n改动:新增 A4 实施报告;技术架构补 system_config_versions 与 dict:manage 口径;开发计划 A4 标记完成并回写门禁/遗留;README 索引补 A4 报告。\n影响面:仅文档;不改变需求与技术边界。\n回滚:回滚本提交即可。\n验证:prettier 通过;文档链接已检查。
12 KiB
12 KiB
灵犀 A4 数据字典与系统配置 · 实施报告 V0.1
| 项 | 内容 |
|---|---|
| 实施日期 | 2026-11-04 |
| 批次 | A4(数据字典与系统配置) |
| 状态 | 已完成(开发 + 全部门禁 + 浏览器 e2e 通过;未推送) |
| 依据 | 开发计划 V0.1 §6.5 / §7.3 / §12.1;PRD V1.6 §5.4.5 / R-01 / R-10 / R-26;技术架构 V0.4 §5.3 / §5.7 / §6.3.7;ADR-001 |
| 视觉基线 | A0「方向 A · 云白蓝」;用户选定 方案 A · 主从分栏 |
| 迁移 | prisma/migrations/20261104024511_a4_dict_system_configs/ |
| 前端路由 | /system/dicts、/system/dicts/:type、/system/settings |
1 范围与完成度
| 任务 | 内容 | 状态 |
|---|---|---|
| A4-BE-01 | 字典模型与三层合并(dict_types / dict_items,树 / 词表一张表) |
✅ |
| A4-BE-02 | 系统配置与版本(system_configs + system_config_versions,阈值同源) |
✅ |
| A4-BE-03 | 命名规则服务(R-01 占位符 / 白名单 / 同名序号 / ≤100) | ✅ |
| A4-BE-04 | 字典快照(R-10,共享契约 + toDictItemSnapshot) |
✅ |
| A4-FE-01 | 字典管理(方案 A 主从分栏,树 / 词表双视图) | ✅ |
| A4-FE-02 | 系统配置页(6 组 / 命名预览 / 阈值二次确认 / 变更记录) | ✅ |
| A4-QA-01 | 合并 / 快照 / 同源用例 + 浏览器 e2e | ✅ |
权限口径:A2 定稿的 30 个权限点中字典为单个
dict:manage;系统配置为system:configure。本轮未新增权限点,已同步技术架构 §6.3.7 与开发计划附录 B / C。
2 交付物
2.1 文档与决策
- 技术架构 §5.3 / §5.7 / §6.3.7 回写:新增
system_config_versions,字典权限统一dict:manage。 - 开发计划 §7.3 / 附录 B / 附录 C 权限口径回写。
prisma/SEED.md更新:system_configs26 项、A4 字典 13 类 / 96 条。- 前端原型:
design/a4-ui-v1/(三套候选 + 两轮独立评审 + 用户选定 A);实现截图:design/a4-ui-v1/impl/。
2.2 共享契约(packages/shared)
schemas/dict.ts:字典类型 / 条目 / 三层来源 / 类型与条目编码不可改 / 查询与写接口 /toDictItemSnapshot。schemas/config.ts:系统配置响应、批量保存(expectedVersion)、变更记录、effective读取。config/system-configs.ts:26 项配置定义(key / group / label / description / impact / type / default / 约束 / public)与validateSystemConfigValue;前后端共用,R-26 唯一事实源。naming.ts:NAMING_TOKEN_DEFINITIONS、validateNamingTemplate、resolveNamingTemplate、formatNamingDate、formatNamingSequence;保留既有dedupeName/sanitizeFilename。schemas/client.ts:CUSTOMER_BASE_FIELD_HARD_LIMIT = 50作为 DTO 防御性硬上界;实际生效上限由服务端动态读取。- 单测:
dict.spec.ts/config.spec.ts/naming.spec.ts覆盖合并口径、快照、配置校验、模板解析、同名序号。
2.3 数据库与种子
- 迁移
20261104024511_a4_dict_system_configs:dict_types(code unique;group / kind / industry_scoped / builtin / status / sort);dict_items(type_id + code + industry_id;parent_code;dict_items_no_self_parent_check);- 部分唯一索引:通用层
(type_id, code) WHERE industry_id IS NULL、行业层(type_id, code, industry_id) WHERE industry_id IS NOT NULL; system_config_versions(append-only:config_key / version / old_value / new_value / reason / changed_by / changed_at;(config_key, version)唯一)。
- 种子:
system_configs26 项,元数据来自SYSTEM_CONFIG_DEFINITIONS;重复执行不覆盖人工调整值 / 版本;- 字典 13 类 / 96 条,行业作用域映射 A3 行业(电商 / 服饰鞋包 / 美妆个护 / 金融 / 信贷 等);内置类型不可删除、条目编码不可改。
2.4 后端(apps/api)
| 模块 | 接口 |
|---|---|
catalog |
GET /dicts、GET /dicts/{code}、POST /dicts、PATCH /dicts/{code}、PATCH /dicts/{code}/status、DELETE /dicts/{code}、GET /dicts/{code}/items、POST /dicts/{code}/items、PATCH /dicts/{code}/items/{itemId} |
platform |
GET /system/configs、GET /system/configs/effective、PUT /system/configs、GET /system/configs/history |
- 三层合并为纯函数
dict-merge.ts:通用 0 / 父行业 1 / 本行业 2,同 code 覆盖;返回source/overridden/layers/effectiveCount/disabledCount。 - 编码不可改:schema
.strict()不接收 code / kind / industryScoped / parentCode / industryId 更新;冲突 40902;内置类型 / 有子条目删除 40001。 - 配置保存:事务 +
expectedVersion乐观锁(40901)+ 条件version+1+system_config_versions+@Audit('platform.system_config.update')前后值。 - 阈值同源:
roles/users/customers/customer-fields已改为SystemConfigService.getNumber('limit.*');shared 常量只作 fallback。 - 命名服务:
resolveScriptName/resolveImageName/resolveVideoName,读naming.*+max_length+duplicate_start;缺占位符 40001,非法 / 超长 / 同名过多 42201。 - 快照:
dict.mapper#toDictSnapshot委托 shared;单测证明改名后历史快照不变(R-10)。 - 审计:字典类型 / 条目写操作、系统配置保存均 @Audit + 结构化日志前后值(A5 落审计表)。
2.5 前端(apps/web,方案 A)
| 页面 | 路由 | 实现 |
|---|---|---|
| 数据字典 | /system/dicts |
左 320 类型列表(4 分组折叠、搜索、状态筛选、统计)+ 右类型头 / 行业三层 chips / 树表切换 / 条目表 |
| 字典详情 | /system/dicts/:type |
同一主从骨架深链;默认 script_direction + 电商/服饰鞋包,7 项三层合并 |
| 系统配置 | /system/settings |
左 200 分组导航 + 6 组表单 + 命名占位符 / 实时预览 + 阈值二次确认 + 保存条 + 变更记录抽屉 |
- 组件:
DictTypeListPanel/DictDetailPanel/DictItemsView/DictItemSheet/DictTypeModal;SettingsPage/SettingsNav/SettingsGroupForm/ConfigRow/NamingAside/ConfigHistorySheet。 - API / 状态:
api/dicts.ts/api/configs.ts,shared Zod parse;use-dicts/use-configs,含useEffectiveSystemConfigsQuery/useCustomerFieldLimits。 - 阈值同源:客户列表 / 详情 / 字段审批的 10 / 5 显示与禁用判断改为 effective config;后端最终校验不变。
- 路由 / 壳层:
/system/dicts、/system/dicts/:type、/system/settings已注册并按dict:manage/system:configure守卫;AppLayout满高 flush;main.tsx引入dicts.css/settings.css。 - 侧边栏已更名「数据字典」(commit
32b44bd)。
3 验证结果
| 门禁 | 命令 | 结果 |
|---|---|---|
| 格式 | pnpm format:check |
✅ |
| Lint | pnpm lint |
✅ 0 warning |
| 类型 | pnpm typecheck |
✅ 10/10 tasks |
| 单测(shared) | pnpm --filter @lingxi/shared test |
✅ 13 files / 147 tests |
| 单测(api) | pnpm --filter @lingxi/api test |
✅ 18 files / 137 tests |
| 单测(web) | pnpm --filter @lingxi/web test |
✅ 29 files / 154 tests |
| 集成 | pnpm --filter @lingxi/api test:int |
✅ 7 files / 58 tests(新增 catalog 10 + config 9) |
| 构建 | pnpm build |
✅ 8/8 tasks |
| OpenAPI | pnpm openapi:export + pnpm openapi:diff |
✅ 无破坏性变更 |
| 浏览器 e2e | A4_E2E=1 pnpm exec playwright test e2e/a4-dicts-settings.spec.ts |
✅ 7 passed(约 1.1m) |
| 本地 CI | pnpm ci:local |
✅ 262.3s,全部门禁通过(build + test:int + openapi + smoke e2e) |
浏览器人工验收(真实 API + PG + seed):
/system/dicts默认脚本方向 / 电商·服饰鞋包,显示三层 chips(通用 5 + 父行业 2 + 本行业 1 = 生效 7),种草短视频「父行业 / 已覆盖」、直播切片「父行业」、穿搭教程「本行业」;- 树 / 词表切换、非行业类型「全平台通用」、条目新增 / 编辑(编码只读)/ 停用 / 启用、类型新建 / 编辑(编码只读);
/system/settings6 组 / 26 项、命名占位符与实时预览、阈值旧→新二次确认、保存后版本 +1、变更记录(版本 / 前后值 / 操作人 / 时间 / 原因);- 1440×900 与 1024×900 无横向溢出;截图:impl。
4 关键口径与实现说明
- 一张表同时表达树与词表:
dict_items.parent_code表达二级树;不另建行业二级表(ADR-002 后行业树由industries承载)。 - 三层合并:查询取通用 + 父行业 + 本行业;同 code 以更具体层覆盖,返回 source / overridden;非行业作用域类型强制只读通用层。
- 编码不可改:类型
code与条目code创建后不可改;更新 DTO.strict()不接收;前端编辑态只读。 - 配置唯一来源:
SYSTEM_CONFIG_DEFINITIONS为元数据 / 默认值 / 校验唯一事实源;system_configs只存值 / 版本;/system/configs/effective供前端读取 public 项(上限),前端不再硬编码 10 / 5。 - 版本与变更:保存走事务 + 乐观锁;每次变更写
system_config_versions;审计日志记录前后值,A5 落审计表。 - 命名规则:shared 纯函数被前端预览与后端
NamingService共用;字符白名单、占位符、≤100、同名序号一致。 - 快照:
toDictItemSnapshot生成填写时 typeCode / code / name / parentCode / industryId / capturedAt;业务记录在 B0 / M1 引用时保存,A4 只提供契约与工具。
5 已知差异与遗留
| 编号 | 项 | 说明 / 建议 |
|---|---|---|
| 1 | refCount 固定 0 |
A4 尚无引用表;B0 / B1 资产与行业包落地后接入真实引用计数 |
| 2 | 配置项 26 项(原型 24 项) | 含 A0 已有 2 项 AI 成本比例配置;A7 会提供专门 AI 配置页,A4 暂列在「业务阈值」 |
| 3 | 审计落库 | A4 只写结构化 @Audit 日志;审计表 / 检索 / 导出按 A5 |
| 4 | 只读态不可达 | 路由守卫为 dict:manage / system:configure,无独立 view 权限;页面保留 canWrite=false 防御分支,后续如拆分 view/manage 权限可直接生效 |
| 5 | 详情面包屑 | /system/dicts/:type 面包屑仍为「管理中心 / 数据字典」,未展示类型名;breadcrumbs.ts 无类型参数,后续统一处理(与 A3 客户详情同源问题) |
| 6 | 字典类型分页 | 当前 13 类,前端一次拉 100 条后本地筛选;类型数 >100 时需补游标分页 UI |
| 7 | updatedBy 存储 |
字典表存操作人姓名;system_configs.updated_by 存 ULID,列表 / 历史批量 join 返回姓名 |
| 8 | DTO 硬上界 | 客户基础字段 schema 硬上界 50(防御性);若配置 >50 仍会被 Zod 拦截,需同步调整 shared 硬上界 |
| 9 | e2e | A4_E2E=1 用例已落地并 7/7 通过;默认 skip,不影响 smoke / CI;e2e/README.md 待补登记一行 |
6 回滚方式
- 代码:回滚 A4 提交(catalog / platform 配置与命名 / 前端字典与配置页 / shared 契约 / 路由 / 样式 / 迁移 / seed / 文档)。
- 数据库:A4 为新增表,回滚可保留表;彻底回滚删除顺序:
system_config_versions → dict_items → dict_types;system_configs为 A0 既有表,保留。 - 配置:删除 A4 新增
system_configs行或恢复默认值;system_config_versions清空即可。 - 前端:回滚路由 / 导航 / 页面 / 样式;
/system/dicts//system/settings将回退到 Workspace 占位页(导航保留)。
7 本地运行
pnpm check:dev
pnpm db:init
pnpm dev
# 浏览器 http://127.0.0.1:5173 → 管理中心 / 数据字典、系统配置
A4 e2e(需先起 pnpm dev):
A4_E2E=1 pnpm exec playwright test e2e/a4-dicts-settings.spec.ts --reporter=list