--- 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` | 业务与主数据 |