Files
social-app/docs/plans/2026-02-26-social-data-model-redesign.md
T

22 KiB
Raw Blame History

Plan: social-app 数据库数据模型重设计(支持社交/事项/自动化)

Date: 2026-02-26 Author: AI Assistant Status: Draft

Overview

本方案面向 social-app 的下一阶段功能升级,重设计 PostgreSQL 数据模型,统一支持用户专属 agent、好友/群组协作、待处理消息、设置、可订阅且可授权编辑的日程事项、待办联动与自动化定时任务。目标是在 FastAPI + Flutter 协作场景下提供长期稳定的数据基础,降低后续 API 演进和跨端同步复杂度。

Requirements

Functional

  • 每个用户有专属 agent,且模型可扩展到未来多 agent 能力
  • 用户支持好友关系、群组创建与成员管理
  • 用户支持 inbox/pending 待处理消息
  • 用户支持个性化设置(偏好/隐私/通知)
  • 用户支持“绑定日程的事项”,可多人订阅,且仅特定人可修改
  • 用户支持待办事项(可由日程事项提取,也可手动创建)
  • 用户支持自动化定时任务(循环触发)

Non-Functional

  • 性能:核心读路径(inbox 列表、待办列表、事项列表)P95 < 150ms(单用户典型数据量)
  • 安全:权限以后端业务授权为准;数据库层保留 RLS 防御边界
  • 一致性:关键写路径(好友状态、权限变更、任务触发)使用事务保障
  • 可演进:支持旧表迁移、双写与灰度切换

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) profilessettings 作为 JSONB 内嵌)
  • auth.users (1) - (1) user_agents
  • auth.users (N) - (N) auth.users 通过 friendships
  • auth.users (1) - (N) groups(创建者)
  • groups (1) - (N) group_membersauth.users (1) - (N) group_members
  • auth.users (1) - (N) schedule_items(创建者)
  • schedule_items (1) - (N) schedule_subscriptionsauth.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 JSONBagent 配置参数)
    • capability_version INTEGER DEFAULT 1
  • 时间字段: created_at, updated_at, deleted_at
  • 状态字段: statusactive|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
  • 状态字段: statuspending|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
  • 状态字段: statusactive|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_sourceinvited|joined
    • invited_by, joined_at
  • 时间字段: created_at, updated_at, removed_at
  • 状态字段: statusactive|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 内嵌方式,渐进式扩展无需改表结构:

{
  "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_typemanual | imported | agent_generated
  • 时间字段: created_at, updated_at
  • 状态字段: statusactive | completed | canceled
  • 索引:
    • INDEX(owner_id, start_at)
    • INDEX(status, start_at)
  • 审计: created_by

schedule_subscriptions

  • PK: id UUID
  • 关键字段:
    • item_id
    • subscriber_id
    • permissionJSONB 数组,权柄组合:["view"] / ["view", "edit"] / ["view", "invite", "edit"]
    • notify_levelall | mentions | none
  • 时间字段: created_at
  • 状态字段: statusactive | 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_typefriend_request / group_invitation / schedule_item_shared
    • contentTEXT 或 JSONB,消息内容)
    • related_type(关联业务类型:friendship / group / schedule_item
    • related_id(关联业务 ID
  • 时间字段: created_at, read_at, acted_at
  • 状态字段: statuspending|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
  • 状态字段: statuspending | 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
    • paramsJSONB
    • schedule_type(枚举:daily | weekly
    • schedule_time(时间,如 09:00 表示每天/每周几点执行)
  • 时间字段: created_at, updated_at
  • 状态字段: statusactive | disabled
  • 索引: INDEX(owner_id, status)
  • 审计: created_by

automation_runs

  • PK: id UUID
  • 关键字段:
    • job_id
    • scheduled_at(计划执行时间)
    • started_at
    • finished_at
    • statusqueued | running | succeeded | failed
    • attempt
    • error_message
    • resultJSONB
  • 时间字段: 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 = [<id1>, <id2>]
  • 待办完成时无需反向更新来源事项状态(简化设计)

F. 定时任务模型

1) 调度规则

  • schedule_type 枚举:daily(每日) | weekly(每周)
  • schedule_time 格式:HH:MM(如 09:00 表示每天/每周几点执行)
  • 调度器扫描 status='active' 的任务,按 schedule_type + schedule_time 计算下次执行时间

2) 执行记录

  • 每次执行生成 automation_runs 记录
  • 状态流转:queuedrunningsucceeded | 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/CHECKfriendships 规范化约束)

P1(协作事项) 7. CREATE TABLE schedule_items (...) 8. CREATE TABLE schedule_subscriptions (...) 9. CREATE INDEXowner/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 扩展:pgcryptoUUID/哈希,若已启用可跳过)
  • 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. 自动化任务失败重试上限与退避策略(固定/指数)。