Files
workspace/code/fms/FMS新系统核心表结构设计.md
T
2026-08-20 22:38:37 +08:00

592 lines
35 KiB
Markdown
Raw 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 新系统模块核心表结构设计
> 本方案沿用旧系统以 `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` | 新增业务审计与字段变更(旧系统无) |