Files
social-app/docs/plans/2026-02-26-social-data-model-redesign.md
T
qzl 76853452f6 chore: commit remaining workspace updates
include AGENTS guidance updates, plan doc replacements, and utility script changes left in working tree
2026-02-26 17:59:30 +08:00

652 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan: social-app 数据库数据模型重设计(支持社交/事项/自动化)
**Date:** 2026-02-26
**Author:** AI Assistant
**Status:** Draft
## 枚举存储约定
**统一使用枚举名称(字符串)存储,不使用整数值。**
- 数据库层:`VARCHAR(20)` + `CHECK` 约束
- 代码层:Python `Enum` 类继承 `str`
- 优势:调试可读、易扩展(新增枚举值无需迁移旧数据)、ORM 友好
```python
class AgentType(str, Enum):
INTENT_RECOGNITION = "INTENT_RECOGNITION"
TASK_EXECUTION = "TASK_EXECUTION"
RESULT_REPORTING = "RESULT_REPORTING"
```
```sql
-- Migration
ALTER TABLE user_agents ADD CONSTRAINT chk_agent_type
CHECK (agent_type IN ('INTENT_RECOGNITION', 'TASK_EXECUTION', 'RESULT_REPORTING'));
```
## 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 形态演进 |
| 记忆系统采用单表 + memory_type 区分 | user 类型可选 agent_idwork 类型必须绑定 agent_id |
| 好友关系用单表双向规范化表示 | 避免 A-B / B-A 重复,降低去重成本 |
| 事项权限采用 ACL 表而非仅 owner | 满足“仅特定人可修改”的协作场景 |
| 待办采用主表 + 关联表 | `todos` + `todo_sources` 保证来源关系可校验 |
| 自动化采用 Jobs 单表 + Sessions 关联 | `sessions` 通过 `session_type + job_id` 区分普通对话与自动化运行 |
| inbox 采用单表接收者视角 | 发送者 + 消息类型 + 关联业务,一表搞定待处理消息 |
## A. 设计原则与边界
### 1) 核心实体与聚合边界
- 用户聚合:`profiles`(含 settings JSONB, `user_agents`, `memories`
- 社交聚合:`friendships`, `groups`, `group_members`
- 协作事项聚合:`schedule_items`, `schedule_subscriptions`(当前仅用户主体)
- 消息聚合:`inbox_messages`
- 待办聚合:`todos`
- 自动化聚合:`automation_jobs`
### 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 (1) - (N) memories`
- `user_agents (1) - (N) memories`work 类型)
- `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) sessions`(通过 `sessions.job_id` 关联)
### 关键约束
- 唯一性:
- `user_agents.user_id` 唯一
- `friendships(user_low_id, user_high_id)` 唯一
- `group_members(group_id, user_id)` 唯一
- `schedule_subscriptions(item_id, subscriber_id)` 唯一
- CHECK
- `friendships`: `user_low_id < user_high_id``user_low_id <> user_high_id`
- `schedule_subscriptions`: `permission BETWEEN 0 AND 7`
- `memories`: `work` 类型必须有 `agent_id``user` 类型必须无 `agent_id`
- `sessions`: `session_type/job_id` 组合一致
- 外键:统一显式 `ON DELETE` 策略(见下)
- 可空性:权限关键字段、状态字段默认 `NOT NULL`
- 删除策略:
- 用户删除:大部分 `CASCADE`(用户私有数据);跨用户协作数据优先软删
- 事项删除:对子表 `CASCADE`;待办保留历史,改 `status = 'archived'`
### 外键删除策略明细(必做)
- `sessions.job_id -> automation_jobs.id`: `ON DELETE RESTRICT`
- `todo_sources.todo_id -> todos.id`: `ON DELETE CASCADE`
- `todo_sources.schedule_item_id -> schedule_items.id`: `ON DELETE CASCADE`
- `inbox_messages.friendship_id -> friendships.id`: `ON DELETE CASCADE`
- `inbox_messages.schedule_item_id -> schedule_items.id`: `ON DELETE CASCADE`
- `inbox_messages.group_id -> groups.id`: `ON DELETE CASCADE`
## C. 数据库表设计(PostgreSQL
以下为推荐主表(方案 1,规范化优先)。字段示例采用 `UUID + timestamptz + enum/text-check`
### 1) 用户与 agent
#### `profiles`(已有,建议补齐)
- PK: `id UUID` (`auth.users.id`)
- 关键字段: `username`, `avatar_url`, `bio`
- **新增 JSONB 字段**:
- `settings JSONB`(用户自定义设置,含 `version`, `preferences`, `privacy`, `notification` 四大块)
- 时间字段: `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 配置参数)
- 时间字段: `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`
#### `memories`
- PK: `id UUID`
- 关键字段:
- `owner_id`(用户,NOT NULL
- `agent_id`work 类型时必需)
- `memory_type`(枚举:`user | work`
- `title`
- `content`JSONB,存储具体记忆结构)
- `source``manual | agent | imported`
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status``active | disabled`
- 索引:
- `INDEX(owner_id, memory_type, status)`
- `INDEX(agent_id, memory_type, status)`
- `GIN(content)`(支持 JSONB 内容查询)
- 约束: `CHECK ((memory_type = 'work' AND agent_id IS NOT NULL) OR (memory_type = 'user' AND agent_id IS NULL))`
**memory_type 说明**
| 类型 | agent_id | 说明 |
|------|----------|------|
| `user` | 可空 | 用户记忆:偏好、背景信息、实体等 |
| `work` | 必需 | 工作记忆:长期运行后对工作流程的经验整理,避免重复错误 |
**content JSONB 示例**
```json
// 用户记忆
{"type": "preference", "data": {"style": "concise", "language": "zh-CN"}}
// 工作记忆
{"type": "workflow_summary", "data": {"task": "代码审查", "learnings": ["优先检查安全漏洞", "关注性能热点"], "improvements": []}}
```
### 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`, `owner_id`
- 时间字段: `created_at`, `updated_at`, `deleted_at`
- 状态字段: `status``active|archived`
- 索引: `INDEX(owner_id, status)`
- 审计: `created_by`, `updated_by`
#### `group_members`
- PK: `id UUID`
- 关键字段:
- `group_id`, `user_id`
- `role`(枚举:`owner` | `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 | 含义 |
|------|------|
| `owner` | 群主/创建者 |
| `admin` | 管理员 |
| `member` | 普通成员 |
- 角色可升降:服务层变更 role 字段即可
### 3) 用户设置(已合并至 profiles 表)
用户设置采用 JSONB 内嵌方式,渐进式扩展无需改表结构:
```json
{
"version": 1,
"preferences": {
"interface_language": "zh-CN",
"ai_language": "zh-CN",
"timezone": "Asia/Shanghai"
},
"privacy": {},
"notification": {}
}
```
- 索引策略:对高频查询字段使用表达式索引
- 更新方式:服务层使用 JSONB merge 或字段级 UPDATE,避免读-改-写并发问题(建议用 `jsonb_set` 原子操作)
### 4) 事项与订阅/权限
#### `schedule_items`
- PK: `id UUID`
- 关键字段:
- `owner_id`
- `title`
- `description`
- `start_at`
- `end_at`
- `timezone`(用于将日程时间转换为用户本地时间显示)
- `metadata`JSONB,扩展字段)
- `recurrence_rule`(可选,支持循环日程)
- `source_type``manual | imported | agent_generated`
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status``active | completed | canceled | archived`
- 索引:
- `INDEX(owner_id, start_at)`
- `INDEX(status, start_at)`
- 审计: `created_by`
**metadata JSONB 示例**
```json
{
"color": "#FF6B6B",
"location": "会议室A",
"notes": "记得提前准备投影仪",
"attachments": [
{
"name": "会议纪要.pdf",
"url": "https://...",
"visible_to": [],
"type": "document"
},
{
"name": "投影仪提醒",
"visible_to": ["uuid1"],
"type": "reminder",
"content": "记得带投影仪"
},
{
"name": "技术方案.docx",
"url": "https://...",
"visible_to": ["uuid2"],
"type": "document",
"note": "需要他确认预算"
}
],
"version": 1
}
```
| type | 说明 | 特殊字段 |
|------|------|----------|
| document | 文档/文件 | url, note |
| reminder | 提醒 | content |
#### `schedule_subscriptions`
- PK: `id UUID`
- 关键字段:
- `item_id`
- `subscriber_id`
- `permission`(INTEGER,用位运算存储权限组合,`NOT NULL DEFAULT 1`
- `notify_level``all | mentions | none``NOT NULL DEFAULT 'all'`
- 时间字段: `created_at`
- 状态字段: `status``active | paused | unsubscribed``NOT NULL DEFAULT 'active'`
- 约束: `UNIQUE(item_id, subscriber_id)`
- 约束补充: `CHECK(permission BETWEEN 0 AND 7)``view=1, invite=2, edit=4``0` 表示无权限)
- 索引: `INDEX(subscriber_id, status)`, `INDEX(item_id, status)`
- 审计: `created_by`
**权柄说明(位运算)**
| 权柄 | 值 | 二进制 | 说明 |
|------|-----|--------|------|
| view | 1 | 001 | 查看事项详情 |
| invite | 2 | 010 | 邀请其他人订阅此事项 |
| edit | 4 | 100 | 修改事项内容、管理订阅 |
- 权限检查:`permission & 2 = 2` 检查是否有 invite 权限
- 权限添加:`permission | 2` 添加 invite 权限
- 事项 owner 默认拥有全部权柄:`7`111
- owner 权柄由服务层恒等判定为 `7`,不依赖 owner 是否在 `schedule_subscriptions` 中存在记录
**当前版本边界**
- `schedule_subscriptions` 仅支持用户订阅(`subscriber_id -> auth.users.id`
- 事项协作暂不引入群主体授权
### 5) 待处理消息(Inbox
#### `inbox_messages`
- PK: `id UUID`
- 关键字段:
- `recipient_id`(接收者)
- `sender_id`(发送者,系统消息可为 NULL
- `message_type`(枚举:`friend_request | calendar | system | group`
- `friendship_id`(可空,`friend_request` 时必填)
- `schedule_item_id`(可空,`calendar` 时必填)
- `group_id`(可空,`group` 时必填)
- `content`TEXT,消息内容,系统消息用)
- 时间字段: `created_at`
- 状态字段:
- `is_read`BOOLEAN,是否已读)
- `status``pending | accepted | rejected | dismissed`
- 索引:
- `INDEX(recipient_id, status, created_at DESC)`
- 部分索引 `INDEX(recipient_id, created_at DESC) WHERE status='pending'`
- 审计: `created_by`
**message_type 与业务字段对应关系**
| message_type | 对应业务字段 |
|--------------|-----------------|
| friend_request | friendship_id -> friendships.id |
| calendar | schedule_item_id -> schedule_items.id |
| system | 三个业务字段均为 NULL(内容直接在 content |
| group | group_id -> groups.id |
**说明**:一张表搞定,接收者视角,通过 `message_type + 对应业务字段` 直接定位要处理的业务,避免单列多态外键带来的引用不一致问题。
**一致性约束(必做)**
- 使用 `CHECK` 保证不同 `message_type` 下仅允许对应业务字段非空(`system` 时业务字段全空)
- 使用 `CHECK` 保证 `message_type='system'``sender_id IS NULL`,否则 `sender_id IS NOT NULL`
- `friendship_id``schedule_item_id``group_id` 分别建立 FK,并显式声明 `ON DELETE` 策略
### 6) 待办
#### `todos`
- PK: `id UUID`
- 关键字段:
- `owner_id`
- `title`
- `description`
- `due_at`
- `priority`(INTEGER,用于四象限:1=重要且紧急, 2=重要不紧急, 3=紧急不重要, 4=不重要不紧急)
- 时间字段: `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`
#### `todo_sources`
- PK: `id UUID`
- 关键字段:
- `todo_id`FK -> todos.id
- `schedule_item_id`FK -> schedule_items.id
- 时间字段: `created_at`
- 约束: `UNIQUE(todo_id, schedule_item_id)`
- 索引: `INDEX(todo_id)`, `INDEX(schedule_item_id)`
**说明**
- 手动创建待办:不写 `todo_sources`
- 从事项提取待办:写入 `todo_sources`,替代 JSONB 数组,保证来源关系可校验
### 7) 自动化定时任务
#### `automation_jobs`
- PK: `id UUID`
- 关键字段:
- `owner_id`
- `title`(任务标题)
- `prompt`AI 执行的 prompt
- `schedule_type`(枚举:`daily | weekly`
- `run_at`(首次运行时间)
- `next_run_at`(下次运行时间,调度器扫描主字段)
- `timezone`(时区,如 `Asia/Shanghai`
- `last_run_at`(最近运行时间,可空)
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status``active | disabled`
- 索引: `INDEX(owner_id, status)`, `INDEX(status, next_run_at)`
- 约束补充: `UNIQUE(id, owner_id)`(用于 `sessions(job_id, user_id)` 归属一致性外键)
- 审计: `created_by`
**说明**:定时任务执行时,在 sessions 表创建记录存储 AI 对话内容。
### 8) 会话表扩展(已有 sessions
#### `sessions`(更新)
- 新增字段:
- `session_type``chat | automation`
- `job_id`(可空,FK -> automation_jobs.id
- 一致性约束:
- `CHECK((session_type = 'chat' AND job_id IS NULL) OR (session_type = 'automation' AND job_id IS NOT NULL))`
- 通过复合 FK 约束归属一致性:`FOREIGN KEY(job_id, user_id) -> automation_jobs(id, owner_id)`
- 索引:
- `INDEX(user_id, session_type, last_activity_at DESC)`
- `INDEX(job_id)`
## D. 权限与协作模型
### 1) 事项权限落表
- 权限直接存储在 `schedule_subscriptions.permission` 整数中(位运算)
- owner 不写入 `schedule_subscriptions`owner 权限仅由 `schedule_items.owner_id` 推导
- 权限决策顺序:
1. `schedule_items.owner_id` → 服务层恒等全部权柄 `["view", "invite", "edit"]`7
2. `schedule_subscriptions` 中该用户的 `permission` 位图
3. 非 owner 且非 subscriber 默认无权限(0
### 2) 当前版本边界
- 事项权限仅处理用户主体(owner + subscriber
- 群组与事项权限继承关系不在本期范围
## E. 消息与待办联动
### 1) inbox 关联业务对象
- `inbox_messages.message_type` 枚举:
- `friend_request`(好友请求)→ `friendship_id` 指向 friendships
- `calendar`(日程邀请)→ `schedule_item_id` 指向 schedule_items
- `system`(系统消息)→ 业务字段均为 NULL
- `group`(群组邀请)→ `group_id` 指向 groups
- 通过 `message_type + 对应业务字段` 直接定位业务对象,并用 `CHECK` 约束保证字段一致性
### 2) 待办来源提取
- 从事项提取待办时,写入 `todo_sources(todo_id, schedule_item_id)`
- 手动创建的待办不写 `todo_sources`
- 支持多来源:同一待办可关联多个日程事项(多行 `todo_sources`
- 待办完成时无需反向更新来源事项状态(简化设计)
## F. 定时任务模型
### 1) 调度规则
- `schedule_type` 枚举:`daily`(每日) | `weekly`(每周)
- `run_at` 用于首次执行时间,`next_run_at` 用于后续调度
- 调度器扫描 `status='active' AND next_run_at <= now()` 的任务,执行后回写下一次 `next_run_at`
- `timezone` 参与下一次执行时间计算,避免时区偏差
### 2) 执行记录
- 每次执行在 sessions 表创建记录,通过 `sessions.job_id` 关联 job
- `sessions` 通过 `session_type` 区分 `chat``automation`
- 执行失败时记录在 `automation_jobs`(如 `last_error`,可后续细化)
## G. 数据库迁移思路
### 策略:重建数据库 + Alembic ORM 迁移
由于是全新设计的数据模型,且当前处于开发初期(可清除旧数据),采用**重建数据库**策略:
**执行门禁(强制)**
- 仅允许在本地开发环境执行
- 禁止在生产/共享环境执行 `rm backend/alembic/versions/*.py`
- 执行前必须备份数据库或创建 git tag
1. **删除所有旧 migration 脚本**(保留 `env.py`
2. **创建 ORM 模型文件**
3. **生成 Alembic migration**
4. **重建数据库并执行迁移**
### 执行步骤
1. 删除旧 migration 文件
```bash
rm backend/alembic/versions/*.py
```
2. 重建空数据库(确保以空库基线生成 initial migration
```bash
docker compose --env-file .env -f infra/docker/docker-compose.yml down -v
docker compose --env-file .env -f infra/docker/docker-compose.yml up -d db
```
3. 创建 ORM 模型文件(参考 `models/` 目录结构)
- 新增:`user_agents.py`, `memories.py`, `friendships.py`, `groups.py`, `group_members.py`, `schedule_items.py`, `schedule_subscriptions.py`, `inbox_messages.py`, `todos.py`, `todo_sources.py`, `automation_jobs.py`
- 更新:`profile.py` - 添加 `settings` 字段
- 更新:`agent_chat_session.py` - 添加 `session_type`、`job_id` 字段
- 重写:`create_profile_for_new_user` 触发器,确保 `profiles.settings` 有默认值
4. 更新 `models/__init__.py` 导出所有模型
5. 更新 `alembic/env.py` 添加模型导入
6. 生成 initial migration(以空库为对比基线)
```bash
cd backend && uv run alembic revision --autogenerate -m "initial schema"
```
7. 为所有新建 `public` 业务表补齐 RLS`SELECT/INSERT/UPDATE/DELETE` policy
- 每张表都执行 `ENABLE ROW LEVEL SECURITY`
- 每张表都显式创建 `SELECT/INSERT/UPDATE/DELETE` policy
- downgrade 必须可逆,不得弱化既定安全边界
- `anon/authenticated` 默认全部 deny
RLS 最小策略矩阵(本期统一模板):
- `anon``SELECT/INSERT/UPDATE/DELETE` 全部 deny
- `authenticated``SELECT/INSERT/UPDATE/DELETE` 全部 deny
- `service_role`:由后端服务连接使用,不依赖 RLS 放行
8. 执行迁移
```bash
cd backend && uv run alembic upgrade head
```
9. 验证表结构
## H. 交付物
### ORM 模型文件清单
| 文件 | 说明 |
|------|------|
| `models/user_agents.py` | 用户专属 agent |
| `models/memories.py` | 用户/工作记忆 |
| `models/friendships.py` | 好友关系 |
| `models/groups.py` | 群组 |
| `models/group_members.py` | 群组成员 |
| `models/schedule_items.py` | 日程事项 |
| `models/schedule_subscriptions.py` | 日程订阅与权限 |
| `models/inbox_messages.py` | 待处理消息 |
| `models/todos.py` | 待办 |
| `models/todo_sources.py` | 待办与事项来源关联 |
| `models/automation_jobs.py` | 定时任务 |
| `models/profile.py` | 更新:添加 `settings` 字段 |
| `models/agent_chat_session.py` | 更新:添加 `session_type`、`job_id` 字段 |
### 执行步骤
1. 删除旧 migration 文件
```bash
rm backend/alembic/versions/*.py
```
2. 重建空数据库(确保以空库基线生成 initial migration
```bash
docker compose --env-file .env -f infra/docker/docker-compose.yml down -v
docker compose --env-file .env -f infra/docker/docker-compose.yml up -d db
```
3. 创建/更新 ORM 模型文件
4. 更新 `models/__init__.py` 导出所有模型
5. 更新 `alembic/env.py` 添加模型导入
6. 生成 initial migration(以空库为对比基线)
```bash
cd backend && uv run alembic revision --autogenerate -m "initial schema"
```
7. 为所有新建 `public` 业务表补齐 RLS`SELECT/INSERT/UPDATE/DELETE` policy
- 每张表都执行 `ENABLE ROW LEVEL SECURITY`
- 每张表都显式创建 `SELECT/INSERT/UPDATE/DELETE` policy
- downgrade 必须可逆,不得弱化既定安全边界
8. 执行迁移
```bash
cd backend && uv run alembic upgrade head
```
9. 更新测试文件适配新表结构
## I. 数据库表名规范与审计
### 1) 命名规范(统一执行)
- 使用 `snake_case`
- 业务表统一使用复数名词(如 `profiles`, `friendships`, `automation_jobs`
- 关联表使用 `<主实体复数>_<从实体复数>` 或约定俗成复数短语(如 `group_members`, `todo_sources`
- 禁止过于泛化的表名(如 `messages`, `sessions`),必须带业务前缀
- 存量历史表允许短期例外,但必须在审计表中登记并给出迁移计划
- 缩写保持一致:LLM 统一使用 `llm` 前缀,不混用 `model`/`llm` 两套命名
### 2) 表名审计结果
| 当前表名 | 审计结论 | 建议表名 | 说明 |
|----------|----------|----------|------|
| `profiles` | 通过 | - | 符合复数名词规范 |
| `user_agents` | 通过 | - | 语义清晰 |
| `memories` | 通过 | - | 语义清晰 |
| `friendships` | 通过 | - | 关系表命名清晰 |
| `groups` | 通过 | - | 符合规范 |
| `group_members` | 通过 | - | 关联表命名清晰 |
| `schedule_items` | 通过 | - | 语义清晰 |
| `schedule_subscriptions` | 通过 | - | 语义清晰 |
| `inbox_messages` | 通过 | - | 带业务前缀,避免歧义 |
| `todos` | 通过 | - | 简洁且清晰 |
| `todo_sources` | 通过 | - | 关联关系明确 |
| `automation_jobs` | 通过 | - | 语义清晰 |
| `llms` | 通过 | - | 与 LLM 语义一致 |
| `llm_factory` | 建议调整 | `llm_factories` | 当前为单数,建议改复数以统一规范 |
| `sessions` | 建议调整 | `agent_chat_sessions` | 过于泛化,建议加业务前缀 |
| `messages` | 建议调整 | `agent_chat_messages` | 过于泛化,建议加业务前缀 |
### 3) 落地建议
- 本期命名边界:不重命名 `llm_factory/sessions/messages`,仅在新表严格执行命名规范
- 本期最小可行:先保持现有表名可运行,新增表全部遵循规范
- 下期统一治理:通过一次性迁移将 `llm_factory/sessions/messages` 重命名到规范名
- 若本期直接重命名,需同步 ORM 模型、外键、索引、RLS policy 名称与运行文档