Files
workspace/code/g3soft-libs/docs/content/backend/schema.md
T
2026-10-13 22:32:50 +08:00

792 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.
---
title: 数据表结构
description: 核心元数据表、组织与权限表、日志表、工作流表的完整字段说明
---
# 数据表结构
本文记录 `G3ERP` 库的结构与约定,**字段以真库为准**。
按前缀分四类,本文**只写系统级表**(业务表详见各自业务域):
| 前缀 | 类别 | 本文是否覆盖 |
|---|---|---|
| `s_` | 系统 / 元数据 / 权限 / 日志 | ✅ 全部 20 张 |
| `wf_` | 工作流 | ✅ 全部 13 张 |
| `bf_` | 文件附件 | ✅ 2 张 |
| `b_` | 业务与主数据 | 只写组织用户等基础表;**业务字典表不写** |
| `v_` | 视图 | 查询入口,见文末 |
## 1. 命名与字段约定
### 1.1 表名
- 统一小写 `snake_case`,下划线分词。
- 系统表 `s_`;业务与主数据 `b_`;业务域表用明确的域前缀(如 `bf_files`);工作流 `wf_`(属业务域前缀,**不用 `s_`**)。
- 表名表达稳定的业务含义,**不用页面名或路由名当表名**。
### 1.2 字段命名
| 用途 | 约定 |
|---|---|
| 主键 | 业务编码主键 `b_id`;关系表用联合主键 |
| 关联 | `<对象>_id`,如 `b_module_id`、`b_dept_id` |
| 排序 | `b_xh`,默认 `0` |
| 启用 | `b_canuse`,`0/1`,默认 `1` |
| 备注 | `b_bz` |
| 名称 | `b_name` |
| 多语言 | `b_i18n`(指向 `s_i18n.b_key`) |
| 主表字段 | `b_` 前缀 |
| 明细 / 子表字段 | `mx_` 前缀 |
### 1.3 类型选择
| 用途 | 类型 |
|---|---|
| 名称、备注、多语言文本 | `nvarchar` |
| 编码、状态、类型、SQL 标识 | `varchar` |
| 日期时间 | **`datetime2`** |
| 启用、顺序标记 | `tinyint`(0/1) |
| 数量、金额 | 按业务精度设 `precision` / `scale` |
### 1.4 审计字段
| 字段 | 含义 |
|---|---|
| `b_created_by` / `b_created_at` | 创建人(用户编码)/ 创建时间 |
| `b_updated_by` / `b_updated_at` | 最后修改人 / 最后修改时间 |
**不建触发器**,由写入方维护(通用保存在服务端统一填,前端不能指定)。
审计字段**不是所有表都必须有**:客户维护、需追溯变更的业务表才需要;系统内部关联表、字典表可省略。**授权类表例外**——`s_user_power` / `s_user_field_power` / `s_user_data_power` 由用户在授权界面操作,需追溯授权变更人,因此保留审计字段。`s_power` 是开发期维护的系统元数据,不设审计字段。
> 这些字段只表示**系统层面**的创建/修改信息,**不替代业务字段**里的录入人、审核人、负责人。
### 1.5 约束
- **不依赖数据库外键、`CHECK` 约束和触发器**保证业务规则,合法性由业务层校验。
- 状态、类型、动作的取值写在**列注释**里,不用 `CHECK` 枚举——加值不需要改表。
- 关联完整性与删除规则由业务层处理,删除策略见 `s_relation.b_on_delete` 与 `s_delete_rule`。
## 2. 键与 ID 策略
| 场景 | 策略 | 示例 |
|---|---|---|
| 配置表(模块、菜单、权限、流程定义) | `varchar` **业务编码主键**(人可读、跨环境稳定) | `s_module.b_id`、`wf_definition.b_id` |
| 业务数据 | 应用层生成的 **bigint 雪花 ID** | `bf_files.subid`、业务表 `b_id` |
| 工作流运行对象 | **UUIDv7**(`uniqueidentifier`,按时间有序) | `wf_instance.b_id`、`wf_task.b_id` |
| 日志 | **UUIDv7** | `s_log_audit.b_id` |
**跨表引用业务主键时统一按字符串保存**(如 `wf_instance.b_business_id`、`s_log_audit.b_data_id`),不依赖业务表物理主键类型——业务表可能是 `bigint`,老表可能是 `varchar`。
## 3. 模块元数据
系统以「模块」为配置单元,五张表配合完成查询与界面配置。
### 3.1 `s_module` — 模块定义
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `varchar(50)` PK | 模块编码 |
| `b_parent_id` | `varchar(50)` | 父模块,NULL 为根 |
| `b_depth` | `int` 默认 0 | 树层级,根为 0 |
| `b_path` | `varchar(1000)` | 根到当前节点路径,如 `/sea/container/` |
| `b_name` | `nvarchar(200)` | 模块名称 |
| `b_i18n` | `varchar(150)` | 多语言资源键 |
| `b_module_type` | `varchar(20)` | `module` / `data` / `virtual` |
| `b_view_table` | `varchar(128)` | 默认查询表或视图 |
| `b_save_table` | `varchar(128)` | 默认保存目标表 |
| `b_key_field` | `varchar(50)` | 主键字段 |
| `b_scope_field` | `varchar(50)` | **数据范围判定字段**(记录归属人字段) |
| `b_order_sql` | `varchar(500)` | 默认排序 SQL |
| `b_query_sql` | `nvarchar(max)` | 可选查询 SQL 或模板 |
| `b_config_json` | `nvarchar(max)` | 低频扩展配置 |
| `b_canuse` | `tinyint` 默认 1 | 是否启用 |
| `b_xh` | `int` 默认 0 | 同级显示顺序 |
| `b_bz` | `nvarchar(2000)` | 备注 |
索引:`ix_s_module_parent(b_parent_id, b_canuse, b_depth, b_xh, b_id)`、`ix_s_module_path(b_path, b_canuse, b_id)`、`ix_s_module_type(b_module_type, b_canuse, b_xh, b_id)`、`ix_s_module_view_table`、`ix_s_module_save_table`。
**模块类型**:
| 取值 | 含义 |
|---|---|
| `module` | 业务分组(海运、财务…),默认不配字段与表 |
| `data` | 具体数据对象,必须配 `b_view_table` 或 `b_query_sql` |
| `virtual` | 不适合标准表表达的能力,由 SQL 或业务处理器实现 |
> **`b_scope_field` 是数据范围权限的唯一判定依据**,配在模块上而非授权行上:同一模块的所有用户必须用同一字段判范围(如 `b_inputuser_id`)。
### 3.2 `s_field` — 模块字段
| 列 | 类型 | 说明 |
|---|---|---|
| `b_module_id` | `varchar(50)` PK | 所属模块 |
| `b_field` | `varchar(50)` PK | 字段编码 / 查询别名 |
| `b_name` | `nvarchar(200)` | 字段名称 |
| `b_i18n` | `varchar(150)` | 多语言资源键 |
| `b_type` | `varchar(30)` | 业务类型 |
| `b_canuse` | `tinyint` 默认 1 | 是否启用 |
| `b_xh` | `int` 默认 0 | 默认顺序 |
主键 `(b_module_id, b_field)`;索引 `ix_s_field_module_order(b_module_id, b_canuse, b_xh, b_field)`。
> **`s_field` 不保存数据库类型、长度、精度**——这些由后端在运行时直接读表/视图结构(`MetadataReader`)。字段同步按来源结构对齐,但**不覆盖人工维护的名称与业务类型**。
> `b_type` 的取值集合由业务层维护,**本设计不做固定枚举**。
### 3.3 `s_module_schema` — 界面配置
列表 / 表单 / 查询配置统一存 JSON,**不再各建一张表**。
| 列 | 类型 | 说明 |
|---|---|---|
| `b_module_id` | `varchar(50)` PK | 模块编码 |
| `b_schema_type` | `varchar(20)` PK | `view` / `edit` / `query` |
| `b_schema_json` | `nvarchar(max)` | 完整 JSON 配置 |
| `b_canuse` | `tinyint` 默认 1 | 是否启用 |
| `b_updated_by` | `varchar(50)` | 最后修改人 |
| `b_updated_at` | `datetime2` | 最后修改时间 |
主键 `(b_module_id, b_schema_type)`。JSON 顶层含 `schemaVersion` 便于结构升级。
### 3.4 `s_relation` — 模块关系
| 列 | 类型 | 说明 |
|---|---|---|
| `b_source_module_id` | `varchar(50)` PK | 源模块 |
| `b_source_field` | `varchar(50)` PK | 源字段 |
| `b_target_module_id` | `varchar(50)` PK | 目标模块 |
| `b_target_field` | `varchar(50)` PK | 目标字段 |
| `b_relation_type` | `varchar(20)` | `one_to_one` / `one_to_many` / `many_to_one` |
| `b_on_delete` | `varchar(20)` 默认 `restrict` | 删除策略:`restrict` / `cascade` / `set_null` |
| `b_canuse` / `b_xh` | — | 启用 / 顺序 |
索引:`ix_s_relation_source`、`ix_s_relation_target`。
### 3.5 `s_autocode` — 自动编码
| 列 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `b_module_id` | `varchar(50)` PK | — | 模块编码 |
| `b_field` | `varchar(50)` PK | — | 字段编码 |
| `b_prefix` | `nvarchar(200)` | `''` | 前缀 |
| `b_dateformat` | `varchar(30)` | `yyyyMM` | 日期段格式 |
| `b_separator` | `nvarchar(20)` | `-` | 分隔符 |
| `b_seqwidth` | `int` | 5 | 流水号位数 |
| `b_resettype` | `varchar(10)` | `month` | 重置周期 |
| `b_startvalue` | `bigint` | 1 | 起始值 |
| `b_currentperiod` | `varchar(20)` | — | 当前周期 |
| `b_currentvalue` | `bigint` | 0 | 当前值 |
| `b_canuse` | `tinyint` | 1 | 是否启用 |
### 3.6 `s_menu` / `s_menu_module`
菜单树与模块挂载关系。
`s_menu`:`b_id`(PK) / `b_parent_id` / `b_depth` / `b_path` / `b_name` / `b_i18n` / `b_menu_type` / `b_route`(路由) / `b_icon` / `b_xh` / `b_canuse`。
`s_menu_module`:`b_menu_id` + `b_module_id`(联合 PK) / `b_xh` / `b_canuse`——把菜单挂到一个或多个模块上。
索引:`ix_s_menu_parent`、`ix_s_menu_path`、`ix_s_menu_route`、`ix_s_menu_module_module`。
### 3.7 多语言
| 表 | 结构 |
|---|---|
| `s_i18n` | `b_key` + `b_locale`(联合 PK) / `b_value nvarchar(1000)` / `b_canuse` |
| `s_i18n_type` | `b_id`(PK, 如 `zh-CN`) / `b_name` / `b_canuse` / `b_default`(是否默认语言) / `b_xh` |
### 3.8 `s_user_module_pref` — 用户个性化
用户对模块界面的个人偏好(列宽、顺序等),覆盖模块默认配置。
`b_user_id` + `b_module_id` + `b_schema_type`(联合 PK) / `b_schema_json` / `b_updated_by` / `b_updated_at`。
### 3.9 `s_delete_rule` — 删除规则
| 列 | 说明 |
|---|---|
| `b_id` | `bigint` 主键 |
| `b_module_id` | 模块编码 |
| `b_sql` | 删除前检查/处理的 SQL |
| `b_message` | 不满足条件时给用户的提示 |
| `b_canuse` / `b_xh` | 启用 / 顺序 |
| `b_created_by` / `b_created_at` / `b_updated_by` / `b_updated_at` | 审计字段 |
## 4. 组织与用户
| 表 | 主键 | 说明 |
|---|---|---|
| `b_user` | `b_id varchar(50)` | 用户账号 |
| `b_dept` | `b_id varchar(50)` | 部门树 |
| `b_position` | `b_id varchar(50)` | 岗位 |
| `b_org_role` | `b_id varchar(50)` | 组织角色 |
| `b_user_position` | `b_user_id` + `b_position_id` | 用户-岗位关联 |
| `b_user_org_role` | `b_user_id` + `b_role_id` | 用户-组织角色关联 |
**两个对工作流审批人解析至关重要的字段**:
| 字段 | 含义 | 用于 |
|---|---|---|
| `b_dept.b_manager_id` | 部门负责人(用户编码) | `dept_manager` 规则 |
| `b_user.b_leader_id` | 用户的直属领导(用户编码) | `initiator_manager` 规则 |
部门树用 `b_parent_id` 作权威关系,`b_depth` / `b_path` 是派生字段;`b_path` 的节点编码前缀用于查下级部门(数据范围 `dept_tree` 依赖)。
关联表都有 `b_canuse` 与审计字段,并建了反向索引(如 `ix_b_user_position_position`)。
## 5. 权限
权限只保留四张表,分别回答四个问题:
### 5.1 `s_power` — 有哪些权限点
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `varchar(150)` PK | 权限点编码,如 `action.{模块}.{动作}` |
| `b_name` | `nvarchar(200)` | 权限点名称 |
| `b_type` | `varchar(20)` | 权限点类型 |
| `b_object_type` | `varchar(20)` | 对象类型,动作权限为 `module` |
| `b_object_id` | `varchar(50)` | 对象编码(模块编码) |
| `b_action` | `varchar(30)` | 动作编码,如 `submit` / `approve` / `export` |
| `b_canuse` / `b_xh` / `b_bz` / `b_i18n` | — | — |
**唯一索引 `ux_s_power_object_action(b_object_type, b_object_id, b_action)`** —— 保证同一对象同一动作只有一个权限点。
另有 `ix_s_power_object`、`ix_s_power_type`。
> `s_power` **不设审计字段**(开发期维护的系统元数据)。
### 5.2 `s_user_power` — 用户有哪些权限点
| 列 | 说明 |
|---|---|
| `b_user_id` + `b_power_id` | 联合主键 |
| `b_canuse` | 是否启用 |
| `b_created_by` / `b_created_at` / `b_updated_by` / `b_updated_at` | 审计字段(授权变更需可追溯) |
索引 `ix_s_user_power_power(b_power_id, b_user_id)`。
### 5.3 `s_user_field_power` — 字段级权限
| 列 | 默认 | 说明 |
|---|---|---|
| `b_user_id` + `b_module_id` + `b_field_id` | 联合 PK | — |
| `b_view` | 1 | 是否可见 |
| `b_edit` | 0 | 是否可编辑 |
| `b_query` | 1 | 是否可作为查询条件 |
| `b_export` | 1 | 是否可导出 |
| `b_canuse` | 1 | 是否启用 |
| `b_created_by` / `b_created_at` / `b_updated_by` / `b_updated_at` | — | 审计字段 |
> **无记录按模块默认**(可看可导出)。`b_view=0` 或 `b_export=0` 的列会在导出时**被移除**。
### 5.4 `s_user_data_power` — 数据范围
| 列 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `b_user_id` | `varchar(50)` PK | — | 用户编码 |
| `b_module_id` | `varchar(50)` PK | — | 模块编码 |
| `b_operation` | `varchar(30)` PK | `*` | 操作;`*` 表示适用于所有操作 |
| `b_xh` | `int` PK | 0 | 同组内的排序 / 区分序号 |
| `b_scope_type` | `varchar(20)` | — | 范围类型,见下 |
| `b_scope_field` | `varchar(50)` | — | 判定字段(**实际取值以模块配置 `s_module.b_scope_field` 为准**) |
| `b_condition_sql` | `nvarchar(max)` | — | `custom` 类型的条件片段 |
| `b_canuse` | `tinyint` | 1 | 是否启用 |
| 审计字段 | — | — | `b_created_*` / `b_updated_*` |
主键 `(b_user_id, b_module_id, b_operation, b_xh)`;索引 `ix_s_user_data_power_module`。
| `b_scope_type` | 含义 |
|---|---|
| `all` 或空 | 全部数据,不追加条件 |
| `self` | 只本人:判定字段 = 当前用户 |
| `dept` | 只本部门:判定字段 = 当前用户所属部门 |
| `dept_tree` | 本部门及下级:按 `b_dept.b_path` 前缀匹配 |
| `custom` | 自定义:使用 `b_condition_sql` |
**判定规则**:
- 按操作**精确匹配优先**——命中精确规则就**不再叠加** `'*'` 规则;没有精确规则时才用 `'*'`。
- 多条规则之间 **OR**,整体再与其它条件 **AND**。
- 判定字段取自**模块配置**而不是授权行:同一模块的所有用户必须用同一字段判范围。
- 未归属部门时 `dept` / `dept_tree` 规则**不生效**(不报错,等于不追加条件)。
- `custom` 的 `b_condition_sql` 只由管理员配置,运行时仅替换 `{userId}` / `{deptId}` 受控占位符,并做只读校验(禁分号、禁注释、禁写关键字)。
## 6. 日志
三张日志表都以 **UUIDv7** 为主键,都建了按时间与业务维度的索引。
### 6.1 `s_log_audit` — 数据变更审计
| 列 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `b_id` | `uniqueidentifier` PK | — | UUIDv7 |
| `b_event_code` | `varchar(50)` | — | 事件编码 |
| `b_module_id` | `varchar(50)` | — | 模块编码 |
| `b_data_id` | `varchar(50)` | — | **业务主键(统一字符串保存)** |
| `b_business_no` | `varchar(50)` | — | 业务单号 |
| `b_operation` | `varchar(100)` | — | 操作名(如「审批状态投影」) |
| `b_operator_id` | `varchar(50)` | — | 操作人 |
| `b_operator_name` | `nvarchar(200)` | — | 操作人姓名 |
| `b_source_type` | `varchar(20)` | `user` | 来源类型(`user` / `system`) |
| `b_visibility` | `varchar(20)` | `internal` | 可见性 |
| `b_request_id` / `b_trace_id` | `varchar(64)` | — | 请求 / 链路 ID |
| `b_payload_text` | `nvarchar(max)` | — | 事件载荷 |
| `b_occurdatetime` | `datetime2` | `getdate()` | 发生时间 |
索引:`ix_s_log_audit_module(b_module_id, b_data_id, b_occurdatetime)`、`ix_s_log_audit_occur(b_occurdatetime, b_id)`。
### 6.2 `s_log_audit_field` — 字段级前后值
| 列 | 说明 |
|---|---|
| `b_id` | PK |
| `b_event_id` | **关联 `s_log_audit.b_id`** |
| `b_module_id` / `b_data_id` | 冗余存储,便于直接按数据查 |
| `b_field` | 字段编码 |
| `b_field_label` | 字段名称 |
| `b_before_value` / `b_after_value` | **原始值** |
| `b_before_display` / `b_after_display` | **展示值**(如枚举翻译后的文本) |
| `b_visibility` | 可见性 |
索引 `ix_s_log_audit_field_event(b_event_id, b_id)`。
> **原始值与展示值分开存**:原始值用于比对与回放,展示值用于界面直接呈现,避免查日志时再翻译一遍。
### 6.3 `s_log_login` — 登录日志
| 列 | 说明 |
|---|---|
| `b_id` | PK(UUIDv7) |
| `b_user_id` | 用户账号(**失败时也可能有值**) |
| `b_login_type` | 登录方式 |
| `b_login_result` | `tinyint`,0 失败 / 1 成功 |
| `b_fail_reason` | 失败原因(「账号不存在」/「密码错误」) |
| `b_ip` / `b_ip_location` | 来源 IP 与归属地 |
| `b_browser` / `b_browser_version` | 浏览器 |
| `b_os` / `b_os_version` | 操作系统 |
| `b_device_type` | 设备类型 |
| `b_user_agent` | 原始 UA |
| `b_session_id` | 会话 ID(成功时) |
| `b_request_id` / `b_trace_id` | 请求 / 链路 ID |
| `b_occurdatetime` | 发生时间 |
索引:`ix_s_log_login_occur`、`ix_s_log_login_user(b_user_id, b_occurdatetime)`。
> **对外提示不区分「账号不存在」与「密码错误」(都是 401 同一句),但日志里分别记录** —— 既能防账号探测,又能支撑排查。
### 6.4 `s_log_technical` — 技术日志
| 列 | 说明 |
|---|---|
| `b_id` | PK(UUIDv7) |
| `b_user_id` | 用户编码 |
| `b_sourcetype` | 来源类型 |
| `b_loglevel` | 日志级别 |
| `b_logcategory` | 日志分类 |
| `b_operation` | 操作名 |
| `b_module_id` | 模块编码 |
| `b_request_id` / `b_trace_id` | 请求 / 链路 ID |
| `b_server_node` | 服务节点 |
| `b_duration_ms` | 耗时(毫秒) |
| `b_result_code` | 结果码 |
| `b_error_code` / `b_error_message` | 错误码与说明 |
| `b_exception_text` | 异常堆栈 |
| `b_context_text` | 上下文 |
| `b_occurdatetime` | 发生时间 |
索引:`ix_s_log_technical_occur`、`ix_s_log_technical_trace(b_trace_id, b_occurdatetime)`——按链路 ID 串起一次请求的全部日志。
## 7. 附件
### 7.1 `bf_files` — 文件记录
| 列 | 类型 | 说明 |
|---|---|---|
| `subid` | `bigint` PK | 雪花 ID(**应用层取号,非自增**) |
| `father` | `bigint` | 宿主业务主键,**不建外键** |
| `mx_filename` | `nvarchar(500)` | 原始文件名 |
| `mx_filesize` | `varchar(50)` | 文件大小 |
| `mx_fileext` | `varchar(50)` | 扩展名 |
| `mx_mapfilename` | `nvarchar(500)` | **访问地址**(预览/下载直接用) |
| `mx_moduleid` | `varchar(50)` | 模块编码 |
| `mx_cate_id` | `bigint` | 分类 ID(关联 `bf_files_category.b_id`) |
| `mx_xh` / `mx_sh` | `int` / `tinyint` | 顺序 / 审核标记(保留,当前未用) |
| `mx_inputuser_id` / `mx_inputdatetime` | — | 录入人 / 录入时间 |
| `mx_shuser_id` / `mx_shdatetime` | — | 审核人 / 审核时间(保留) |
| `mx_cutfilename` / `mx_cutfilesize` | — | 缩略图文件名 / 大小(保留) |
| `mx_updateuser_id` / `mx_updatedatetime` | — | 最后修改人 / 时间(保留) |
| `mx_orderid` / `mx_bz` | — | 排序 / 备注(保留) |
索引 `ix_bf_files_host(father, mx_moduleid, mx_cate_id, mx_xh, subid)` —— 列表查询按宿主 + 模块 + 分类过滤。
> **旧库遗留字段** `mx_sh*` / `mx_cut*` / `mx_orderid` / `mx_bz` / `mx_update*` 保留以兼容历史数据迁移,**新代码不使用**(写入 0 或空值)。但 `subid` / `father` / `mx_*` 的列名已被前后端实现引用,保持现状。
### 7.2 `bf_files_category` — 文件分类
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `bigint` PK | 雪花 ID(与 `bf_files.mx_cate_id` 类型一致) |
| `b_module_id` | `varchar(50)` | 所属模块;**为空表示跨模块通用分类** |
| `b_name` | `nvarchar(50)` | 分类名称 |
| `b_i18n` | `varchar(150)` | 多语言资源键 |
| `b_files_type` | `varchar(50)` | 文件类型 |
| `b_xh` / `b_canuse` | `int` / `tinyint` | 顺序 / 启用 |
| `b_bz` | `nvarchar(200)` | 备注 |
| 审计字段 | — | `b_created_*` / `b_updated_*` |
索引 `ix_bf_files_category_module(b_module_id, b_canuse, b_xh, b_id)`。
分类树按当前模块过滤:`b_module_id IS NULL OR b_module_id = 当前模块`。
## 8. 工作流
工作流是**独立子系统**:配置表用 `varchar` 业务编码主键,运行表用 **UUIDv7**。全部 13 张表。
```text
配置:wf_definition → wf_version → { wf_node, wf_edge, wf_actor_rule }
wf_binding(模块+事件 → 版本)
运行:wf_instance → wf_node_run → wf_task → wf_action
wf_cc(抄送) wf_delegation(委托) wf_outbox(通知)
```
### 8.1 `wf_definition` — 流程定义
稳定逻辑身份,**执行内容在版本表**。
`b_id varchar(50)` PK(编码,**创建后不可修改**)/ `b_name nvarchar(200)` / `b_i18n` / `b_canuse` / `b_bz` / 审计字段。
索引:`ix_wf_definition_canuse(b_canuse, b_id)`。
### 8.2 `wf_version` — 流程版本
| 列 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `b_id` | `varchar(50)` PK | — | 版本 ID |
| `b_definition_id` | `varchar(50)` | — | 所属定义 |
| `b_version_no` | `int` | — | 版本号(同流程内递增) |
| `b_status` | `varchar(20)` | `draft` | `draft` / `published` / `retired` |
| `b_schema_version` | `int` | 1 | 配置结构版本 |
| `b_canvas_json` | `nvarchar(max)` | — | 设计器布局,**不参与执行** |
| `b_checksum` | `varchar(128)` | — | 发布时计算的配置摘要 |
| `b_published_by` / `b_published_at` | — | — | 发布人与时间 |
**四个索引,其中两个是筛选唯一索引**:
| 索引 | 作用 |
|---|---|
| `ux_wf_version_draft(b_definition_id) WHERE b_status='draft'` | **同一定义只能有一条草稿** |
| `ux_wf_version_no(b_definition_id, b_version_no)` | 版本号不重复 |
| `ix_wf_version_status` | 按状态查询 |
> **已发布版本不可原地修改**;运行中的实例固定引用提交时的 `b_version_id`。
### 8.3 `wf_node` — 节点
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `varchar(50)` PK | 节点 ID |
| `b_version_id` | `varchar(50)` | 所属版本 |
| `b_node_key` | `varchar(80)` | **版本内稳定编码**(历史记录引用它) |
| `b_name` / `b_i18n` | — | 名称 / 多语言键 |
| `b_node_type` | `varchar(30)` | 见下 |
| `b_approval_mode` | `varchar(30)` | 见下 |
| `b_reject_mode` | `varchar(30)` | 见下 |
| `b_due_hours` | `decimal(10,2)` | **审批时限(小时)**;不配则该节点任务永不过期 |
| `b_config_json` | `nvarchar(max)` | 扩展配置(`requiredCount` / `requiredRatio` / `formPolicy` 等) |
| `b_xh` / `b_canuse` | — | 顺序 / 启用 |
| 列 | 取值 |
|---|---|
| `b_node_type` | `start` / `user_task` / `cc_task` / `service_task` / `exclusive_gateway` / `parallel_gateway` / `end` |
| `b_approval_mode` | `single` / `any` / `all` / `serial` / `n_of_m` / `percentage` |
| `b_reject_mode` | `terminate` / `return_initiator` / `return_previous` |
唯一索引 `ux_wf_node_key(b_version_id, b_node_key)`——**同一版本内节点编码唯一**。
### 8.4 `wf_edge` — 连线
| 列 | 说明 |
|---|---|
| `b_id` | 连线 ID(`varchar(50)` PK) |
| `b_from_node_key` / `b_to_node_key` | 两端节点编码 |
| `b_condition_json` | 条件 AST,**留空 = 无条件(默认边)** |
| `b_priority` | 判断优先级,**小的先判断** |
| `b_name` | 连线名称(画布标签) |
| `b_canuse` / `b_bz` | — |
索引:`ix_wf_edge_from`、`ix_wf_edge_to`(都含 `b_canuse, b_priority`)。
### 8.5 `wf_actor_rule` — 审批人规则
| 列 | 说明 |
|---|---|
| `b_id` | 规则 ID |
| `b_node_key` | 适用节点 |
| `b_actor_type` | 解析类型,见下 |
| `b_actor_ref` | 用户 / 岗位 / 角色 / 字段编码 |
| `b_resolve_json` | 解析参数 |
| `b_condition_json` | **规则适用条件 AST** |
| `b_fallback_type` | 解析为空时的兜底:`none` / `admin` / `initiator` / `custom` |
| `b_fallback_ref` | 兜底引用 |
| `b_priority` | 优先级——**串行会签的顺序来源** |
| `b_canuse` / `b_bz` | — |
| `b_actor_type` | 含义 | 依赖字段 |
|---|---|---|
| `user` | 指定用户 | `b_actor_ref` |
| `initiator` | 发起人 | — |
| `initiator_manager` | 发起人直属领导 | **`b_user.b_leader_id`** |
| `dept_manager` | 部门负责人 | **`b_dept.b_manager_id`** |
| `position` | 岗位 | `b_user_position` |
| `org_role` | 组织角色 | `b_user_org_role` |
| `field_user` | 业务字段里的用户 | `b_actor_ref` 指定的字段 |
| `custom` | 自定义解析器 | — |
索引 `ix_wf_actor_rule_node(b_version_id, b_node_key, b_canuse, b_priority, b_actor_type)`。
> **规则顺序即优先级顺序,也是串行会签的先后**。组织主数据(岗位 / 角色表)不存在时对应类型会降级为空并记原因,由 `b_fallback_type` 兜底。
### 8.6 `wf_binding` — 模块绑定
决定「哪个模块的哪个业务事件走哪个流程版本」。
| 列 | 说明 |
|---|---|
| `b_module_id` | 模块编码(`data` / `virtual` 模块) |
| `b_event` | `submit` / `resubmit` / `change` |
| `b_version_id` | **必须是已发布版本** |
| `b_match_json` | 业务匹配条件 AST |
| `b_org_scope_json` | 公司 / 组织范围 |
| `b_status_field` | 业务状态列名(状态投影写这一列) |
| `b_status_map_json` | 流程状态 → 业务状态值映射 |
| `b_priority` | 匹配优先级,小的先匹配 |
| `b_effective_from` / `b_effective_to` | 生效区间 |
| `b_canuse` / `b_bz` / 审计字段 | — |
**关键筛选唯一索引**:
```sql
ux_wf_binding_active_priority(b_module_id, b_event, b_priority) WHERE b_canuse = 1
```
→ **同一模块同一事件下不能有两条同优先级的启用绑定**(否则无法确定用哪个流程)。
另有 `ix_wf_binding_select`、`ix_wf_binding_version`。
### 8.7 `wf_instance` — 流程实例
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `uniqueidentifier` PK | UUIDv7 |
| `b_definition_id` / `b_version_id` / `b_binding_id` | `varchar(50)` | 定义 / 版本(提交时固定)/ 命中的绑定 |
| `b_module_id` / `b_event` | `varchar` | 业务模块与事件 |
| `b_business_id` | `varchar(50)` | **业务主键,统一按字符串保存** |
| `b_business_no` | `nvarchar(200)` | 业务单号(列表展示) |
| `b_org_id` | `varchar(50)` | 公司 / 组织编码 |
| `b_initiator_id` / `b_initiator_dept_id` | `varchar(50)` | 发起人及其部门 |
| `b_status` | `varchar(30)` | 见下 |
| `b_context_json` | `nvarchar(max)` | 运行变量(条件分支与审批人解析的输入) |
| `b_snapshot_json` | `nvarchar(max)` | 提交时业务快照 |
| `b_current_summary_json` | `nvarchar(max)` | 列表展示用当前节点摘要 |
| `b_row_version` | `bigint` 默认 0 | **乐观锁版本号** |
| `b_idempotency_key` | `varchar(100)` | 幂等键 |
| `b_started_at` / `b_completed_at` | `datetime2` | 开始 / 结束时间 |
| `b_status` | 含义 |
|---|---|
| `running` | 运行中 |
| `approved` / `rejected` / `returned` | 已通过 / 已驳回 / 已退回 |
| `withdrawn` / `cancelled` | 已撤回 / 已取消 |
| `error` | 异常待处理(审批人解析为空、路由走不通、配置损坏) |
> 列注释里还列了 `expired`,但**实现中实例从不进入该状态**——超时只作用于任务(见 §8.9)。
**关键筛选唯一索引**:
```sql
ux_wf_instance_idempotency(b_module_id, b_business_id, b_idempotency_key)
WHERE b_idempotency_key IS NOT NULL
```
→ 送审幂等的**唯一保证**。代码靠捕获唯一键冲突(SQL 2601/2627/23000)回查既有实例返回 `duplicated`,**索引不存在则幂等完全失效**。
另有 `ix_wf_instance_business`、`ix_wf_instance_initiator`、`ix_wf_instance_version`。
### 8.8 `wf_node_run` — 节点运行
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `uniqueidentifier` PK | UUIDv7 |
| `b_instance_id` | `uniqueidentifier` | 所属实例 |
| `b_node_key` | `varchar(80)` | 节点编码 |
| `b_run_no` | `int` 默认 1 | **同一节点的第几次运行**(退回、循环用) |
| `b_status` | `varchar(30)` | `pending` / `running` / `completed` / `skipped` |
| `b_required_count` | `int` | 应签人数(按比例时激活时换算) |
| `b_completed_count` / `b_rejected_count` | `int` 默认 0 | 已通过 / 已驳回人数 |
| `b_entered_at` / `b_exited_at` | `datetime2` | 进入 / 离开时间 |
| `b_actor_snapshot_json` | `nvarchar(max)` | **审批人规则快照**(含解析结果与说明) |
> **节点状态是 `completed`,不是任务的 `approved`** —— 若这里写 `approved`,并行汇合的「入线来源节点都已完成」判定与「退回上一节点」查找都会失效。
> **审批人快照必须落库**:组织架构以后变了,也不能改动已产生的历史审批责任。
### 8.9 `wf_task` — 待办任务
| 列 | 类型 | 说明 |
|---|---|---|
| `b_id` | `uniqueidentifier` PK | UUIDv7 |
| `b_instance_id` / `b_node_run_id` | `uniqueidentifier` | 实例 / 节点运行(会签聚合判断用) |
| `b_node_key` | `varchar(80)` | 节点编码 |
| `b_actor_rule_id` | `varchar(50)` | 命中的审批人规则 |
| `b_assignee_type` | `varchar(30)` | `user` / `position` / `role` / **`delegate`**(委托物化) |
| `b_assignee_id` | `varchar(50)` | **实际受理人** |
| `b_seq` | `int` 默认 0 | 同节点内处理顺序(**串行会签生效**) |
| `b_status` | `varchar(30)` | 见下 |
| `b_due_at` | `datetime2` | **应处理时间**(由节点 `b_due_hours` 算出) |
| `b_claimed_by` / `b_claimed_at` | — | 抢占人与时间 |
| `b_completed_by` / `b_completed_at` | — | 处理人与时间 |
| `b_rule_snapshot_json` | `nvarchar(max)` | 审批人解析快照(委托时含原审批人与委托单号) |
| `b_status` | 含义 |
|---|---|
| `waiting` | **串行会签排队中**(未轮到) |
| `pending` / `claimed` | 待处理 / 已抢占 |
| `approved` / `rejected` | 已通过 / 已驳回 |
| `cancelled` | 被撤回、取消,或同节点他人已处理而取消 |
| `expired` | **已超期**(仅由超时扫描写入) |
索引:`ix_wf_task_assignee(b_assignee_id, b_status, b_due_at, b_id)`(待办列表)、
`ix_wf_task_due(b_status, b_due_at, b_id)`(**超时扫描**)、
`ix_wf_task_node_run(b_node_run_id, b_status, b_seq, b_id)`(串行推进)、
`ix_wf_task_instance`、`ix_wf_task_completed_by`(已办列表)。
> **`b_due_at` 为空 = 永不超时**:节点没配 `b_due_hours` 时不设应处理时间。
### 8.10 `wf_action` — 审批动作历史
| 列 | 说明 |
|---|---|
| `b_id` | PK(UUIDv7) |
| `b_instance_id` | 实例 |
| `b_node_run_id` / `b_task_id` | 节点运行 / 任务(**实例级动作为 null**) |
| `b_action` | 动作编码,见下 |
| `b_operator_id` | 操作人(**系统动作为 null**) |
| `b_from_status` / `b_to_status` | 变更前后状态 |
| `b_comment` | 审批意见 |
| `b_payload_json` | 动作载荷(命中的条件、边、加签位置等) |
| `b_request_id` / `b_trace_id` | 请求 / 链路 ID |
| `b_occurdatetime` | 发生时间 |
| `b_action` | 含义 |
|---|---|
| `submit` / `approve` / `reject` / `return` | 送审 / 通过 / 驳回 / 退回 |
| `withdraw` / `cancel` | 撤回 / 取消 |
| `claim` / `transfer` / `add_approver` / `urge` | 抢占 / 转交 / 加签 / 催办 |
| `cc` | 抄送 |
| **`activate`** | 串行会签激活下一位 |
| **`service`** | 服务节点自动完成 |
| **`route`** | 分支点路由留痕 |
| **`expire`** | 任务超期(系统动作,操作人为 null) |
**筛选唯一索引**:
```sql
ux_wf_action_request(b_request_id) WHERE b_request_id IS NOT NULL
```
→ 审批幂等的保证(与 `wf_instance` 的幂等键同理)。
> 该列**无 `CHECK` 约束**,加动作码不需要改表;但下游若按枚举过滤需同步。
> **`wf_action` 是不可变历史**,与 `s_log_audit` 分工:前者记流程动作,后者记数据变更。
### 8.11 `wf_cc` — 抄送
`b_id`(PK) / `b_instance_id` / `b_node_run_id` / `b_node_key` / `b_recipient_id` / `b_actor_rule_id` / `b_status`(`unread` 默认 / `read`)/ `b_read_at` / `b_rule_snapshot_json`。
索引:`ix_wf_cc_recipient(b_recipient_id, b_status, b_created_at, b_id)`(抄送列表)、`ix_wf_cc_instance`。
> **抄送节点不产生待办**,也不参与节点完成判定(写入即完成)。
### 8.12 `wf_delegation` — 委托
| 列 | 说明 |
|---|---|
| `b_id` | `varchar(50)` PK |
| `b_from_user_id` / `b_to_user_id` | 委托 / 受托人 |
| `b_scope_json` | **委托范围**:`{moduleIds, actions, orgIds}`,**空数组 = 不限** |
| `b_start_at` / `b_end_at` | 生效 / 失效时间 |
| `b_reason` | 委托原因 |
| `b_canuse` / 审计字段 | — |
索引 `ix_wf_delegation_user(b_from_user_id, b_canuse, b_start_at, b_end_at, b_to_user_id)`。
> **委托在任务生成时就物化**:候选人若有生效中的委托,任务直接建给代理人(`b_assignee_type='delegate'`),这样代理人能在待办里看到它。原审批人与委托单号进任务快照,动作期复核 `actions` 范围。
### 8.13 `wf_outbox` — 通知消息
| 列 | 默认 | 说明 |
|---|---|---|
| `b_id` | — | PK(UUIDv7) |
| `b_event_code` | — | 事件编码 |
| `b_aggregate_type` | — | `instance` / `task` |
| `b_aggregate_id` | — | 聚合 ID |
| `b_payload_json` | — | 消息载荷 |
| `b_status` | `pending` | `pending` / `sent` / `failed` |
| `b_retry_count` | 0 | 重试次数 |
| `b_next_retry_at` | — | 下次重试时间 |
| `b_last_error` | — | 最后错误 |
| `b_occurdatetime` | — | 发生时间 |
索引 `ix_wf_outbox_dispatch(b_status, b_next_retry_at, b_occurdatetime, b_id)`。
**事件码词表**:
| 事件码 | 触发 |
|---|---|
| `instance.submitted` / `instance.withdrawn` / `instance.cancelled` | 实例级状态变化 |
| `instance.approved` / `instance.rejected` / `instance.returned` | 实例结束 |
| `instance.error` | 实例进入异常 |
| `task.created` / `task.activated` | 建任务 / 串行激活下一位 |
| `task.transferred` / `task.approver_added` / `task.urged` | 转交 / 加签 / 催办 |
| `task.expired` | **任务超期**(超时扫描写) |
| `cc.created` | 抄送产生 |
| `instance.projection_skipped` | 业务主键类型不匹配,跳过投影 |
| `instance.projection_missing` | **状态投影影响行数 ≠ 1**(静默丢更新的留痕) |
| `audit.skipped` | `s_log_audit` 不存在,审计无法落库 |
> 最后三个是**降级留痕**:出问题时不会阻断审批,但会在 outbox 里留下可追溯的记录。
## 9. 超时机制(与表结构的关系)
超时是唯一会**主动改任务状态**的后台行为,涉及 `wf_task` 与上述三张表:
```text
节点 b_due_hours → 建任务时算出 b_due_at
↓
[后台扫描,默认 5 分钟]
选出 b_status in ('pending','claimed') and b_due_at < sysutcdatetime()
↓
wf_task.b_status = 'expired'
wf_action += 一条 expire(操作人为 null)
wf_outbox += task.expired
```
**明确不做**:不动 `wf_node_run`、不动 `wf_instance`(仍 `running`)、不自动通过/否决、不置 `error`、**不提升串行后继**。
**几个由此而来的结论**:
- 到期任务从待办列表(只查 `pending`/`claimed`)**自动消失**,但实例仍在运行,需管理员加签或转派。
- **没有「我的超期任务」出口**:`doneList` 查的是 `b_completed_by`,超期任务没人处理过,所以也查不到。
- **串行链上当前审批人超期会停住**:那把唯一的 `pending` 失效后,`waiting` 的不会自动提升,整条链停在那里。
- 配 `erp.workflow.expire-sweep-interval: 0s` 可**完全关闭**扫描。
## 10. 视图
查询入口以视图为主,业务模块通常指向视图而不是物理表:
| 视图 | 说明 |
|---|---|
| `v_wf_definition` / `v_wf_version` / `v_wf_binding` / `v_wf_instance` | 工作流配置与实例(配置侧列表用) |
| `v_s_log_audit` / `v_s_log_login` / `v_s_log_technical` | 日志查询 |
| `v_b_othercompany` / `v_b_contact` / `v_b_user` | 业务与主数据 |