# FMS 新系统模块核心表结构设计 > 本方案沿用旧系统以 `s_module` 为核心的元数据模型,删除独立的 `s_entity`;分类节点、数据模块和查询模块统一由 `s_module` 自关联树表达。 > 平台表以 `s_` 开头,字段以 `b_` 开头,复合词采用驼峰拼接(如 `b_viewtable`、`b_inputdatetime`),逻辑关联字段使用 `b_xxx_id`。 ## 1. 设计原则 本方案坚持五项原则: 1. **沿用模块主体**:`s_module` 继续作为旧系统的数据配置主体,分类节点和数据节点在同一棵树中维护,不再增加 `s_entity`。 2. **模块与字段分离**:`data/query` 模块声明查询源、保存源和主键字段;字段挂模块保存数据库列定义,列表、表单、查询行为由字段配置子表承载。 3. **菜单与模块分离**:`s_menu` 是纯导航树,只引用数据模块或固定页面;本方案只恢复旧系统的模块数据主体,不把菜单、路由和角色授权重新塞回 `s_module`。 4. **字段定义与视图配置分责**:字段定义、列表、表单、查询是四类不同职责;本方案保留 `s_field` 作为字段定义表,并将列表、表单、查询行为分别放入 `s_field_list`、`s_field_edit`、`s_field_query`,避免一张宽表充满互不相关的空字段。 5. **配置入口与存储职责分离**:管理员可从模块树或菜单树定位数据模块,在一个配置工作台内完成数据源、字段、列表、表单、查询、编码和权限配置;底层仍按模块、菜单和权限分别存储。 其余约定: - 内部主键 `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` 节点只查询不保存。菜单、角色和权限仍由独立表维护。 ```sql 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); ``` 模块的查/存配置方式: ```text 分类模块: b_module_type = category, 查/存字段全部为空 数据模块: b_module_type = data, b_viewtable 为查询源, b_savetable 为保存表 查询模块: b_module_type = query, b_viewtable 为查询源, b_savetable 必须为空 ``` 删除策略取值(`b_delete_policy`): ```text restrict 主数据:有引用时拒绝删除,只能停用或合并 lifecycle 业务单据:草稿且无下游时可删,正式后只能取消/作废 cascade 主表拥有的从数据:随主表同一事务级联删除 hard 临时记录:校验权限后直接物理删除 ``` ### 3.2 s_field 字段 只保存字段本身的规范定义,不混入列表、表单和查询布局。字段同步、数据库列校验和业务审计均以本表为准;三类界面行为分别放在后续配置表中。 ```sql 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 字段分组 分组是模块级独立定义,支持分组树。列表和表单可以分别通过自己的配置表引用同一个分组,也可以选择不同分组。 ```sql 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 字段列表配置 保存字段在列表中的显示、顺序、宽度和分组。每个字段最多一条列表配置;未创建配置记录表示该字段不进入列表。 ```sql 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 字段表单配置 保存字段在新增、编辑表单中的布局和交互行为。控件类型属于表单行为,因此放在本表,而不是放在字段物理定义中。 ```sql 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 字段查询配置 查询条件是可选集合,只有需要进入查询区的字段才创建记录。查询组件、操作符和默认查询值独立于字段默认值。 ```sql 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 模块关系 描述模块间的引用与主子关系,运行时据此执行外键校验、主子事务与级联删除。 ```sql 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` 模块,并直接使用该模块的字段、列表、表单和查询配置。 ```sql 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`),避免自增竞争。 ```sql 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 角色 ```sql 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 用户-角色 ```sql 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 权限点 ```sql 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 角色-权限 ```sql 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 多语言 ```sql 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 等日志平台,本表用于集中日志库或短期数据库兜底,不建议长期写入租户业务库。 ```sql 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` 与技术日志关联。 ```sql 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` 控制内部/客户可见。 ```sql 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` 关联审计事件;客户门户查询按单号 + 客户可见过滤。 ```sql 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` | 逻辑外键写入时应执行以下校验: ```text 引用是否已启用 -> 目标资源是否存在且类型匹配 -> 字段、分组和模块关系是否属于正确的数据模块 -> 删除或停用前是否仍被其他资源引用 ``` ## 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` | 新增业务审计与字段变更(旧系统无) |