35 KiB
FMS 新系统模块核心表结构设计
本方案沿用旧系统以
s_module为核心的元数据模型,删除独立的s_entity;分类节点、数据模块和查询模块统一由s_module自关联树表达。 平台表以s_开头,字段以b_开头,复合词采用驼峰拼接(如b_viewtable、b_inputdatetime),逻辑关联字段使用b_xxx_id。
1. 设计原则
本方案坚持五项原则:
- 沿用模块主体:
s_module继续作为旧系统的数据配置主体,分类节点和数据节点在同一棵树中维护,不再增加s_entity。 - 模块与字段分离:
data/query模块声明查询源、保存源和主键字段;字段挂模块保存数据库列定义,列表、表单、查询行为由字段配置子表承载。 - 菜单与模块分离:
s_menu是纯导航树,只引用数据模块或固定页面;本方案只恢复旧系统的模块数据主体,不把菜单、路由和角色授权重新塞回s_module。 - 字段定义与视图配置分责:字段定义、列表、表单、查询是四类不同职责;本方案保留
s_field作为字段定义表,并将列表、表单、查询行为分别放入s_field_list、s_field_edit、s_field_query,避免一张宽表充满互不相关的空字段。 - 配置入口与存储职责分离:管理员可从模块树或菜单树定位数据模块,在一个配置工作台内完成数据源、字段、列表、表单、查询、编码和权限配置;底层仍按模块、菜单和权限分别存储。
其余约定:
- 内部主键
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。 s_module.b_parent_id只表达模块树位置;模块间真实的引用、主从、外键和级联关系由s_relation表达。b_module_type使用category/data/query:分类节点不配置数据源,数据节点可查可存,查询节点只读。- 一个数据模块默认只有一套字段和页面配置;菜单只是模块的导航入口,不额外引入菜单级字段覆盖层。
2. 表清单
| # | 表名 | 说明 |
|---|---|---|
| 1 | s_module |
模块树(分类/数据/查询节点,数据所有者) |
| 2 | s_field |
字段定义(数据库列与通用控件元数据) |
| 3 | s_field_group |
字段分组(模块级) |
| 4 | s_field_list |
字段列表配置(显示、排序、列宽、分组) |
| 5 | s_field_edit |
字段表单配置(显示、排序、分组、必填、只读、禁用) |
| 6 | s_field_query |
字段查询配置(启用、排序、组件、操作符、默认值) |
| 7 | s_relation |
模块关系(引用/主从,驱动外键校验与级联) |
| 8 | s_menu |
菜单/页面导航(data 菜单直接绑定数据模块) |
| 9 | s_autocode |
自动编码规则 |
| 10 | s_role |
角色 |
| 11 | s_user_role |
用户-角色 |
| 12 | s_power |
权限点(menu/module/action/field) |
| 13 | s_role_power |
角色-权限 |
| 14 | s_i18n |
多语言资源 |
| 15 | s_log_technical |
技术日志(请求链路/SQL/异常/性能) |
| 16 | s_log_login |
登录日志(登录/退出/失败/锁定) |
| 17 | s_log_audit |
业务审计事件 |
| 18 | s_log_audit_field |
审计字段变更明细 |
3. 各表结构
3.1 s_module 模块
沿用旧系统核心语义:s_module 既是模块树节点,也是数据配置主体。category 节点只负责分类;data 节点可查询和保存;query 节点只查询不保存。菜单、角色和权限仍由独立表维护。
create table s_module (
b_id bigint identity(1, 1) not null, -- 内部主键
b_code varchar(50) not null, -- 模块编码(全局唯一)
b_parent_id bigint null, -- 父模块(配置树位置)
b_name nvarchar(100) not null, -- 模块名称
b_i18n varchar(150) null, -- 显示名多语言 key
b_module_type varchar(20) not null, -- category/data/query
b_viewtable varchar(128) null, -- 查询数据源(data/query 使用)
b_savetable varchar(128) null, -- 保存目标(data 可用,query 必须为空)
b_keyfield varchar(50) null, -- 主键字段(data/query 使用)
b_orderfield varchar(500) null, -- 默认排序表达式
b_delete_policy varchar(20) null, -- restrict/lifecycle/cascade/hard
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 ck_s_module_type check (b_module_type in ('category', 'data', 'query')),
constraint ck_s_module_parent check (b_parent_id is null or b_parent_id <> b_id),
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, b_id);
create index ix_s_module_savetable on s_module (b_savetable);
模块的查/存配置方式:
分类模块: b_module_type = category, 查/存字段全部为空
数据模块: b_module_type = data, b_viewtable 为查询源, b_savetable 为保存表
查询模块: b_module_type = query, b_viewtable 为查询源, b_savetable 必须为空
删除策略取值(b_delete_policy):
restrict 主数据:有引用时拒绝删除,只能停用或合并
lifecycle 业务单据:草稿且无下游时可删,正式后只能取消/作废
cascade 主表拥有的从数据:随主表同一事务级联删除
hard 临时记录:校验权限后直接物理删除
3.2 s_field 字段
只保存字段本身的规范定义,不混入列表、表单和查询布局。字段同步、数据库列校验和业务审计均以本表为准;三类界面行为分别放在后续配置表中。
create table s_field (
b_id bigint identity(1, 1) not null, -- 内部主键
b_module_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_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_module_field unique (b_module_id, b_field),
constraint fk_s_field_module foreign key (b_module_id) references s_module (b_id)
);
create index ix_s_field_module on s_field (b_module_id, b_canuse, b_id);
字段是否允许用户写入属于表单配置,由 s_field_edit 表达;如果需要声明数据库计算列、系统维护列等全局写入语义,可在本表增加 b_writemode varchar(20)(direct/createOnly/readOnly/computed/actionOnly/system)。
3.3 s_field_group 字段分组
分组是模块级独立定义,支持分组树。列表和表单可以分别通过自己的配置表引用同一个分组,也可以选择不同分组。
create table s_field_group (
b_id bigint identity(1, 1) not null, -- 内部主键
b_module_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_module foreign key (b_module_id) references s_module (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_module on s_field_group (b_module_id, b_xh, b_canuse);
3.4 s_field_list 字段列表配置
保存字段在列表中的显示、顺序、宽度和分组。每个字段最多一条列表配置;未创建配置记录表示该字段不进入列表。
create table s_field_list (
b_id bigint identity(1, 1) not null,
b_field_id bigint not null, -- 字段
b_group_id bigint null, -- 列表分组
b_i18n varchar(150) null, -- 列表标题多语言 key(空则继承字段)
b_visible int not null default 1, -- 是否显示
b_xh int not null default 0, -- 列表排序
b_width int null, -- 列宽(px)
constraint pk_s_field_list primary key (b_id),
constraint ux_s_field_list_field unique (b_field_id),
constraint fk_s_field_list_field foreign key (b_field_id) references s_field (b_id),
constraint fk_s_field_list_group foreign key (b_group_id) references s_field_group (b_id)
);
create index ix_s_field_list_order on s_field_list (b_xh, b_id);
3.5 s_field_edit 字段表单配置
保存字段在新增、编辑表单中的布局和交互行为。控件类型属于表单行为,因此放在本表,而不是放在字段物理定义中。
create table s_field_edit (
b_id bigint identity(1, 1) not null,
b_field_id bigint not null, -- 字段
b_group_id bigint null, -- 表单分组
b_component varchar(30) null, -- input/select/date/datetime/textarea/number/checkbox/radio
b_visible int not null default 1, -- 是否显示
b_required int not null default 0, -- 是否必填
b_readonly int not null default 0, -- 是否只读
b_disabled int not null default 0, -- 是否禁用
b_xh int not null default 0, -- 表单排序
constraint pk_s_field_edit primary key (b_id),
constraint ux_s_field_edit_field unique (b_field_id),
constraint fk_s_field_edit_field foreign key (b_field_id) references s_field (b_id),
constraint fk_s_field_edit_group foreign key (b_group_id) references s_field_group (b_id)
);
create index ix_s_field_edit_order on s_field_edit (b_xh, b_id);
数据库可空性以 s_field.b_nullable 为准,表单必填以 s_field_edit.b_required 为准。元数据校验器应保证:数据库不可空且无数据库默认值的可编辑字段,表单不能配置为非必填。
3.6 s_field_query 字段查询配置
查询条件是可选集合,只有需要进入查询区的字段才创建记录。查询组件、操作符和默认查询值独立于字段默认值。
create table s_field_query (
b_id bigint identity(1, 1) not null,
b_field_id bigint not null, -- 字段
b_component varchar(30) not null, -- input/select/date-picker/number-range/radio-group
b_operator varchar(20) not null, -- eq/like/gt/ge/lt/le/in/between
b_default_value nvarchar(500) null, -- 默认查询值
b_canuse int not null default 1, -- 是否启用
b_xh int not null default 0, -- 查询条件排序
constraint pk_s_field_query primary key (b_id),
constraint ux_s_field_query_field unique (b_field_id),
constraint fk_s_field_query_field foreign key (b_field_id) references s_field (b_id)
);
create index ix_s_field_query_order on s_field_query (b_canuse, b_xh, b_id);
3.7 s_relation 模块关系
描述模块间的引用与主子关系,运行时据此执行外键校验、主子事务与级联删除。
create table s_relation (
b_id bigint identity(1, 1) not null, -- 内部主键
b_module_id bigint not null, -- 主模块
b_target_module_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_module foreign key (b_module_id) references s_module (b_id),
constraint fk_s_relation_target_module foreign key (b_target_module_id) references s_module (b_id)
);
create index ix_s_relation_target on s_relation (b_target_module_id);
3.8 s_menu 菜单/页面
菜单是独立导航树,不保存字段、列表或表单配置。data 菜单通过 b_module_id 绑定一个 data/query 模块,并直接使用该模块的字段、列表、表单和查询配置。
create table s_menu (
b_id bigint identity(1, 1) not null, -- 内部主键
b_parent_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_module_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)
);
create index ix_s_menu_parent on s_menu (b_parent_id, b_xh, b_canuse);
create index ix_s_menu_module on s_menu (b_module_id);
菜单类型约束由元数据校验器执行:directory 不填业务路由;page 必须填 b_route;data 必须填 b_module_id;external 必须填 b_route 和 b_power_code。目录仅在存在至少一个可访问子页面时显示。
配置工作台按以下顺序呈现:页面基本信息 → 绑定模块 → 字段与分组 → 列表 → 表单 → 查询 → 关联模块 → 自动编码 → 权限。保存时由服务层校验菜单类型、模块类型和模块关系。
没有独立菜单的子模块、费用明细、选择器和公共字典,通过主模块的 s_relation 挂在所属数据模块下;它们不需要重复创建菜单,也可以在同一个配置工作台中维护。
3.9 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_module_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_module_field unique (b_module_id, b_field),
constraint fk_s_autocode_module foreign key (b_module_id) references s_module (b_id)
);
3.10 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.11 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.12 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/module/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.13 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.14 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/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.15 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.16 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.17 业务审计事件与字段变更
审计事件与字段变更分表存储:事件一条、字段变更多条,通过 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_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_module on s_log_audit (b_module_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_module_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_module_code, b_data_id, b_visibility, b_event_id);
4. 关键设计决策说明
4.1 为什么保留 s_module 并删除 s_entity
旧系统已经以 s_module 作为配置、查询和保存的核心主体,现有接口、字段表和迁移工具也普遍使用 b_module_id。继续拆出 s_entity 会增加概念、外键和迁移成本,却没有形成足够独立的业务职责。因此新方案保留 s_module 自关联树,由 category/data/query 区分分类节点和数据节点;菜单和权限仍保持独立,避免恢复旧系统全部职责混杂的问题。
4.2 为什么不设置模块依赖表
当前 s_module 不是可独立安装、发布的软件包,因此不额外维护模块依赖 DAG。数据模块之间的引用和主从关系由 s_relation 表达,数据库对象依赖由实际表、视图和 Schema Compiler 校验,部署依赖由构建或插件系统管理。
4.3 为什么字段定义与页面行为分表
旧系统的字段、列表、编辑、查询虽然都挂在 b_module_id 下,但运行时和管理端都把它们当作四类独立配置。列表与表单的显示、排序、分组并不相同,查询条件也只覆盖部分字段。强行并入 s_field 会形成宽表,并混淆数据库列定义与页面行为。因此本方案保留四张职责明确的表,但统一改为通过 b_field_id 关联字段,字段归属再由 s_field.b_module_id 确定。
4.4 为什么菜单独立且引用模块
导航是展示层概念,数据模块是业务数据概念。s_menu 引用 b_module_id,不重复保存数据源;同一数据模块可以被多个菜单入口引用,但默认共享模块上的同一套字段、列表、表单和查询配置。
4.5 为什么暂不增加页面配置档
旧系统实际以一个 data 模块承载一套字段、列表、编辑和查询配置,没有菜单级字段覆盖机制。当前若再增加 s_page_profile、s_page_field,会与字段配置表重复,并引入菜单、配置档、模块三者的一致性维护成本。因此第一阶段规定一个数据模块只有一套页面配置。
4.6 为什么权限引入角色层
旧系统 b_user_power 是用户直挂权限,用户一多授权矩阵爆炸。本方案 s_role + s_user_role + s_role_power 三层,权限点 s_power 支持到 menu/module/action/field 四级粒度,为后续数据范围、字段脱敏策略留出扩展位。
4.7 自动编码的并发处理
b_currentvalue 若采用"读-改-写"必然在并发下产生重号。方案要求应用层用原子自增语句取号(update ... set b_currentvalue = b_currentvalue + 1 output inserted.b_currentvalue),并把取号与业务保存放同一事务,确保单号不重不漏。
4.8 日志的写入去向
三类日志职责不同、量级不同,不建议全部落租户业务库:
- 技术日志高频(每条请求、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_list |
b_group_id |
分组必须与 b_field_id 所属字段位于同一数据模块 |
s_field_edit |
b_group_id |
分组必须与 b_field_id 所属字段位于同一数据模块 |
s_menu |
b_power_code |
s_power.b_code |
s_module |
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_menu |
保留模块数据主体,拆出导航与角色权限 |
s_module_field |
s_field |
字段挂模块 |
s_module_field_list |
s_field_list |
改为通过字段关联模块 |
s_module_field_edit |
s_field_edit |
改为通过字段关联模块 |
s_module_field_query |
s_field_query |
改为通过字段关联模块 |
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_relation |
新增模块关系 |
| (无) | s_log_technical |
新增技术日志(旧系统无) |
| (无) | s_log_audit + s_log_audit_field |
新增业务审计与字段变更(旧系统无) |