76853452f6
include AGENTS guidance updates, plan doc replacements, and utility script changes left in working tree
652 lines
26 KiB
Markdown
652 lines
26 KiB
Markdown
# 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_id,work 类型必须绑定 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 名称与运行文档
|