Files
Cloud-Tour-to-Libo/docs/关系数据库与通用数据平台技术方案.md

900 lines
20 KiB
Markdown
Raw Permalink 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.

# 关系数据库与通用数据平台技术方案
> 版本V2精简版
>
> 适用范围:云游荔波及平台后续项目
>
> 数据前提:进入平台的数据已经完成采集、清洗、去重、融合和人工确认。
## 1. 核心结论
平台只保留两套业务数据存储:
1. **PostgreSQL保存处理完成后的正式业务数据是唯一权威数据源。**
2. **FalkorDB保存正式业务数据生成的图谱节点和关系主要用于关系查询和 ToB 可视化。**
不在新的关系数据平台中重复建设原始采集、候选审核、字段级溯源等复杂流程。未经处理的数据不能直接进入正式业务库,应在平台外完成处理后,再按标准模板导入。
```mermaid
flowchart LR
A["平台外数据处理\n采集、清洗、去重、融合、审核"] --> B["标准数据文件或 API"]
B --> C["格式与关联校验"]
C -->|通过| D["PostgreSQL\n正式业务数据"]
C -->|不通过| E["错误报告\n退回修正"]
D --> F["图谱同步队列"]
F --> G["FalkorDB\n图谱投影"]
D --> H["通用数据后台\n地图、详情、导入导出"]
G --> I["图谱浏览器\n关系查询、ToB 展示"]
```
## 2. 数据职责
### 2.1 PostgreSQL 保存
- 酒店、美食、景区、交通等正式实体。
- 酒店房型、设施、政策等正式明细。
- 美食团购、菜系、营业时间等正式明细。
- 公交线路、方向、站点顺序和线路坐标。
- 评论、评价标签和图片。
- 实体之间已经确认的正式关系。
- 高德、携程、大众点评等外部平台 ID 和 URL。
- 数据修改记录。
- 导入、导出任务结果。
- 图谱同步队列和同步状态。
### 2.2 PostgreSQL 不保存
- 未清洗的网页或接口原始响应。
- 未确认的候选实体。
- 自动匹配过程中的每个中间结果。
- 重复数据的全部计算过程。
- 临时爬虫缓存。
- 无业务意义的实时文案,例如“仅剩 2 间”。
### 2.3 FalkorDB 保存
- 酒店、美食、景区、交通、公交线路、公交站、片区和行政区节点。
- 实体与片区、片区与荔波县的空间关系。
- 景区与子景点关系。
- 公交线路与站点关系。
- 已确认的附近、包含、分类等业务关系。
- 图谱展示需要的名称、类型、坐标和少量摘要属性。
### 2.4 FalkorDB 不保存
- 完整评论正文。
- 酒店全部房型和报价。
- 美食全部团购套餐。
- 大量设施、政策等详情字段。
- 导入文件和错误数据。
- 可从 PostgreSQL 重新生成的重复明细。
## 3. 简化后的平台结构
```mermaid
flowchart TB
subgraph PG["PostgreSQL 正式业务库"]
P1["项目与权限"]
P2["通用实体"]
P3["酒店、美食、景区、交通明细"]
P4["评论、图片、设施、套餐"]
P5["正式关系"]
P6["导入导出记录"]
P7["图谱同步队列"]
end
subgraph API["FastAPI"]
A1["通用数据表 API"]
A2["批量导入导出"]
A3["地图与详情 API"]
A4["图谱同步服务"]
end
subgraph FK["FalkorDB"]
F1["正式实体节点"]
F2["正式业务关系"]
F3["片区与行政区关系"]
end
subgraph UI["React 管理后台"]
U1["通用数据管理"]
U2["荔波地图与详情"]
U3["图谱浏览器"]
end
PG <--> API
API --> FK
API <--> UI
FK --> U3
```
## 4. PostgreSQL 表结构
整体分成四组,避免结构混乱:
1. 平台管理表。
2. 正式实体表。
3. 业务明细表。
4. 同步与任务表。
### 4.1 平台管理表
| 表 | 作用 |
| --- | --- |
| `projects` | 项目工作区 |
| `data_table_registry` | 注册通用后台可管理的业务表 |
| `data_field_registry` | 定义业务表字段显示和校验规则 |
| `data_relation_registry` | 定义表之间的关联 |
| `data_change_logs` | 保存数据修改记录 |
通用后台只能访问 `data_table_registry` 中已注册的表,不能任意访问 PostgreSQL。
### 4.2 正式实体主表
所有地点类业务统一进入 `poi_entities`
```text
poi_entities
├── entity_id UUID PK
├── tenant_id
├── project_id
├── entity_type
├── name
├── category_l1
├── category_l2
├── category_l3
├── address
├── district
├── adcode
├── phone
├── longitude
├── latitude
├── geom
├── h3_r9
├── h3_r10
├── status
├── version
├── created_at
├── updated_at
└── extra_data JSONB
```
`entity_type` 的当前标准值:
```text
hotel
restaurant
scenic
transport
bus_stop
```
主表只保存所有 POI 共有且经常查询的字段。项目特有但暂不稳定的少量字段可以放入 `extra_data`,不能把大段明细或一对多数据塞入其中。
### 4.3 外部平台标识
`entity_external_links` 只保存已经确认与实体对应的平台标识,不保存原始响应:
```text
entity_external_links
├── id
├── tenant_id
├── project_id
├── entity_id
├── platform
├── external_id
├── external_name
├── external_url
└── updated_at
```
示例:
```text
同一个酒店实体
├── amap高德 POI ID 和 URL
└── ctrip携程酒店 ID、名称和 URL
同一个美食实体
├── amap高德 POI ID 和 URL
└── dianping大众点评商户 ID、名称和 URL
```
### 4.4 公共明细表
| 表 | 内容 |
| --- | --- |
| `entity_images` | 实体和房型、套餐图片 |
| `entity_relations` | 已确认的正式实体关系 |
| `reviews` | 酒店、美食等真实评论 |
| `review_tags` | 评论摘要标签 |
`entity_relations` 保存正式关系:
```text
relation_id
tenant_id
project_id
source_entity_id
relation_type
target_entity_id
properties JSONB
status
updated_at
```
### 4.5 酒店表
```text
hotel_profiles
hotel_room_types
hotel_facilities
hotel_policies
```
#### `hotel_profiles`
一间酒店一条记录,保存:
- 携程酒店名。
- 开业时间。
- 客房数量。
- 酒店钻级。
- 携程评分。
- 用户点评数量。
- 参考起价。
- 酒店简介。
#### `hotel_room_types`
一个房型一条记录:
- 房型名称。
- 图片 URL。
- 床型。
- 面积。
- 楼层。
- 窗户。
- 可住人数。
- 早餐。
- 取消政策。
- 支付方式。
- 参考价格。
不保存“仅剩几间”等采集时刻的临时库存文案。
#### `hotel_facilities`
一个设施一条记录:
- 设施分类。
- 设施名称。
- 是否免费。
- 收费说明。
- 来源 URL。
### 4.6 美食表
```text
restaurant_profiles
restaurant_deals
restaurant_business_hours
```
#### `restaurant_profiles`
- 大众点评店名。
- 点评分类。
- 榜单排名。
- 营业状态。
- 点评评分。
- 用户点评数。
- 人均消费。
#### `restaurant_deals`
一个套餐一条记录:
- 套餐名称。
- 图片 URL。
- 当前价格。
- 原价。
- 折扣。
- 使用规则。
- 有效期。
### 4.7 景区表
```text
scenic_profiles
scenic_children
```
#### `scenic_profiles`
- 景区类型。
- 景区等级。
- 是否国家级。
- 游客价值分类。
- 开放时间。
- 门票说明。
- 官方简介。
#### `scenic_children`
保存景区内部子景点:
- 所属景区。
- 子景点名称。
- 子景点类型。
- 坐标。
- 游览说明。
### 4.8 交通表
```text
transport_profiles
bus_routes
bus_route_directions
bus_route_stops
route_geometries
```
线路站序必须使用独立表,不把站点数组拼接到一个文本字段。
### 4.9 空间表
保留并复用:
```text
kg_geo_cells
kg_route_metrics
```
实体坐标在 `poi_entities.geom` 中使用 PostGIS 保存。H3 片区 ID 同时保存在实体主表中。
## 5. 通用数据后台
通用后台管理的是“平台注册的正式数据表”,不是数据库管理器。
### 5.1 表注册
`data_table_registry`
```text
table_code
project_id
display_name
physical_table
primary_key
title_field
parent_table_code
allow_create
allow_update
allow_delete
allow_import
allow_export
graph_enabled
display_order
```
### 5.2 字段注册
`data_field_registry`
```text
table_code
field_code
display_name
data_type
required
searchable
sortable
editable
visible_in_list
visible_in_detail
importable
exportable
dictionary_values JSONB
validation_rule JSONB
display_order
```
后台根据注册信息自动生成:
- 数据列表。
- 查询条件。
- 新增表单。
- 编辑表单。
- 详情页面。
- CSV/XLSX 模板。
- 导入校验。
- 导出字段。
### 5.3 第一阶段不做在线建物理表
为了保持结构清晰:
- 项目管理员可以配置显示、字段顺序、校验和权限。
- 新增正式物理表或修改列类型仍然通过数据库迁移完成。
- 后续确实需要用户在线建表时,再作为独立能力设计。
这样既能复用一个通用后台,又不会让数据库结构失控。
## 6. 增删改查
统一 API
```text
GET /v1/admin/data/{table_code}
POST /v1/admin/data/{table_code}
GET /v1/admin/data/{table_code}/{record_id}
PATCH /v1/admin/data/{table_code}/{record_id}
DELETE /v1/admin/data/{table_code}/{record_id}
POST /v1/admin/data/{table_code}/{record_id}/restore
```
### 6.1 查询
- 所有查询自动限制 `tenant_id``project_id`
- 只允许查询已注册且标记为可搜索的字段。
- 使用参数化 SQL不接受原始 SQL。
- 默认每页 50 条,单页上限 200 条。
- 支持筛选、搜索、排序和保存列配置。
### 6.2 新增和修改
- 按字段注册规则校验。
- `entity_id` 由服务器生成。
- 使用 `version` 防止多人覆盖。
- 保存操作人、时间和修改前后内容。
- 同一事务写入图谱同步队列。
### 6.3 删除
默认软删除:
```text
status = deleted
deleted_at
deleted_by
```
删除后图谱同步服务删除对应投影。管理员可以恢复。
## 7. 批量导入
进入平台的是已经处理好的数据,因此导入流程只做结构校验和正式入库,不再做清洗、融合和候选审核。
```mermaid
flowchart LR
A["上传处理好的 CSV/XLSX"] --> B["字段和关联校验"]
B -->|失败| C["下载错误报告"]
B -->|通过| D["预览新增和更新数量"]
D --> E["确认导入"]
E --> F["事务写入正式表"]
F --> G["同步图谱"]
```
### 7.1 导入只保留一个任务表
```text
data_import_jobs
├── job_id
├── tenant_id
├── project_id
├── table_code
├── file_name
├── file_hash
├── import_mode
├── total_rows
├── inserted_rows
├── updated_rows
├── failed_rows
├── error_file
├── status
├── created_by
├── created_at
└── completed_at
```
不把每一条原始行长期保存在 PostgreSQL。校验失败行写入临时错误文件供用户下载修正。
### 7.2 导入模式
| 模式 | 说明 |
| --- | --- |
| 仅新增 | 已存在的唯一键直接报错或跳过 |
| 新增并更新 | 按主键或外部平台 ID 更新,推荐默认 |
第一阶段不提供“完整覆盖并删除缺失数据”,避免误删。
### 7.3 校验内容
- 必填字段。
- 数据类型。
- 唯一 ID。
- 外键是否存在。
- 枚举值。
- 经纬度范围。
- 坐标是否位于项目允许区域。
- 公交站序是否连续。
- 酒店房型是否能找到酒店。
- 评论是否能找到对应实体。
### 7.4 多表数据包
酒店、美食等一对多数据使用 ZIP 多表导入:
```text
酒店数据.zip
├── 酒店实体.csv
├── 酒店详情.csv
├── 房型.csv
├── 设施.csv
├── 政策.csv
├── 评论.csv
└── 图片.csv
```
系统先校验整个数据包的主外键,全部通过后再提交,避免只导入主表、明细缺失。
## 8. 批量导出
导出全部来自 PostgreSQL 正式表。
支持:
- 当前表。
- 当前筛选结果。
- 选中记录。
- 主表及全部关联明细。
- 整个项目数据包。
- 图谱节点和关系。
云游荔波项目导出结构:
```text
云游荔波正式数据_YYYYMMDD.zip
├── 数据字典.csv
├── 酒店
│ ├── 酒店实体.csv
│ ├── 酒店详情.csv
│ ├── 房型.csv
│ ├── 设施.csv
│ ├── 政策.csv
│ └── 评论.csv
├── 美食
│ ├── 美食实体.csv
│ ├── 美食详情.csv
│ ├── 团购套餐.csv
│ └── 评论.csv
├── 景区
│ ├── 景区实体.csv
│ └── 子景点.csv
├── 交通
│ ├── 交通站点.csv
│ ├── 公交线路.csv
│ ├── 线路方向.csv
│ └── 线路站序.csv
└── 图谱
├── 节点.csv
└── 关系.csv
```
导出任务只需要一个 `data_export_jobs` 表记录状态、筛选条件、记录数、文件路径和文件哈希。
## 9. 图谱同步
为了避免 PostgreSQL 保存成功但 FalkorDB 写入失败,保留一个精简的同步队列表:
```text
graph_sync_queue
├── event_id
├── tenant_id
├── project_id
├── table_code
├── record_id
├── operation
├── record_version
├── status
├── retry_count
├── error_message
├── created_at
└── completed_at
```
业务写入和队列记录在同一个 PostgreSQL 事务中完成。
同步流程:
1. PostgreSQL 新增、修改或删除正式数据。
2. 同事务写入 `graph_sync_queue`
3. 后台 Worker 读取待处理记录。
4.`entity_id + version` 幂等更新 FalkorDB。
5. 成功后标记完成。
6. 失败自动重试并在后台显示错误。
不允许从 FalkorDB 反向修改 PostgreSQL。
## 10. 图谱投影
### 10.1 节点
```text
Area
GeoCell界面显示“片区”
Hotel
Restaurant
ScenicArea
Attraction
TransportStop
BusStop
BusRoute
Category
```
### 10.2 关系
```text
POI -[IN_H3_R9]-> GeoCell
GeoCell -[LOCATED_IN]-> Area
POI -[LOCATED_IN]-> Area
ScenicArea -[CONTAINS]-> Attraction
BusRoute -[SERVES_STOP]-> BusStop
BusStop -[NEXT_STOP]-> BusStop
POI -[HAS_CATEGORY]-> Category
POI -[NEARBY]-> POI
```
### 10.3 图节点属性
只同步:
- `entity_id`
- 名称。
- 类型。
- 分类。
- 经纬度。
- 地址摘要。
- 状态。
- PostgreSQL 数据版本。
详情页面需要的完整房型、设施、评论和套餐继续从 PostgreSQL 查询。
## 11. 页面数据来源
| 页面 | 数据来源 |
| --- | --- |
| 通用数据后台 | PostgreSQL |
| 荔波地图 POI | PostgreSQL + PostGIS |
| 酒店、美食详情侧栏 | PostgreSQL |
| 房型、设施、套餐、评论 | PostgreSQL |
| 公交路线地图 | PostgreSQL |
| 图谱浏览器 | FalkorDB |
| 图谱关系查询 | FalkorDB |
| CSV/XLSX 导出 | PostgreSQL |
现有荔波地图和详情布局保持不变,只调整后端数据来源。
## 12. 权限与项目隔离
所有正式业务表都必须包含:
```text
tenant_id
project_id
```
后端从当前项目上下文自动获得这两个值,不接受普通用户任意修改。
角色建议:
| 角色 | 权限 |
| --- | --- |
| 系统管理员 | 管理所有项目和注册表 |
| 项目管理员 | 管理本项目数据、导入导出 |
| 数据编辑 | 本项目增删改查 |
| 只读用户 | 查询和受限导出 |
## 13. 数据修改记录
只保留一个清晰的修改日志表:
```text
data_change_logs
├── change_id
├── tenant_id
├── project_id
├── table_code
├── record_id
├── operation
├── before_data JSONB
├── after_data JSONB
├── actor
├── reason
└── created_at
```
不再额外设计字段级来源选择、候选状态机和多套版本表。
## 14. 索引
核心索引:
```text
poi_entities (tenant_id, project_id, entity_type, status)
poi_entities (tenant_id, project_id, updated_at DESC)
poi_entities UNIQUE (tenant_id, project_id, entity_id)
entity_external_links UNIQUE
(tenant_id, project_id, platform, external_id)
poi_entities USING GIST (geom)
bus_route_stops UNIQUE (route_direction_id, stop_order)
reviews (entity_id, created_at DESC)
graph_sync_queue (status, created_at)
```
地图接口必须按当前视窗查询,不允许一次返回全部 POI。
## 15. 现有系统如何迁移
### 第一步:备份
- 导出当前 PostgreSQL 快照。
- 导出当前 FalkorDB RDB。
- 统计酒店、美食、景区、交通、公交和片区数量。
### 第二步:建立正式关系表
- 引入 Alembic。
- 补齐 PostGIS。
- 创建正式实体和业务明细表。
- 创建表注册、导入导出、修改日志和图谱队列表。
### 第三步:只迁移最终数据
- 从当前 FalkorDB 和已经整理好的 CSV 导出最终结果。
- 不迁移临时爬虫数据和中间匹配过程。
-`entity_id`、高德 POI ID 和已确认平台 ID 建立关联。
- 酒店房型、设施、评论和美食套餐拆成明细表。
### 第四步:上线通用后台
- 注册酒店、美食、景区、交通等正式表。
- 自动生成列表、详情、编辑和导入导出页面。
- 现有业务页面暂时不变。
### 第五步:切换详情和地图
- 荔波地图、统计、筛选和详情读取 PostgreSQL。
- 图谱浏览器继续读取 FalkorDB。
### 第六步:启用图谱同步
- 开启 `graph_sync_queue` Worker。
- 对比新图谱和当前图谱。
- 确认一致后停止其他脚本直接修改 FalkorDB。
### 第七步:旧流程只读保留
现有以下数据仍可保留用于历史查看,但不属于新正式业务数据链路:
- `raw_records`
- `candidate_entities`
- `candidate_relations`
- `review_actions`
- `publish_jobs`
新数据不再经过这些表。
## 16. 代码结构
后端:
```text
app/data_platform/
├── registry.py
├── record_service.py
├── query_service.py
├── import_service.py
├── export_service.py
└── graph_sync_service.py
app/api/data_platform.py
app/workers/graph_sync_worker.py
app/migrations/
```
前端:
```text
admin-web/src/panels/data-platform/
├── TableList.tsx
├── DataTable.tsx
├── RecordForm.tsx
├── RecordDetail.tsx
├── ImportDialog.tsx
├── ExportDialog.tsx
└── SyncStatus.tsx
```
## 17. 验收标准
### 数据
- PostgreSQL 中的实体数量与最终确认数据一致。
- 每个实体只有一个稳定 `entity_id`
- 酒店房型、设施、评论等明细均能正确关联主实体。
- 所有地图实体都有合法坐标或明确的无坐标状态。
- 不迁移临时库存和重复文本。
### 后台
- 注册一张业务表后,可自动获得查询、新增、编辑、删除、导入和导出能力。
- 普通用户不能访问未注册表。
- 不同项目之间不能看到或修改对方数据。
- 删除记录可以恢复。
### 图谱
- PostgreSQL 修改后自动更新 FalkorDB。
- 同步失败不影响 PostgreSQL 保存。
- 重复处理同一同步事件不会产生重复节点。
- FalkorDB 可以从 PostgreSQL 全量重建。
- 实体、片区和荔波县保持连通,不产生独立片区。
### 性能
- 列表默认 50 条分页。
- 常规查询 P95 小于 500 ms。
- 地图只查询当前视窗。
- 10,000 行处理好数据能够完成校验和导入。
## 18. 实施优先级
### P0正式数据表
- `poi_entities`
- 外部平台链接。
- 酒店、美食、景区、交通明细。
- 评论、图片和正式关系。
### P1通用后台
- 表和字段注册。
- 通用增删改查。
- 软删除和修改日志。
### P2导入导出
- 标准模板。
- 单表和 ZIP 多表导入。
- 错误报告。
- 多表数据包导出。
### P3图谱同步
- `graph_sync_queue`
- 图谱 Worker。
- 同步状态和失败重试。
### P4现有页面切换
- 荔波地图。
- 酒店、美食、景区、交通详情。
- 公交线路。
- 图谱浏览器保持不变。
## 19. 最终原则
1. 只允许处理完成的数据进入正式业务库。
2. PostgreSQL 只保存最终业务数据和必要运行记录。
3. FalkorDB 只保存最终数据的图谱投影。
4. 通用后台只管理平台注册的正式表。
5. 一对多数据必须拆成关系表。
6. 导入只做校验和入库,不再做数据清洗与融合。
7. 导出以 PostgreSQL 为唯一来源。
8. 所有业务修改先写 PostgreSQL再异步更新图谱。
9. 现有候选审核流程与新正式数据平台分离。
10. 先完成清晰、稳定的数据主链路,再增加高级能力。