diff --git a/README.md b/README.md index 763ce75..4bc552a 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ docker compose exec falkordb redis-cli -p 6379 GRAPH.LIST | --- | --- | | [docs/PROJECT_OVERVIEW.md](docs/PROJECT_OVERVIEW.md) | 系统定位、业务能力和功能地图 | | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 容器、后端、前端和数据架构 | +| [docs/关系数据库与通用数据平台技术方案.md](docs/关系数据库与通用数据平台技术方案.md) | PostgreSQL 权威业务库、通用后台、导入导出和 FalkorDB 投影的完整实施路线 | | [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Docker 部署、端口、环境变量和常见问题 | | [docs/DATA_SNAPSHOTS.md](docs/DATA_SNAPSHOTS.md) | 数据快照、恢复、重导出和校验 | | [docs/API_REFERENCE.md](docs/API_REFERENCE.md) | API 分组、常用接口和调用示例 | diff --git a/docs/关系数据库与通用数据平台技术方案.md b/docs/关系数据库与通用数据平台技术方案.md new file mode 100644 index 0000000..da92c31 --- /dev/null +++ b/docs/关系数据库与通用数据平台技术方案.md @@ -0,0 +1,1230 @@ +# 关系数据库与通用数据平台技术方案 + +> 适用平台:旅行知识图谱管理系统 / 云游荔波及后续多项目 +> 方案状态:拟实施 +> 核心结论: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. 现有业务页面保持可用,按阶段平滑迁移。 +