# 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 | 满足“仅特定人可修改”的协作场景 | | 待办采用单表 + JSONB 来源数组 | 一张表搞定待办,source_ids 存储关联日程事件 | | 自动化采用 Jobs + Runs 双表 | 只支持 daily/weekly 两种循环,active/disabled 两种状态 | | inbox 采用单表接收者视角 | 发送者 + 消息类型 + 关联业务,一表搞定待处理消息 | ## A. 设计原则与边界 ### 1) 核心实体与聚合边界 - 用户聚合:`profiles`(含 settings JSONB), `user_agents` - 社交聚合:`friendships`, `groups`, `group_members` - 协作事项聚合:`schedule_items`, `schedule_subscriptions` - 消息聚合:`inbox_messages` - 待办聚合:`todos` - 自动化聚合:`automation_jobs`, `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_subscriptions`,`auth.users (1) - (N) schedule_subscriptions` - `auth.users (1) - (N) inbox_messages` - `auth.users (1) - (N) todos` - `auth.users (1) - (N) automation_jobs` - `automation_jobs (1) - (N) automation_runs` ### 关键约束 - 唯一性: - `user_agents.user_id` 唯一 - `friendships(user_low_id, user_high_id)` 唯一 - `group_members(group_id, user_id)` 唯一 - `schedule_subscriptions(item_id, subscriber_id)` 唯一 - 外键:统一显式 `ON DELETE` 策略(见下) - 可空性:权限关键字段、状态字段默认 `NOT NULL` - 删除策略: - 用户删除:大部分 `CASCADE`(用户私有数据);跨用户协作数据优先软删 - 事项删除:对子表 `CASCADE`;待办保留历史,改 `status = 'archived'` ## 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`(枚举:`creator` | `admin` | `member`) - `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, role, status)` - `INDEX(user_id, status)` - 审计: `created_by`, `updated_by` **role 说明**: | role | 含义 | 创建事项时默认给群组的权限 | |------|------|---------------------------| | `creator` | 群主/创建者 | `["view", "invite", "edit"]` | | `admin` | 管理员 | `["view", "invite"]` | | `member` | 普通成员 | `["view"]` | - 角色可升降:服务层变更 role 字段即可 - 角色决定了该用户在群里创建的日程事项默认授予群组的权限(见下方映射) ### 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` - 状态字段: `status`(`active | completed | canceled`) - 索引: - `INDEX(owner_id, start_at)` - `INDEX(status, start_at)` - 审计: `created_by` #### `schedule_subscriptions` - PK: `id UUID` - 关键字段: - `item_id` - `subscriber_id` - `permission`(JSONB 数组,权柄组合:`["view"]` / `["view", "edit"]` / `["view", "invite", "edit"]`) - `notify_level`(`all | mentions | none`) - 时间字段: `created_at` - 状态字段: `status`(`active | paused | unsubscribed`) - 约束: `UNIQUE(item_id, subscriber_id)` - 索引: `INDEX(subscriber_id, status)`, `INDEX(item_id, status)` - 审计: `created_by` **权柄说明**: | 权柄 | 含义 | |------|------| | `view` | 查看事项详情 | | `invite` | 邀请其他人订阅此事项 | | `edit` | 修改事项内容、管理订阅 | - 事项 owner 默认拥有全部权柄:`["view", "invite", "edit"]` - 权限变更使用 JSONB 原子操作:`UPDATE ... SET permission = permission || '["edit"]'::jsonb` ### 5) 待处理消息(Inbox) #### `inbox_messages` - PK: `id UUID` - 关键字段: - `recipient_id`(接收者) - `sender_id`(发送者) - `message_type`(`friend_request` / `group_invitation` / `schedule_item_shared`) - `content`(TEXT 或 JSONB,消息内容) - `related_type`(关联业务类型:`friendship` / `group` / `schedule_item`) - `related_id`(关联业务 ID) - 时间字段: `created_at`, `read_at`, `acted_at` - 状态字段: `status`(`pending|read|accepted|rejected|dismissed`) - 索引: - `INDEX(recipient_id, status, created_at DESC)` - 部分索引 `INDEX(recipient_id, created_at DESC) WHERE status='pending'` - 审计: `created_by` **说明**:一张表搞定,接收者视角,只关心谁发的、什么类型、关联什么业务对象。 ### 6) 待办 #### `todos` - PK: `id UUID` - 关键字段: - `owner_id` - `title` - `description` - `due_at` - `priority`(枚举:`low | medium | high | urgent`) - `source_ids`(JSONB 数组,关联的日程事件 ID,`[]` 表示手动创建) - 时间字段: `created_at`, `completed_at` - 状态字段: `status`(`pending | done | canceled`) - 索引: - `INDEX(owner_id, status, due_at)` - `INDEX(owner_id, created_at DESC)` - 部分索引 `INDEX(owner_id, due_at) WHERE status='pending'` - 审计: `created_by` ### 7) 自动化定时任务 #### `automation_jobs` - PK: `id UUID` - 关键字段: - `owner_id` - `name` - `job_type` - `target_type` - `target_id` - `params`(JSONB) - `schedule_type`(枚举:`daily | weekly`) - `schedule_time`(时间,如 `09:00` 表示每天/每周几点执行) - 时间字段: `created_at`, `updated_at` - 状态字段: `status`(`active | disabled`) - 索引: `INDEX(owner_id, status)` - 审计: `created_by` #### `automation_runs` - PK: `id UUID` - 关键字段: - `job_id` - `scheduled_at`(计划执行时间) - `started_at` - `finished_at` - `status`(`queued | running | succeeded | failed`) - `attempt` - `error_message` - `result`(JSONB) - 时间字段: `created_at` - 索引: `INDEX(job_id, scheduled_at DESC)`, `INDEX(status, scheduled_at)` ## D. 权限与协作模型 ### 1) 事项权限落表 - 权限直接存储在 `schedule_subscriptions.permission` JSONB 数组中 - 权限决策顺序: 1. `schedule_items.owner_id` → 全部权柄 `["view", "invite", "edit"]` 2. `schedule_subscriptions` 中该用户的 `permission` 数组 3. 默认只有 `["view"]` ### 2) 群组与事项权限 - 群成员在事项中的权限通过 `schedule_subscriptions.subject_type = 'group'` 关联 - 群角色决定默认权限: - `creator` → `["view", "invite", "edit"]` - `admin` → `["view", "invite"]` - `member` → `["view"]` ## E. 消息与待办联动 ### 1) inbox 关联业务对象 - `inbox_messages.message_type` 枚举: - `friend_request`(好友请求) - `group_invitation`(群组邀请) - `schedule_item_shared`(日程事项分享) - 通过 `related_type` / `related_id` 关联业务对象 ### 2) 待办来源提取 - 从事项提取待办时,将日程事件 ID 存入 `todos.source_ids` JSONB 数组 - 手动创建的待办 `source_ids = []` - 支持多来源:同一待办可关联多个日程事项 `source_ids = [, ]` - 待办完成时无需反向更新来源事项状态(简化设计) ## F. 定时任务模型 ### 1) 调度规则 - `schedule_type` 枚举:`daily`(每日) | `weekly`(每周) - `schedule_time` 格式:`HH:MM`(如 `09:00` 表示每天/每周几点执行) - 调度器扫描 `status='active'` 的任务,按 `schedule_type + schedule_time` 计算下次执行时间 ### 2) 执行记录 - 每次执行生成 `automation_runs` 记录 - 状态流转:`queued` → `running` → `succeeded` | `failed` - 失败重试:`attempt` 字段记录当前重试次数(需业务确认最大重试次数) ## 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`, `schedule_subscriptions`, `inbox_messages`, `todos`, `automation_jobs`, `automation_runs` 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. 好友关系采用标准化双向一行模型,避免重复边。 5. 群组采用 `groups + group_members`,角色内建 creator/admin/member。 6. 事项、订阅、权限三表解耦,支持多人订阅与精细编辑授权。 7. inbox 采用单表 `inbox_messages`,接收者视角简洁设计。 8. 待办采用单表 `todos`,通过 `source_ids` JSONB 数组存储来源日程事件。 9. 自动化采用 `jobs + runs` 双表,只支持 daily/weekly 两种循环。 10. 所有关键表补齐状态机字段与审计字段,支持可观测与追责。 11. 索引以"用户维度 + 状态 + 时间"为主,兼顾移动端列表查询。 12. 迁移走"四阶段":并行建模 -> 双写回填 -> 读切换 -> 清理。 13. 通过幂等键、部分索引和事务边界保障高并发稳定性。 ### 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,description,start_at,end_at,timezone,recurrence_rule,source_type,status,created_at` - `ScheduleSubscriptionDTO`: `id,item_id,subscriber_id,permission (JSONB数组),notify_level,status,created_at` - Inbox - `InboxMessageDTO`: `id,recipient_id,sender_id,message_type,content,related_type,related_id,status,created_at,read_at,acted_at` - Todo - `TodoDTO`: `id,owner_id,title,description,due_at,priority,status,source_ids (JSONB数组),created_at,completed_at` - 自动化 - `AutomationJobDTO`: `id,owner_id,name,job_type,target_type,target_id,params,schedule_type,schedule_time,status,created_at` - `AutomationRunDTO`: `id,job_id,scheduled_at,started_at,finished_at,status,attempt,error_message,result` ### 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_subscriptions (...)` 9. `CREATE INDEX`(owner/status/time + subscription 查询) P2(消息与待办) 11. `CREATE TABLE inbox_messages (...)` 12. `CREATE TABLE todos (...)` P3(自动化) 13. `CREATE TABLE automation_jobs (...)` 14. `CREATE TABLE automation_runs (...)` 15. `CREATE INDEX idx_automation_runs_job_scheduled (...)` 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. 自动化任务失败重试上限与退避策略(固定/指数)。