Files
workspace/code/fms/FMS新系统核心架构设计.md
2026-10-07 22:01:17 +08:00

733 lines
17 KiB
Markdown
Raw Permalink 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 新系统核心架构设计 V2.0
> 文档版本:V2.0
> 编制日期:2026-09-30
> 文档状态:核心架构设计草案
> 适用范围:FMS/ERP 新系统第一阶段重构
## 1. 文档目的
本文档重新定义 FMS 新系统的核心架构,重点解决以下问题:
- SQL 是系统的第一等公民;
- 代码风格和模块实现方式统一;
- 第一阶段保持简单,不建设通用低代码平台;
- 真实业务表、业务 SQL 和业务代码拥有清晰边界;
- 支持一个租户多个公司,但不提前建设复杂的平台能力。
本文档替代旧设计中以 `s_module`、通用 CRUD 和动态 JSON 为中心的架构思路。旧表结构中的业务数据、工作流和可复用 SQL 可以迁移,但运行时中心需要重新组织。
## 2. 根本改变
### 2.1 旧架构
旧设计的主链路是:
```text
s_module
→ s_field
→ s_module_schema
→ 通用 SQL 拼接
→ 通用保存
→ 页面
```
它的优点是页面和简单模块创建较快,缺点是业务模块逐渐依赖元数据解释器,容易变成一个低代码平台。
### 2.2 新架构
新设计的主链路是:
```text
业务域代码包
→ 真实 SQL 表和视图
→ 业务查询与业务动作
→ API / 页面
```
新系统的中心不再是全局模块元数据,而是**业务域代码包**。每个业务域自己拥有表结构、迁移、SQL、业务动作和页面入口。
```text
客户域
├── 数据库迁移
├── 业务表和视图
├── 查询 SQL
├── 保存 SQL
├── 业务动作
├── 接口
├── 页面
└── 多语言资源
```
### 2.3 核心原则
```text
业务域是组织中心
SQL 表和视图是事实来源
SQL 文件是查询和处理的主要实现
业务代码负责动作和事务
页面是业务域的使用界面
配置只解决少量表现和参数问题
```
## 3. 总体架构
```text
租户注册配置
│
▼
租户数据库路由
│
▼
SQL Server 租户数据库
├── 系统内核
│ ├── 用户、角色和业务上下文
│ ├── 公司、组织、仓库和账簿
│ ├── 多语言和审计
│ └── 文件和运行记录
├── 业务域
│ ├── 客户和供应商
│ ├── 产品和基础资料
│ ├── 销售
│ ├── 采购
│ ├── 库存
│ └── 财务
├── 工作流
└── SQL 视图和报表
```
运行时链路:
```text
请求
→ 租户路由
→ 建立用户和公司上下文
→ 进入业务域
→ 执行查询或业务动作
→ SQL 事务
→ 审计记录
→ 返回结果
```
## 4. 核心内核
核心内核只提供所有业务域都需要的能力,不负责解释所有业务。
### 4.1 租户路由
第一阶段采用一个租户一个数据库:
- 租户注册信息使用配置文件;
- 一个租户对应一个 SQL Server 数据库;
- 租户库内不重复使用 `tenant_id` 过滤业务数据;
- 数据库连接由统一的 `TenantRegistry` 获取;
- 每个租户数据库单独执行迁移并记录版本。
平台库、在线租户开通和集中运维不进入第一阶段。
### 4.2 业务上下文
每次请求建立统一上下文:
```text
tenant_code
user_id
company_id
organization_id
warehouse_id
ledger_id
language
timezone
currency
request_id
```
上下文由运行时创建,业务域只能读取,不由页面自行拼接。SQL 和业务动作通过上下文得到当前公司、用户和账簿。
### 4.3 基础表
第一阶段的系统基础表:
```text
s_user
s_role
s_user_role
s_company
s_organization
s_warehouse
s_ledger
s_user_company
s_user_organization
s_user_warehouse
s_user_ledger
s_i18n_type
s_i18n
s_audit_log
s_file
```
角色先采用简单模型,不实现角色继承和复杂拒绝规则。公司、组织、仓库和账簿是明确的独立对象,不使用一个 `org_id` 代替它们。
## 5. 业务域代码包
### 5.1 业务域定义
业务域是新系统的主要代码组织单位,例如:
```text
customer
supplier
product
sales
purchase
inventory
finance
workflow
```
一个业务域负责自己的:
- 业务表和表关系;
- 数据库迁移;
- 查询视图;
- 查询 SQL;
- 保存 SQL;
- 业务动作;
- 事务边界;
- API;
- 页面;
- 多语言资源;
- 审计事件。
业务域之间通过明确的业务表关系、SQL 查询和服务接口协作,不通过全局 JSON 规则互相解释。
### 5.2 业务对象
业务对象仍然存在,但它属于业务域,而不是全局通用元数据引擎。
一个业务对象至少包含:
```text
对象编码
主表或视图
查询 SQL
保存方式
业务动作
```
例如销售订单:
```text
sales_order
├── b_sales_order
├── b_sales_order_line
├── sales_order_list.sql
├── sales_order_save.sql
├── submit_sales_order()
└── approve_sales_order()
```
### 5.3 业务域目录
建议使用以下代码组织方式:
```text
fms/
├── core/
│ ├── tenant/
│ ├── context/
│ ├── auth/
│ ├── audit/
│ └── i18n/
├── domains/
│ ├── customer/
│ │ ├── migration/
│ │ ├── sql/
│ │ ├── repository/
│ │ ├── service/
│ │ ├── api/
│ │ └── page/
│ ├── product/
│ ├── sales/
│ ├── purchase/
│ ├── inventory/
│ └── finance/
├── workflow/
└── shared/
```
每个业务域使用同一种目录结构,不允许某个模块把 SQL 放数据库字段、另一个模块把 SQL 放页面代码、第三个模块再放配置 JSON。
## 6. SQL 设计
### 6.1 SQL 的职责
SQL 负责:
- 查询;
- 关联;
- 分组和统计;
- 排序和分页;
- 批量更新;
- 事务内的数据处理;
- 报表和视图。
业务代码负责:
- 业务动作入口;
- 事务编排;
- 状态变化;
- 幂等处理;
- 跨模块规则;
- 库存和财务业务约束。
### 6.2 SQL 文件规范
SQL 统一放在业务域目录下:
```text
domains/sales/sql/
├── order_list.sql
├── order_detail.sql
├── order_save.sql
├── order_submit.sql
└── order_report.sql
```
SQL 命名统一使用:
```text
对象_动作.sql
```
例如:
```text
customer_list.sql
customer_save.sql
customer_disable.sql
sales_order_submit.sql
inventory_lock.sql
```
### 6.3 通用 CRUD 的边界
新架构不移除通用 CRUD,但不把通用 CRUD 作为所有业务的默认核心。它可以用于简单基础资料,核心业务必须由业务域自己的 SQL 和业务动作实现。
以下内容不再作为系统核心:
- 数据库中的 `b_query_sql` 作为所有模块的默认查询入口;
- `b_save_table` 驱动所有业务模块的通用保存;
- `b_order_sql` 在运行时任意拼接排序;
- 全局 `data / virtual` 模块解释器;
- 页面 JSON 中的业务公式和业务写入规则;
- 一张通用配置表承载所有业务行为。
内部开发人员可以自由编写 SQL,但 SQL 由对应业务域维护,并与代码和迁移一起发布。
### 6.4 通用保存边界
通用保存仅用于简单基础资料。以下内容必须使用业务域专用服务和 SQL:
- 库存扣减、锁定和释放;
- 采购和销售状态推进;
- 财务过账和反过账;
- 公司间交易;
- 审批提交和审批动作;
- 任何需要多个业务表一致提交的操作。
## 7. 数据库模型
### 7.1 表分层
```text
s_* 系统内核和控制数据
b_* 业务主表和业务明细表
wf_* 工作流定义和运行数据
```
核心业务表使用真实列和真实关系,不使用全量 EAV。
### 7.2 公司字段
公司级业务表根据业务实际需要包含:
```text
company_id
organization_id
warehouse_id
ledger_id
```
跨公司业务另外保存:
```text
source_company_id
target_company_id
settlement_company_id
```
共享基础资料不复制多份,使用共享范围或公司关联表表达。
### 7.3 动态字段
第一阶段不支持普通用户通过页面配置直接创建数据库字段。新增业务字段使用:
```text
迁移脚本增加物理列或扩展表
→ 更新查询 SQL 和视图
→ 更新页面字段定义
→ 增加多语言资源
```
字段分为:
1. 核心字段:真实列,参与业务逻辑和统计;
2. 扩展字段:通过迁移增加的可选列或扩展表字段;
3. 低频扩展:JSON,仅用于非核心展示。
需要筛选、排序、统计或导出的字段不能只放在 JSON 中。
运行时字段分为两种情况:
- **表现字段**:标签、显隐、只读、排序和布局可以运行时调整;
- **物理字段**:需要存储、查询、排序、统计或导出的字段,必须通过 SQL Migration 增加物理列或扩展表。
后续如果确实需要管理员在线增加物理字段,可以增加一个受控的字段变更服务,由它生成并执行 SQL Migration,同时更新视图、字段定义和页面配置。这个过程仍然是数据库结构变更,不是普通 JSON 配置。
### 7.4 数据库约束
数据库负责结构完整性:
- 主键;
- 外键;
- 唯一索引;
- 非空约束;
- 必要 CHECK;
- 查询和关联索引。
不使用触发器替代库存、财务和审批业务流程。业务规则放在业务域服务和事务 SQL 中。
## 8. 页面和配置
### 8.1 页面不是元数据生成的
核心业务页面由业务域代码实现。页面可以复用通用列表、表单和查询组件,但页面的字段、动作和业务行为由业务域明确声明。
页面配置只保存:
- 列显示顺序;
- 列宽;
- 表单分组;
- 查询默认值;
- 用户个人偏好;
- 显示格式。
不使用 JSON 描述完整业务页面和业务处理器。
### 8.2 模块表的定位
如果保留 `s_module`,它只用于:
- 菜单和路由登记;
- 权限对象登记;
- 模块名称和多语言资源键;
- 业务域对象的索引。
它不负责:
- 生成所有 SQL;
- 推断所有保存逻辑;
- 承载业务状态;
- 解释任意 JSON 规则。
### 8.3 公司差异
第一阶段不建设通用的租户、公司、部门、角色多层配置继承。
确实存在公司差异时,在具体业务表中直接使用 `company_id`,例如:
```text
s_number_rule(company_id, domain_code, field_code)
wf_binding(company_id, domain_code, event_code)
s_print_template(company_id, template_code)
```
不把所有差异都抽象为一个通用覆盖引擎。
## 9. 权限
权限采用简单的角色模型:
```text
s_role
s_user_role
s_role_permission
s_role_data_scope
s_user_company
```
权限对象绑定业务域和业务动作:
```text
customer.read
customer.create
sales_order.submit
sales_order.approve
inventory.adjust
finance.post
```
第一阶段支持:
- 用户可用公司;
- 用户可用组织;
- 用户可用仓库;
- 模块访问权限;
- 业务动作权限;
- 简单数据范围。
不实现角色继承、拒绝权限和通用权限表达式。复杂范围由对应业务域的 SQL 实现。
## 10. 工作流
工作流是业务域的流程编排器,不是业务规则中心。
工作流绑定业务域动作:
```text
sales_order.submit
sales_order.approve
purchase_order.submit
expense.approve
```
工作流可以决定:
- 下一节点;
- 审批人;
- 当前流程状态;
- 需要调用哪个业务动作。
业务域负责真正的动作:
- 销售订单批准后是否允许出库;
- 采购收货如何增加库存;
- 财务单据如何过账;
- 审批失败如何补偿。
工作流实例固定流程版本,历史审批记录只追加不修改。
## 11. 多语言
第一阶段保持简单:
```text
s_i18n_type
s_i18n
```
系统文案使用稳定资源键:
```text
sales.order.name
sales.order.customer
action.submit
```
业务主数据使用业务域自己的翻译表:
```text
b_product
b_product_translation
b_customer
b_customer_translation
```
系统资源可以放在代码资源文件或租户库中,但必须统一一种来源。第一阶段不实现三层 overlay、翻译变更集和运行时语言管理。
历史单据保存产品名称、客户名称、税率、地址、币种和金额等快照。
## 12. 配置和版本
第一阶段不建设覆盖所有对象的通用配置版本引擎。
配置分为三类:
| 类型 | 实现方式 |
| --- | --- |
| 数据库结构 | SQL Migration |
| 业务逻辑 | 代码和业务 SQL |
| 页面表现 | 页面代码和少量布局配置 |
| 工作流 | 独立流程版本表 |
| 业务参数 | 明确的参数表 |
只有工作流、编号规则、打印模板等确实需要历史追溯的内容,才单独设计版本。
### 12.1 SQL Migration
SQL Migration 是按顺序执行、可追踪的数据库结构变更脚本。例如:
```text
migration/
├── V001__create_company.sql
├── V002__create_customer.sql
└── V003__add_customer_tax_no.sql
```
迁移工具在每个租户数据库中记录已执行的版本。发布新代码时,先执行缺少的迁移,再启动依赖新结构的代码。
Migration 可以包含:
- 建表、改表和删表;
- 增加或停用字段;
- 创建视图、索引和约束;
- 初始化字典和系统数据;
- 必要的数据回填。
Migration 不等于运行时页面配置。页面配置可以立即保存,数据库结构变化则必须经过迁移记录。
数据库迁移记录:
```text
schema_version
migration_name
checksum
executed_at
```
## 13. 代码风格和目录规范
新系统的代码按业务域组织,不按页面组织,也不按一组通用配置表组织。
```text
domains/
sales/
migration/
sql/
repository/
service/
api/
page/
i18n/
```
统一要求:
- 一个业务动作只有一个主要入口;
- SQL 统一放业务域的 `sql` 目录;
- 页面只调用业务域 API,不直接写业务 SQL;
- 业务域不通过另一个域的内部表直接修改数据;
- 数据库表、SQL、服务、接口和页面使用同一业务名称;
- 新增抽象必须来自至少两个真实业务域的重复需求。
数据库命名在新系统中一次确定:
- 表名使用小写 `snake_case`;
- 系统表使用 `s_`;
- 业务表使用 `b_` 或明确业务域前缀;
- 工作流表使用 `wf_`;
- 字段命名统一使用一套规则,不再混用 `b_`、`mx_` 和裸字段名。
## 14. 第一阶段建设范围
第一阶段只建设架构内核和一条完整业务链路。
### 14.1 必须完成
1. SQL Server 迁移工具和数据库版本表;
2. 租户数据库连接和路由;
3. 用户、角色、公司、组织、仓库和账簿;
4. 统一业务上下文;
5. 业务域目录和 SQL 调用约定;
6. 基础权限和公司范围;
7. 审计日志;
8. 基础多语言;
9. 一个业务域的表、SQL、服务、接口和页面完整闭环。
### 14.2 建议示例
建议先做客户或产品域:
```text
SQL Migration
→ 真实业务表
→ 查询 SQL
→ 保存 SQL
→ 业务域服务
→ API
→ 页面
→ 公司权限
→ 审计
```
这个示例必须验证“业务域代码包是中心”,不只是验证通用页面是否能显示。
### 14.3 第一阶段不做
- 以全局 `s_module` 驱动的通用 CRUD 作为核心业务默认实现;
- 全局 `s_field` 驱动的运行时字段创建;
- 通用页面设计器;
- 通用公式和规则解释器;
- 全对象配置版本和变更集;
- 多层公司配置覆盖;
- 复杂多语言发布体系;
- 集团分析库和跨租户业务查询。
## 15. 第一阶段验收标准
### 15.1 架构中心
- 一个业务域拥有自己的迁移、SQL、服务和页面;
- 业务域可以不依赖通用模块解释器运行;
- 页面配置不能替代业务代码;
- 工作流只能调用明确登记的业务动作。
### 15.2 SQL 驱动
- 业务查询可以直接在 SQL Server 中验证;
- 复杂查询使用业务域 SQL 文件或视图;
- 业务表和关联关系是真实 SQL 结构;
- 新增可统计字段需要物理列或索引扩展;
- 不为了页面功能复制业务表。
### 15.3 简单和一致
- 同类业务域使用相同目录和调用流程;
- 不存在同一类配置的多种格式;
- 不存在同一业务动作的多个通用入口;
- 数据库迁移和代码可以一起发布;
- 第一阶段不依赖通用低代码运行时。
## 16. 旧设计迁移原则
旧设计不是全部废弃,而是按职责迁移:
| 旧内容 | 新系统处理方式 |
| --- | --- |
| 真实业务表 | 保留并按业务域归属整理 |
| 业务 SQL | 移入对应业务域 `sql` 目录 |
| `wf_*` 工作流表 | 保留,改为绑定业务动作 |
| `s_module` | 降为菜单、权限和对象索引 |
| `s_field` | 降为字段说明,不负责运行时造字段 |
| `s_module_schema` | 页面局部配置,不作为核心运行时 |
| 通用保存接口 | 仅保留简单基础资料使用 |
| JSON 公式和动态规则 | 迁移到业务代码或 SQL |
| `b_query_sql` 等全局 SQL 字段 | 逐步迁移到业务域 SQL 文件 |
## 17. 最终架构关系
```text
业务域代码包是组织中心
SQL 表和视图是事实来源
SQL 文件是查询和处理的主要实现
业务代码负责动作和事务
页面负责交互
工作流负责流程编排
少量配置负责表现和参数
```
新系统不是旧系统增加一层配置,也不是把旧模块表重新命名。它的核心变化是:
> **从元数据驱动的通用模块,改成业务域驱动的 SQL 应用。**