基于 2026-02-11 会议讨论设计(完整版)
使用模型: Claude Opus 4.5
最后更新: 2026-02-11
负责人: 何文松(后端接口+Mock数据)
来源: 2026-02-11 会议 - 吕强讲解
传统做法的问题:
我们的解决方案:
将图数据库的核心概念(节点 Node、关系 Relationship)平铺到关系型数据库中:
┌─────────────────────────────────────────────────────────────┐
│ 图数据库核心概念 │
├─────────────────────────────────────────────────────────────┤
│ 节点 (Node) → 圆圈圈,代表实体 │
│ 关系 (Edge) → 连线,描述节点之间的关联 │
│ 属性 (Property) → 节点或关系上的附加信息 │
└─────────────────────────────────────────────────────────────┘
↓
平铺到关系型数据库
↓
┌─────────────────────────────────────────────────────────────┐
│ 关系型数据库实现 │
├─────────────────────────────────────────────────────────────┤
│ nodes 表 → 存储所有节点(实体) │
│ edges 表 → 存储所有关系 │
│ properties 表 → 存储节点/关系的属性 │
│ 视图 (View) → 组合出业务接口 │
└─────────────────────────────────────────────────────────────┘
| 优势 | 说明 |
|---|---|
| 表结构稳定 | 不管有多少种报告类型,底层表结构不变 |
| 扩展性强 | 新增报告类型只需新增数据,不需要改表结构 |
| AI 友好 | 结构化的关系描述,AI 容易理解和操作 |
| 查询灵活 | 通过视图组合出任意业务接口 |
| 实体复用 | 同一个实体(如手机号)不管出现在哪里,只存一份 |
┌─────────────────────────────────────────────────────────────────────────────┐
│ 用户使用流程 │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────┐
│ 1. 用户上传文件 │
│ • 样本文档(人工整理好的报告) │
│ • 附件(可选,可同时上传多个) │
└──────────────────────────┬────────────────────────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 2. 解析样本文档 │ │ 3. 解析附件 │
│ 识别要素结构 │ │ 提取实体(NER) │
└────────┬─────────┘ └────────┬─────────┘
│ │
└──────────────┬──────────────┘
│
▼
┌───────────────────────────────────────────────────────┐
│ 4. 要素提取 + 规则提取(现阶段 Mock) │
│ • 从样本中提取动态要素定义 │
│ • 从附件实体中推荐规则配置 │
└──────────────────────────┬────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────┐
│ 5. 页面确认 │
│ • 确认要素定义 │
│ • 确认/编辑规则配置 │
│ • 【用户可从附件实体中添加规则】 │
│ • 确认后形成【模板】 │
└──────────────────────────┬────────────────────────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 6. 规则执行 │ │ 7. 复制到新项目 │
│ 填充要素值 │ │ 复用模板+规则 │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 8. 生成文档 │ │ 新报告实例 │
│ 导出下载 │ │ (重新上传附件) │
└──────────────────┘ └──────────────────┘
关键说明:
基于业务流程,我们设计了 三层数据模型:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 三层数据模型 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第一层:原始文件 (SOURCE_FILE) │
│ ───────────────────────────── │
│ • 用户上传的样本文档 │
│ • 存储原文内容、文档结构 │
│ • 作为溯源和重新解析的依据 │
│ • 生命周期:上传后基本不变 │
└─────────────────────────────────────────────────────────────────────────┘
│
│ PARSED_TO(解析生成)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 第二层:模板 (TEMPLATE) │
│ ───────────────────────── │
│ • 从样本中提取的"规则骨架" │
│ • 包含:要素定义、规则配置、占位符、DSL │
│ • 【可复用】- 一个模板可生成多个报告 │
│ • 生命周期:确认后相对稳定 │
└─────────────────────────────────────────────────────────────────────────┘
│
│ INSTANCE_OF(实例化)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 第三层:报告 (REPORT) │
│ ───────────────────────── │
│ • 基于模板生成的项目实例 │
│ • 包含:要素值、附件、生成结果 │
│ • 每个项目一份,可独立编辑 │
│ • 生命周期:每个项目不同,可编辑 │
└─────────────────────────────────────────────────────────────────────────┘
| 问题 | 如果不分层 | 分层后 |
|---|---|---|
| 复用 | 复制时需要复制整个记录,清空要素值,保留规则,逻辑混乱 | 模板独立存在,报告只需引用模板 |
| 多项目 | 无法表达"一个模板对应多个报告"的关系 | 清晰的一对多关系 |
| 溯源 | 无法追溯原始样本 | 原始文件保留,可重新解析 |
| 维护 | 修改模板会影响所有报告 | 模板和报告独立,互不影响 |
| 层次 | node_type | 存储内容 | 关联的子节点 |
|---|---|---|---|
| 原始文件 | SOURCE_FILE |
原文HTML、文档结构、上传信息 | - |
| 模板 | TEMPLATE |
模板名称、分类、描述 | ELEMENT(要素定义) |
| 报告 | REPORT |
报告标题、状态、项目关联 | VALUE(要素值)、ATTACHMENT(附件)、RULE(规则) |
用户可以从两个来源提取实体并添加规则:
样本文件解析后,系统会提取其中的实体,用户可以直接从本文中选择实体绑定到动态要素:
原始文件 (SOURCE_FILE)
│
│ HAS_ENTITY(本文实体)
▼
实体 (ENTITY) ←── 从样本文件中提取的实体
│
│ INPUT_FROM(被规则引用)
▼
规则 (RULE) ←── 规则可以引用本文中的实体作为输入
附件可以在两个时机上传:
后续单独上传:在报告编辑过程中随时上传
报告 (REPORT)
│
│ HAS_ATTACHMENT
▼
附件 (ATTACHMENT) ←── 解析状态、解析文本
│
│ HAS_ENTITY(附件实体)
▼
实体 (ENTITY) ←── 从附件中提取的实体
│
│ INPUT_FROM(被规则引用)
▼
规则 (RULE) ←── 规则可以引用附件中的实体作为输入
样本文件解析:
附件解析:
用户可以从本文或附件中选择实体,绑定到动态要素:
执行规则时,直接使用该实体的值填充要素
用户操作示例:
┌─────────────────────────────────────────────────────────────────────────┐
│ 📄 本文:成都院复审报告样本.docx │
│ ──────────────────────────── │
│ 识别的实体: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ☑ 中国电建集团成都勘测设计研究院有限公司 [ORG] │ │
│ │ └─ [添加规则] → 绑定到要素:评审对象 │ │
│ │ ☐ 成都院 [ORG] │ │
│ │ ☑ BZ-0092-2024 [CODE] │ │
│ │ └─ [添加规则] → 绑定到要素:项目编号 │ │
│ └───────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ 📎 附件:复审通知.docx │
│ ───────────────────── │
│ 识别的实体: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ☐ 何彦锋 [PERSON] │ │
│ │ ☑ 2024年7月13日 [DATE] │ │
│ │ └─ [添加规则] → 绑定到要素:评审开始日期 │ │
│ │ ☑ 2024年10月17日 [DATE] │ │
│ │ └─ [添加规则] → 绑定到要素:评审结束日期 │ │
│ │ ☐ 93.33 [NUMBER] │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
| 类型 | 所属层 | 说明 | 示例 |
|---|---|---|---|
SOURCE_FILE |
第一层 | 用户上传的原始样本文件 | 成都院复审报告样本.docx |
TEMPLATE |
第二层 | 报告模板(规则骨架) | 电力安全生产标准化复审报告模板 |
ELEMENT |
第二层 | 动态要素定义(属于模板) | 项目编号、评审对象 |
REPORT |
第三层 | 报告实例(属于项目) | 成都院复审报告 |
VALUE |
第三层 | 要素值(属于报告) | "BZ-0092-2024" |
RULE |
第三层 | 规则实例(属于报告) | 项目编号提取规则 |
ATTACHMENT |
第三层 | 附件(属于报告) | 复审通知.docx |
ENTITY |
跨层 | 识别实体(属于样本文件或附件) | 中国电建集团成都勘测设计研究院有限公司 |
USER |
基础 | 用户 | 管理员 |
PROJECT |
基础 | 项目 | 电力安全评审项目 |
注意:
ENTITY是跨层的,可以从SOURCE_FILE(本文)或ATTACHMENT(附件)中提取,通过HAS_ENTITY关系关联。
| 关系 | 说明 | 方向 |
|---|---|---|
PARSED_TO |
原始文件解析生成模板 | 原始文件 → 模板 |
HAS_ELEMENT |
模板包含要素定义 | 模板 → 要素定义 |
INSTANCE_OF |
报告基于模板 | 报告 → 模板 |
HAS_VALUE |
报告有要素值 | 报告 → 要素值 |
HAS_RULE |
报告有规则 | 报告 → 规则 |
FOR_ELEMENT |
规则/值对应的要素 | 规则/值 → 要素定义 |
HAS_ATTACHMENT |
报告有附件 | 报告 → 附件 |
HAS_ENTITY |
文件有实体(本文或附件) | 原始文件/附件 → 实体 |
INPUT_FROM |
规则输入来源 | 规则 → 原始文件/附件/实体/要素值 |
COPIED_FROM |
复制来源 | 报告 → 源报告 |
BELONGS_TO |
归属关系 | 报告 → 项目 |
CREATED_BY |
创建者 | 节点 → 用户 |
| 类型 | 占位符格式 | 说明 |
|---|---|---|
text |
{{namespace.field}} |
短文本 |
paragraph |
{{namespace.field}} |
长文本/段落 |
table |
{{+tableName}} |
表格数据 |
| 类型 | 说明 | 示例 |
|---|---|---|
ORG |
组织机构 | 中国电建集团成都勘测设计研究院有限公司 |
PERSON |
人名 | 何彦锋 |
DATE |
日期 | 2024年7月13日 |
NUMBER |
数值 | 93.33 |
LOCATION |
地址 | 成都市温江区政和街8号 |
┌─────────────────────────────────────────────────────────────────────────────┐
│ 节点与关系完整图 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────┐
│ USER │
└────┬────┘
│ CREATED_BY
▼
┌─────────────┐ PARSED_TO ┌─────────────┐ HAS_ELEMENT ┌─────────────┐
│ SOURCE_FILE │─────────────▶│ TEMPLATE │──────────────▶│ ELEMENT │
│ (原始文件) │ │ (模板) │ │ (要素定义) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ HAS_ENTITY │ INSTANCE_OF │ FOR_ELEMENT
│ (本文实体) ▼ │
│ ┌─────────────┐ │
│ ┌─────────────┐ │ REPORT │ ┌─────────────┐ │
│ │ PROJECT │◀───│ (报告) │───▶│ PROJECT │ │
│ │ (项目) │ └──────┬──────┘ └─────────────┘ │
│ └─────────────┘ │ BELONGS_TO │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ VALUE │ │ RULE │ │ATTACHMENT │ │
│ │ (要素值) │ │ (规则) │ │ (附件) │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ │ │ │ HAS_ENTITY │
│ │ │ │ (附件实体) │
│ │ │ ▼ │
│ │ │ ┌───────────┐ │
└────────────┼──────────────┼───────▶│ ENTITY │ │
│ │ │ (实体) │ │
│ │ └─────┬─────┘ │
│ │ │ │
│ │◀─────────────┘ │
│ │ INPUT_FROM │
│◀─────────────┘ │
│ INPUT_FROM │
│ │
└─────────────────────────────────────────────┘
FOR_ELEMENT
说明:
- SOURCE_FILE(本文)和 ATTACHMENT(附件)都可以通过 HAS_ENTITY 关联到 ENTITY
- RULE 可以通过 INPUT_FROM 引用来自本文或附件的实体
| 表名 | 说明 |
|---|---|
nodes |
节点表 - 存储所有实体 |
edges |
关系表 - 存储节点间关系 |
node_properties |
节点属性表 |
edge_properties |
关系属性表 |
| 表名 | 说明 |
|---|---|
node_types |
节点类型定义 |
edge_types |
关系类型定义 |
property_definitions |
属性定义 |
| 表名 | 说明 |
|---|---|
sys_users |
用户扩展表(关联nodes中的USER节点) |
sys_sessions |
会话表 |
sys_login_logs |
登录日志表 |
sys_roles |
角色表 |
sys_permissions |
权限表(菜单+按钮) |
sys_user_roles |
用户角色关联表 |
sys_role_permissions |
角色权限关联表 |
sys_configs |
系统配置表 |
sys_dict_types |
数据字典类型表 |
sys_dict_items |
数据字典项表 |
sys_operation_logs |
操作日志表 |
sys_files |
文件存储表 |
sys_tasks |
异步任务队列表 |
┌─────────────────────────────────────────────────────────────────────┐
│ 核心图结构 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ node_types │ │ edge_types │ │
│ │ (节点类型) │ │ (关系类型) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ nodes │◄────────│ edges │ │
│ │ (节点) │ │ (关系) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │node_properties│ │edge_properties│ │
│ │ (节点属性) │ │ (关系属性) │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
│
│ 通过视图组合
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 业务视图层 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ v_templates │ │ v_reports │ │ v_elements │ │ v_rules │ │
│ │ (模板视图) │ │ (报告视图) │ │ (要素视图) │ │ (规则视图) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │v_attachments│ │ v_entities │ │v_rule_config│ │
│ │ (附件视图) │ │ (实体视图) │ │(规则配置视图)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 类型ID |
| type_code | VARCHAR(50) | NOT NULL, UNIQUE | 类型编码 |
| type_name | VARCHAR(100) | NOT NULL | 类型名称 |
| description | TEXT | 描述 | |
| icon | VARCHAR(100) | 图标 | |
| color | VARCHAR(20) | 颜色 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 类型ID |
| type_code | VARCHAR(50) | NOT NULL, UNIQUE | 类型编码 |
| type_name | VARCHAR(100) | NOT NULL | 类型名称 |
| from_node_type | VARCHAR(50) | 起始节点类型 | |
| to_node_type | VARCHAR(50) | 目标节点类型 | |
| description | TEXT | 描述 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 节点ID |
| node_type | VARCHAR(50) | NOT NULL | 节点类型 |
| node_key | VARCHAR(200) | 节点唯一标识(业务键) | |
| name | VARCHAR(500) | NOT NULL | 节点名称 |
| status | VARCHAR(50) | DEFAULT 'active' | 状态 |
| created_by | BIGINT | 创建人ID | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
唯一约束: (node_type, node_key) - 同类型下 node_key 唯一
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 关系ID |
| edge_type | VARCHAR(50) | NOT NULL | 关系类型 |
| from_node_id | BIGINT | NOT NULL, FK | 起始节点ID |
| to_node_id | BIGINT | NOT NULL, FK | 目标节点ID |
| sort_order | INT | DEFAULT 0 | 排序 |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
唯一约束: (edge_type, from_node_id, to_node_id) - 防止重复关系
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 属性ID |
| node_id | BIGINT | NOT NULL, FK | 节点ID |
| prop_key | VARCHAR(100) | NOT NULL | 属性键 |
| prop_value | TEXT | 文本值 | |
| prop_json | JSONB | JSON值 | |
| prop_number | DECIMAL(20,4) | 数值 | |
| prop_date | TIMESTAMP | 日期值 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
唯一约束: (node_id, prop_key) - 同节点下属性键唯一
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 属性ID |
| edge_id | BIGINT | NOT NULL, FK | 关系ID |
| prop_key | VARCHAR(100) | NOT NULL | 属性键 |
| prop_value | TEXT | 文本值 | |
| prop_json | JSONB | JSON值 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
唯一约束: (edge_id, prop_key)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 定义ID |
| owner_type | VARCHAR(20) | NOT NULL | 所属类型: node/edge |
| target_type | VARCHAR(50) | NOT NULL | 目标节点/关系类型 |
| prop_key | VARCHAR(100) | NOT NULL | 属性键 |
| prop_name | VARCHAR(200) | NOT NULL | 属性名称 |
| data_type | VARCHAR(50) | NOT NULL | 数据类型: text/json/number/date |
| required | BOOLEAN | DEFAULT false | 是否必填 |
| default_value | TEXT | 默认值 | |
| description | TEXT | 描述 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
扩展 nodes 中的 USER 节点,存储认证相关信息
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 用户ID |
| node_id | BIGINT | FK(nodes.id) | 关联的USER节点ID |
| username | VARCHAR(100) | NOT NULL, UNIQUE | 用户名 |
| password_hash | VARCHAR(255) | NOT NULL | 密码哈希 |
| salt | VARCHAR(50) | 盐值 | |
| VARCHAR(200) | 邮箱 | ||
| phone | VARCHAR(20) | 手机号 | |
| avatar | VARCHAR(500) | 头像URL | |
| real_name | VARCHAR(100) | 真实姓名 | |
| department | VARCHAR(200) | 部门 | |
| position | VARCHAR(100) | 职位 | |
| status | VARCHAR(20) | DEFAULT 'active' | 状态: active/disabled |
| last_login_at | TIMESTAMP | 最后登录时间 | |
| last_login_ip | VARCHAR(50) | 最后登录IP | |
| login_count | INT | DEFAULT 0 | 登录次数 |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 会话ID |
| user_id | BIGINT | NOT NULL, FK | 用户ID |
| session_token | VARCHAR(255) | NOT NULL, UNIQUE | 会话令牌 |
| refresh_token | VARCHAR(255) | 刷新令牌 | |
| device_type | VARCHAR(50) | 设备类型 | |
| device_info | TEXT | 设备信息 | |
| ip_address | VARCHAR(50) | IP地址 | |
| user_agent | TEXT | User-Agent | |
| expires_at | TIMESTAMP | NOT NULL | 过期时间 |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 日志ID |
| user_id | BIGINT | FK | 用户ID |
| username | VARCHAR(100) | 用户名 | |
| login_type | VARCHAR(20) | 登录类型: password/sso/token | |
| ip_address | VARCHAR(50) | IP地址 | |
| user_agent | TEXT | User-Agent | |
| device_type | VARCHAR(50) | 设备类型 | |
| location | VARCHAR(200) | 登录地点 | |
| status | VARCHAR(20) | NOT NULL | 状态: success/failed |
| message | TEXT | 消息 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 角色ID |
| role_code | VARCHAR(50) | NOT NULL, UNIQUE | 角色编码 |
| role_name | VARCHAR(100) | NOT NULL | 角色名称 |
| description | TEXT | 描述 | |
| sort_order | INT | DEFAULT 0 | 排序 |
| status | VARCHAR(20) | DEFAULT 'active' | 状态 |
| created_by | BIGINT | 创建人 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 权限ID |
| perm_code | VARCHAR(100) | NOT NULL, UNIQUE | 权限编码 |
| perm_name | VARCHAR(200) | NOT NULL | 权限名称 |
| perm_type | VARCHAR(20) | NOT NULL | 类型: menu/button/api |
| parent_id | INT | FK(self) | 父级ID |
| path | VARCHAR(500) | 路由路径 | |
| icon | VARCHAR(100) | 图标 | |
| component | VARCHAR(200) | 前端组件 | |
| sort_order | INT | DEFAULT 0 | 排序 |
| status | VARCHAR(20) | DEFAULT 'active' | 状态 |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | ID |
| user_id | BIGINT | NOT NULL, FK | 用户ID |
| role_id | INT | NOT NULL, FK | 角色ID |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
唯一约束: (user_id, role_id)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | ID |
| role_id | INT | NOT NULL, FK | 角色ID |
| permission_id | INT | NOT NULL, FK | 权限ID |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
唯一约束: (role_id, permission_id)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 配置ID |
| config_key | VARCHAR(100) | NOT NULL, UNIQUE | 配置键 |
| config_value | TEXT | 配置值 | |
| config_json | JSONB | JSON配置 | |
| config_type | VARCHAR(50) | 类型: string/number/boolean/json | |
| description | TEXT | 描述 | |
| is_public | BOOLEAN | DEFAULT false | 是否公开(前端可见) |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 类型ID |
| dict_code | VARCHAR(100) | NOT NULL, UNIQUE | 字典编码 |
| dict_name | VARCHAR(200) | NOT NULL | 字典名称 |
| description | TEXT | 描述 | |
| status | VARCHAR(20) | DEFAULT 'active' | 状态 |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 项ID |
| dict_type_id | INT | NOT NULL, FK | 字典类型ID |
| item_code | VARCHAR(100) | NOT NULL | 项编码 |
| item_name | VARCHAR(200) | NOT NULL | 项名称 |
| item_value | TEXT | 项值 | |
| sort_order | INT | DEFAULT 0 | 排序 |
| status | VARCHAR(20) | DEFAULT 'active' | 状态 |
| extra | JSONB | 扩展信息 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
唯一约束: (dict_type_id, item_code)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 日志ID |
| user_id | BIGINT | 用户ID | |
| username | VARCHAR(100) | 用户名 | |
| module | VARCHAR(100) | 模块 | |
| action | VARCHAR(100) | 操作 | |
| method | VARCHAR(10) | HTTP方法 | |
| url | VARCHAR(500) | 请求URL | |
| params | TEXT | 请求参数 | |
| result | TEXT | 响应结果 | |
| ip_address | VARCHAR(50) | IP地址 | |
| user_agent | TEXT | User-Agent | |
| duration_ms | INT | 耗时(毫秒) | |
| status | VARCHAR(20) | 状态: success/failed | |
| error_msg | TEXT | 错误信息 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 文件ID |
| file_key | VARCHAR(100) | NOT NULL, UNIQUE | 文件唯一标识 |
| original_name | VARCHAR(500) | NOT NULL | 原始文件名 |
| storage_name | VARCHAR(200) | NOT NULL | 存储文件名 |
| storage_path | VARCHAR(500) | NOT NULL | 存储路径 |
| file_type | VARCHAR(100) | MIME类型 | |
| file_size | BIGINT | 文件大小(字节) | |
| md5_hash | VARCHAR(32) | MD5哈希 | |
| storage_type | VARCHAR(20) | DEFAULT 'local' | 存储类型: local/oss/s3 |
| bucket | VARCHAR(100) | 存储桶 | |
| url | VARCHAR(1000) | 访问URL | |
| thumbnail_url | VARCHAR(1000) | 缩略图URL | |
| uploaded_by | BIGINT | 上传人 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGSERIAL | PRIMARY KEY | 任务ID |
| task_type | VARCHAR(50) | NOT NULL | 任务类型: parse/export/llm |
| task_key | VARCHAR(100) | 任务标识 | |
| payload | JSONB | 任务参数 | |
| status | VARCHAR(20) | DEFAULT 'pending' | 状态: pending/running/success/failed |
| priority | INT | DEFAULT 0 | 优先级 |
| retry_count | INT | DEFAULT 0 | 重试次数 |
| max_retries | INT | DEFAULT 3 | 最大重试次数 |
| result | JSONB | 执行结果 | |
| error_msg | TEXT | 错误信息 | |
| started_at | TIMESTAMP | 开始时间 | |
| finished_at | TIMESTAMP | 完成时间 | |
| created_by | BIGINT | 创建人 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 |
设计原则: 一个视图对应一个前端接口,底层表结构不用改
CREATE OR REPLACE VIEW v_source_files AS
SELECT
n.id,
n.name AS file_name,
n.node_key AS file_key,
n.status,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'original_name') AS original_name,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'file_path') AS file_path,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'file_type') AS file_type,
(SELECT prop_number FROM node_properties WHERE node_id = n.id AND prop_key = 'file_size')::bigint AS file_size,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'content_html') AS content_html,
(SELECT prop_json FROM node_properties WHERE node_id = n.id AND prop_key = 'content_json') AS content_json,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'parse_status') AS parse_status,
(SELECT prop_date FROM node_properties WHERE node_id = n.id AND prop_key = 'parsed_at') AS parsed_at,
-- 生成的模板
(SELECT n2.id FROM edges e JOIN nodes n2 ON n2.id = e.to_node_id
WHERE e.from_node_id = n.id AND e.edge_type = 'PARSED_TO' LIMIT 1) AS template_id,
-- 创建人
n.created_by,
(SELECT n2.name FROM nodes n2 WHERE n2.id = n.created_by) AS created_by_name
FROM nodes n
WHERE n.node_type = 'SOURCE_FILE';
CREATE OR REPLACE VIEW v_templates AS
SELECT
n.id,
n.name,
n.node_key AS template_code,
n.status,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'category') AS category,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'description') AS description,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'content_html') AS content_html,
(SELECT prop_json FROM node_properties WHERE node_id = n.id AND prop_key = 'content_json') AS content_json,
(SELECT COUNT(*) FROM edges e WHERE e.from_node_id = n.id AND e.edge_type = 'HAS_ELEMENT') AS element_count,
(SELECT n2.name FROM nodes n2 WHERE n2.id = n.created_by) AS created_by_name
FROM nodes n
WHERE n.node_type = 'TEMPLATE';
CREATE OR REPLACE VIEW v_reports AS
SELECT
n.id,
n.name AS title,
n.node_key AS report_code,
n.status,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'report_type') AS report_type,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'content_html') AS content_html,
-- 模板信息
(SELECT n2.id FROM edges e JOIN nodes n2 ON n2.id = e.to_node_id
WHERE e.from_node_id = n.id AND e.edge_type = 'INSTANCE_OF' LIMIT 1) AS template_id,
(SELECT n2.name FROM edges e JOIN nodes n2 ON n2.id = e.to_node_id
WHERE e.from_node_id = n.id AND e.edge_type = 'INSTANCE_OF' LIMIT 1) AS template_name,
-- 项目信息
(SELECT n2.id FROM edges e JOIN nodes n2 ON n2.id = e.to_node_id
WHERE e.from_node_id = n.id AND e.edge_type = 'BELONGS_TO' AND n2.node_type = 'PROJECT' LIMIT 1) AS project_id,
(SELECT n2.name FROM edges e JOIN nodes n2 ON n2.id = e.to_node_id
WHERE e.from_node_id = n.id AND e.edge_type = 'BELONGS_TO' AND n2.node_type = 'PROJECT' LIMIT 1) AS project_name,
-- 复制来源
(SELECT n2.id FROM edges e JOIN nodes n2 ON n2.id = e.to_node_id
WHERE e.from_node_id = n.id AND e.edge_type = 'COPIED_FROM' LIMIT 1) AS source_report_id,
-- 统计
(SELECT COUNT(*) FROM edges e WHERE e.from_node_id = n.id AND e.edge_type = 'HAS_ATTACHMENT') AS attachment_count,
-- 创建人
n.created_by,
(SELECT n2.name FROM nodes n2 WHERE n2.id = n.created_by) AS created_by_name
FROM nodes n
WHERE n.node_type = 'REPORT';
CREATE OR REPLACE VIEW v_template_elements AS
SELECT
n.id,
n.name AS element_name,
n.node_key AS element_key,
n.created_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'element_type') AS element_type,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'namespace') AS namespace,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'field_name') AS field_name,
(SELECT prop_json FROM node_properties WHERE node_id = n.id AND prop_key = 'table_columns') AS table_columns,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'required')::boolean AS required,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'default_value') AS default_value,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'description') AS description,
-- 所属模板
(SELECT e.from_node_id FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_ELEMENT' LIMIT 1) AS template_id,
(SELECT e.sort_order FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_ELEMENT' LIMIT 1) AS sort_order
FROM nodes n
WHERE n.node_type = 'ELEMENT';
CREATE OR REPLACE VIEW v_report_element_values AS
SELECT
n.id AS value_id,
n.node_key AS element_key,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'value_text') AS value_text,
(SELECT prop_json FROM node_properties WHERE node_id = n.id AND prop_key = 'value_json') AS value_json,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'is_filled')::boolean AS is_filled,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'fill_source') AS fill_source,
-- 所属报告
(SELECT e.from_node_id FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_VALUE' LIMIT 1) AS report_id
FROM nodes n
WHERE n.node_type = 'VALUE';
CREATE OR REPLACE VIEW v_attachments AS
SELECT
n.id,
n.name AS display_name,
n.node_key AS file_key,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'file_name') AS file_name,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'file_path') AS file_path,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'file_type') AS file_type,
(SELECT prop_number FROM node_properties WHERE node_id = n.id AND prop_key = 'file_size')::bigint AS file_size,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'parse_status') AS parse_status,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'parsed_text') AS parsed_text,
(SELECT prop_date FROM node_properties WHERE node_id = n.id AND prop_key = 'parsed_at') AS parsed_at,
-- 实体数量
(SELECT COUNT(*) FROM edges e WHERE e.from_node_id = n.id AND e.edge_type = 'HAS_ENTITY') AS entity_count,
-- 所属报告
(SELECT e.from_node_id FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_ATTACHMENT' LIMIT 1) AS report_id,
(SELECT e.sort_order FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_ATTACHMENT' LIMIT 1) AS sort_order
FROM nodes n
WHERE n.node_type = 'ATTACHMENT';
CREATE OR REPLACE VIEW v_entities AS
SELECT
n.id,
n.name AS entity_text,
n.node_key AS entity_key,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'entity_type') AS entity_type,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'business_label') AS business_label,
(SELECT prop_number FROM node_properties WHERE node_id = n.id AND prop_key = 'confidence') AS confidence,
(SELECT prop_number FROM node_properties WHERE node_id = n.id AND prop_key = 'occurrence_count')::int AS occurrence_count,
-- 所属附件
(SELECT e.from_node_id FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_ENTITY' LIMIT 1) AS attachment_id
FROM nodes n
WHERE n.node_type = 'ENTITY';
CREATE OR REPLACE VIEW v_rules AS
SELECT
n.id,
n.name AS rule_name,
n.node_key AS element_key,
n.status,
n.created_at,
n.updated_at,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'description') AS description,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'rule_type') AS rule_type,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'action_type') AS action_type,
(SELECT prop_json FROM node_properties WHERE node_id = n.id AND prop_key = 'action_config') AS action_config,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'dsl_content') AS dsl_content,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'last_output_text') AS last_output_text,
(SELECT prop_json FROM node_properties WHERE node_id = n.id AND prop_key = 'last_output_json') AS last_output_json,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'last_run_status') AS last_run_status,
(SELECT prop_date FROM node_properties WHERE node_id = n.id AND prop_key = 'last_run_time') AS last_run_time,
(SELECT prop_value FROM node_properties WHERE node_id = n.id AND prop_key = 'last_run_error') AS last_run_error,
-- 所属报告
(SELECT e.from_node_id FROM edges e WHERE e.to_node_id = n.id AND e.edge_type = 'HAS_RULE' LIMIT 1) AS report_id
FROM nodes n
WHERE n.node_type = 'RULE';
CREATE OR REPLACE VIEW v_rule_inputs AS
SELECT
e.id AS input_id,
e.from_node_id AS rule_id,
e.to_node_id AS source_node_id,
e.sort_order,
(SELECT prop_value FROM edge_properties WHERE edge_id = e.id AND prop_key = 'input_key') AS input_key,
(SELECT prop_value FROM edge_properties WHERE edge_id = e.id AND prop_key = 'input_name') AS input_name,
(SELECT prop_value FROM edge_properties WHERE edge_id = e.id AND prop_key = 'input_type') AS input_type,
(SELECT prop_value FROM edge_properties WHERE edge_id = e.id AND prop_key = 'fixed_value') AS fixed_value,
-- 来源节点信息
n.node_type AS source_type,
n.name AS source_name
FROM edges e
JOIN nodes n ON n.id = e.to_node_id
WHERE e.edge_type = 'INPUT_FROM';
CREATE OR REPLACE VIEW v_report_full AS
SELECT
r.*,
-- 动态要素填充统计
(SELECT COUNT(*) FROM v_report_element_values v WHERE v.report_id = r.id AND v.is_filled = true) AS filled_count,
(SELECT COUNT(*) FROM v_template_elements te WHERE te.template_id = r.template_id) AS total_elements,
-- 规则统计
(SELECT COUNT(*) FROM v_rules ru WHERE ru.report_id = r.id) AS rule_count,
(SELECT COUNT(*) FROM v_rules ru WHERE ru.report_id = r.id AND ru.last_run_status = 'success') AS success_rule_count
FROM v_reports r;
-- 类型索引(最常用)
CREATE INDEX idx_nodes_type ON nodes(node_type);
-- 类型+状态组合索引
CREATE INDEX idx_nodes_type_status ON nodes(node_type, status);
-- 业务键索引
CREATE INDEX idx_nodes_key ON nodes(node_key);
-- 类型+业务键唯一索引
CREATE UNIQUE INDEX idx_nodes_type_key ON nodes(node_type, node_key) WHERE node_key IS NOT NULL;
-- 创建人索引
CREATE INDEX idx_nodes_created_by ON nodes(created_by);
-- 创建时间索引
CREATE INDEX idx_nodes_created_at ON nodes(created_at DESC);
-- 关系类型索引
CREATE INDEX idx_edges_type ON edges(edge_type);
-- 起始节点索引
CREATE INDEX idx_edges_from ON edges(from_node_id);
-- 目标节点索引
CREATE INDEX idx_edges_to ON edges(to_node_id);
-- 类型+起始节点组合索引(查询某节点的特定关系)
CREATE INDEX idx_edges_type_from ON edges(edge_type, from_node_id);
-- 类型+目标节点组合索引(反向查询)
CREATE INDEX idx_edges_type_to ON edges(edge_type, to_node_id);
-- 防止重复关系
CREATE UNIQUE INDEX idx_edges_unique ON edges(edge_type, from_node_id, to_node_id);
-- 节点ID索引
CREATE INDEX idx_node_props_node ON node_properties(node_id);
-- 节点+属性键组合索引
CREATE UNIQUE INDEX idx_node_props_unique ON node_properties(node_id, prop_key);
-- 属性键索引(用于跨节点查询特定属性)
CREATE INDEX idx_node_props_key ON node_properties(prop_key);
-- 关系ID索引
CREATE INDEX idx_edge_props_edge ON edge_properties(edge_id);
-- 关系+属性键组合索引
CREATE UNIQUE INDEX idx_edge_props_unique ON edge_properties(edge_id, prop_key);
操作:用户上传样本文件(可同时上传附件) → 系统解析 → 生成模板
步骤:
1. 创建 SOURCE_FILE 节点(样本文件)
2. 解析样本文件,提取要素定义
3. 创建 TEMPLATE 节点
4. 创建 PARSED_TO 关系(SOURCE_FILE → TEMPLATE)
5. 为每个要素定义创建 ELEMENT 节点
6. 创建 HAS_ELEMENT 关系(TEMPLATE → ELEMENT)
7. 【可选】同时上传附件:
a. 创建 ATTACHMENT 节点
b. 创建 HAS_ATTACHMENT 关系(关联到后续创建的 REPORT)
c. 解析附件,提取实体
d. 创建 ENTITY 节点
e. 创建 HAS_ENTITY 关系(ATTACHMENT → ENTITY)
说明:附件可以在上传样本时同时上传,系统会先解析附件并提取实体,这样用户在确认要素和规则时,可以直接从附件实体中选择绑定。
SQL 示例:
-- 1. 创建原始文件节点
INSERT INTO nodes (node_type, node_key, name, created_by)
VALUES ('SOURCE_FILE', 'SF-001', '成都院复审报告样本.docx', 1)
RETURNING id; -- 假设返回 id = 100
-- 2. 添加原始文件属性
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(100, 'original_name', '成都院复审报告样本.docx'),
(100, 'file_path', '/uploads/2026/02/SF-001.docx'),
(100, 'file_type', 'docx'),
(100, 'parse_status', 'completed');
-- 3. 创建模板节点
INSERT INTO nodes (node_type, node_key, name, created_by)
VALUES ('TEMPLATE', 'TPL-001', '电力安全生产标准化复审报告模板', 1)
RETURNING id; -- 假设返回 id = 101
-- 4. 创建 PARSED_TO 关系
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('PARSED_TO', 100, 101);
-- 5. 创建要素定义节点
INSERT INTO nodes (node_type, node_key, name)
VALUES ('ELEMENT', 'basicInfo.projectCode', '项目编号')
RETURNING id; -- 假设返回 id = 102
-- 6. 添加要素定义属性
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(102, 'element_type', 'text'),
(102, 'namespace', 'basicInfo'),
(102, 'field_name', 'projectCode'),
(102, 'required', 'true');
-- 7. 创建 HAS_ELEMENT 关系
INSERT INTO edges (edge_type, from_node_id, to_node_id, sort_order)
VALUES ('HAS_ELEMENT', 101, 102, 1);
操作:用户选择模板 → 创建报告 → 关联项目
步骤:
1. 创建 REPORT 节点
2. 创建 INSTANCE_OF 关系(REPORT → TEMPLATE)
3. 创建 BELONGS_TO 关系(REPORT → PROJECT)
4. 为每个要素定义创建空的 VALUE 节点
5. 创建 HAS_VALUE 关系(REPORT → VALUE)
6. 创建 FOR_ELEMENT 关系(VALUE → ELEMENT)
SQL 示例:
-- 1. 创建报告节点
INSERT INTO nodes (node_type, node_key, name, status, created_by)
VALUES ('REPORT', 'RPT-001', '成都院2024年复审报告', 'draft', 1)
RETURNING id; -- 假设返回 id = 200
-- 2. 创建 INSTANCE_OF 关系(报告 → 模板)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('INSTANCE_OF', 200, 101);
-- 3. 创建 BELONGS_TO 关系(报告 → 项目)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('BELONGS_TO', 200, 1); -- 假设项目ID为1
-- 4. 为每个要素创建空的 VALUE 节点
INSERT INTO nodes (node_type, node_key, name)
VALUES ('VALUE', 'RPT-001:basicInfo.projectCode', '项目编号值')
RETURNING id; -- 假设返回 id = 201
-- 5. 添加 VALUE 属性(初始为空)
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(201, 'is_filled', 'false');
-- 6. 创建 HAS_VALUE 关系(报告 → 值)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('HAS_VALUE', 200, 201);
-- 7. 创建 FOR_ELEMENT 关系(值 → 要素定义)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('FOR_ELEMENT', 201, 102);
操作:用户选择已有报告 → 复制到新项目
复制内容:
✓ 模板引用(INSTANCE_OF)
✓ 规则配置(RULE 节点及其 INPUT_FROM 关系结构)
✗ 要素值(VALUE 节点清空,需重新填充)
✗ 附件(ATTACHMENT 节点不复制,需重新上传)
✗ 实体(ENTITY 节点不复制)
步骤:
1. 创建新的 REPORT 节点
2. 复制 INSTANCE_OF 关系(指向同一模板)
3. 创建 BELONGS_TO 关系(指向新项目)
4. 创建 COPIED_FROM 关系(新报告 → 源报告)
5. 为每个要素创建空的 VALUE 节点
6. 复制 RULE 节点(复制规则配置,但清空执行结果)
7. 复制 INPUT_FROM 关系结构(但引用需要重新绑定)
SQL 示例:
-- 1. 创建新报告节点
INSERT INTO nodes (node_type, node_key, name, status, created_by)
VALUES ('REPORT', 'RPT-002', '华东院2024年复审报告', 'draft', 1)
RETURNING id; -- 假设返回 id = 300
-- 2. 获取源报告的模板ID
SELECT to_node_id FROM edges
WHERE from_node_id = 200 AND edge_type = 'INSTANCE_OF'; -- 返回 101
-- 3. 创建 INSTANCE_OF 关系(指向同一模板)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('INSTANCE_OF', 300, 101);
-- 4. 创建 BELONGS_TO 关系(指向新项目)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('BELONGS_TO', 300, 2); -- 假设新项目ID为2
-- 5. 创建 COPIED_FROM 关系(溯源)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('COPIED_FROM', 300, 200);
-- 6. 复制规则节点(清空执行结果)
INSERT INTO nodes (node_type, node_key, name, status)
SELECT 'RULE',
REPLACE(node_key, 'RPT-001', 'RPT-002'),
name,
'active'
FROM nodes
WHERE node_type = 'RULE'
AND id IN (SELECT to_node_id FROM edges WHERE from_node_id = 200 AND edge_type = 'HAS_RULE');
-- 7. 复制规则属性(保留配置,清空结果)
INSERT INTO node_properties (node_id, prop_key, prop_value, prop_json)
SELECT new_rule.id, np.prop_key,
CASE WHEN np.prop_key IN ('last_output_text', 'last_run_status', 'last_run_error') THEN NULL
ELSE np.prop_value END,
CASE WHEN np.prop_key = 'last_output_json' THEN NULL ELSE np.prop_json END
FROM node_properties np
JOIN nodes old_rule ON old_rule.id = np.node_id AND old_rule.node_type = 'RULE'
JOIN nodes new_rule ON new_rule.node_key = REPLACE(old_rule.node_key, 'RPT-001', 'RPT-002')
WHERE old_rule.id IN (SELECT to_node_id FROM edges WHERE from_node_id = 200 AND edge_type = 'HAS_RULE');
操作:用户上传附件 → 系统解析 → 提取实体
步骤:
1. 创建 ATTACHMENT 节点
2. 创建 HAS_ATTACHMENT 关系(REPORT → ATTACHMENT)
3. 解析附件内容
4. 提取实体(NER)
5. 为每个实体创建 ENTITY 节点(全局去重)
6. 创建 HAS_ENTITY 关系(ATTACHMENT → ENTITY)
SQL 示例:
-- 1. 创建附件节点
INSERT INTO nodes (node_type, node_key, name)
VALUES ('ATTACHMENT', 'ATT-001', '01-复审通知')
RETURNING id; -- 假设返回 id = 400
-- 2. 添加附件属性
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(400, 'file_name', '复审通知.docx'),
(400, 'file_path', '/uploads/2026/02/复审通知.docx'),
(400, 'file_type', 'docx'),
(400, 'parse_status', 'completed');
-- 3. 创建 HAS_ATTACHMENT 关系
INSERT INTO edges (edge_type, from_node_id, to_node_id, sort_order)
VALUES ('HAS_ATTACHMENT', 200, 400, 1);
-- 4. 创建实体节点(全局去重,使用 ON CONFLICT)
INSERT INTO nodes (node_type, node_key, name)
VALUES ('ENTITY', 'ORG:中国电建集团成都勘测设计研究院有限公司', '中国电建集团成都勘测设计研究院有限公司')
ON CONFLICT (node_type, node_key) DO UPDATE SET updated_at = NOW()
RETURNING id; -- 假设返回 id = 500
-- 5. 添加实体属性
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(500, 'entity_type', 'ORG'),
(500, 'business_label', '评审对象')
ON CONFLICT (node_id, prop_key) DO UPDATE SET prop_value = EXCLUDED.prop_value;
-- 6. 创建 HAS_ENTITY 关系
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('HAS_ENTITY', 400, 500)
ON CONFLICT DO NOTHING;
操作:用户在页面上选择附件中的实体 → 绑定到动态要素 → 自动创建规则
场景:用户查看附件解析出的实体列表,选择某个实体直接绑定到要素
步骤:
1. 用户选择实体(如"中国电建集团成都勘测设计研究院有限公司")
2. 用户选择要绑定的动态要素(如"评审对象")
3. 系统自动创建 RULE 节点(规则类型为 direct_entity)
4. 创建 HAS_RULE 关系(REPORT → RULE)
5. 创建 FOR_ELEMENT 关系(RULE → ELEMENT)
6. 创建 INPUT_FROM 关系(RULE → ENTITY)
7. 执行规则,直接使用实体值填充要素
SQL 示例:
-- 假设:
-- 报告ID = 200
-- 要素定义ID = 102(评审对象)
-- 实体ID = 500(中国电建集团成都勘测设计研究院有限公司)
-- 1. 创建规则节点(直接引用实体类型)
INSERT INTO nodes (node_type, node_key, name, status)
VALUES ('RULE', 'RPT-001:project.reviewObject', '评审对象-直接引用实体', 'active')
RETURNING id; -- 假设返回 id = 610
-- 2. 添加规则属性(规则类型为 direct_entity,表示直接使用实体值)
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(610, 'rule_type', 'direct_entity'),
(610, 'action_type', 'use_entity_value'),
(610, 'description', '直接使用附件实体值填充要素');
-- 3. 创建 HAS_RULE 关系(报告 → 规则)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('HAS_RULE', 200, 610);
-- 4. 创建 FOR_ELEMENT 关系(规则 → 要素定义)
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('FOR_ELEMENT', 610, 102);
-- 5. 创建 INPUT_FROM 关系(规则 → 实体)
INSERT INTO edges (edge_type, from_node_id, to_node_id, sort_order)
VALUES ('INPUT_FROM', 610, 500, 1);
-- 6. 添加输入属性
INSERT INTO edge_properties (edge_id, prop_key, prop_value)
SELECT id, 'input_key', 'entity' FROM edges
WHERE edge_type = 'INPUT_FROM' AND from_node_id = 610 AND to_node_id = 500;
INSERT INTO edge_properties (edge_id, prop_key, prop_value)
SELECT id, 'input_type', 'entity_ref' FROM edges
WHERE edge_type = 'INPUT_FROM' AND from_node_id = 610 AND to_node_id = 500;
-- 7. 执行规则:直接使用实体的 name 作为要素值
-- 获取实体值
SELECT name FROM nodes WHERE id = 500; -- 返回 '中国电建集团成都勘测设计研究院有限公司'
-- 8. 更新要素值节点
UPDATE node_properties
SET prop_value = '中国电建集团成都勘测设计研究院有限公司', updated_at = NOW()
WHERE node_id = (SELECT id FROM nodes WHERE node_key = 'RPT-001:project.reviewObject' AND node_type = 'VALUE')
AND prop_key = 'value_text';
操作:用户配置复杂规则(正则提取/LLM生成等) → 绑定输入 → 执行规则 → 填充要素值
步骤:
1. 创建 RULE 节点
2. 创建 HAS_RULE 关系(REPORT → RULE)
3. 创建 FOR_ELEMENT 关系(RULE → ELEMENT)
4. 配置规则输入(创建 INPUT_FROM 关系,可引用附件/实体/其他要素值)
5. 执行规则,获取结果
6. 更新 VALUE 节点的值
SQL 示例:
-- 1. 创建规则节点
INSERT INTO nodes (node_type, node_key, name, status)
VALUES ('RULE', 'RPT-001:basicInfo.projectCode', '项目编号提取规则', 'active')
RETURNING id; -- 假设返回 id = 600
-- 2. 添加规则属性
INSERT INTO node_properties (node_id, prop_key, prop_value, prop_json) VALUES
(600, 'rule_type', 'extraction', NULL),
(600, 'action_type', 'extract_pattern', NULL),
(600, 'action_config', NULL, '{"pattern": "项目编号[::]\\s*(\\S+)", "group": 1}'),
(600, 'dsl_content', 'EXTRACT pattern="项目编号[::]\\s*(\\S+)" FROM input1', NULL);
-- 3. 创建 HAS_RULE 关系
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('HAS_RULE', 200, 600);
-- 4. 创建 FOR_ELEMENT 关系
INSERT INTO edges (edge_type, from_node_id, to_node_id)
VALUES ('FOR_ELEMENT', 600, 102);
-- 5. 配置规则输入(引用附件)
INSERT INTO edges (edge_type, from_node_id, to_node_id, sort_order)
VALUES ('INPUT_FROM', 600, 400, 1);
-- 6. 添加输入属性
INSERT INTO edge_properties (edge_id, prop_key, prop_value)
SELECT id, 'input_key', 'input1' FROM edges
WHERE edge_type = 'INPUT_FROM' AND from_node_id = 600 AND to_node_id = 400;
-- 7. 执行规则后,更新 VALUE 节点
UPDATE node_properties
SET prop_value = 'BZ-0092-2024', updated_at = NOW()
WHERE node_id = 201 AND prop_key = 'value_text';
UPDATE node_properties
SET prop_value = 'true', updated_at = NOW()
WHERE node_id = 201 AND prop_key = 'is_filled';
INSERT INTO node_properties (node_id, prop_key, prop_value)
VALUES (201, 'fill_source', 'rule')
ON CONFLICT (node_id, prop_key) DO UPDATE SET prop_value = EXCLUDED.prop_value;
-- 8. 更新规则执行状态
UPDATE node_properties
SET prop_value = 'BZ-0092-2024', updated_at = NOW()
WHERE node_id = 600 AND prop_key = 'last_output_text';
INSERT INTO node_properties (node_id, prop_key, prop_value)
VALUES (600, 'last_run_status', 'success')
ON CONFLICT (node_id, prop_key) DO UPDATE SET prop_value = EXCLUDED.prop_value;
| 维度 | 传统设计(安源) | 图结构设计(灵越) |
|---|---|---|
| 表数量 | 140+ 张,持续增长 | 4 张核心表,固定不变 |
| 新增报告类型 | 需要新建表 | 只需新增数据 |
| 实体复用 | 每张表独立存储 | 全局唯一,引用ID |
| AI 操作 | 需要针对每张表编码 | 统一的图结构,易于理解 |
| 查询灵活性 | 需要修改表结构 | 通过视图组合 |
| 日期 | 版本 | 变更内容 | 作者 |
|---|---|---|---|
| 2026-02-11 | 1.0 | 初始版本,基于会议讨论的图数据库思想设计 | Claude Opus 4.5 |
| 2026-02-11 | 1.1 | 增加三层数据模型(原始文件→模板→报告)、业务流程、核心操作流程 | Claude Opus 4.5 |
| 2026-02-11 | 1.2 | 补充附件与样本同时上传、用户从附件添加规则的流程 | Claude Opus 4.5 |
| 2026-02-11 | 1.3 | 补充本文(样本文件)也可以提取实体并添加规则 | Claude Opus 4.5 |