17 KiB
FMS 新系统核心架构设计 V2.0
文档版本:V2.0
编制日期:2026-09-30
文档状态:核心架构设计草案
适用范围:FMS/ERP 新系统第一阶段重构
1. 文档目的
本文档重新定义 FMS 新系统的核心架构,重点解决以下问题:
- SQL 是系统的第一等公民;
- 代码风格和模块实现方式统一;
- 第一阶段保持简单,不建设通用低代码平台;
- 真实业务表、业务 SQL 和业务代码拥有清晰边界;
- 支持一个租户多个公司,但不提前建设复杂的平台能力。
本文档替代旧设计中以 s_module、通用 CRUD 和动态 JSON 为中心的架构思路。旧表结构中的业务数据、工作流和可复用 SQL 可以迁移,但运行时中心需要重新组织。
2. 根本改变
2.1 旧架构
旧设计的主链路是:
s_module
→ s_field
→ s_module_schema
→ 通用 SQL 拼接
→ 通用保存
→ 页面
它的优点是页面和简单模块创建较快,缺点是业务模块逐渐依赖元数据解释器,容易变成一个低代码平台。
2.2 新架构
新设计的主链路是:
业务域代码包
→ 真实 SQL 表和视图
→ 业务查询与业务动作
→ API / 页面
新系统的中心不再是全局模块元数据,而是业务域代码包。每个业务域自己拥有表结构、迁移、SQL、业务动作和页面入口。
客户域
├── 数据库迁移
├── 业务表和视图
├── 查询 SQL
├── 保存 SQL
├── 业务动作
├── 接口
├── 页面
└── 多语言资源
2.3 核心原则
业务域是组织中心
SQL 表和视图是事实来源
SQL 文件是查询和处理的主要实现
业务代码负责动作和事务
页面是业务域的使用界面
配置只解决少量表现和参数问题
3. 总体架构
租户注册配置
│
▼
租户数据库路由
│
▼
SQL Server 租户数据库
├── 系统内核
│ ├── 用户、角色和业务上下文
│ ├── 公司、组织、仓库和账簿
│ ├── 多语言和审计
│ └── 文件和运行记录
├── 业务域
│ ├── 客户和供应商
│ ├── 产品和基础资料
│ ├── 销售
│ ├── 采购
│ ├── 库存
│ └── 财务
├── 工作流
└── SQL 视图和报表
运行时链路:
请求
→ 租户路由
→ 建立用户和公司上下文
→ 进入业务域
→ 执行查询或业务动作
→ SQL 事务
→ 审计记录
→ 返回结果
4. 核心内核
核心内核只提供所有业务域都需要的能力,不负责解释所有业务。
4.1 租户路由
第一阶段采用一个租户一个数据库:
- 租户注册信息使用配置文件;
- 一个租户对应一个 SQL Server 数据库;
- 租户库内不重复使用
tenant_id过滤业务数据; - 数据库连接由统一的
TenantRegistry获取; - 每个租户数据库单独执行迁移并记录版本。
平台库、在线租户开通和集中运维不进入第一阶段。
4.2 业务上下文
每次请求建立统一上下文:
tenant_code
user_id
company_id
organization_id
warehouse_id
ledger_id
language
timezone
currency
request_id
上下文由运行时创建,业务域只能读取,不由页面自行拼接。SQL 和业务动作通过上下文得到当前公司、用户和账簿。
4.3 基础表
第一阶段的系统基础表:
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 业务域定义
业务域是新系统的主要代码组织单位,例如:
customer
supplier
product
sales
purchase
inventory
finance
workflow
一个业务域负责自己的:
- 业务表和表关系;
- 数据库迁移;
- 查询视图;
- 查询 SQL;
- 保存 SQL;
- 业务动作;
- 事务边界;
- API;
- 页面;
- 多语言资源;
- 审计事件。
业务域之间通过明确的业务表关系、SQL 查询和服务接口协作,不通过全局 JSON 规则互相解释。
5.2 业务对象
业务对象仍然存在,但它属于业务域,而不是全局通用元数据引擎。
一个业务对象至少包含:
对象编码
主表或视图
查询 SQL
保存方式
业务动作
例如销售订单:
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 业务域目录
建议使用以下代码组织方式:
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 统一放在业务域目录下:
domains/sales/sql/
├── order_list.sql
├── order_detail.sql
├── order_save.sql
├── order_submit.sql
└── order_report.sql
SQL 命名统一使用:
对象_动作.sql
例如:
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 表分层
s_* 系统内核和控制数据
b_* 业务主表和业务明细表
wf_* 工作流定义和运行数据
核心业务表使用真实列和真实关系,不使用全量 EAV。
7.2 公司字段
公司级业务表根据业务实际需要包含:
company_id
organization_id
warehouse_id
ledger_id
跨公司业务另外保存:
source_company_id
target_company_id
settlement_company_id
共享基础资料不复制多份,使用共享范围或公司关联表表达。
7.3 动态字段
第一阶段不支持普通用户通过页面配置直接创建数据库字段。新增业务字段使用:
迁移脚本增加物理列或扩展表
→ 更新查询 SQL 和视图
→ 更新页面字段定义
→ 增加多语言资源
字段分为:
- 核心字段:真实列,参与业务逻辑和统计;
- 扩展字段:通过迁移增加的可选列或扩展表字段;
- 低频扩展: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,例如:
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. 权限
权限采用简单的角色模型:
s_role
s_user_role
s_role_permission
s_role_data_scope
s_user_company
权限对象绑定业务域和业务动作:
customer.read
customer.create
sales_order.submit
sales_order.approve
inventory.adjust
finance.post
第一阶段支持:
- 用户可用公司;
- 用户可用组织;
- 用户可用仓库;
- 模块访问权限;
- 业务动作权限;
- 简单数据范围。
不实现角色继承、拒绝权限和通用权限表达式。复杂范围由对应业务域的 SQL 实现。
10. 工作流
工作流是业务域的流程编排器,不是业务规则中心。
工作流绑定业务域动作:
sales_order.submit
sales_order.approve
purchase_order.submit
expense.approve
工作流可以决定:
- 下一节点;
- 审批人;
- 当前流程状态;
- 需要调用哪个业务动作。
业务域负责真正的动作:
- 销售订单批准后是否允许出库;
- 采购收货如何增加库存;
- 财务单据如何过账;
- 审批失败如何补偿。
工作流实例固定流程版本,历史审批记录只追加不修改。
11. 多语言
第一阶段保持简单:
s_i18n_type
s_i18n
系统文案使用稳定资源键:
sales.order.name
sales.order.customer
action.submit
业务主数据使用业务域自己的翻译表:
b_product
b_product_translation
b_customer
b_customer_translation
系统资源可以放在代码资源文件或租户库中,但必须统一一种来源。第一阶段不实现三层 overlay、翻译变更集和运行时语言管理。
历史单据保存产品名称、客户名称、税率、地址、币种和金额等快照。
12. 配置和版本
第一阶段不建设覆盖所有对象的通用配置版本引擎。
配置分为三类:
| 类型 | 实现方式 |
|---|---|
| 数据库结构 | SQL Migration |
| 业务逻辑 | 代码和业务 SQL |
| 页面表现 | 页面代码和少量布局配置 |
| 工作流 | 独立流程版本表 |
| 业务参数 | 明确的参数表 |
只有工作流、编号规则、打印模板等确实需要历史追溯的内容,才单独设计版本。
12.1 SQL Migration
SQL Migration 是按顺序执行、可追踪的数据库结构变更脚本。例如:
migration/
├── V001__create_company.sql
├── V002__create_customer.sql
└── V003__add_customer_tax_no.sql
迁移工具在每个租户数据库中记录已执行的版本。发布新代码时,先执行缺少的迁移,再启动依赖新结构的代码。
Migration 可以包含:
- 建表、改表和删表;
- 增加或停用字段;
- 创建视图、索引和约束;
- 初始化字典和系统数据;
- 必要的数据回填。
Migration 不等于运行时页面配置。页面配置可以立即保存,数据库结构变化则必须经过迁移记录。
数据库迁移记录:
schema_version
migration_name
checksum
executed_at
13. 代码风格和目录规范
新系统的代码按业务域组织,不按页面组织,也不按一组通用配置表组织。
domains/
sales/
migration/
sql/
repository/
service/
api/
page/
i18n/
统一要求:
- 一个业务动作只有一个主要入口;
- SQL 统一放业务域的
sql目录; - 页面只调用业务域 API,不直接写业务 SQL;
- 业务域不通过另一个域的内部表直接修改数据;
- 数据库表、SQL、服务、接口和页面使用同一业务名称;
- 新增抽象必须来自至少两个真实业务域的重复需求。
数据库命名在新系统中一次确定:
- 表名使用小写
snake_case; - 系统表使用
s_; - 业务表使用
b_或明确业务域前缀; - 工作流表使用
wf_; - 字段命名统一使用一套规则,不再混用
b_、mx_和裸字段名。
14. 第一阶段建设范围
第一阶段只建设架构内核和一条完整业务链路。
14.1 必须完成
- SQL Server 迁移工具和数据库版本表;
- 租户数据库连接和路由;
- 用户、角色、公司、组织、仓库和账簿;
- 统一业务上下文;
- 业务域目录和 SQL 调用约定;
- 基础权限和公司范围;
- 审计日志;
- 基础多语言;
- 一个业务域的表、SQL、服务、接口和页面完整闭环。
14.2 建议示例
建议先做客户或产品域:
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. 最终架构关系
业务域代码包是组织中心
SQL 表和视图是事实来源
SQL 文件是查询和处理的主要实现
业务代码负责动作和事务
页面负责交互
工作流负责流程编排
少量配置负责表现和参数
新系统不是旧系统增加一层配置,也不是把旧模块表重新命名。它的核心变化是:
从元数据驱动的通用模块,改成业务域驱动的 SQL 应用。