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