578 lines
25 KiB
Markdown
578 lines
25 KiB
Markdown
# Plan: social-app 数据库数据模型重设计(支持社交/事项/自动化)
|
||
|
||
**Date:** 2026-02-26
|
||
**Author:** AI Assistant
|
||
**Status:** Draft
|
||
|
||
## Overview
|
||
|
||
本方案面向 `social-app` 的下一阶段功能升级,重设计 PostgreSQL 数据模型,统一支持用户专属 agent、好友/群组协作、待处理消息、设置、可订阅且可授权编辑的日程事项、待办联动与自动化定时任务。目标是在 FastAPI + Flutter 协作场景下提供长期稳定的数据基础,降低后续 API 演进和跨端同步复杂度。
|
||
|
||
## Requirements
|
||
|
||
### Functional
|
||
- [x] 每个用户有专属 agent,且模型可扩展到未来多 agent 能力
|
||
- [x] 用户支持好友关系、群组创建与成员管理
|
||
- [x] 用户支持 inbox/pending 待处理消息
|
||
- [x] 用户支持个性化设置(偏好/隐私/通知)
|
||
- [x] 用户支持“绑定日程的事项”,可多人订阅,且仅特定人可修改
|
||
- [x] 用户支持待办事项(可由日程事项提取,也可手动创建)
|
||
- [x] 用户支持自动化定时任务(循环触发)
|
||
|
||
### Non-Functional
|
||
- [x] 性能:核心读路径(inbox 列表、待办列表、事项列表)P95 < 150ms(单用户典型数据量)
|
||
- [x] 安全:权限以后端业务授权为准;数据库层保留 RLS 防御边界
|
||
- [x] 一致性:关键写路径(好友状态、权限变更、任务触发)使用事务保障
|
||
- [x] 可演进:支持旧表迁移、双写与灰度切换
|
||
|
||
## Technical Approach
|
||
|
||
采用“认证域(`auth.users`)+ 业务域(`public.*`)”分层建模。保持 `auth.users` 作为身份主键来源,业务表统一引用 `user_id UUID -> auth.users.id`。领域边界拆分为:Identity/Profile、Social Graph、Collaboration(事项/订阅/权限)、Inbox、Todo、Automation。通过“规范化主模型 + 局部物化/冗余快照”平衡一致性与查询性能。
|
||
|
||
### Key Decisions
|
||
|
||
| Decision | Rationale |
|
||
|----------|-----------|
|
||
| 用户与 agent 采用 1:1 主约束 + 可扩展结构 | 当前满足“每用户专属 agent”,未来允许多 agent 形态演进 |
|
||
| 好友关系用单表双向规范化表示 | 避免 A-B / B-A 重复,降低去重成本 |
|
||
| 事项权限采用 ACL 表而非仅 owner | 满足“仅特定人可修改”的协作场景 |
|
||
| 待办采用主表 + 源映射表 | 支持从事项提取、手动创建、去重与追踪来源 |
|
||
| 自动化调度采用 `rrule + cron + interval` 三选一 | 同时覆盖日历型循环和工程型间隔任务 |
|
||
| inbox 采用事件聚合模型 | 将好友请求/群组邀请/事项变更统一进入待处理中心 |
|
||
|
||
## A. 设计原则与边界
|
||
|
||
### 1) 核心实体与聚合边界
|
||
- 用户聚合:`profiles`(含 settings JSONB), `user_agents`
|
||
- 社交聚合:`friendships`, `groups`, `group_members`
|
||
- 协作事项聚合:`schedule_items`, `schedule_item_subscriptions`, `schedule_item_permissions`
|
||
- 消息聚合:`inbox_events`, `inbox_receipts`
|
||
- 待办聚合:`todos`, `todo_sources`
|
||
- 自动化聚合:`automation_jobs`, `automation_schedules`, `automation_runs`
|
||
|
||
### 2) 一致性分级
|
||
- 强一致(同事务):好友关系状态迁移、群组成员角色变更、事项权限写入、定时任务抢占执行
|
||
- 最终一致:inbox 衍生、待办同步、提醒派发(允许异步补偿)
|
||
|
||
### 3) 多租户假设
|
||
- 默认假设:单租户产品(同一业务库服务所有用户),以 `user_id` 做数据隔离
|
||
- 扩展预留:各核心表可预留 `tenant_id UUID NULL`(需业务确认是否近期引入组织空间)
|
||
|
||
## B. 领域模型与关系图(文字化)
|
||
|
||
### 实体与关系
|
||
- `auth.users (1) - (1) profiles`(settings 作为 JSONB 内嵌)
|
||
- `auth.users (1) - (1) user_agents`
|
||
- `auth.users (N) - (N) auth.users` 通过 `friendships`
|
||
- `auth.users (1) - (N) groups`(创建者)
|
||
- `groups (1) - (N) group_members`,`auth.users (1) - (N) group_members`
|
||
- `auth.users (1) - (N) schedule_items`(创建者)
|
||
- `schedule_items (1) - (N) schedule_item_subscriptions`,`auth.users (1) - (N) schedule_item_subscriptions`
|
||
- `schedule_items (1) - (N) schedule_item_permissions`,`auth.users (1) - (N) schedule_item_permissions`
|
||
- `inbox_events (1) - (N) inbox_receipts`,`auth.users (1) - (N) inbox_receipts`
|
||
- `auth.users (1) - (N) todos`
|
||
- `todos (1) - (N) todo_sources`(一条待办可有多个来源记录,默认 1 条)
|
||
- `auth.users (1) - (N) automation_jobs`
|
||
- `automation_jobs (1) - (N) automation_schedules`
|
||
- `automation_jobs (1) - (N) automation_runs`
|
||
|
||
### 关键约束
|
||
- 唯一性:
|
||
- `user_agents.user_id` 唯一
|
||
- `friendships(user_low_id, user_high_id)` 唯一
|
||
- `group_members(group_id, user_id)` 唯一
|
||
- `schedule_item_subscriptions(item_id, subscriber_id)` 唯一
|
||
- `schedule_item_permissions(item_id, subject_type, subject_id, permission)` 唯一
|
||
- `todo_sources(source_type, source_id, owner_id)` 唯一(防止重复抽取)
|
||
- `automation_runs(job_id, idempotency_key)` 唯一
|
||
- 外键:统一显式 `ON DELETE` 策略(见下)
|
||
- 可空性:权限关键字段、状态字段、外部幂等键默认 `NOT NULL`
|
||
- 删除策略:
|
||
- 用户删除:大部分 `CASCADE`(用户私有数据);跨用户协作数据优先软删
|
||
- 事项删除:对子表 `CASCADE`;待办来源保留历史可改 `SET NULL` + 软删
|
||
|
||
## C. 数据库表设计(PostgreSQL)
|
||
|
||
以下为推荐主表(方案 1,规范化优先)。字段示例采用 `UUID + timestamptz + enum/text-check`。
|
||
|
||
### 1) 用户与 agent
|
||
|
||
#### `profiles`(已有,建议补齐)
|
||
- PK: `id UUID` (`auth.users.id`)
|
||
- 关键字段: `username`, `avatar_url`, `bio`
|
||
- **新增 JSONB 字段**:
|
||
- `settings JSONB`(用户自定义设置,含 `preferences`, `privacy`, `notification` 三大块)
|
||
- `settings_version INTEGER DEFAULT 1`(兼容旧数据的版本字段)
|
||
- 时间字段: `created_at`, `updated_at`, `deleted_at`
|
||
- 索引:
|
||
- `INDEX(username)`(允许重名,仅用于列表查询)
|
||
- `GIN(settings)`(支持 JSONB 表达式查询)
|
||
- 表达式索引:`(settings->>'notification_enabled')`(按需,对高频查询字段单独建)
|
||
- 审计: `created_by`, `updated_by`(可等于 id)
|
||
- 删除策略: 用户删除时 `CASCADE`
|
||
|
||
#### `user_agents`
|
||
- PK: `id UUID`
|
||
- 关键字段:
|
||
- `user_id UNIQUE`(每用户专属 agent)
|
||
- `llm_id UUID NOT NULL`(关联绑定的 LLM 模型)
|
||
- `agent_type VARCHAR(20) NOT NULL`(枚举限制:`INTENT_RECOGNITION` | `TASK_EXECUTION` | `RESULT_REPORTING`)
|
||
- `config JSONB`(agent 配置参数)
|
||
- `capability_version INTEGER DEFAULT 1`
|
||
- 时间字段: `created_at`, `updated_at`, `deleted_at`
|
||
- 状态字段: `status`(`active|paused|migrating`)
|
||
- 索引:
|
||
- `UNIQUE(user_id) WHERE deleted_at IS NULL`
|
||
- `INDEX(status)`
|
||
- `INDEX(agent_type)`
|
||
- `GIN(config)`(按需)
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
### 2) 社交关系
|
||
|
||
#### `friendships`
|
||
- PK: `id UUID`
|
||
- 关键字段:
|
||
- `user_low_id`(两者中较小的 UUID)
|
||
- `user_high_id`(两者中较大的 UUID)
|
||
- `initiator_id`(发起请求方的 user_id,用于追溯谁主动)
|
||
- `status`, `requested_at`, `accepted_at`, `blocked_by`
|
||
- 时间字段: `created_at`, `updated_at`
|
||
- 状态字段: `status`(`pending|accepted|blocked|declined|canceled`)
|
||
- 约束:
|
||
- `CHECK(user_low_id < user_high_id)`(强制小值放 low,大值放 high,确保 A→B 和 B→A 是同一行)
|
||
- `CHECK(initiator_id IN (user_low_id, user_high_id))`
|
||
- `UNIQUE(user_low_id, user_high_id)`
|
||
- 索引:
|
||
- `INDEX(user_low_id, status)`
|
||
- `INDEX(user_high_id, status)`
|
||
- 部分索引 `INDEX(status) WHERE status='pending'`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
**查询示例**:
|
||
- 查询用户 A 的所有好友:`SELECT * FROM friendships WHERE user_low_id = A OR user_high_id = A`
|
||
|
||
#### `groups`
|
||
- PK: `id UUID`
|
||
- 关键字段: `name`, `description`, `visibility`, `owner_id`
|
||
- 时间字段: `created_at`, `updated_at`, `deleted_at`
|
||
- 状态字段: `status`(`active|archived`)
|
||
- 索引: `INDEX(owner_id, status)`, `INDEX(visibility)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `group_members`
|
||
- PK: `id UUID`
|
||
- 关键字段:
|
||
- `group_id`, `user_id`
|
||
- `role`(JSONB 数组,权柄组合,如 `["view"]` / `["view", "invite"]` / `["view", "invite", "edit"]`)
|
||
- `join_source`(`invited|joined`)
|
||
- `invited_by`, `joined_at`
|
||
- 时间字段: `created_at`, `updated_at`, `removed_at`
|
||
- 状态字段: `status`(`active|muted|removed`)
|
||
- 约束: `UNIQUE(group_id, user_id)`
|
||
- 索引:
|
||
- `INDEX(group_id, status)`
|
||
- `INDEX(user_id, status)`
|
||
- GIN 索引支持权柄查询:`INDEX group_members_role USING GIN(role)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
**权柄说明**:
|
||
| 权柄 | 含义 |
|
||
|------|------|
|
||
| `view` | 查看群组信息、成员列表、聊天记录 |
|
||
| `invite` | 邀请新成员入群 |
|
||
| `edit` | 修改群组信息、管理成员(禁言/移除) |
|
||
|
||
- 群主(创建者)默认拥有全部权柄:`["view", "invite", "edit"]`
|
||
- 权柄可动态变更:服务层使用 `jsonb ||` 或 `jsonb -` 原子操作增减权柄
|
||
|
||
### 3) 用户设置(已合并至 profiles 表)
|
||
|
||
用户设置采用 JSONB 内嵌方式,渐进式扩展无需改表结构:
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"preferences": {
|
||
"theme": "dark",
|
||
"language": "zh-CN",
|
||
"timezone": "Asia/Shanghai"
|
||
},
|
||
"privacy": {
|
||
"profile_visible_to": "friends",
|
||
"activity_visible_to": "friends",
|
||
"allow_friend_requests": true
|
||
},
|
||
"notification": {
|
||
"enabled": true,
|
||
"push_enabled": true,
|
||
"email_enabled": false,
|
||
"quiet_hours_start": "22:00",
|
||
"quiet_hours_end": "08:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
- 扩展方式:新增字段时递增 `settings_version`,应用层做 schema 兼容
|
||
- 索引策略:对高频查询字段(如 `notification.enabled`)使用表达式索引
|
||
- 更新方式:服务层使用 JSONB merge 或字段级 UPDATE,避免读-改-写并发问题(建议用 `jsonb_set` 原子操作)
|
||
|
||
### 4) 事项与订阅/权限
|
||
|
||
#### `schedule_items`
|
||
- PK: `id UUID`
|
||
- 关键字段:
|
||
- `owner_id`, `title`, `description`
|
||
- `start_at`, `end_at`, `timezone`
|
||
- `recurrence_rule`(可空)
|
||
- `source_type`(`manual|imported|agent_generated`)
|
||
- 时间字段: `created_at`, `updated_at`, `deleted_at`
|
||
- 状态字段: `status`(`running|completed|archived`)
|
||
- 索引:
|
||
- `INDEX(owner_id, start_at)`
|
||
- `INDEX(status, start_at)`
|
||
- `INDEX(updated_at DESC)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `schedule_item_subscriptions`
|
||
- PK: `id UUID`
|
||
- 关键字段: `item_id`, `subscriber_id`, `notify_level`, `subscription_source`
|
||
- 时间字段: `created_at`, `updated_at`, `unsubscribed_at`
|
||
- 状态字段: `status`(`active|paused|unsubscribed`)
|
||
- 约束: `UNIQUE(item_id, subscriber_id)`
|
||
- 索引: `INDEX(subscriber_id, status)`, `INDEX(item_id, status)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `schedule_item_permissions`
|
||
- PK: `id UUID`
|
||
- 关键字段: `item_id`, `subject_type`(`user|group|friend_circle`), `subject_id`, `permission`
|
||
- 时间字段: `created_at`, `updated_at`, `expires_at`
|
||
- 状态字段: `status`(`active|revoked|expired`)
|
||
- 约束:
|
||
- `UNIQUE(item_id, subject_type, subject_id, permission)`
|
||
- `permission` 建议枚举:`view|comment|edit|manage`
|
||
- 索引:
|
||
- `INDEX(item_id, permission, status)`
|
||
- `INDEX(subject_type, subject_id, status)`
|
||
- 审计: `granted_by`, `updated_by`
|
||
|
||
### 5) 待处理消息(Inbox)
|
||
|
||
#### `inbox_events`
|
||
- PK: `id UUID`
|
||
- 关键字段: `event_type`, `actor_id`, `object_type`, `object_id`, `payload_jsonb`, `dedupe_key`
|
||
- 时间字段: `created_at`, `updated_at`
|
||
- 状态字段: `status`(`open|resolved|canceled`)
|
||
- 约束: `UNIQUE(dedupe_key)`
|
||
- 索引: `INDEX(event_type, created_at DESC)`, `GIN(payload_jsonb)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `inbox_receipts`
|
||
- PK: `id UUID`
|
||
- 关键字段: `event_id`, `recipient_id`, `inbox_state`, `action_required`, `acted_at`
|
||
- 时间字段: `created_at`, `updated_at`, `read_at`
|
||
- 状态字段: `inbox_state`(`pending|read|accepted|rejected|dismissed|expired`)
|
||
- 约束: `UNIQUE(event_id, recipient_id)`
|
||
- 索引:
|
||
- `INDEX(recipient_id, inbox_state, created_at DESC)`
|
||
- 部分索引 `INDEX(recipient_id, created_at DESC) WHERE inbox_state='pending'`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
### 6) 待办与来源映射
|
||
|
||
#### `todos`
|
||
- PK: `id UUID`
|
||
- 关键字段: `owner_id`, `title`, `description`, `due_at`, `priority`, `origin_type`, `source_hash`
|
||
- 时间字段: `created_at`, `updated_at`, `completed_at`, `deleted_at`
|
||
- 状态字段: `status`(`pending|in_progress|done|canceled|archived`)
|
||
- 索引:
|
||
- `INDEX(owner_id, status, due_at)`
|
||
- `INDEX(owner_id, updated_at DESC)`
|
||
- 部分索引 `INDEX(owner_id, due_at) WHERE status IN ('pending','in_progress')`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `todo_sources`
|
||
- PK: `id UUID`
|
||
- 关键字段: `todo_id`, `owner_id`, `source_type`(`schedule_item|manual|inbox_event|automation`), `source_id`, `extracted_at`, `sync_mode`
|
||
- 时间字段: `created_at`, `updated_at`
|
||
- 状态字段: `status`(`linked|unlinked|stale`)
|
||
- 约束: `UNIQUE(owner_id, source_type, source_id)`(去重关键)
|
||
- 索引:
|
||
- `INDEX(todo_id)`
|
||
- `INDEX(owner_id, source_type, status)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
### 7) 自动化定时任务
|
||
|
||
#### `automation_jobs`
|
||
- PK: `id UUID`
|
||
- 关键字段: `owner_id`, `name`, `job_type`, `target_type`, `target_id`, `params_jsonb`
|
||
- 时间字段: `created_at`, `updated_at`, `deleted_at`
|
||
- 状态字段: `status`(`active|paused|disabled`)
|
||
- 索引: `INDEX(owner_id, status)`, `INDEX(target_type, target_id)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `automation_schedules`
|
||
- PK: `id UUID`
|
||
- 关键字段:
|
||
- `job_id UNIQUE`
|
||
- `schedule_type`(`cron|rrule|interval`)
|
||
- `cron_expr` / `rrule_text` / `interval_seconds`(三选一)
|
||
- `timezone`, `start_at`, `end_at`, `next_run_at`
|
||
- 时间字段: `created_at`, `updated_at`
|
||
- 状态字段: `status`(`active|paused|expired|invalid`)
|
||
- 约束:
|
||
- `CHECK` 保证三种表达互斥且至少一项有效
|
||
- 索引:
|
||
- 部分索引 `INDEX(next_run_at) WHERE status='active'`
|
||
- `INDEX(job_id, status)`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
#### `automation_runs`
|
||
- PK: `id UUID`
|
||
- 关键字段: `job_id`, `scheduled_for`, `started_at`, `finished_at`, `attempt`, `max_retries`, `idempotency_key`, `worker_id`, `result_jsonb`, `error_code`
|
||
- 时间字段: `created_at`, `updated_at`
|
||
- 状态字段: `status`(`queued|running|succeeded|failed|canceled|dead_letter`)
|
||
- 约束:
|
||
- `UNIQUE(job_id, idempotency_key)`
|
||
- `CHECK(attempt <= max_retries + 1)`
|
||
- 索引:
|
||
- `INDEX(job_id, scheduled_for DESC)`
|
||
- `INDEX(status, scheduled_for)`
|
||
- 部分索引 `INDEX(status, updated_at) WHERE status IN ('queued','running')`
|
||
- 审计: `created_by`, `updated_by`
|
||
|
||
## D. 权限与协作模型
|
||
|
||
### 1) 事项编辑权限落表
|
||
- 权限决策顺序:
|
||
1. `schedule_items.owner_id`(天然 `manage`)
|
||
2. `schedule_item_permissions` 针对 `subject_type=user` 的显式授权
|
||
3. 用户所在群组在 `subject_type=group` 的授权
|
||
4. 默认无编辑权限
|
||
- 建议在服务层计算“有效权限”,可落缓存字段 `effective_permission`(可选)
|
||
|
||
### 2) 群组角色与事项关系
|
||
- `group_members.role`:JSONB 数组,权柄组合 `["view"]` / `["view", "invite"]` / `["view", "invite", "edit"]`
|
||
- 群主(创建者)默认拥有全部权柄
|
||
- 权柄与事项权限映射:
|
||
- 群成员 `view` → 事项 `view`
|
||
- 群成员 `invite` → 隐含 `view`
|
||
- 群成员 `edit` → 事项 `edit|manage`
|
||
- 若事项绑定群组上下文,可增 `schedule_items.context_group_id NULL`
|
||
|
||
## E. 消息与待办联动
|
||
|
||
### 1) inbox 关联来源
|
||
- `inbox_events.event_type` 建议枚举:
|
||
- `friend_request`
|
||
- `group_invitation`
|
||
- `group_role_changed`
|
||
- `schedule_item_changed`
|
||
- `schedule_item_permission_granted`
|
||
- `automation_run_failed`
|
||
- 通过 `object_type/object_id` 直接关联业务对象
|
||
|
||
### 2) 待办提取与防循环
|
||
- 从事项提取待办:
|
||
- 先计算 `source_hash = sha256(owner_id + source_type + source_id + title + due_at_bucket)`
|
||
- 查 `todo_sources(owner_id, source_type, source_id)` 唯一约束防重复
|
||
- 防循环同步:
|
||
- `todo_sources.sync_mode`:`one_way|two_way`
|
||
- 当来源为 `schedule_item` 且 `two_way` 时,写回需携带 `sync_trace_id`
|
||
- 同一 `sync_trace_id` 在同一对象链路只消费一次(应用层幂等)
|
||
|
||
## F. 定时任务模型
|
||
|
||
### 1) 循环表达建议
|
||
- 推荐优先级:
|
||
- 用户型日历任务:`rrule`
|
||
- 运维/工程任务:`cron`
|
||
- 简单轮询:`interval_seconds`
|
||
- 数据层统一在 `automation_schedules`,并通过 `schedule_type` 区分
|
||
|
||
### 2) 触发记录与重试
|
||
- 任务调度器按 `next_run_at` 扫描活跃计划,插入 `automation_runs(status='queued')`
|
||
- 执行器抢占:`SELECT ... FOR UPDATE SKIP LOCKED`
|
||
- 重试策略:`attempt` 递增,失败后按退避策略更新下一次 `scheduled_for`
|
||
|
||
### 3) 幂等与并发冲突
|
||
- 幂等键:`idempotency_key = sha256(job_id + scheduled_for + logical_partition)`
|
||
- 冲突处理:唯一约束冲突时视为重复投递,直接忽略或合并结果
|
||
- 并发安全:`automation_jobs.version`(乐观锁)可选
|
||
|
||
## G. 演进与迁移计划(从旧表到新模型)
|
||
|
||
### Phase 1: 基础并行建模(8-12 小时)
|
||
1. 新建核心表(不删旧表):`user_agents`, `friendships`, `groups`, `group_members`
|
||
2. 对 `profiles` 表新增 `settings JSONB`, `settings_version` 字段
|
||
3. 建立最小外键和索引,启用 RLS deny-all 策略
|
||
4. 提供只写新表的影子接口(内部开关)
|
||
|
||
### Phase 2: 协作与联动接入(12-16 小时)
|
||
1. 新建 `schedule_items*`, `inbox_*`, `todos*`, `automation_*`
|
||
2. 编写回填脚本(从旧事项/旧消息结构回填,若不存在则跳过)
|
||
3. 开启双写:旧接口写旧表同时写新表,记录双写差异日志
|
||
|
||
### Phase 3: 读切换与一致性校验(8-12 小时)
|
||
1. API 读路径灰度切换到新表(按用户百分比)
|
||
2. 每日对账:记录数、状态分布、关键字段哈希比对
|
||
3. 指标稳定后停止旧表写入,保留只读回滚窗口
|
||
|
||
### Phase 4: 收尾与清理(4-8 小时)
|
||
1. 下线旧读路径和旧双写逻辑
|
||
2. 保留旧表冷备份后归档/删除
|
||
3. 固化运行手册与告警阈值
|
||
|
||
### 回滚策略
|
||
- 任意阶段回滚:读切回旧表 + 关闭新表写开关
|
||
- 双写阶段故障:保留操作日志,可按时间窗重放补偿
|
||
- 最终切换前必须满足:新旧关键查询结果偏差 < 0.1%
|
||
|
||
## H. 两套方案对比
|
||
|
||
### 方案 1(推荐):规范化、可维护性优先
|
||
- 特点:按领域拆表,ACL 与来源映射独立,严格约束
|
||
- 优点:一致性好、权限边界清晰、长期演进成本低
|
||
- 缺点:联表较多,初期 API 复杂度较高
|
||
|
||
### 方案 2:开发效率优先、适度反规范化
|
||
- 特点:将部分结构合并为 JSONB(如 `permissions_snapshot`, `todo_origin`)
|
||
- 优点:开发快、迁移初期改动小
|
||
- 缺点:约束弱、查询和审计困难、后续重构成本高
|
||
- 注:`user_settings` 已按此思路内嵌至 profiles,其他模块仍建议保持规范化
|
||
|
||
### 对比矩阵
|
||
|
||
| 维度 | 方案 1(规范化) | 方案 2(反规范化) |
|
||
|------|------------------|--------------------|
|
||
| 复杂度 | 中高 | 中 |
|
||
| 查询性能 | 读热点需索引与缓存优化 | 单行读取快,复杂筛选慢 |
|
||
| 写入成本 | 中(多表事务) | 低到中 |
|
||
| 扩展性 | 高 | 中低 |
|
||
| 风险 | 中(实施复杂) | 中高(数据质量和权限风险) |
|
||
|
||
### 推荐结论
|
||
- 推荐方案 1:当前业务已包含社交关系、协作权限、跨域联动和自动化调度,若选择反规范化将把复杂性转移到应用层并放大后续维护风险。方案 1 在 FastAPI + PostgreSQL 下更符合长期可维护与可审计目标。
|
||
|
||
## I. 交付物
|
||
|
||
### I-1. 可直接进入实现计划的结论性摘要(12 条)
|
||
1. 以 `auth.users` 为身份主键,业务表统一 `user_id` 外键。
|
||
2. 引入 `user_agents`,通过 `UNIQUE(user_id)` 满足每用户专属 agent。
|
||
3. 用户设置内嵌至 `profiles.settings JSONB`,支持渐进式扩展。
|
||
4. 好友关系采用标准化双向一行模型,避免重复边。
|
||
4. 群组采用 `groups + group_members`,角色内建 owner/admin/member。
|
||
5. 事项、订阅、权限三表解耦,支持多人订阅与精细编辑授权。
|
||
6. inbox 采用 `events + receipts`,统一承载待处理动作。
|
||
7. 待办采用 `todos + todo_sources`,实现来源追踪与去重。
|
||
8. 自动化采用 `jobs + schedules + runs`,支持 cron/rrule/interval。
|
||
9. 所有关键表补齐状态机字段与审计字段,支持可观测与追责。
|
||
10. 索引以“用户维度 + 状态 + 时间”为主,兼顾移动端列表查询。
|
||
11. 迁移走“四阶段”:并行建模 -> 双写回填 -> 读切换 -> 清理。
|
||
12. 通过幂等键、部分索引和事务边界保障高并发稳定性。
|
||
|
||
### I-2. 后续 API 设计所需数据契约清单
|
||
|
||
- 用户/agent
|
||
- `UserAgentDTO`: `id,user_id,llm_id,agent_type,status,capability_version,config,updated_at`
|
||
- `UserSettingsDTO`(内嵌于 Profile): `settings JSONB, settings_version`
|
||
- 好友
|
||
- `FriendshipDTO`: `id,user_a,user_b,status,initiator_id,requested_at,accepted_at`
|
||
- 状态流转:`pending -> accepted|declined|canceled|blocked`
|
||
- 群组
|
||
- `GroupDTO`: `id,name,owner_id,visibility,status`
|
||
- `GroupMemberDTO`: `group_id,user_id,role,status,joined_at`
|
||
- 事项
|
||
- `ScheduleItemDTO`: `id,owner_id,title,start_at,end_at,status,timezone,recurrence_rule`
|
||
- `ScheduleSubscriptionDTO`: `item_id,subscriber_id,status,notify_level`
|
||
- `SchedulePermissionDTO`: `item_id,subject_type,subject_id,permission,status`
|
||
- Inbox
|
||
- `InboxEventDTO`: `id,event_type,object_type,object_id,payload,status,created_at`
|
||
- `InboxReceiptDTO`: `event_id,recipient_id,inbox_state,action_required,read_at,acted_at`
|
||
- Todo
|
||
- `TodoDTO`: `id,owner_id,title,due_at,status,priority,origin_type`
|
||
- `TodoSourceDTO`: `todo_id,source_type,source_id,sync_mode,status`
|
||
- 自动化
|
||
- `AutomationJobDTO`: `id,owner_id,job_type,target_type,target_id,status`
|
||
- `AutomationScheduleDTO`: `job_id,schedule_type,cron_expr,rrule_text,interval_seconds,next_run_at,status`
|
||
- `AutomationRunDTO`: `id,job_id,scheduled_for,status,attempt,idempotency_key,error_code`
|
||
|
||
### I-3. 最小可行迁移 DDL 清单(按优先级)
|
||
|
||
P0(身份与社交基础)
|
||
1. `ALTER TABLE profiles ADD COLUMN settings JSONB DEFAULT '{}'`
|
||
2. `ALTER TABLE profiles ADD COLUMN settings_version INTEGER DEFAULT 1`
|
||
3. `CREATE INDEX idx_profiles_settings_notification ON profiles ((settings->>'notification_enabled'))`
|
||
4. `CREATE TABLE user_agents (...)`
|
||
5. `CREATE TABLE friendships (...)`
|
||
6. `CREATE TABLE groups (...)`
|
||
7. `CREATE TABLE group_members (...)`
|
||
8. `CREATE INDEX/UNIQUE/CHECK`(friendships 规范化约束)
|
||
|
||
P1(协作事项)
|
||
7. `CREATE TABLE schedule_items (...)`
|
||
8. `CREATE TABLE schedule_item_subscriptions (...)`
|
||
9. `CREATE TABLE schedule_item_permissions (...)`
|
||
10. `CREATE INDEX`(owner/status/time + permission 查询)
|
||
|
||
P2(消息与待办)
|
||
11. `CREATE TABLE inbox_events (...)`
|
||
12. `CREATE TABLE inbox_receipts (...)`
|
||
13. `CREATE TABLE todos (...)`
|
||
14. `CREATE TABLE todo_sources (...)`
|
||
15. `CREATE UNIQUE INDEX uq_todo_sources_owner_source (...)`
|
||
|
||
P3(自动化)
|
||
16. `CREATE TABLE automation_jobs (...)`
|
||
17. `CREATE TABLE automation_schedules (...)`
|
||
18. `CREATE TABLE automation_runs (...)`
|
||
19. `CREATE UNIQUE INDEX uq_automation_runs_job_idempotency (...)`
|
||
20. `CREATE INDEX idx_automation_schedules_next_run_active (...)`
|
||
|
||
P4(安全与治理)
|
||
21. 对新增 `public` 表执行 `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`
|
||
22. 为 `anon, authenticated` 创建默认 deny-all `SELECT/INSERT/UPDATE/DELETE` policy
|
||
23. 审计字段回填脚本与触发器(如需)
|
||
|
||
## Dependencies
|
||
|
||
- [ ] PostgreSQL 扩展:`pgcrypto`(UUID/哈希,若已启用可跳过)
|
||
- [ ] Alembic 迁移体系(现有)
|
||
- [ ] 后端任务执行器(Celery)用于自动化触发与补偿
|
||
|
||
## Testing Strategy
|
||
|
||
- **Unit Tests:** 状态流转、权限决策、去重哈希、幂等键生成
|
||
- **Integration Tests:** 迁移升级/回滚、双写一致性、RLS policy 基线、并发抢占执行
|
||
- **E2E Tests:** 好友请求到 inbox、群组邀请处理、事项订阅变更到待办、自动化任务触发到结果回显
|
||
|
||
## Risks & Mitigations
|
||
|
||
| Risk | Impact | Likelihood | Mitigation |
|
||
|------|--------|------------|------------|
|
||
| 旧表结构未知导致回填失败 | High | Medium | 先做 schema 探测脚本,按表存在性分支回填 |
|
||
| 双写期间新旧数据不一致 | High | Medium | 引入操作日志 + 对账任务 + 幂等补偿 |
|
||
| 权限模型过复杂影响上线进度 | Medium | Medium | 先上线最小权限集(owner/user/group),后续扩展 |
|
||
| 自动化任务并发冲突 | Medium | Medium | `SKIP LOCKED` + 幂等键 + 乐观锁版本号 |
|
||
| 索引不足导致移动端列表慢 | Medium | Medium | 先覆盖 P0/P1 热路径索引,监控后增量优化 |
|
||
|
||
## Estimated Effort
|
||
|
||
| Phase | Effort |
|
||
|-------|--------|
|
||
| Phase 1 | 8-12 hours |
|
||
| Phase 2 | 12-16 hours |
|
||
| Phase 3 | 8-12 hours |
|
||
| Phase 4 | 4-8 hours |
|
||
| **Total** | **32-48 hours** |
|
||
|
||
## 需业务确认(关键不确定项)
|
||
|
||
1. ~~`profiles.username` 是否允许重名~~(已确认:允许重名,仅建普通索引)。
|
||
2. ~~group_members.role 权柄组合~~(已确认:JSONB 数组 `["view", "invite", "edit"]`)。
|
||
3. 是否近期需要“组织/团队”多租户(决定 `tenant_id` 是否立即强制)。
|
||
4. 事项是否必须绑定群组上下文(`context_group_id` 是否必需)。
|
||
5. 待办与事项同步是否默认双向(推荐默认单向,降低循环风险)。
|
||
6. 自动化任务失败重试上限与退避策略(固定/指数)。
|