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

20 KiB
Raw Permalink Blame History

关系数据库与通用数据平台技术方案

版本V2精简版

适用范围:云游荔波及平台后续项目

数据前提:进入平台的数据已经完成采集、清洗、去重、融合和人工确认。

1. 核心结论

平台只保留两套业务数据存储:

  1. PostgreSQL保存处理完成后的正式业务数据是唯一权威数据源。
  2. 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 表结构

整体分成四组,避免结构混乱:

  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

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_idproject_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 事务中完成。

同步流程:

  1. PostgreSQL 新增、修改或删除正式数据。
  2. 同事务写入 graph_sync_queue
  3. 后台 Worker 读取待处理记录。
  4. entity_id + version 幂等更新 FalkorDB。
  5. 成功后标记完成。
  6. 失败自动重试并在后台显示错误。

不允许从 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_queue Worker。
  • 对比新图谱和当前图谱。
  • 确认一致后停止其他脚本直接修改 FalkorDB。

第七步:旧流程只读保留

现有以下数据仍可保留用于历史查看,但不属于新正式业务数据链路:

  • raw_records
  • candidate_entities
  • candidate_relations
  • review_actions
  • publish_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. 最终原则

  1. 只允许处理完成的数据进入正式业务库。
  2. PostgreSQL 只保存最终业务数据和必要运行记录。
  3. FalkorDB 只保存最终数据的图谱投影。
  4. 通用后台只管理平台注册的正式表。
  5. 一对多数据必须拆成关系表。
  6. 导入只做校验和入库,不再做数据清洗与融合。
  7. 导出以 PostgreSQL 为唯一来源。
  8. 所有业务修改先写 PostgreSQL再异步更新图谱。
  9. 现有候选审核流程与新正式数据平台分离。
  10. 先完成清晰、稳定的数据主链路,再增加高级能力。