Files
workspace/code/fms/重构实施计划.md
T
2026-07-19 21:57:40 +08:00

522 lines
23 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.
# FMS 模块化重构实施计划
## 1. 文档目的
本文档用于指导旧 `g3hd` 系统向新 `fms` 系统逐步迁移。重构始终保留“以模块为核心”的系统结构,但不照搬旧系统中前端提交任意表名、SQL 和多版本通用接口的实现。
计划的核心目标是先建立稳定的模块运行内核,再逐批迁移业务,保证每一阶段都有可验收的业务闭环。
## 2. 重构原则
1. `module_id` 是查询、保存、权限、字段配置和页面入口的共同标识。
2. 菜单不等于模块,但二者使用同一棵功能树。页面菜单可以直接绑定主模块,隐藏模块可以挂在页面或其他模块下;一个模块也可以被多个功能节点引用。
3. 查询对象与保存对象分离。查询优先使用稳定视图,保存只能写入明确配置的物理表。
4. 前端保留手写页面。模块配置只驱动通用 Table、Form、查询条件和选项,不生成完整业务页面。
5. 单表和主子表都使用通用事务保存;手写页面负责组织多张表的数据变更。
6. 权限必须由后端校验,前端隐藏按钮只用于改善交互。
7. 新接口只接收 `module_id`、结构化条件和结构化数据,不接收任意表名、字段名或 SQL。
8. 后端只提供少量稳定的平台级模块接口,普通模块不得单独增加查询、新增、修改、删除接口。
9. 后端不建立按模块分派的业务处理器,也不因业务差异新增 Controller。
10. 一个能力只保留一个正式接口,不复制旧系统的 V1、V2、V3 和临时兼容版本。
11. 每迁移一个模块,就完成数据、接口、页面、权限和验收的完整闭环,不长期维护半迁移状态。
12. 旧代码用于确认业务行为,旧数据库用于核对字段含义;新代码、新接口和新数据库不兼容旧系统数据库结构。
13. 前端仅面向桌面端浏览器开发,不需要兼容移动端布局;后续开发也不做移动端适配,不引入为窄屏服务的响应式断点与自动收起逻辑。
### 2.1 后端接口准入规则
1. 查询、详情、新增、修改和删除优先复用统一模块接口,不为表、视图或业务页面分别创建 CRUD 接口。
2. 配置保存对象的模块使用通用查询和通用保存;主子页面一次提交多张表的变更。
3. 所有表变更统一进入 `saveobjt`,后端不建立业务处理器注册表或模块专用保存入口。
4. “前端调用方便”“某个页面参数不同”或“旧系统原来有接口”不能作为新增接口的理由。
## 3. 目标运行链路
```text
路由或页面提供 module_id
-> 后端加载模块定义
-> 校验机构、用户和模块权限
-> 加载模块字段与选项配置
-> 解析结构化查询条件
-> 生成参数化 SQL
-> 返回模块元数据和业务数据
-> 前端 ModuleTable / ModuleForm 渲染
-> 页面执行自身业务流程
-> saveobjt 提交单表或多表事务
```
模块是这条链路的中心,页面、数据库对象和权限都不能绕开模块直接建立隐式关系。
## 4. 当前基础
新 `fms` 已具备以下基础能力:
- 机构数据库配置加载与动态路由。
- JWT 登录、活动会话和同账号会话替换。
- 统一响应、异常处理和鉴权过滤。
- 普通查询、SQL 查询、分页查询、事务保存和雪花 ID。
- Vue 登录、会话管理、基础布局、列表和详情页面骨架。
这些通用数据接口作为过渡接口继续存在,但不再作为最终模块接口继续扩展。
## 5. 总体实施顺序
| 阶段 | 目标 | 代表性成果 |
| --- | --- | --- |
| 0 | 固化边界与迁移清单 | 模块台账、对象映射、决策记录 |
| 1 | 建立最小模块数据模型 | 模块、字段、分组、选项配置表 |
| 2 | 建立后端模块内核 | 模块定义、结构化查询、权限校验 |
| 3 | 建立权限和菜单基础 | 后端授权、菜单和动作权限 |
| 4 | 建立前端模块运行层 | ModuleTable、ModuleForm、查询组件 |
| 5 | 完成简单模块闭环 | 一个基础资料模块完整上线 |
| 6 | 完成复杂业务闭环 | 业务单主子表通用事务保存 |
| 7 | 完成只读模块闭环 | 统计报表、分页和导出 |
| 8 | 建立配置管理与校验 | 配置页面、发布校验和缓存失效 |
| 9 | 分批迁移业务域 | 合作伙伴、业务、财务、审批等 |
| 10 | 数据迁移与切换 | 全量迁移、增量迁移、对账、切换 |
| 11 | 收口旧能力 | 停用过渡接口和旧系统 |
## 6. 分阶段执行计划
### 阶段 0:固化重构边界
#### 工作内容
1. 建立旧模块台账,至少记录:
- 旧 `module_id`。
- 菜单位置和页面路由。
- 查询视图、保存主表和子表。
- 主键、业务编号和默认排序。
- 查询项、列表列、表单字段和选项来源。
- 查看、新增、修改、删除、导出及特殊操作权限。
- 审批、自动编号、存储过程、报表和附件依赖。
2. 为每个旧模块确定新系统处置方式:`迁移`、`合并`、`拆分`、`只读归档` 或 `废弃`。
3. 建立旧数据库对象到新数据库对象的映射表,禁止只凭相似名称直接迁移。
4. 确认新数据库的 SQL Server 版本、排序规则、时区、金额精度和雪花 ID worker 分配方式。
5. 建立架构决策记录,后续关键变化必须说明原因和影响。
#### 产物
- `docs/refactor/模块迁移台账.md` 或同目录下的对应表格。
- `docs/refactor/数据库对象映射.md`。
- `docs/refactor/架构决策记录.md`。
阶段 0 的重构治理文档统一放在 `docs/refactor/` 目录,不散落在项目根目录。后续产生的模块迁移说明、数据迁移说明和决策记录也优先归档到该目录。
#### 验收标准
- 所有准备迁移的旧路由都能对应到一个明确模块或明确废弃结论。
- 每个模块都能说明查询对象、保存对象和权限边界。
- 不存在“先复制页面,后面再确认数据来源”的模块。
### 阶段 1:建立最小模块数据模型
本阶段采用纵向闭环方式交付:配置表、必要后端支持和手写模块管理页面必须同时完成,阶段结束后可以直接从“系统管理 / 模块管理”页面验证成果。此阶段不开发通用 `ModuleTable` 和 `ModuleForm`。
#### 工作内容
1. 创建并固化以下配置表:
- `dbo.s_module`:模块定义。
- `dbo.s_module_field`:模块字段定义。
- `dbo.s_module_group`:表单分组。
- `dbo.s_option_source`:动态选项来源。
- `dbo.s_option_item`:固定选项。
2. 在 `s_module` 中明确:
- `b_query_object`。
- `b_save_object`。
- `b_key_field`。
- `b_default_order`。
- 系统主键固定由后端生成雪花 ID,不配置主键策略。
- 业务主键使用独立唯一字段,例如业务单号,可在模块中标识对应字段。
3. 在 `s_module_field` 中分开配置:
- 对前端稳定的字段编码。
- 查询视图字段。
- 保存表字段。
- 列表、查询和表单属性。
4. 为配置表建立主键、唯一索引、外键和启用状态约束。
5. 复用现有通用查询、分页和事务保存接口,不新增模块管理专用 CRUD 接口。
6. 开发手写的模块管理页面,至少支持:
- 按业务目录、页面、技术分组和模块位置组织的功能树。
- 模块、查询对象、路径和启停状态搜索。
- 模块基本信息新增、修改和删除。
- 模块字段与表单分组在同一设计器内维护。
- 页面菜单与主模块绑定,以及隐藏子模块的树形归档。
- 模块、字段和分组一次事务保存;功能树节点独立保存。
7. 使用初始化脚本写入“模块管理”自身的种子配置,确保模块系统可以管理自身。
8. 选项来源先建立数据库结构和字段引用,本阶段不要求完成独立的选项来源管理页面。
#### 产物
- 可重复执行的配置表 DDL。
- 第一批模块种子数据脚本。
- 模块字段与选项来源的数据字典。
- 可操作的“系统管理 / 模块管理”页面。
#### 验收标准
- 给定一个 `module_id`,可以唯一确定查询对象、保存对象和主键字段。
- 重复字段、无效分组、无效选项来源能够被约束或检测。
- 查询视图字段和保存字段不存在隐式同名假设。
- 可以通过页面完成模块、字段和表单分组的新增、修改、删除与重新加载。
- 可以通过功能树定位隐藏模块并维护菜单绑定,未归档模块始终可搜索。
- 页面保存使用现有通用事务接口,后端没有增加模块管理专用 CRUD Controller。
### 阶段 2:建立后端模块内核
#### 工作内容
1. 实现模块定义读取层,并按机构缓存稳定配置。
2. 定义唯一一套平台级模块接口,建议只包含:
- 获取模块元数据。
- 模块分页查询。
- 获取单条记录。
- 保存模块数据。
- 获取字段选项。
- 执行模块声明的特殊动作。
3. 上述接口按 `module_id` 服务所有模块,不为 `b_contact`、`bs_business`、`cw_bill` 等模块复制 Controller 和 CRUD 路径。
4. 定义结构化查询格式,例如:
```json
{
"filters": [
{ "field": "customer_name", "operator": "like", "value": "物流" },
{ "field": "business_date", "operator": "between", "value": ["2026-01-01", "2026-01-31"] }
],
"sort": [
{ "field": "business_no", "direction": "desc" }
],
"page_no": 1,
"page_size": 20
}
```
5. 后端只允许使用模块字段白名单中的字段和操作符。
6. 所有值使用预编译参数,数据库对象名和排序字段从模块配置解析。
7. 建立统一保存服务:配置保存对象的模块允许保存,未配置保存对象的模块保持只读;主子页面使用 `saveobjt` 提交多表事务。
8. 雪花 ID 在后端保存事务中生成,并以字符串形式通过 JSON 返回,避免 JavaScript 精度损失。
9. 为模块定义错误、字段错误、权限错误、并发冲突和业务校验失败定义稳定错误码。
10. 过渡接口继续保留,但新页面不得继续增加对任意 SQL 接口的依赖,也不得增加模块专用 CRUD 接口。
#### 产物
- `ModuleDefinitionService`。
- `ModuleQueryService`。
- `ModuleSaveService`。
- 查询请求、分页响应和保存请求契约。
#### 验收标准
- 修改请求中的 `module_id` 不能访问另一个模块未授权的对象或字段。
- 客户端不能通过条件、排序或字段名注入任意 SQL。
- 通用单表保存、通用多表事务和只读模块的边界明确,且不依赖保存模式字段。
### 阶段 3:建立权限和菜单基础
#### 工作内容
1. 建立统一功能树,节点区分 `directory`、`page`、`group` 和 `module`;菜单从可见目录与页面节点投影,模块定义和权限仍独立建模。
2. 确定授权模型。建议使用角色授权为主,确有需要时提供用户级例外,而不是完整照搬旧系统逐用户配置。
3. 至少支持以下模块操作:
- `view`。
- `create`。
- `update`。
- `delete`。
- `export`。
- 模块声明的特殊动作,例如 `submit`、`approve`、`reject`、`void`。
4. 查询、详情、保存、导出和特殊动作都在后端校验权限。
5. 登录后返回或按需加载用户可见菜单和模块动作。
6. 保留 `g3soft` 内置管理员规则,但数据库普通查询必须继续隐藏该账号。
#### 产物
- 功能树和权限表 DDL。
- 后端模块授权服务。
- 前端功能树投影菜单和动作权限指令/组合函数。
#### 验收标准
- 手工调用接口不能绕过页面按钮权限。
- 菜单隐藏不影响隐藏子模块的合法访问。
- 一个模块可被多个菜单复用且权限结果一致。
### 阶段 4:建立前端模块运行层
#### 工作内容
1. 实现统一模块服务层,页面不直接拼接模块接口请求。
2. 实现 `ModuleTable`:
- 模块元数据加载。
- 动态列、格式、排序和分页。
- 结构化快速查询和高级查询;高级查询由用户为每条条件选择合法操作符。
- 行选择、加载、空数据和错误状态。
- 列插槽、工具栏插槽和事件。
3. 实现 `ModuleForm`:
- 字段分组与桌面端表单布局(不做移动端适配)。
- 输入、数字、日期、选择、文本等基础控件。
- 必填、类型、长度和配置规则校验。
- 新增、编辑和只读模式。
- 字段插槽和分组插槽。
4. 实现选项来源服务,只处理固定选项和模块查询选项。
5. 明确页面职责:页面负责按钮、弹窗、标签页、主子区域和业务流程,组件不擅自保存。
6. 统一加载、权限不足、无数据、校验失败、并发冲突和会话失效的交互。
#### 产物
- `ModuleTable.vue`。
- `ModuleForm.vue`。
- 模块查询组件和选项组件。
- 模块前端服务与类型/契约说明。
#### 验收标准
- 同一查询视图可被两个模块以不同字段配置展示。
- 页面可通过插槽覆盖特殊列和特殊表单区域。
- 组件内部不出现具体业务表名。
### 阶段 5:完成简单模块闭环
#### 推荐试点
优先选择币种、港口、联系人等简单基础资料;不要用用户、权限或业务单作为第一个通用模块。
#### 工作内容
1. 为试点模块创建新业务表、查询视图和模块配置。
2. 使用 `ModuleTable` 实现列表、查询、分页和排序。
3. 使用 `ModuleForm` 实现新增、编辑和只读详情。
4. 配置保存对象,通过通用保存完成单表新增、修改和删除。
5. 接入模块查看、新增、修改、删除和导出权限。
6. 从旧数据库抽取少量脱敏数据验证迁移映射。
#### 验收标准
- 页面不提交表名、主键名或 SQL。
- 字段配置变更后,Table/Form 无需修改页面代码即可反映常规变化。
- 数据库约束、后端校验和前端提示三者一致。
### 阶段 6:完成业务单事务闭环
#### 试点范围
以 `bs_business` 为主模块,包含业务主表、箱信息、应收费用和应付费用。提单、运输、附件和审批先保留扩展点,不要求一次全部完成。
#### 工作内容
1. 重新确认业务单状态机,例如草稿、已提交、审核中、已完成、已作废。
2. 明确主表与各子表的外键、唯一约束、删除规则和并发策略。
3. 手写业务页面组织一个 `saveobjt` 请求,在同一个事务中提交:
- 主表新增或修改。
- 箱信息差异保存。
- 应收、应付费用差异保存。
- 业务编号生成。
- 状态和锁定校验。
- 必要的审计记录。
4. 前端保留手写业务详情页,用 `ModuleForm` 处理主表常规字段,用手写子表组件处理箱和费用。
5. 将旧页面中的金额计算、箱量描述、负责人整理和删除限制逐条分类:
- 展示计算留在前端。
- 数据完整性和状态限制移到后端。
- 可由数据库约束保证的规则下沉到数据库。
6. 页面根据已注册的模块保存对象构造表变更,统一调用 `saveobjt`;后端不增加业务单专用保存代码。
#### 验收标准
- 主表和所有子表要么全部成功,要么全部回滚。
- 已审核、已制单或已锁定费用不能通过直接请求绕过限制。
- 两人同时修改时能够检测冲突,不静默覆盖。
- 保存后重新加载的数据与页面提交结果一致。
### 阶段 7:完成只读报表闭环
#### 工作内容
1. 选择一个典型统计模块,例如应收未收明细或业务员汇总。
2. 将旧前端拼接的统计 SQL 固化为数据库视图。
3. 配置允许查询、排序和汇总的字段。
4. 实现只读分页、汇总、导出和权限校验。
5. 导出必须复用同一模块查询规则,不能由前端另拼一套 SQL。
#### 验收标准
- 页面查询结果、总数、汇总和导出结果使用同一过滤语义。
- 报表模块不能调用保存接口。
- 大数据量查询有稳定排序、索引和可接受的执行计划。
### 阶段 8:建立配置管理与校验工具
三类试点稳定后,再开发配置管理页面。
#### 工作内容
1. 开发模块、字段、分组和选项来源的管理页面。
2. 建立配置发布前校验:
- 查询对象和保存对象是否存在。
- 主键和字段映射是否有效。
- 列表、查询、表单排序是否重复。
- 保存字段是否属于保存对象。
- 选项来源是否有效。
3. 配置修改增加版本号或更新时间,使后端和前端缓存可以准确失效。
4. 生产环境配置修改需要权限和审计记录。
#### 验收标准
- 无效配置不能发布到运行状态。
- 配置错误能够定位到模块和字段,而不是只返回数据库异常。
- 配置更新后缓存可控刷新。
### 阶段 9:按业务域分批迁移
建议迁移顺序如下:
#### 第一批:基础资料
- 币种、港口、运输方式等字典。
- 合作伙伴和联系人。
- 选择项及基础资料维护。
目标是扩大通用模块覆盖面,并稳定选项来源和字段配置。
#### 第二批:合同与价格
- 客户合同。
- 价目表和价目明细。
- 默认价格选择。
目标是验证多模块复用、主子表和业务选择组件。
#### 第三批:业务执行
- 业务单。
- 箱信息、提单、运输和业务附件。
- 批量更新、标记和业务导出。
目标是完成核心业务运行闭环。
#### 第四批:财务链路
- 应收、应付费用。
- 账单及明细。
- 发票及明细。
- 收款、付款及核销。
- 日常费用和收付账户。
财务模块仍使用通用事务保存;审核、制单、核销和锁定状态必须由权限白名单、行版本和数据库约束共同保护。
#### 第五批:审批、统计和办公
- 审批设置、送审、审核、驳回和审批记录。
- 应收、应付和利润统计。
- 报表、Excel、附件和打印。
- 公告、提醒和其他办公功能。
#### 第六批:系统管理
- 用户、角色、权限和菜单管理。
- 模块、字段、查询和选项配置管理。
- 自动编号、审计日志和运维工具。
系统管理页面后迁移,但底层权限和模块配置表必须在前期通过脚本先可用。
### 阶段 10:数据迁移与正式切换
#### 工作内容
1. 每个业务域建立字段级迁移映射和转换规则。
2. 为状态、标志、空字符串、日期、金额和旧主键定义明确转换。
3. 迁移顺序遵循外键依赖:基础资料先于业务,业务先于财务,主表先于子表。
4. 先执行可重复的演练迁移,再执行正式迁移。
5. 建立对账指标:
- 表记录数。
- 主子关系完整性。
- 业务状态分布。
- 应收、应付、账单、发票和收付款金额。
- 孤立记录和重复业务编号。
6. 切换前冻结旧系统写入,执行最后一次增量迁移和对账。
7. 准备回退条件和回退步骤,回退不能依赖人工修改数据库。
#### 验收标准
- 关键业务数量和金额对账一致。
- 所有外键、唯一索引和检查约束通过。
- 新系统完成业务、财务和权限验收后才能切换。
### 阶段 11:收口过渡能力
#### 工作内容
1. 统计新前端对以下过渡接口的调用并逐一清零:
- 任意表/视图查询。
- 任意 SQL 查询。
- 客户端指定表名和主键的保存。
2. 禁止新代码继续引用这些接口。
3. 移除不再使用的 mock 页面、临时路由和兼容代码。
4. 旧系统切换为只读归档,保留规定期限后再下线。
5. 更新运维、部署、备份、恢复和故障处理文档。
#### 验收标准
- 正式模块全部通过模块接口运行。
- 生产环境不再开放任意 SQL 和任意表保存接口。
- 旧系统退出不影响历史数据查询和审计需要。
## 7. 每个模块的标准迁移步骤
以后迁移任何模块,都按以下顺序执行:
1. 确认旧模块业务目的,不以旧页面文件名代替需求。
2. 列出旧查询视图、保存表、子表、存储过程和报表依赖。
3. 确认新模块是可保存还是只读,并配置保存对象;主子页面明确一次事务涉及的表。
4. 设计新表、视图、约束、索引和状态字段。
5. 配置模块、字段、分组、选项和默认排序。
6. 配置查看、编辑、删除、导出和特殊动作权限。
7. 实现或复用后端查询和保存能力。
8. 实现手写页面,并组合 `ModuleTable`、`ModuleForm` 和业务组件。
9. 编写旧数据到新数据的迁移规则。
10. 核对查询、保存、权限、并发、导出和异常流程。
11. 完成用户验收后切换该模块。
12. 停止旧模块写入并记录迁移结论。
## 8. 当前下一步任务清单
按照现状,下一步应从阶段 0 和阶段 1 开始,建议严格按以下顺序执行:
1. 创建模块迁移台账,先录入 `b_hl`、`b_contact`、`bs_business` 和一个只读统计模块。
2. 核对新 FMS 数据库中现有配置表,确定哪些保留、哪些重建。
3. 完成 `s_module`、`s_module_field`、`s_module_group`、`s_option_source`、`s_option_item` 的最终 DDL。
4. 为一个简单基础资料模块编写种子配置。
5. 定义模块元数据、结构化查询和模块保存的 JSON 契约。
6. 实现后端模块定义读取和配置校验。
7. 实现后端模块分页查询,并停止让新页面直接传 `view_name`。
8. 实现最小模块权限校验。
9. 实现前端模块服务层、最小版 `ModuleTable` 和最小版 `ModuleForm`。
10. 用简单基础资料模块完成第一次端到端闭环。
11. 闭环验收后再实现 `ModuleForm` 的完整分组和选项能力。
12. 最后使用 `bs_business` 验证主子表 `saveobjt` 事务,不提前迁移财务模块。
## 9. 明确禁止的做法
- 不按旧路由顺序逐页复制代码。
- 不把旧 `frontend/src/backup` 中的文件迁入新项目。
- 不为每张表、每个模块或每个页面创建独立的查询和保存 Controller。
- 不因请求字段不同或前端调用方便而新增功能重复的接口。
- 不在新模块接口中接受任意 SQL、表名、主键名或排序片段。
- 不为了兼容旧调用继续增加 `V2`、`V3`、`new`、`old` 接口。
- 不只依赖前端控制业务状态流转、审核、核销和锁定,必须同时配置权限和数据库约束。
- 不用配置表描述完整页面和任意可执行脚本。
- 不在数据库约束尚未确定时先批量迁移数据。
- 不只做前端权限隐藏而缺少后端授权。
- 不在一个模块中长期混用旧接口和新模块接口。
- 不一次性迁移全部业务后再统一验收。
## 10. 完成定义
一个模块只有同时满足以下条件才算迁移完成:
- 新数据库表、视图、约束和索引已经确定。
- 模块、字段、选项和权限配置完整。
- 页面只通过 `module_id` 使用正式模块接口。
- 后端完成字段白名单、权限、业务校验和事务处理。
- 查询、详情、保存、删除、导出和异常流程可用。
- 旧数据迁移和业务对账完成。
- 旧模块停止写入,并有明确回退方案。
重构的衡量标准不是“旧页面复制了多少”,而是“有多少模块已经在新内核上形成可独立运行、可验证、可切换的完整闭环”。