20 KiB
关系数据库与通用数据平台技术方案
版本:V2(精简版)
适用范围:云游荔波及平台后续项目
数据前提:进入平台的数据已经完成采集、清洗、去重、融合和人工确认。
1. 核心结论
平台只保留两套业务数据存储:
- PostgreSQL:保存处理完成后的正式业务数据,是唯一权威数据源。
- FalkorDB:保存正式业务数据生成的图谱节点和关系,主要用于关系查询和 ToB 可视化。
不在新的关系数据平台中重复建设原始采集、候选审核、字段级溯源等复杂流程。未经处理的数据不能直接进入正式业务库,应在平台外完成处理后,再按标准模板导入。
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. 简化后的平台结构
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 表结构
整体分成四组,避免结构混乱:
- 平台管理表。
- 正式实体表。
- 业务明细表。
- 同步与任务表。
4.1 平台管理表
| 表 | 作用 |
|---|---|
projects |
项目工作区 |
data_table_registry |
注册通用后台可管理的业务表 |
data_field_registry |
定义业务表字段显示和校验规则 |
data_relation_registry |
定义表之间的关联 |
data_change_logs |
保存数据修改记录 |
通用后台只能访问 data_table_registry 中已注册的表,不能任意访问 PostgreSQL。
4.2 正式实体主表
所有地点类业务统一进入 poi_entities:
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 的当前标准值:
hotel
restaurant
scenic
transport
bus_stop
主表只保存所有 POI 共有且经常查询的字段。项目特有但暂不稳定的少量字段可以放入 extra_data,不能把大段明细或一对多数据塞入其中。
4.3 外部平台标识
entity_external_links 只保存已经确认与实体对应的平台标识,不保存原始响应:
entity_external_links
├── id
├── tenant_id
├── project_id
├── entity_id
├── platform
├── external_id
├── external_name
├── external_url
└── updated_at
示例:
同一个酒店实体
├── 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 保存正式关系:
relation_id
tenant_id
project_id
source_entity_id
relation_type
target_entity_id
properties JSONB
status
updated_at
4.5 酒店表
hotel_profiles
hotel_room_types
hotel_facilities
hotel_policies
hotel_profiles
一间酒店一条记录,保存:
- 携程酒店名。
- 开业时间。
- 客房数量。
- 酒店钻级。
- 携程评分。
- 用户点评数量。
- 参考起价。
- 酒店简介。
hotel_room_types
一个房型一条记录:
- 房型名称。
- 图片 URL。
- 床型。
- 面积。
- 楼层。
- 窗户。
- 可住人数。
- 早餐。
- 取消政策。
- 支付方式。
- 参考价格。
不保存“仅剩几间”等采集时刻的临时库存文案。
hotel_facilities
一个设施一条记录:
- 设施分类。
- 设施名称。
- 是否免费。
- 收费说明。
- 来源 URL。
4.6 美食表
restaurant_profiles
restaurant_deals
restaurant_business_hours
restaurant_profiles
- 大众点评店名。
- 点评分类。
- 榜单排名。
- 营业状态。
- 点评评分。
- 用户点评数。
- 人均消费。
restaurant_deals
一个套餐一条记录:
- 套餐名称。
- 图片 URL。
- 当前价格。
- 原价。
- 折扣。
- 使用规则。
- 有效期。
4.7 景区表
scenic_profiles
scenic_children
scenic_profiles
- 景区类型。
- 景区等级。
- 是否国家级。
- 游客价值分类。
- 开放时间。
- 门票说明。
- 官方简介。
scenic_children
保存景区内部子景点:
- 所属景区。
- 子景点名称。
- 子景点类型。
- 坐标。
- 游览说明。
4.8 交通表
transport_profiles
bus_routes
bus_route_directions
bus_route_stops
route_geometries
线路站序必须使用独立表,不把站点数组拼接到一个文本字段。
4.9 空间表
保留并复用:
kg_geo_cells
kg_route_metrics
实体坐标在 poi_entities.geom 中使用 PostGIS 保存。H3 片区 ID 同时保存在实体主表中。
5. 通用数据后台
通用后台管理的是“平台注册的正式数据表”,不是数据库管理器。
5.1 表注册
data_table_registry:
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:
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:
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 删除
默认软删除:
status = deleted
deleted_at
deleted_by
删除后图谱同步服务删除对应投影。管理员可以恢复。
7. 批量导入
进入平台的是已经处理好的数据,因此导入流程只做结构校验和正式入库,不再做清洗、融合和候选审核。
flowchart LR
A["上传处理好的 CSV/XLSX"] --> B["字段和关联校验"]
B -->|失败| C["下载错误报告"]
B -->|通过| D["预览新增和更新数量"]
D --> E["确认导入"]
E --> F["事务写入正式表"]
F --> G["同步图谱"]
7.1 导入只保留一个任务表
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 多表导入:
酒店数据.zip
├── 酒店实体.csv
├── 酒店详情.csv
├── 房型.csv
├── 设施.csv
├── 政策.csv
├── 评论.csv
└── 图片.csv
系统先校验整个数据包的主外键,全部通过后再提交,避免只导入主表、明细缺失。
8. 批量导出
导出全部来自 PostgreSQL 正式表。
支持:
- 当前表。
- 当前筛选结果。
- 选中记录。
- 主表及全部关联明细。
- 整个项目数据包。
- 图谱节点和关系。
云游荔波项目导出结构:
云游荔波正式数据_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 写入失败,保留一个精简的同步队列表:
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 事务中完成。
同步流程:
- PostgreSQL 新增、修改或删除正式数据。
- 同事务写入
graph_sync_queue。 - 后台 Worker 读取待处理记录。
- 按
entity_id + version幂等更新 FalkorDB。 - 成功后标记完成。
- 失败自动重试并在后台显示错误。
不允许从 FalkorDB 反向修改 PostgreSQL。
10. 图谱投影
10.1 节点
Area
GeoCell(界面显示“片区”)
Hotel
Restaurant
ScenicArea
Attraction
TransportStop
BusStop
BusRoute
Category
10.2 关系
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. 权限与项目隔离
所有正式业务表都必须包含:
tenant_id
project_id
后端从当前项目上下文自动获得这两个值,不接受普通用户任意修改。
角色建议:
| 角色 | 权限 |
|---|---|
| 系统管理员 | 管理所有项目和注册表 |
| 项目管理员 | 管理本项目数据、导入导出 |
| 数据编辑 | 本项目增删改查 |
| 只读用户 | 查询和受限导出 |
13. 数据修改记录
只保留一个清晰的修改日志表:
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. 索引
核心索引:
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_queueWorker。 - 对比新图谱和当前图谱。
- 确认一致后停止其他脚本直接修改 FalkorDB。
第七步:旧流程只读保留
现有以下数据仍可保留用于历史查看,但不属于新正式业务数据链路:
raw_recordscandidate_entitiescandidate_relationsreview_actionspublish_jobs
新数据不再经过这些表。
16. 代码结构
后端:
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/
前端:
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. 最终原则
- 只允许处理完成的数据进入正式业务库。
- PostgreSQL 只保存最终业务数据和必要运行记录。
- FalkorDB 只保存最终数据的图谱投影。
- 通用后台只管理平台注册的正式表。
- 一对多数据必须拆成关系表。
- 导入只做校验和入库,不再做数据清洗与融合。
- 导出以 PostgreSQL 为唯一来源。
- 所有业务修改先写 PostgreSQL,再异步更新图谱。
- 现有候选审核流程与新正式数据平台分离。
- 先完成清晰、稳定的数据主链路,再增加高级能力。