32 KiB
FMS 新系统模块区核心表结构设计
本方案为模块元数据中心的重新设计,表名与字段名命名风格参考旧系统: 平台表以
s_开头,字段以b_开头,复合词采用驼峰拼接(如b_viewtable、b_inputdatetime),逻辑关联字段使用b_xxx_id。
1. 设计原则
针对旧系统 s_module 职责混杂的问题,本方案坚持四个分离:
- 模块与实体分离:
s_module只表达业务能力边界与依赖关系,不承载表、路由、菜单;数据归属由s_entity承担。 - 实体与字段分离:实体是数据所有者,声明查询源/保存源/主键字段;字段挂实体,同时携带数据库列定义与 UI 行为。
- 菜单与模块分离:
s_menu是纯导航树,只引用实体或固定页面,不掺入数据源配置。 - 字段定义与视图配置合并:旧系统把字段拆成
s_module_field+_edit+_list+_query四张表,维护极易失配;本方案合并为单表s_field,用标志列表达列表/表单/查询行为。
其余约定:
- 内部主键
b_id bigint identity(1, 1),跨环境稳定标识使用b_code(参考旧系统)。 - 元数据表保留物理外键(低频、高一致性,防脏数据);若担心部署顺序,可退化为逻辑外键并保留索引。
- 布尔语义使用
int(0/1),与旧系统s_module风格一致。 - 时间使用
datetime2,默认sysdatetime()。 - 审计字段:定义类表保留
b_inputuser_id / b_inputdatetime / b_updateuser_id / b_updatedatetime;关联类表只保留b_inputdatetime。 - 模块归属保留树形(
b_parent_id,仅用于业务分组),模块间依赖使用独立的 DAG 表,两者语义互不混淆。
2. 表清单
| # | 表名 | 说明 |
|---|---|---|
| 1 | s_module |
模块(业务能力边界 + 业务分组树) |
| 2 | s_module_dependency |
模块依赖(DAG,禁止循环) |
| 3 | s_entity |
实体(数据所有者,声明查/存数据源) |
| 4 | s_field |
字段(列定义 + 列表/表单/查询行为合并单表) |
| 5 | s_field_group |
字段分组(实体级) |
| 6 | s_relation |
实体关系(引用/主子,驱动外键校验与级联) |
| 7 | s_menu |
菜单/页面(纯导航,挂模块做组织维度) |
| 8 | s_autocode |
自动编码规则 |
| 9 | s_role |
角色 |
| 10 | s_user_role |
用户-角色 |
| 11 | s_power |
权限点(menu/entity/action/field) |
| 12 | s_role_power |
角色-权限 |
| 13 | s_i18n |
多语言资源 |
| 14 | s_log_technical |
技术日志(请求链路/SQL/异常/性能) |
| 15 | s_log_login |
登录日志(登录/退出/失败/锁定) |
| 16 | s_log_audit |
业务审计事件 |
| 17 | s_log_audit_field |
审计字段变更明细 |
3. 各表结构
3.1 s_module 模块
只保存模块身份、业务分组与启用状态,不承载菜单、路由或数据源。
create table s_module (
b_id bigint identity(1, 1) not null, -- 内部主键
b_code varchar(50) not null, -- 模块编码(如 ocean/finance/master-data)
b_name nvarchar(100) not null, -- 模块名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_parent_id bigint null, -- 父模块(仅业务分组,不承担导航)
b_module_type varchar(20) not null default 'business', -- 类型:platform 平台/tenant 租户/business 业务
b_canuse int not null default 1, -- 是否启用
b_xh int not null default 0, -- 排序号
b_icon varchar(50) null, -- 图标
b_bz nvarchar(1000) null, -- 描述
b_inputuser_id bigint null,
b_inputdatetime datetime2 null default sysdatetime(),
b_updateuser_id bigint null,
b_updatedatetime datetime2 null,
constraint pk_s_module primary key (b_id),
constraint ux_s_module_code unique (b_code),
constraint fk_s_module_parent foreign key (b_parent_id) references s_module (b_id)
);
create index ix_s_module_parent on s_module (b_parent_id, b_xh, b_canuse);
3.2 s_module_dependency 模块依赖
模块依赖必须形成有向无环图,由服务层校验禁止循环。
create table s_module_dependency (
b_id bigint identity(1, 1) not null, -- 内部主键
b_module_id bigint not null, -- 依赖方模块
b_depend_module_id bigint not null, -- 被依赖模块
b_inputdatetime datetime2 null default sysdatetime(),
constraint pk_s_module_dependency primary key (b_id),
constraint ux_s_module_dependency unique (b_module_id, b_depend_module_id),
constraint fk_s_module_dependency_module foreign key (b_module_id) references s_module (b_id),
constraint fk_s_module_dependency_depend foreign key (b_depend_module_id) references s_module (b_id)
);
create index ix_s_module_dependency_depend on s_module_dependency (b_depend_module_id);
3.3 s_entity 实体
实体是数据所有者,同时声明查询数据源与保存数据源;主子表通过 b_parent_id 表达(与菜单树无关)。
create table s_entity (
b_id bigint identity(1, 1) not null, -- 内部主键
b_module_id bigint not null, -- 所属模块
b_parent_id bigint null, -- 父实体(主子表关系)
b_code varchar(50) not null, -- 实体编码(如 ocean_booking)
b_name nvarchar(100) not null, -- 实体名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_viewtable varchar(128) null, -- 查询数据源:真实表或只读视图
b_savetable varchar(128) null, -- 保存目标:真实表(纯查询实体可空)
b_keyfield varchar(50) null, -- 保存对象主键字段(如 b_id)
b_entity_type varchar(20) not null default 'table', -- table 简单实体/view 复杂列表/query 纯查询
b_delete_policy varchar(20) not null default 'lifecycle', -- restrict/lifecycle/cascade/hard
b_canuse int not null default 1, -- 是否启用
b_xh int not null default 0, -- 排序号
b_bz nvarchar(1000) null, -- 描述
b_inputuser_id bigint null,
b_inputdatetime datetime2 null default sysdatetime(),
b_updateuser_id bigint null,
b_updatedatetime datetime2 null,
constraint pk_s_entity primary key (b_id),
constraint ux_s_entity_module_code unique (b_module_id, b_code),
constraint fk_s_entity_module foreign key (b_module_id) references s_module (b_id),
constraint fk_s_entity_parent foreign key (b_parent_id) references s_entity (b_id)
);
create index ix_s_entity_parent on s_entity (b_parent_id);
create index ix_s_entity_savetable on s_entity (b_savetable);
实体的查/存配置方式:
简单实体: b_viewtable = b_savetable = 同一张真实表
复杂列表: b_viewtable = 带关联/聚合的 SQL 视图(只读), b_savetable = 真实表
纯查询实体: b_viewtable = 视图或表, b_savetable = 空(只读)
删除策略取值(b_delete_policy):
restrict 主数据:有引用时拒绝删除,只能停用或合并
lifecycle 业务单据:草稿且无下游时可删,正式后只能取消/作废
cascade 主表拥有的从数据:随主表同一事务级联删除
hard 临时记录:校验权限后直接物理删除
3.4 s_field 字段
合并旧系统 s_module_field + _edit + _list + _query 四张表,字段增删改只需维护一处。
create table s_field (
b_id bigint identity(1, 1) not null, -- 内部主键
b_entity_id bigint not null, -- 所属实体
b_field varchar(50) not null, -- 物理列名(与真实表列一致)
b_name nvarchar(100) not null, -- 字段名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_type varchar(30) not null, -- 存储类型:varchar/decimal/datetime/int/bit/text
b_length int null, -- 长度
b_precision int null, -- 数值精度(总位数)
b_scale int null, -- 数值小数位
b_nullable int not null default 1, -- 数据库层是否可空
b_default_value nvarchar(500) null, -- 默认值(文本表达)
b_options nvarchar(max) null, -- 下拉/单选选项(JSON 文本)
b_edittype varchar(30) null, -- 编辑组件:input/select/date/datetime/textarea/number/checkbox/radio
b_required int not null default 0, -- 编辑必填
b_readonly int not null default 0, -- 编辑只读
b_disabled int not null default 0, -- 编辑禁用
b_listvisible int not null default 1, -- 是否显示在列表
b_listwidth int null, -- 列表列宽(px)
b_queryable int not null default 0, -- 是否出现在查询条件
b_querytype varchar(30) null, -- 查询组件:input/select/date
b_queryop varchar(20) null, -- 查询操作符:eq/like/gt/ge/lt/le/in/between
b_group_id bigint null, -- 所属分组(逻辑外键 s_field_group.b_id)
b_xh int not null default 0, -- 列表/表单排序
b_canuse int not null default 1, -- 是否启用(停用隐藏并保留数据)
b_inputuser_id bigint null,
b_inputdatetime datetime2 null default sysdatetime(),
b_updateuser_id bigint null,
b_updatedatetime datetime2 null,
constraint pk_s_field primary key (b_id),
constraint ux_s_field_entity_field unique (b_entity_id, b_field),
constraint fk_s_field_entity foreign key (b_entity_id) references s_entity (b_id)
);
create index ix_s_field_group on s_field (b_group_id, b_xh, b_canuse);
写入模式由 b_readonly 与 b_canuse 组合表达;如需更细的写入语义(computed/actionOnly/system),可增加 b_writemode varchar(20) 列(direct/createOnly/readOnly/computed/actionOnly/system)。
3.5 s_field_group 字段分组
分组是实体级独立定义,字段通过 b_group_id 引用,支持分组树。
create table s_field_group (
b_id bigint identity(1, 1) not null, -- 内部主键
b_entity_id bigint not null, -- 所属实体
b_parent_id bigint null, -- 父分组(分组树)
b_title nvarchar(100) not null, -- 分组标题
b_i18n varchar(150) null, -- 标题多语言 key
b_xh int not null default 0, -- 分组排序
b_canuse int not null default 1, -- 是否启用(停用整组隐藏)
constraint pk_s_field_group primary key (b_id),
constraint fk_s_field_group_entity foreign key (b_entity_id) references s_entity (b_id),
constraint fk_s_field_group_parent foreign key (b_parent_id) references s_field_group (b_id)
);
create index ix_s_field_group_entity on s_field_group (b_entity_id, b_xh, b_canuse);
3.6 s_relation 实体关系
描述实体间的引用与主子关系,运行时据此执行外键校验、主子事务与级联删除。
create table s_relation (
b_id bigint identity(1, 1) not null, -- 内部主键
b_entity_id bigint not null, -- 主实体
b_target_entity_id bigint not null, -- 目标实体
b_relation_type varchar(20) not null, -- one-to-one/one-to-many/many-to-one/many-to-many
b_foreign_field varchar(50) null, -- 子表侧外键字段
b_link_field varchar(50) null, -- 主实体侧关联字段
b_ownership varchar(20) not null default 'reference', -- reference 引用/owned 主从
b_delete_policy varchar(20) null, -- restrict/set-null/cascade/archive
b_orderfield varchar(500) null, -- 子表排序字段表达式
b_canuse int not null default 1, -- 是否启用
b_xh int not null default 0, -- 排序号
constraint pk_s_relation primary key (b_id),
constraint fk_s_relation_entity foreign key (b_entity_id) references s_entity (b_id),
constraint fk_s_relation_target foreign key (b_target_entity_id) references s_entity (b_id)
);
create index ix_s_relation_target on s_relation (b_target_entity_id);
3.7 s_menu 菜单/页面
纯导航树,通过 b_module_id 挂到模块做组织维度,通过 b_entity_id 引用实体;模块与菜单可互相独立演进(一个模块多个菜单入口、一个菜单也可属于无模块的公共区)。
create table s_menu (
b_id bigint identity(1, 1) not null, -- 内部主键
b_parent_id bigint null, -- 父菜单(导航树自引用)
b_module_id bigint null, -- 所属模块(菜单的组织维度,可空)
b_code varchar(50) not null, -- 菜单编码
b_name nvarchar(100) not null, -- 菜单名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_menu_type varchar(20) not null, -- directory/page/data/external
b_route varchar(200) null, -- 固定页面路由或外链
b_entity_id bigint null, -- data 页面绑定的实体
b_power_code varchar(50) null, -- 页面访问权限编码(对应 s_power.b_code)
b_icon varchar(50) null, -- 图标
b_canmenu int not null default 1, -- 是否显示在菜单
b_xh int not null default 0, -- 排序号
b_canuse int not null default 1, -- 是否启用
constraint pk_s_menu primary key (b_id),
constraint ux_s_menu_code unique (b_code),
constraint fk_s_menu_parent foreign key (b_parent_id) references s_menu (b_id),
constraint fk_s_menu_module foreign key (b_module_id) references s_module (b_id),
constraint fk_s_menu_entity foreign key (b_entity_id) references s_entity (b_id)
);
create index ix_s_menu_parent on s_menu (b_parent_id, b_xh, b_canuse);
create index ix_s_menu_entity on s_menu (b_entity_id);
菜单类型约束由元数据校验器执行:directory 不填业务路由;page 必须填 b_route;data 必须填 b_entity_id;external 必须填 b_route 和 b_power_code。目录仅在存在至少一个可访问子页面时显示。
3.8 s_autocode 自动编码
独立于模块,挂实体 + 目标字段,规则可复用。并发控制:应用层使用原子更新取号(update ... set b_currentvalue = b_currentvalue + 1 output inserted.b_currentvalue),避免自增竞争。
create table s_autocode (
b_id bigint identity(1, 1) not null, -- 内部主键
b_entity_id bigint not null, -- 所属实体
b_field varchar(50) not null, -- 目标字段(物理列名)
b_prefix nvarchar(100) not null default '', -- 前缀
b_dateformat varchar(30) not null default 'yyyyMM', -- 日期格式
b_separator nvarchar(10) not null default '-', -- 分隔符
b_seqwidth int not null default 5, -- 流水位宽
b_resettype varchar(10) not null default 'month', -- 重置周期:none/day/month/year
b_startvalue bigint not null default 1, -- 起始值
b_currentperiod varchar(8) null, -- 当前周期
b_currentvalue bigint not null default 0, -- 当前流水值
b_canuse int not null default 1, -- 是否启用
constraint pk_s_autocode primary key (b_id),
constraint ux_s_autocode_entity_field unique (b_entity_id, b_field),
constraint fk_s_autocode_entity foreign key (b_entity_id) references s_entity (b_id)
);
3.9 s_role 角色
create table s_role (
b_id bigint identity(1, 1) not null, -- 内部主键
b_code varchar(50) not null, -- 角色编码
b_name nvarchar(100) not null, -- 角色名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_remark nvarchar(500) null, -- 描述
b_canuse int not null default 1, -- 是否启用
b_inputuser_id bigint null,
b_inputdatetime datetime2 null default sysdatetime(),
b_updateuser_id bigint null,
b_updatedatetime datetime2 null,
constraint pk_s_role primary key (b_id),
constraint ux_s_role_code unique (b_code)
);
3.10 s_user_role 用户-角色
create table s_user_role (
b_id bigint identity(1, 1) not null, -- 内部主键
b_user_id bigint not null, -- 用户(逻辑外键,当前身份系统)
b_role_id bigint not null, -- 角色
b_canuse int not null default 1, -- 是否启用该关系
b_inputdatetime datetime2 null default sysdatetime(),
constraint pk_s_user_role primary key (b_id),
constraint ux_s_user_role unique (b_user_id, b_role_id),
constraint fk_s_user_role_role foreign key (b_role_id) references s_role (b_id)
);
create index ix_s_user_role_role on s_user_role (b_role_id, b_canuse);
3.11 s_power 权限点
create table s_power (
b_id bigint identity(1, 1) not null, -- 内部主键
b_code varchar(50) not null, -- 权限编码(如 booking.confirm)
b_name nvarchar(100) not null, -- 权限名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_power_type varchar(20) not null, -- menu/entity/action/field
b_resource varchar(128) null, -- 资源标识(菜单 code/实体 code/动作 code/字段)
b_canuse int not null default 1, -- 是否启用
b_xh int not null default 0, -- 排序号
constraint pk_s_power primary key (b_id),
constraint ux_s_power_code unique (b_code)
);
create index ix_s_power_resource on s_power (b_power_type, b_resource, b_canuse);
3.12 s_role_power 角色-权限
create table s_role_power (
b_id bigint identity(1, 1) not null, -- 内部主键
b_role_id bigint not null, -- 角色
b_power_id bigint not null, -- 权限点
b_canuse int not null default 1, -- 是否启用
b_inputdatetime datetime2 null default sysdatetime(),
constraint pk_s_role_power primary key (b_id),
constraint ux_s_role_power unique (b_role_id, b_power_id),
constraint fk_s_role_power_role foreign key (b_role_id) references s_role (b_id),
constraint fk_s_role_power_power foreign key (b_power_id) references s_power (b_id)
);
create index ix_s_role_power_power on s_role_power (b_power_id, b_canuse);
菜单显示继承页面访问权限:用户拥有 s_menu.b_power_code 对应权限时菜单才显示;前端只控制显示,后端运行时必须再次校验同一个权限编码。
3.13 s_i18n 多语言
create table s_i18n (
b_id bigint identity(1, 1) not null, -- 内部主键
b_key varchar(150) not null, -- 资源 key(如 booking.name)
b_locale varchar(20) not null, -- 语言区域(如 zh-CN/en-US)
b_value nvarchar(500) not null, -- 该语言下的显示文本
b_i18ntype varchar(20) null, -- 资源类型:module/entity/field/menu/action
b_canuse int not null default 1, -- 是否启用
constraint pk_s_i18n primary key (b_id),
constraint ux_s_i18n_key_locale unique (b_key, b_locale)
);
create index ix_s_i18n_locale on s_i18n (b_locale, b_canuse);
3.14 s_log_technical 技术日志
用于请求链路、SQL、异常、性能与外部接口排查。高频日志推荐写入 Loki/OpenSearch/Elasticsearch 等日志平台,本表用于集中日志库或短期数据库兜底,不建议长期写入租户业务库。
create table s_log_technical (
b_id bigint identity(1, 1) not null, -- 内部主键
b_org_id varchar(50) null, -- 租户/组织标识(日志来源)
b_user_id bigint null, -- 操作用户(逻辑外键)
b_account varchar(50) null, -- 登录名快照
b_sourcetype varchar(20) null, -- 来源类型:web/api
b_loglevel varchar(16) not null, -- 级别:INFO/WARN/ERROR
b_logcategory varchar(64) null, -- 分类:sql/exception/performance
b_operation varchar(100) null, -- 操作名(如 loadData/saveObject)
b_module_code varchar(50) null, -- 相关模块编码
b_request_id varchar(64) null, -- 请求 ID
b_trace_id varchar(64) null, -- 追踪 ID(串联链路)
b_server_node varchar(128) null, -- 服务节点标识
b_duration_ms bigint null, -- 耗时(毫秒)
b_result_code varchar(50) null, -- 结果码
b_error_code varchar(50) null, -- 错误码
b_error_message nvarchar(2000) null, -- 错误信息
b_exception_text nvarchar(max) null, -- 异常堆栈(文本)
b_context_text nvarchar(max) null, -- 上下文信息(文本)
b_occurdatetime datetime2 null default sysdatetime(), -- 发生时间(UTC)
constraint pk_s_log_technical primary key (b_id)
);
create index ix_s_log_technical_trace on s_log_technical (b_trace_id, b_occurdatetime desc);
create index ix_s_log_technical_org on s_log_technical (b_org_id, b_occurdatetime desc);
create index ix_s_log_technical_level on s_log_technical (b_loglevel, b_occurdatetime desc);
3.15 s_log_login 登录日志
记录登录成功、失败、退出、刷新令牌与账号锁定等安全事件,可通过 b_request_id / b_trace_id 与技术日志关联。
create table s_log_login (
b_id bigint identity(1, 1) not null, -- 内部主键
b_org_id varchar(50) null, -- 租户/组织标识
b_user_id bigint null, -- 用户(逻辑外键)
b_account varchar(50) null, -- 登录名
b_login_type varchar(20) not null, -- 登录类型:password/token/oidc/ldap
b_login_result int not null default 0, -- 登录结果:0失败/1成功
b_fail_reason nvarchar(200) null, -- 失败原因
b_ip varchar(50) null, -- 登录 IP
b_ip_location nvarchar(200) null, -- IP 归属地
b_browser varchar(100) null, -- 浏览器
b_browser_version varchar(50) null, -- 浏览器版本
b_os varchar(100) null, -- 操作系统
b_os_version varchar(50) null, -- 系统版本
b_device_type varchar(20) null, -- 设备类型:pc/mobile/tablet
b_user_agent nvarchar(max) null, -- 完整 UA
b_session_id varchar(50) null, -- 会话 ID
b_request_id varchar(64) null, -- 请求 ID
b_trace_id varchar(64) null, -- 追踪 ID
b_occurdatetime datetime2 null default sysdatetime(), -- 登录时间(UTC)
constraint pk_s_log_login primary key (b_id)
);
create index ix_s_log_login_user on s_log_login (b_user_id, b_occurdatetime desc);
create index ix_s_log_login_account on s_log_login (b_account, b_occurdatetime desc);
create index ix_s_log_login_result on s_log_login (b_login_result, b_occurdatetime desc);
3.16 业务审计事件与字段变更
审计事件与字段变更分表存储:事件一条、字段变更多条,通过 b_event_id 关联。字段级明细是面向客户的硬需求(客户可在门户查看某个单号发生过哪些字段变更),字段一旦发生修改即写入明细,不按字段配置开关;b_visibility 控制内部/客户可见。
create table s_log_audit (
b_id bigint identity(1, 1) not null, -- 内部主键
b_event_code varchar(50) not null, -- 业务事件编码(如 booking.submit)
b_module_code varchar(50) null, -- 模块编码
b_entity_code varchar(50) null, -- 实体编码
b_data_id varchar(50) null, -- 被审计的业务数据 ID
b_business_no varchar(50) null, -- 业务单号(如订舱号)
b_operation varchar(100) null, -- 操作 key(如 saveObject.submit)
b_operator_id bigint null, -- 操作人(逻辑外键)
b_operator_name nvarchar(100) null, -- 操作人名称(快照)
b_source_type varchar(20) not null default 'user', -- 来源类型:user/ai/api
b_visibility varchar(20) not null default 'internal', -- 可见性:internal 内部/customer 客户可见
b_request_id varchar(64) null, -- 请求 ID
b_trace_id varchar(64) null, -- 追踪 ID(关联技术日志)
b_payload_text nvarchar(max) null, -- 事件载荷(文本,如前后快照)
b_occurdatetime datetime2 null default sysdatetime(), -- 发生时间(UTC)
constraint pk_s_log_audit primary key (b_id)
);
create index ix_s_log_audit_entity on s_log_audit (b_entity_code, b_data_id, b_occurdatetime desc);
create index ix_s_log_audit_trace on s_log_audit (b_trace_id);
字段级变更明细表 s_log_audit_field:b_event_id 关联审计事件;客户门户查询按单号 + 客户可见过滤。
create table s_log_audit_field (
b_id bigint identity(1, 1) not null, -- 内部主键
b_event_id bigint not null, -- 所属审计事件
b_entity_code varchar(50) null, -- 实体编码
b_data_id varchar(50) null, -- 业务数据 ID
b_field varchar(50) not null, -- 变更字段(物理列名)
b_field_label nvarchar(100) null, -- 字段标签快照
b_before_value nvarchar(max) null, -- 变更前原始值
b_after_value nvarchar(max) null, -- 变更后原始值
b_before_display nvarchar(500) null, -- 变更前显示值(如引用字段 label)
b_after_display nvarchar(500) null, -- 变更后显示值
b_visibility varchar(20) not null default 'internal', -- 可见性:internal 内部/customer 客户可见
constraint pk_s_log_audit_field primary key (b_id),
constraint fk_s_log_audit_field_event foreign key (b_event_id) references s_log_audit (b_id)
);
create index ix_s_log_audit_field_event on s_log_audit_field (b_event_id);
create index ix_s_log_audit_field_customer on s_log_audit_field (b_entity_code, b_data_id, b_visibility, b_event_id);
4. 关键设计决策说明
4.1 为什么模块与实体分离
旧系统把表、视图、路由、菜单、权限全部压在 s_module 一条记录上,导致"一个模块只能配一张主表"、菜单与数据源互相绑架。本方案中模块只回答"这是什么业务能力、依赖谁",实体回答"数据在哪、怎么存",两者可独立演进:一个模块可拥有多个实体(订舱主表 + 集装箱/费用/运输子表),一个实体也可被多个菜单入口引用。
4.2 为什么保留模块树 + 独立依赖 DAG
树形(b_parent_id)表达"业务分组归属"(基础资料 > 海运 > 订舱),是货代业务的真实组织诉求,且操作直观;但树无法表达"finance 依赖 master-data"这类跨分支依赖,所以依赖单独用 s_module_dependency 表达并强制 DAG。树归树、依赖归依赖,语义不再混淆。
4.3 为什么字段合并单表
旧系统字段配置拆 4 张表,字段新增要同步维护 4 处、分组标题另存一份,配置漂移是常态。合并后字段的"定义 + 列表 + 表单 + 查询"行为集中在一行,增删改一处完成;代价是行较宽,但对元数据表(数量级远小于业务表)完全可接受。
4.4 为什么菜单独立且引用实体
导航是展示层概念,数据是业务层概念。s_menu 引用 b_entity_id 而非模块,同一实体可在多个菜单入口出现(如"订舱列表"与"订舱审核"共用 ocean_booking 实体但绑定不同权限)。
4.5 为什么权限引入角色层
旧系统 b_user_power 是用户直挂权限,用户一多授权矩阵爆炸。本方案 s_role + s_user_role + s_role_power 三层,权限点 s_power 支持到 menu/entity/action/field 四级粒度,为后续数据范围、字段脱敏策略留出扩展位。
4.6 自动编码的并发处理
b_currentvalue 若采用"读-改-写"必然在并发下产生重号。方案要求应用层用原子自增语句取号(update ... set b_currentvalue = b_currentvalue + 1 output inserted.b_currentvalue),并把取号与业务保存放同一事务,确保单号不重不漏。
4.7 日志的写入去向
三类日志职责不同、量级不同,不建议全部落租户业务库:
- 技术日志高频(每条请求、SQL、异常),推荐写 Loki/ES 等日志平台,
s_log_technical仅作集中日志库或短期兜底。 - 登录日志与业务审计属于安全与合规数据,低频但必须长期留存,落库保存;审计字段明细在字段发生修改时全量写入,不做按字段开关,数据量按变更频率自然收敛。
- 三类日志统一以
b_request_id/b_trace_id串联,便于从业务审计反查技术日志。
5. 逻辑外键清单
元数据表已建物理外键;以下为业务侧或跨系统引用,由服务层校验:
| 表 | 字段 | 逻辑目标 |
|---|---|---|
s_user_role |
b_user_id |
当前身份系统的用户 b_id |
s_menu |
b_module_id |
s_module.b_id |
s_field |
b_group_id |
s_field_group.b_id |
s_menu |
b_power_code |
s_power.b_code |
s_entity |
b_viewtable / b_savetable |
Schema Compiler 生成的物理表/视图 |
s_log_technical |
b_user_id |
当前身份系统的用户 b_id |
s_log_login |
b_user_id |
当前身份系统的用户 b_id |
s_log_audit |
b_operator_id |
当前身份系统的用户 b_id |
逻辑外键写入时应执行以下校验:
引用是否已启用
-> 目标资源是否存在且类型匹配
-> 跨模块引用是否合法(依赖图中可达)
-> 删除或停用前是否仍被其他资源引用
6. 与旧系统的映射对照
| 旧系统 | 本方案 | 变化 |
|---|---|---|
s_module(含表/路由/菜单/权限) |
s_module + s_entity + s_menu |
职责拆分 |
s_module_field |
s_field |
字段挂实体 |
s_module_field_edit / _list / _query |
并入 s_field 标志列 |
四表合一 |
s_module_field_group |
s_field_group |
分组挂实体 |
s_module_auto_code |
s_autocode |
挂实体+字段,原子取号 |
s_module_power + b_user_power |
s_power + s_role + s_user_role + s_role_power |
引入角色层 |
b_i18n |
s_i18n |
增加资源类型维度 |
s_login_log |
s_log_login |
字段扩充(设备/UA/请求链路) |
| (无) | s_module_dependency |
新增依赖 DAG |
| (无) | s_relation |
新增实体关系 |
| (无) | s_log_technical |
新增技术日志(旧系统无) |
| (无) | s_log_audit + s_log_audit_field |
新增业务审计与字段变更(旧系统无) |