1231 lines
31 KiB
Markdown
1231 lines
31 KiB
Markdown
# 关系数据库与通用数据平台技术方案
|
||
|
||
> 适用平台:旅行知识图谱管理系统 / 云游荔波及后续多项目
|
||
> 方案状态:拟实施
|
||
> 核心结论:PostgreSQL 作为权威业务数据源,FalkorDB 作为只读图谱投影;建设元数据驱动的通用数据管理后台。
|
||
|
||
## 1. 背景与问题
|
||
|
||
当前平台已经同时部署 PostgreSQL 与 FalkorDB:
|
||
|
||
- PostgreSQL 保存项目、账号权限、采集批次、原始记录、候选实体、审核记录、发布任务等管理数据。
|
||
- FalkorDB 保存最终实体、关系、路线、空间片区和图谱浏览数据。
|
||
- 管理后台已有项目工作区、数据采集、审核入藏、图谱发布、知识广场等模块。
|
||
- 云游荔波已经形成酒店、美食、景区、交通、公交线路、公交站、片区等真实业务数据。
|
||
|
||
当前主要问题不是缺少数据库,而是两个数据库的职责尚未清晰:
|
||
|
||
1. 最终业务详情过度依赖 FalkorDB 节点属性。
|
||
2. 酒店房型、设施、评论、团购等一对多数据不适合塞入图节点。
|
||
3. 一些采集脚本直接双写 PostgreSQL 与 FalkorDB,失败时可能产生数据漂移。
|
||
4. 当前后台偏向候选实体和图谱审核,缺少正式业务数据的通用增删改查。
|
||
5. 不同项目未来会拥有不同数据表,为每个项目编写固定后台不可持续。
|
||
6. 导入、导出、变更历史、恢复和项目隔离需要形成统一机制。
|
||
7. 数据库 DDL 目前主要由应用启动时的 `CREATE TABLE IF NOT EXISTS` 管理,不适合后续复杂演进。
|
||
|
||
因此,本方案不新增第三套数据库,也不推翻现有图谱,而是在现有双数据库基础上重新划分数据职责。
|
||
|
||
## 2. 建设目标
|
||
|
||
### 2.1 目标
|
||
|
||
- PostgreSQL 成为酒店、美食、景区、交通等业务数据的唯一权威数据源。
|
||
- FalkorDB 保留实体关系、空间关系、路线关系和 ToB 图谱展示能力。
|
||
- 建设可服务多个项目的通用数据管理后台。
|
||
- 通用后台通过元数据自动生成列表、表单、筛选、校验和导入导出能力。
|
||
- 所有数据修改先进入 PostgreSQL,再通过可靠事件同步到 FalkorDB。
|
||
- 保留高德、携程、大众点评等每一条来源记录及字段级溯源。
|
||
- 支持软删除、恢复、版本控制、操作审计和批次回滚。
|
||
- 保留现有荔波地图、详情侧栏、公交线路和图谱浏览器。
|
||
|
||
### 2.2 非目标
|
||
|
||
- 不把通用后台建设成可执行任意 SQL 的数据库管理器。
|
||
- 不允许普通用户直接创建、删除任意 PostgreSQL 物理表。
|
||
- 不把评论、价格方案、实时房态等全部建成图节点。
|
||
- 不采用 PostgreSQL 与 FalkorDB 的双向同步。
|
||
- 不允许 FalkorDB 成为可独立修改的第二权威数据源。
|
||
- 第一阶段不替换现有项目业务页面,只替换其底层数据来源。
|
||
|
||
## 3. 架构决策
|
||
|
||
### ADR-01:PostgreSQL 是 System of Record
|
||
|
||
所有正式业务数据、审核结果、来源明细、评论、房型、设施、套餐和修改记录,以 PostgreSQL 为准。
|
||
|
||
### ADR-02:FalkorDB 是派生图谱
|
||
|
||
FalkorDB 只保存适合关系查询和图谱展示的节点、边及少量检索属性。图谱可以重建,不单独承担数据恢复责任。
|
||
|
||
### ADR-03:采用元数据驱动的通用后台
|
||
|
||
后台只允许管理注册到平台的数据模型。字段类型、表单、列表列、校验、权限、导入模板和图谱映射均由元数据控制。
|
||
|
||
### ADR-04:采用关系字段与 JSONB 混合模型
|
||
|
||
- 稳定、高频筛选、需要唯一约束或关联的字段使用正式关系型列。
|
||
- 项目临时扩展、低频使用的字段存入 `extra_data JSONB`。
|
||
- 稳定后的扩展字段通过迁移提升为正式列。
|
||
- 不采用纯 EAV,也不把所有业务字段全部放进 JSONB。
|
||
|
||
### ADR-05:使用事务 Outbox 同步图谱
|
||
|
||
业务写入和图谱事件在同一个 PostgreSQL 事务提交。独立 Worker 异步、幂等地更新 FalkorDB。
|
||
|
||
### ADR-06:默认软删除
|
||
|
||
业务数据默认只允许软删除。永久删除需要高级权限、依赖检查和二次确认。
|
||
|
||
## 4. 目标架构
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Sources["数据来源"]
|
||
A1["高德 API / CSV"]
|
||
A2["携程"]
|
||
A3["大众点评"]
|
||
A4["公交 Excel"]
|
||
A5["人工录入"]
|
||
end
|
||
|
||
subgraph Platform["FastAPI 数据平台"]
|
||
B1["导入暂存与校验"]
|
||
B2["实体融合与审核"]
|
||
B3["通用 CRUD 服务"]
|
||
B4["导出任务"]
|
||
B5["Outbox Worker"]
|
||
B6["统一查询聚合"]
|
||
end
|
||
|
||
subgraph PG["PostgreSQL 16 + PostGIS"]
|
||
C1["元数据与权限"]
|
||
C2["标准实体与业务扩展表"]
|
||
C3["来源、证据与审核"]
|
||
C4["空间与路线缓存"]
|
||
C5["审计、版本、导入导出任务"]
|
||
C6["图谱同步 Outbox"]
|
||
end
|
||
|
||
subgraph Graph["FalkorDB"]
|
||
D1["实体节点"]
|
||
D2["片区与行政区"]
|
||
D3["公交、景区、分类关系"]
|
||
D4["图谱发布版本"]
|
||
end
|
||
|
||
subgraph UI["React 管理后台"]
|
||
E1["通用数据中心"]
|
||
E2["项目业务页面"]
|
||
E3["图谱浏览器"]
|
||
end
|
||
|
||
Sources --> B1
|
||
B1 --> B2
|
||
B2 --> C2
|
||
B3 --> C2
|
||
B4 --> C2
|
||
C2 --> C6
|
||
C6 --> B5
|
||
B5 --> Graph
|
||
C2 --> B6
|
||
Graph --> B6
|
||
B3 --> E1
|
||
B6 --> E2
|
||
Graph --> E3
|
||
```
|
||
|
||
## 5. 数据分层与职责
|
||
|
||
| 数据层 | PostgreSQL | FalkorDB |
|
||
| --- | --- | --- |
|
||
| 项目、租户、权限 | 权威保存 | 不保存 |
|
||
| 数据模型与字段配置 | 权威保存 | 不保存 |
|
||
| 正式业务实体 | 完整保存 | 保存核心节点投影 |
|
||
| 酒店房型、报价、设施、政策 | 完整保存 | 默认不建节点 |
|
||
| 美食套餐、菜系、营业信息 | 完整保存 | 默认不建节点 |
|
||
| 评论、评价标签 | 完整保存 | 可选同步汇总标签 |
|
||
| 多来源原始记录 | 完整保存 | 默认不保存 |
|
||
| 融合、审核、版本历史 | 完整保存 | 只保存最终结果 |
|
||
| 坐标、行政区、H3 片区 | 完整保存并支持空间查询 | 建立空间关系 |
|
||
| 景区、子景点、分类关系 | 完整保存 | 建立图关系 |
|
||
| 公交线路与站序 | 完整保存 | 建立线路图关系 |
|
||
| 地图列表与详情 | 主要读取 | 补充关系 |
|
||
| 图谱查询和 ToB 展示 | 辅助 | 主要读取 |
|
||
| CSV/XLSX 数据交付 | 唯一导出来源 | 只导出图节点和边 |
|
||
|
||
## 6. PostgreSQL 技术基线
|
||
|
||
### 6.1 数据库组件
|
||
|
||
- PostgreSQL 16。
|
||
- PostGIS 3.x,用于点、范围、行政边界和距离查询。
|
||
- `pg_trgm`,用于规范化名称相似检索。
|
||
- `pgcrypto` 或 PostgreSQL UUID 能力,用于稳定 ID。
|
||
- Alembic,负责版本化数据库迁移。
|
||
- psycopg 连接池继续复用。
|
||
|
||
当前 Docker 使用 `postgres:16`,目标实施时应切换到带 PostGIS 的兼容镜像,或在受控镜像中安装 PostGIS。现有 `scripts/sql/001_kg_core_spatial_schema.sql` 已声明 PostGIS 依赖,但运行环境需要同步补齐。
|
||
|
||
### 6.2 Schema 边界
|
||
|
||
建议继续使用一个平台 Schema,例如 `kg_admin_new2`,通过 `tenant_id + project_id` 进行逻辑隔离。后续租户规模扩大后,再评估独立 Schema 或独立数据库。
|
||
|
||
所有平台注册业务表必须至少包含:
|
||
|
||
```text
|
||
tenant_id
|
||
project_id
|
||
id / entity_id
|
||
status
|
||
version
|
||
created_at
|
||
created_by
|
||
updated_at
|
||
updated_by
|
||
deleted_at
|
||
deleted_by
|
||
delete_reason
|
||
extra_data
|
||
```
|
||
|
||
### 6.3 主键与业务唯一键
|
||
|
||
- 关系数据库主键:UUID `entity_id`。
|
||
- 兼容现有图谱:保留 `natural_key`。
|
||
- 图谱节点统一写入 `entity_id` 和 `natural_key`。
|
||
- 外部来源唯一键:`tenant_id + project_id + source_code + source_type + external_id`。
|
||
- 合并后废弃的 ID 进入重定向表,不能立即删除。
|
||
|
||
## 7. 元数据驱动的通用数据后台
|
||
|
||
### 7.1 元数据表
|
||
|
||
建议新增:
|
||
|
||
| 表 | 作用 |
|
||
| --- | --- |
|
||
| `data_models` | 注册后台可管理的数据模型及物理表 |
|
||
| `data_fields` | 字段类型、显示、校验、筛选和图谱属性配置 |
|
||
| `data_model_relations` | 模型间一对一、一对多、多对多关系 |
|
||
| `data_dictionaries` | 字典定义 |
|
||
| `data_dictionary_items` | 字典选项 |
|
||
| `data_views` | 用户保存的列、筛选、排序视图 |
|
||
| `data_validation_rules` | 可复用校验规则 |
|
||
| `data_model_permissions` | 模型级和操作级权限 |
|
||
| `graph_mappings` | 模型到节点、属性和边的映射 |
|
||
|
||
### 7.2 `data_models` 核心字段
|
||
|
||
```text
|
||
model_id
|
||
tenant_id
|
||
project_id
|
||
model_code
|
||
display_name
|
||
physical_table
|
||
primary_key
|
||
title_field
|
||
description
|
||
model_kind
|
||
map_enabled
|
||
graph_enabled
|
||
import_enabled
|
||
export_enabled
|
||
soft_delete_enabled
|
||
status
|
||
created_at
|
||
updated_at
|
||
```
|
||
|
||
`physical_table` 必须来自服务器允许列表,不能由前端传递任意表名。
|
||
|
||
### 7.3 `data_fields` 核心字段
|
||
|
||
```text
|
||
field_id
|
||
model_id
|
||
field_code
|
||
physical_column
|
||
display_name
|
||
data_type
|
||
required
|
||
unique_enabled
|
||
searchable
|
||
sortable
|
||
editable
|
||
visible_in_list
|
||
visible_in_detail
|
||
importable
|
||
exportable
|
||
dictionary_code
|
||
validation_jsonb
|
||
ui_jsonb
|
||
graph_property
|
||
display_order
|
||
status
|
||
```
|
||
|
||
支持的数据类型至少包括:
|
||
|
||
- 单行文本、多行文本。
|
||
- 整数、小数、金额、百分比。
|
||
- 布尔值。
|
||
- 日期、时间、日期时间。
|
||
- 单选、多选、字典。
|
||
- URL、图片 URL、电话。
|
||
- 经纬度、空间点。
|
||
- JSON。
|
||
- 外键、关联列表。
|
||
|
||
### 7.4 通用后台页面
|
||
|
||
建议在主菜单增加“业务数据中心”:
|
||
|
||
1. 数据模型。
|
||
2. 数据表。
|
||
3. 数据导入。
|
||
4. 数据导出。
|
||
5. 数据质量。
|
||
6. 修改历史。
|
||
7. 图谱同步。
|
||
|
||
数据表页面自动支持:
|
||
|
||
- 服务端分页和游标分页。
|
||
- 多字段筛选。
|
||
- 模糊搜索。
|
||
- 排序和自定义列。
|
||
- 固定列与列宽。
|
||
- 新增、详情、编辑。
|
||
- 批量修改。
|
||
- 批量软删除与恢复。
|
||
- 保存查询视图。
|
||
- 查看关联数据。
|
||
- 查看来源与证据。
|
||
- 查看图谱节点。
|
||
- 重新同步图谱。
|
||
|
||
### 7.5 安全边界
|
||
|
||
通用后台不开放:
|
||
|
||
- 任意 SQL。
|
||
- 任意 Schema 或物理表访问。
|
||
- 系统表修改。
|
||
- 直接删除物理表。
|
||
- 任意修改主键或列类型。
|
||
- 绕过项目范围查询。
|
||
- 直接修改 FalkorDB。
|
||
|
||
逻辑增加字段由元数据服务受控执行;需要创建正式物理列时,由 Alembic 迁移完成。
|
||
|
||
## 8. 正式业务数据模型
|
||
|
||
### 8.1 公共实体层
|
||
|
||
#### `business_entities`
|
||
|
||
保存跨项目、跨类型通用字段:
|
||
|
||
```text
|
||
entity_id UUID PK
|
||
tenant_id
|
||
project_id
|
||
entity_type
|
||
natural_key
|
||
name
|
||
normalized_name
|
||
description
|
||
category_code
|
||
status
|
||
confidence
|
||
longitude
|
||
latitude
|
||
geom geometry(Point, 4326)
|
||
address
|
||
district
|
||
adcode
|
||
h3_r9
|
||
h3_r10
|
||
phone
|
||
extra_data JSONB
|
||
version
|
||
created_at / created_by
|
||
updated_at / updated_by
|
||
deleted_at / deleted_by / delete_reason
|
||
```
|
||
|
||
关键约束:
|
||
|
||
```text
|
||
UNIQUE (tenant_id, project_id, natural_key)
|
||
CHECK (longitude BETWEEN -180 AND 180)
|
||
CHECK (latitude BETWEEN -90 AND 90)
|
||
```
|
||
|
||
#### `entity_source_records`
|
||
|
||
保存高德、携程、大众点评等来源记录:
|
||
|
||
```text
|
||
source_record_id
|
||
tenant_id
|
||
project_id
|
||
entity_id
|
||
source_code
|
||
source_type
|
||
external_id
|
||
source_url
|
||
source_name
|
||
raw_data JSONB
|
||
normalized_data JSONB
|
||
match_confidence
|
||
match_rule
|
||
review_status
|
||
collected_at
|
||
effective_at
|
||
expired_at
|
||
```
|
||
|
||
#### `entity_field_values`
|
||
|
||
仅用于字段级溯源和融合结果,不替代正式业务列:
|
||
|
||
```text
|
||
entity_id
|
||
field_code
|
||
final_value JSONB
|
||
selected_source_record_id
|
||
selection_rule
|
||
confidence
|
||
reviewed_by
|
||
reviewed_at
|
||
```
|
||
|
||
#### 其他公共表
|
||
|
||
- `entity_aliases`
|
||
- `entity_images`
|
||
- `entity_categories`
|
||
- `entity_relations`
|
||
- `entity_merge_records`
|
||
- `entity_redirects`
|
||
- `entity_versions`
|
||
|
||
### 8.2 酒店
|
||
|
||
- `hotel_profiles`
|
||
- `hotel_room_types`
|
||
- `hotel_room_offers`
|
||
- `hotel_facilities`
|
||
- `hotel_policies`
|
||
|
||
房型、报价和设施必须是一对多明细,不再拼接成长字符串保存在图节点。
|
||
|
||
实时性较强的“仅剩几间”等信息默认不进入长期知识库;如业务需要保留,只能写入带 `observed_at` 和 `expires_at` 的报价快照。
|
||
|
||
### 8.3 美食
|
||
|
||
- `restaurant_profiles`
|
||
- `restaurant_deals`
|
||
- `restaurant_cuisines`
|
||
- `restaurant_business_hours`
|
||
|
||
团购套餐独立保存名称、图片、现价、原价、折扣、规则和采集时间,不把多个套餐塞入单个字段。
|
||
|
||
### 8.4 景区
|
||
|
||
- `scenic_profiles`
|
||
- `scenic_children`
|
||
- `scenic_levels`
|
||
- `scenic_ticket_policies`
|
||
|
||
明确区分:
|
||
|
||
- 独立景区。
|
||
- 景区子景点。
|
||
- 纪念馆、博物馆等旅游目的地。
|
||
- 不应入库的广场、商业设施等异常候选。
|
||
|
||
### 8.5 交通
|
||
|
||
- `transport_stops`
|
||
- `bus_routes`
|
||
- `bus_route_directions`
|
||
- `bus_route_stops`
|
||
- `route_geometries`
|
||
|
||
站序表至少保存:
|
||
|
||
```text
|
||
route_direction_id
|
||
stop_id
|
||
stop_order
|
||
arrival_offset
|
||
distance_from_previous
|
||
```
|
||
|
||
### 8.6 评论与标签
|
||
|
||
酒店与美食共用:
|
||
|
||
- `reviews`
|
||
- `review_tags`
|
||
- `entity_review_tags`
|
||
|
||
评论保存来源、评分、正文、采集时间、匿名用户标识和原始记录关联。评价摘要由 PostgreSQL 聚合生成,不把“样本数、平均分”硬编码进图谱。
|
||
|
||
## 9. 空间数据设计
|
||
|
||
现有 `kg_place_spatial`、`kg_geo_cells`、`kg_route_metrics` 可以增量复用。
|
||
|
||
目标空间结构:
|
||
|
||
```text
|
||
业务实体
|
||
→ PostGIS Point
|
||
→ H3 R9/R10
|
||
→ GeoCell(前端显示为“片区”)
|
||
→ Area(荔波县)
|
||
```
|
||
|
||
PostgreSQL 负责:
|
||
|
||
- 荔波县边界内判断。
|
||
- 地图视窗范围查询。
|
||
- 半径检索。
|
||
- H3 片区归属。
|
||
- 路线距离缓存。
|
||
- 导入时坐标异常检查。
|
||
|
||
FalkorDB 负责投影:
|
||
|
||
```text
|
||
Entity -[IN_H3_R9]-> GeoCell
|
||
GeoCell -[LOCATED_IN]-> Area
|
||
Entity -[LOCATED_IN]-> Area
|
||
```
|
||
|
||
`GeoCell` 只是技术标签,管理后台和图例统一显示“片区”。
|
||
|
||
## 10. 通用 CRUD API
|
||
|
||
统一前缀建议为:
|
||
|
||
```text
|
||
/v1/admin/data-platform
|
||
```
|
||
|
||
### 10.1 模型接口
|
||
|
||
```text
|
||
GET /models
|
||
GET /models/{model_code}
|
||
POST /models
|
||
PATCH /models/{model_code}
|
||
GET /models/{model_code}/fields
|
||
POST /models/{model_code}/fields
|
||
PATCH /models/{model_code}/fields/{field_code}
|
||
```
|
||
|
||
普通数据管理员无权修改物理表映射。
|
||
|
||
### 10.2 记录接口
|
||
|
||
```text
|
||
GET /models/{model_code}/records
|
||
POST /models/{model_code}/records
|
||
GET /models/{model_code}/records/{record_id}
|
||
PATCH /models/{model_code}/records/{record_id}
|
||
DELETE /models/{model_code}/records/{record_id}
|
||
POST /models/{model_code}/records/{record_id}/restore
|
||
POST /models/{model_code}/records/bulk-update
|
||
POST /models/{model_code}/records/bulk-delete
|
||
GET /models/{model_code}/records/{record_id}/history
|
||
GET /models/{model_code}/records/{record_id}/relations
|
||
```
|
||
|
||
### 10.3 查询协议
|
||
|
||
禁止接收原始 SQL。统一使用安全查询 DSL:
|
||
|
||
```json
|
||
{
|
||
"filters": [
|
||
{"field": "status", "operator": "eq", "value": "active"},
|
||
{"field": "name", "operator": "contains", "value": "酒店"}
|
||
],
|
||
"sort": [{"field": "updated_at", "direction": "desc"}],
|
||
"page_size": 50,
|
||
"cursor": null
|
||
}
|
||
```
|
||
|
||
后端只能使用 `data_fields.searchable=true` 的字段生成参数化 SQL。
|
||
|
||
### 10.4 并发控制
|
||
|
||
所有修改携带版本:
|
||
|
||
```text
|
||
If-Match: 12
|
||
```
|
||
|
||
SQL 更新条件:
|
||
|
||
```text
|
||
WHERE entity_id = :id AND version = :expected_version
|
||
```
|
||
|
||
更新成功后 `version + 1`。版本冲突返回 HTTP 409,并提供当前记录。
|
||
|
||
## 11. 删除、恢复与版本
|
||
|
||
### 11.1 默认软删除
|
||
|
||
删除动作写入:
|
||
|
||
```text
|
||
status = deleted
|
||
deleted_at
|
||
deleted_by
|
||
delete_reason
|
||
version = version + 1
|
||
```
|
||
|
||
同时写入图谱删除事件。FalkorDB 删除节点前先删除或改写关联边。
|
||
|
||
### 11.2 恢复
|
||
|
||
恢复前重新检查:
|
||
|
||
- 唯一键是否被其他实体占用。
|
||
- 外部来源记录是否仍然有效。
|
||
- 关联实体是否存在。
|
||
- 图谱映射是否仍然启用。
|
||
|
||
### 11.3 永久删除
|
||
|
||
仅系统管理员可执行,并要求:
|
||
|
||
- 依赖检查。
|
||
- 二次确认。
|
||
- 审计记录。
|
||
- 数据备份或快照。
|
||
- 明确不可恢复提示。
|
||
|
||
## 12. 批量导入
|
||
|
||
### 12.1 导入状态机
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> uploaded
|
||
uploaded --> mapping
|
||
mapping --> validating
|
||
validating --> preview_ready
|
||
preview_ready --> committing
|
||
committing --> graph_syncing
|
||
graph_syncing --> completed
|
||
validating --> failed
|
||
committing --> failed
|
||
failed --> validating
|
||
completed --> rolled_back
|
||
```
|
||
|
||
### 12.2 导入表
|
||
|
||
在现有 `import_templates`、`mapping_profiles`、`import_batches`、`raw_records` 基础上补充:
|
||
|
||
- `import_jobs`
|
||
- `import_files`
|
||
- `import_staging_rows`
|
||
- `import_validation_errors`
|
||
- `import_mutations`
|
||
- `import_job_events`
|
||
|
||
现有 `candidate_entities` 与 `candidate_relations` 继续承担候选审核层,不直接替代正式实体表。
|
||
|
||
### 12.3 导入步骤
|
||
|
||
1. 选择项目和数据模型。
|
||
2. 下载元数据自动生成的 CSV/XLSX 模板。
|
||
3. 上传文件并计算 SHA-256。
|
||
4. 自动识别表头和字符编码。
|
||
5. 用户确认字段映射。
|
||
6. 暂存原始行,不直接写正式表。
|
||
7. 校验字段类型、必填、字典、外键、坐标和行政区。
|
||
8. 执行来源 ID、自然键、名称地址和空间距离去重。
|
||
9. 预览新增、更新、跳过、冲突和失败数量。
|
||
10. 用户确认后写入正式表。
|
||
11. 同事务写入 Outbox。
|
||
12. 后台同步图谱并记录任务状态。
|
||
|
||
### 12.4 导入模式
|
||
|
||
| 模式 | 行为 | 默认权限 |
|
||
| --- | --- | --- |
|
||
| `append` | 只新增,不修改已有记录 | 数据管理员 |
|
||
| `upsert` | 按唯一标识新增或更新 | 数据管理员,推荐默认 |
|
||
| `snapshot` | 文件作为完整快照,可停用缺失记录 | 项目管理员 |
|
||
|
||
### 12.5 幂等与去重
|
||
|
||
优先级:
|
||
|
||
```text
|
||
entity_id
|
||
→ source_code + source_external_id
|
||
→ 高德 POI ID
|
||
→ natural_key
|
||
→ 规范化名称 + 地址
|
||
→ 名称相似度 + 地址相似度 + 坐标距离
|
||
```
|
||
|
||
同一文件哈希、同一模型和同一导入模式重复提交时,默认返回已有任务,不重复写入。
|
||
|
||
### 12.6 回滚
|
||
|
||
每个正式写入动作记录在 `import_mutations`:
|
||
|
||
```text
|
||
insert / update / soft_delete
|
||
record_id
|
||
before_jsonb
|
||
after_jsonb
|
||
```
|
||
|
||
回滚不是数据库全量恢复,而是按批次生成反向业务变更,并再次产生图谱同步事件。
|
||
|
||
## 13. 批量导出
|
||
|
||
### 13.1 导出能力
|
||
|
||
- 导出当前表。
|
||
- 导出当前筛选结果。
|
||
- 导出勾选记录。
|
||
- 导出主表及关联明细。
|
||
- 导出完整项目数据包。
|
||
- 导出图谱节点与边。
|
||
- 支持 CSV、XLSX、ZIP。
|
||
- 大任务异步执行。
|
||
|
||
### 13.2 导出任务表
|
||
|
||
- `export_jobs`
|
||
- `export_job_files`
|
||
- `export_job_events`
|
||
|
||
任务保存:
|
||
|
||
```text
|
||
tenant_id
|
||
project_id
|
||
model_code
|
||
filter_jsonb
|
||
field_list
|
||
include_relations
|
||
format
|
||
status
|
||
row_count
|
||
file_path / object_key
|
||
sha256
|
||
created_by
|
||
expires_at
|
||
```
|
||
|
||
### 13.3 云游荔波完整导出结构
|
||
|
||
```text
|
||
云游荔波数据导出_YYYYMMDD_HHMMSS.zip
|
||
├── manifest.json
|
||
├── 数据字典.csv
|
||
├── 酒店
|
||
│ ├── 酒店实体.csv
|
||
│ ├── 酒店来源记录.csv
|
||
│ ├── 房型.csv
|
||
│ ├── 报价方案.csv
|
||
│ ├── 服务设施.csv
|
||
│ ├── 评论.csv
|
||
│ └── 评价标签.csv
|
||
├── 美食
|
||
│ ├── 美食实体.csv
|
||
│ ├── 美食来源记录.csv
|
||
│ ├── 团购套餐.csv
|
||
│ ├── 评论.csv
|
||
│ └── 评价标签.csv
|
||
├── 景区
|
||
│ ├── 景区实体.csv
|
||
│ ├── 子景点.csv
|
||
│ └── 景区关系.csv
|
||
├── 交通
|
||
│ ├── 交通站点.csv
|
||
│ ├── 公交线路.csv
|
||
│ ├── 公交方向.csv
|
||
│ └── 线路站序.csv
|
||
└── 图谱
|
||
├── 节点.csv
|
||
└── 关系.csv
|
||
```
|
||
|
||
`manifest.json` 至少包含项目、数据模型版本、筛选条件、导出时间、记录数和各文件哈希。
|
||
|
||
导出使用 PostgreSQL `REPEATABLE READ` 快照,保证同一个 ZIP 内各表数据版本一致。
|
||
|
||
## 14. 图谱投影与可靠同步
|
||
|
||
### 14.1 Outbox 表
|
||
|
||
建议新增 `graph_sync_outbox`:
|
||
|
||
```text
|
||
event_id UUID PK
|
||
tenant_id
|
||
project_id
|
||
aggregate_type
|
||
aggregate_id
|
||
event_type
|
||
aggregate_version
|
||
payload_version
|
||
payload JSONB
|
||
status
|
||
retry_count
|
||
next_retry_at
|
||
created_at
|
||
processed_at
|
||
last_error
|
||
```
|
||
|
||
事件类型至少包括:
|
||
|
||
```text
|
||
entity.upsert
|
||
entity.delete
|
||
relation.upsert
|
||
relation.delete
|
||
model.rebuild
|
||
```
|
||
|
||
### 14.2 Worker
|
||
|
||
Worker 使用:
|
||
|
||
```text
|
||
SELECT ... FOR UPDATE SKIP LOCKED
|
||
```
|
||
|
||
批量领取待处理事件,按 `tenant_id + project_id + aggregate_id + version` 幂等处理。
|
||
|
||
失败策略:
|
||
|
||
- 指数退避。
|
||
- 最大重试次数。
|
||
- 超限进入 dead-letter 状态。
|
||
- 管理后台支持查看错误和人工重试。
|
||
|
||
### 14.3 同步状态
|
||
|
||
新增 `graph_sync_state`:
|
||
|
||
```text
|
||
tenant_id
|
||
project_id
|
||
aggregate_type
|
||
aggregate_id
|
||
relational_version
|
||
graph_version
|
||
sync_status
|
||
last_synced_at
|
||
last_error
|
||
```
|
||
|
||
通用列表显示:
|
||
|
||
- 已同步。
|
||
- 待同步。
|
||
- 同步中。
|
||
- 同步失败。
|
||
- 图谱已落后。
|
||
|
||
### 14.4 图谱映射
|
||
|
||
`graph_mappings` 配置:
|
||
|
||
```text
|
||
model_code
|
||
node_label
|
||
node_key_field
|
||
node_title_field
|
||
property_mapping JSONB
|
||
relation_mapping JSONB
|
||
enabled
|
||
mapping_version
|
||
```
|
||
|
||
示例:
|
||
|
||
```text
|
||
hotel → Hotel
|
||
restaurant → Restaurant
|
||
scenic → ScenicArea / Attraction
|
||
transport_stop → TransportStop / BusStop
|
||
bus_route → BusRoute
|
||
geo_cell → GeoCell(UI 显示“片区”)
|
||
area → Area
|
||
```
|
||
|
||
### 14.5 一致性原则
|
||
|
||
- PostgreSQL 写成功、图谱暂时失败:业务数据仍然有效,图谱显示待同步。
|
||
- FalkorDB 不允许反向覆盖 PostgreSQL。
|
||
- 定时执行全量对账:节点数量、关系数量、版本、孤立节点和悬空边。
|
||
- 必须提供按项目重建图谱能力。
|
||
|
||
## 15. 查询与页面数据来源
|
||
|
||
| 场景 | 数据来源 |
|
||
| --- | --- |
|
||
| 通用表格列表 | PostgreSQL |
|
||
| 实体详情侧栏 | PostgreSQL |
|
||
| 酒店房型、政策、设施 | PostgreSQL |
|
||
| 美食团购、评论、标签 | PostgreSQL |
|
||
| 地图 POI 与分类统计 | PostgreSQL + PostGIS |
|
||
| 周边地点 | PostgreSQL 空间查询,必要时叠加路线缓存 |
|
||
| 图谱浏览器 | FalkorDB |
|
||
| 图关系问答 | FalkorDB |
|
||
| 属性问答 | PostgreSQL |
|
||
| 混合业务问答 | 查询聚合服务组合两边结果 |
|
||
| CSV/XLSX 导出 | PostgreSQL |
|
||
|
||
现有 `http://localhost:8102/admin/plaza/overview` 的视觉设计可以保持不变,只把详情、筛选和统计接口逐步切换到 PostgreSQL。
|
||
|
||
## 16. 权限与项目隔离
|
||
|
||
### 16.1 角色
|
||
|
||
| 角色 | 权限 |
|
||
| --- | --- |
|
||
| 系统管理员 | 所有项目、模型、迁移、永久删除 |
|
||
| 项目管理员 | 本项目模型配置、导入导出、批量操作 |
|
||
| 数据管理员 | 本项目增删改查、导入、导出 |
|
||
| 审核人员 | 候选审核、融合、恢复 |
|
||
| 普通用户 | 查询及受限导出 |
|
||
| 只读用户 | 仅查看 |
|
||
|
||
### 16.2 强制隔离
|
||
|
||
- 所有请求通过 `ProjectContext` 获取 `tenant_id`、`project_id`。
|
||
- 后端自动拼接项目条件,禁止客户端自行指定任意租户。
|
||
- 关键表使用组合索引和组合唯一约束。
|
||
- 第二阶段评估 PostgreSQL Row Level Security。
|
||
- 文件导入、导出和对象存储路径同样按租户、项目隔离。
|
||
|
||
## 17. 审计、质量与可观测性
|
||
|
||
### 17.1 操作审计
|
||
|
||
新增统一 `data_change_logs`:
|
||
|
||
```text
|
||
change_id
|
||
tenant_id
|
||
project_id
|
||
model_code
|
||
record_id
|
||
operation
|
||
before_jsonb
|
||
after_jsonb
|
||
changed_fields
|
||
actor
|
||
request_id
|
||
reason
|
||
created_at
|
||
```
|
||
|
||
敏感字段可配置脱敏,不在普通审计页面显示原值。
|
||
|
||
### 17.2 数据质量
|
||
|
||
质量规则至少包括:
|
||
|
||
- 必填字段。
|
||
- 唯一标识。
|
||
- 荔波县行政区和边界。
|
||
- 坐标异常。
|
||
- 电话格式。
|
||
- 来源 ID 重复。
|
||
- 名称地址相似重复。
|
||
- 孤立房型、评论、站序。
|
||
- 未同步图谱。
|
||
- 图谱孤立节点和悬空边。
|
||
|
||
### 17.3 指标
|
||
|
||
- CRUD 请求量、错误率、P95 延迟。
|
||
- 导入行数、失败行数、重复率。
|
||
- 导出任务耗时和文件大小。
|
||
- Outbox 积压数量和最老事件时间。
|
||
- 图谱同步成功率和延迟。
|
||
- 每项目实体数、来源覆盖率和字段完整率。
|
||
|
||
## 18. 索引与性能
|
||
|
||
基础索引:
|
||
|
||
```text
|
||
(tenant_id, project_id, entity_type, status)
|
||
(tenant_id, project_id, updated_at DESC)
|
||
UNIQUE (tenant_id, project_id, natural_key)
|
||
UNIQUE (tenant_id, project_id, source_code, source_type, external_id)
|
||
GIST (geom)
|
||
GIN (normalized_name gin_trgm_ops)
|
||
(entity_id, created_at DESC) on reviews
|
||
(route_direction_id, stop_order) on bus_route_stops
|
||
```
|
||
|
||
原则:
|
||
|
||
- 列表接口默认服务端分页,默认 50 条,单页上限 200。
|
||
- 大规模列表优先游标分页。
|
||
- 只有注册为可搜索的 JSONB 字段才允许建立表达式索引。
|
||
- 评论和来源记录达到千万级后,再按项目或时间分区。
|
||
- 导入与图谱同步采用批量写入,避免逐行网络往返。
|
||
- 地图接口只返回当前视窗和当前缩放级别需要的数据。
|
||
|
||
## 19. 代码结构调整
|
||
|
||
建议新增:
|
||
|
||
```text
|
||
app/
|
||
├── api/
|
||
│ └── data_platform/
|
||
│ ├── models.py
|
||
│ ├── records.py
|
||
│ ├── imports.py
|
||
│ ├── exports.py
|
||
│ └── graph_sync.py
|
||
├── data_platform/
|
||
│ ├── metadata_service.py
|
||
│ ├── query_compiler.py
|
||
│ ├── record_service.py
|
||
│ ├── validation_service.py
|
||
│ ├── import_service.py
|
||
│ ├── export_service.py
|
||
│ ├── graph_projector.py
|
||
│ └── repositories/
|
||
├── workers/
|
||
│ ├── graph_sync_worker.py
|
||
│ ├── import_worker.py
|
||
│ └── export_worker.py
|
||
└── migrations/
|
||
```
|
||
|
||
前端建议新增:
|
||
|
||
```text
|
||
admin-web/src/panels/data-platform/
|
||
├── DataModelList.tsx
|
||
├── DataTableView.tsx
|
||
├── DynamicRecordForm.tsx
|
||
├── RecordDetail.tsx
|
||
├── ImportWizard.tsx
|
||
├── ExportCenter.tsx
|
||
├── ChangeHistory.tsx
|
||
└── GraphSyncMonitor.tsx
|
||
```
|
||
|
||
现有 `app/db.py` 暂时保留查询函数,但新增数据平台不应继续把所有 SQL 和 DDL写入单一文件。
|
||
|
||
## 20. 数据迁移路线
|
||
|
||
### 阶段 0:冻结基线与盘点
|
||
|
||
- 导出 PostgreSQL 与 FalkorDB 快照。
|
||
- 统计每个项目的实体、关系、来源和孤立节点。
|
||
- 冻结当前节点标签、自然键和前端所需字段。
|
||
- 形成字段映射矩阵。
|
||
|
||
云游荔波当前界面可见基线可作为参考:
|
||
|
||
- 酒店约 1,220。
|
||
- 美食约 1,115。
|
||
- 交通设施约 379。
|
||
- 景点约 100。
|
||
- POI 总量约 2,799。
|
||
- 片区约 384。
|
||
|
||
最终迁移验收必须以迁移开始时的实时扫描为准。
|
||
|
||
### 阶段 1:数据库基础
|
||
|
||
- 引入 Alembic。
|
||
- 补齐 PostGIS 环境。
|
||
- 建立元数据表、正式实体表、Outbox、审计和任务表。
|
||
- 注册现有业务表,不切换页面。
|
||
|
||
### 阶段 2:数据回填
|
||
|
||
- 从现有 CSV、PostgreSQL 候选层和 FalkorDB 回填正式实体表。
|
||
- 为所有实体分配稳定 `entity_id`。
|
||
- 保留现有 `natural_key`。
|
||
- 建立来源记录和字段溯源。
|
||
- 重建实体与片区、行政区关系。
|
||
- 输出迁移冲突清单,不静默覆盖。
|
||
|
||
数据取值优先级:
|
||
|
||
```text
|
||
人工审核后的最终值
|
||
→ 已确认融合值
|
||
→ 高置信结构化来源
|
||
→ 当前图谱属性
|
||
→ 原始采集值
|
||
```
|
||
|
||
### 阶段 3:通用后台 MVP
|
||
|
||
- 模型列表。
|
||
- 通用数据表格。
|
||
- 动态详情和编辑表单。
|
||
- 单条增删改查。
|
||
- 软删除和恢复。
|
||
- 修改历史。
|
||
- CSV/XLSX 导入预览。
|
||
- 多表 ZIP 导出。
|
||
|
||
### 阶段 4:图谱同步影子运行
|
||
|
||
- 新增 Outbox Worker。
|
||
- 暂时保留现有图谱写入路径。
|
||
- 新 Worker 写入独立测试图或影子版本。
|
||
- 对比节点、边、属性和查询结果。
|
||
- 达标后停止应用直接双写。
|
||
|
||
### 阶段 5:读路径切换
|
||
|
||
- 通用后台完全读取 PostgreSQL。
|
||
- 荔波地图列表、统计和详情切换到 PostgreSQL。
|
||
- 图谱浏览器继续读取 FalkorDB。
|
||
- 问答服务按问题类型路由到 PostgreSQL、FalkorDB 或聚合服务。
|
||
|
||
### 阶段 6:收敛与清理
|
||
|
||
- 移除不受控的直接 FalkorDB 写入。
|
||
- 保留候选审核层和发布机制。
|
||
- 增加全量重建和对账命令。
|
||
- 更新快照、部署文档、API 文档和数据字典。
|
||
|
||
## 21. 测试与验收
|
||
|
||
### 21.1 功能验收
|
||
|
||
- 任意已注册模型可自动生成列表、详情和编辑表单。
|
||
- CRUD、批量导入、批量导出均受项目权限控制。
|
||
- 软删除可恢复,恢复后图谱重新出现。
|
||
- 同一个实体的高德、携程、大众点评记录可独立查看。
|
||
- 酒店房型、设施、评论和美食套餐以独立明细表展示。
|
||
- 地图和现有详情页面视觉上不发生破坏性变化。
|
||
|
||
### 21.2 数据验收
|
||
|
||
- 迁移前后每类实体数量可解释。
|
||
- 来源记录不丢失。
|
||
- 空坐标、重复来源 ID、孤立明细有明确清单。
|
||
- 所有正式实体具有稳定 `entity_id`。
|
||
- 所有空间实体能够定位到片区和荔波县。
|
||
- 图谱不存在悬空边。
|
||
|
||
### 21.3 一致性验收
|
||
|
||
- PostgreSQL 提交后 60 秒内完成图谱同步。
|
||
- 同一事件重复消费不产生重复节点或边。
|
||
- 图谱不可用时,PostgreSQL CRUD 仍可用。
|
||
- FalkorDB 清空后可以按项目完整重建。
|
||
- 定时对账能够发现并修复图谱漂移。
|
||
|
||
### 21.4 性能目标
|
||
|
||
- 常规列表 P95 小于 500 ms。
|
||
- 单记录详情 P95 小于 800 ms。
|
||
- 10,000 行 CSV 可完成校验、预览和错误下载。
|
||
- 地图只加载视窗内数据,不一次返回全部 POI。
|
||
- 默认图谱查询限制节点和边数量,避免浏览器卡顿。
|
||
|
||
### 21.5 安全验收
|
||
|
||
- 不同项目之间无越权读取和修改。
|
||
- 普通用户不能访问未注册表。
|
||
- 所有 SQL 使用参数化查询。
|
||
- 导入文件不能覆盖服务器路径。
|
||
- 导出文件具有有效期和下载权限。
|
||
- API Key、数据库密码不进入仓库和导出文件。
|
||
|
||
## 22. 回滚与灾备
|
||
|
||
- 每次数据库迁移前导出 PostgreSQL 快照。
|
||
- 图谱投影切换前保留现有 FalkorDB RDB。
|
||
- 数据回填使用新表,不直接覆盖旧表。
|
||
- 页面切换使用功能开关:
|
||
|
||
```text
|
||
DATA_PLATFORM_READ_ENABLED
|
||
GRAPH_OUTBOX_WRITE_ENABLED
|
||
LEGACY_GRAPH_WRITE_ENABLED
|
||
```
|
||
|
||
- 出现问题时可把读取切回旧接口。
|
||
- 正式切换后,PostgreSQL 是恢复源,FalkorDB 可从 PostgreSQL 重建。
|
||
|
||
## 23. 推荐实施优先级
|
||
|
||
### P0:数据安全基础
|
||
|
||
- Alembic。
|
||
- PostGIS。
|
||
- 正式实体表。
|
||
- 元数据表。
|
||
- Outbox 与审计。
|
||
|
||
### P1:通用后台 MVP
|
||
|
||
- 通用查询、详情、编辑。
|
||
- 新增、软删除、恢复。
|
||
- 模板化导入。
|
||
- 多表导出。
|
||
|
||
### P2:云游荔波迁移
|
||
|
||
- 酒店、美食、景区、交通回填。
|
||
- 房型、设施、评论、套餐拆表。
|
||
- 地图和详情切换。
|
||
|
||
### P3:图谱投影
|
||
|
||
- Graph Projector。
|
||
- 影子图验证。
|
||
- 对账、重建和监控。
|
||
- 停止直接双写。
|
||
|
||
### P4:平台化增强
|
||
|
||
- 项目管理员创建逻辑模型和扩展字段。
|
||
- 保存视图。
|
||
- 批量编辑。
|
||
- 数据质量规则配置。
|
||
- 定时导入导出。
|
||
|
||
## 24. 最终原则
|
||
|
||
1. PostgreSQL 管完整数据,FalkorDB 管关系投影。
|
||
2. 用户编辑业务模型,不直接编辑任意数据库表。
|
||
3. 所有正式修改只写 PostgreSQL。
|
||
4. 图谱通过 Outbox 异步、幂等更新。
|
||
5. 所有数据带租户、项目、来源、版本和审计。
|
||
6. 一对多明细使用关系表,不拼接成长文本。
|
||
7. 批量导入先暂存、校验和预览,再正式提交。
|
||
8. 批量导出保留多表结构、数据字典和文件哈希。
|
||
9. 图数据库可重建,关系数据库不可丢失。
|
||
10. 现有业务页面保持可用,按阶段平滑迁移。
|
||
|