feat: 添加飞书用户模块和订阅功能支持

- 新增feishu_users模块用于处理飞书用户身份验证和权限管理
- 新增subscriptions模块用于处理订阅相关功能
- 新增personalization模块用于个性化服务
- 在alembic迁移配置中注册新的模型模块
- 在API路由器中添加feishu_users和subscriptions路由
- 实现事件调度服务的改进,包括错误处理和状态更新优化
- 添加飞书命令处理的权限检查机制
- 实现飞书应用票据事件处理
- 改进审计日志记录功能
```
This commit is contained in:
2026-07-27 08:02:17 +08:00
parent db751f03b4
commit d7db84571d
148 changed files with 17110 additions and 765 deletions

View File

@@ -0,0 +1,251 @@
# 设计文档
## 概述
本设计在现有 FastAPI 模块化单体内增加飞书终端用户身份、个人画像与持久化订阅三个
业务域。内部 API 继续使用服务 API Key飞书终端权限只能来自已经通过验签或 SDK
验证的事件,身份主键固定为 `(tenant_key, open_id)`
平台只写自身数据库。旧业务 MySQL 保持 SELECT-only。个人定时提示词不允许调用工具、
读取公司报表或写入问答历史;群订阅也不加载创建者个人画像。
## 架构设计
### 系统架构图
```mermaid
flowchart LR
F["飞书 webhook / 长连接"] --> V["事件验证与解密边界"]
V --> I["feishu_users<br/>身份与权限"]
I --> C["飞书命令编排"]
C --> P["personalization<br/>规则/偏好/会话"]
C --> S["subscriptions<br/>计划/投递"]
C --> A["ai_agent<br/>受限问答"]
API["内部 API Key"] --> I
API --> S
DB[("平台数据库")] --- I
DB --- P
DB --- S
T["每分钟持久化扫描"] --> S
S --> A
S --> FC["FeishuClient"]
```
### 数据流图
```mermaid
sequenceDiagram
participant F as 飞书
participant E as 事件服务
participant U as 用户服务
participant C as 命令服务
participant P as 个性化服务
participant A as AI
F->>E: 已签名消息事件
E->>E: 验签/解密、事件去重
E->>U: tenant_key + sender open_id
U->>U: 查找或自动注册普通用户
U-->>E: FeishuPrincipal
E->>C: principal + chat context + mentions
C->>P: 仅加载 principal 所有的数据
P->>A: 安全规则→公司规则→个人规则→请求→偏好/记忆→历史
A-->>C: 回答或明确不可用
C-->>F: 原会话回复
```
## 组件与接口
### `app/modules/feishu_users`
- `models.py` 定义 `FeishuUser`,公开随机 `code`,内部使用整数主键。
- `principal.py` 定义不可伪造的 `FeishuPrincipal`包含用户编号、租户、open_id、角色、
状态、当前 chat_id/chat_type 和结构化 mentions。
- `services/identity.py` 只接收验证边界传入的身份,负责首次注册、初始管理员引导、
最后活跃时间和旧自选认领。
- `services/management.py` 负责列表、角色/状态修改、最后管理员保护和审计。
- `routes.py` 暴露服务 API Key 保护的用户管理接口;正文中的 actor/open_id 不参与授权。
### `app/modules/personalization`
- `UserPreference` 保存白名单类别:`language``tone``detail``topic``interest`
- `AIConversation` 唯一标识“用户 + 私聊/群聊”;`AIConversationMessage` 保存消息,
每次读写时清理 30 天外记录并裁剪到最近 20 轮。
- `services/preferences.py` 提供显式偏好管理和真实 AI 可用时的结构化提取;敏感类别及
密钥样式在入库前拒绝。
- `services/conversations.py` 提供历史加载、追加、重置。
- `services/context.py` 按固定优先级组装问答上下文。
- `services/erasure.py` 实现一次性确认码和事务性“忘记我”,审计只保留随机匿名主体。
### `app/modules/subscriptions`
- `services/schedule_parser.py` 实现受控中文语法,不使用不确定的自由文本推断。
- `services/subscriptions.py` 管理创建、列表、暂停、恢复、退订、时区和安静时段。
- `services/scanner.py` 每分钟领取到期订阅,使用行锁(支持时)与唯一投递键创建投递,
并推进订阅的下一执行时间。
- `services/delivery.py` 生成受限 AI 内容并发送;个人订阅仅使用个人上下文,群订阅仅使用
系统/公司规则。HTTP 错误、429、5xx 和飞书业务 `code != 0` 都是失败。
- `tasks/``task_queue/` 仅作为执行适配层;关闭 Celery 时由扫描器直接处理持久化投递。
### 共享集成
- `FeishuEventService` 在验证后、注册事件前解析 `tenant_key/open_id`;缺失身份不创建数据。
- `FeishuCommandService.handle_text` 接收 `FeishuPrincipal | None`。内部预览接口不构造终端
用户身份,因此只能预览不涉及个人数据的安全路径。
- 管理员能力由统一权限守卫保护:公司规则、公司/财务/风险/考勤命令、用户管理、群订阅。
- `FeishuClient.send_message` 接收租户键和可选稳定 `uuid`,并把非零业务码转换为可分类失败。
- 自建应用使用 `/auth/v3/tenant_access_token/internal`;商店应用先使用最近一次已验证的
`app_ticket` 获取 `app_access_token`,再按目标 `tenant_key` 获取
`tenant_access_token`。应用令牌按应用缓存,租户令牌按 `(app_id, tenant_key)` 隔离缓存。
- 只有通过 webhook 验真或长连接 SDK 验证的 `app_ticket` 事件可以轮换持久化票据;
环境变量票据仅作为启动兜底,票据和访问令牌不得进入日志、审计或响应。
- 命令回复、卡片、图片和个人/群订阅都显式携带事件主体的 `tenant_key`;固定默认群报表
使用 `FEISHU_DEFAULT_TENANT_KEY`,不得复用另一租户的令牌。
- AI adapter context 增加每用户 session id`noop` 明确返回不可用且不写会话、偏好或记忆。
## 数据模型
### `FeishuUser`
- `id`, `code`, `tenant_key`, `open_id`, `union_id`, `user_id`
- `role` (`user|admin`), `status` (`active|disabled`)
- `timezone`, `quiet_hours_start`, `quiet_hours_end`
- `last_active_at`, `created_at`, `updated_at`
- 唯一约束:`(tenant_key, open_id)``code` 全局唯一。
### `FeishuAdminBootstrapTombstone`
- 仅保存带域隔离的 `tenant_key + open_id` 不可逆摘要和创建时间。
- 不保存原始飞书身份、owner、角色或其他画像仅用于阻止已执行“忘记我”的
配置初始管理员在重新联系时被自动重授管理员。
### `UserPreference`
- `id`, `code`, `owner_id`, `category`, `value`, `source`, `created_at`, `updated_at`
- 唯一约束:`(owner_id, category, normalized_value)`
### `AIConversation` / `AIConversationMessage`
- 会话:`id`, `code`, `owner_id`, `chat_type`, `chat_key`, `created_at`, `updated_at`
- 消息:`id`, `conversation_id`, `role`, `content`, `created_at`
- 唯一约束:`(owner_id, chat_type, chat_key)`
### 现有模型扩展
- `AIMemoryEntry.owner_id` 可空;公司规则 `owner_id=NULL`,个人规则/记忆必须有 owner。
- `AIMemoryEntry.kind` 区分 `company_rule|personal_rule|memory`,旧显式规则迁为
`company_rule/legacy_company`,旧无所有者自动记忆归档。
- 指纹唯一性改为 `(owner_id, fingerprint)`,公司数据使用空 owner 的独立范围。
- `MarketWatchlist.owner_id` 可空;旧 actor 暂存为 legacy claim key首次注册时认领
认领前个人查询不可见。
### `PushSubscription`
- `id`, `code`, `owner_id`, `target_type` (`user|chat`), `target_id`
- `prompt`, `schedule_type`, `schedule_config`, `timezone`, `next_run_at`
- `status`, `consented_at`, `last_run_at`, `created_at`, `updated_at`
- 群目标仅从当前已验证群事件写入。
### `PushDelivery`
- `id`, `code`, `subscription_id`, `scheduled_for`, `idempotency_key`, `message_uuid`
- `status`, `attempt_count`, `next_attempt_at`, `provider_message_id`
- `last_error`, `sent_at`, `created_at`, `updated_at`
- `idempotency_key``message_uuid` 全局唯一。
### `FeishuAppTicket`
- `id`, `app_id`, `app_ticket`, `received_at`, `updated_at`
- `app_id` 全局唯一;只保留当前有效票据,不保留票据历史。
## 业务流程
### 身份与权限
```mermaid
flowchart TD
E["收到事件"] --> V{"来源已验证?"}
V -- 否 --> R["拒绝且不创建个人数据"]
V -- 是 --> K{"tenant_key/open_id 完整?"}
K -- 否 --> R
K -- 是 --> U["查找或注册普通用户"]
U --> B["应用 FEISHU_ADMIN_IDENTITIES 引导"]
B --> S{"用户有效且有权限?"}
S -- 否 --> D["拒绝并审计"]
S -- 是 --> C["执行命令"]
```
初始管理员配置按 `tenant_key:open_id` 精确匹配,多条配置均可各自完成首次引导。若该
配置身份曾执行“忘记我”,系统在同一删除事务内保留不可逆 bootstrap tombstone再次
联系时只注册为普通用户。飞书管理员命令只读取事件中的结构化 mention open_id内部
API 修改由服务 principal 审计。降级或停用前锁定目标并统计有效管理员,禁止移除最后
一个。
### 问答与偏好
1. 验证 principal 并按用户 + chat 建立会话。
2. 加载启用的公司规则、当前用户个人规则、偏好/兴趣、相关个人记忆和最近历史。
3. 按固定顺序传给 AI使用由用户与会话派生的 provider session id。
4. 真实 AI 成功后保存本轮消息,再做一次受限偏好提取;定时任务跳过两步。
5. 每次读写裁剪到最近 20 轮并删除 30 天前消息。
### 计划解析
支持:
- 单次:今天/明天 HH:mm`YYYY-MM-DD HH:mm`
- 周期:每天、工作日、每周一至周日、每月 1-31 号
- 间隔:每隔 N 分钟/小时,折算后不得短于 15 分钟
解析输出 `schedule_type + schedule_config + timezone + next_run_at(UTC)`。日期不存在、时间
已过、模糊表达、无效 IANA 时区均拒绝。月末没有目标日期时跳到下个有效月份。
### 扫描、投递与重试
```mermaid
flowchart TD
S["每分钟扫描 next_run_at<=now"] --> L["领取订阅并创建唯一投递"]
L --> Q{"用户/订阅/频控/安静时段允许?"}
Q -- 否 --> P["跳过或延后,并推进计划"]
Q -- 是 --> G["生成受限内容"]
G --> F["以 delivery UUID 发送飞书"]
F --> O{"HTTP 与业务 code 成功?"}
O -- 是 --> X["标记 sent"]
O -- 可重试 --> B["1/5/15 分钟后 retry"]
O -- 最终失败 --> Z["标记 failed"]
```
投递尝试总计最多四次(初次 + 三次重试)。扫描器重启后继续处理
`pending/retry` 状态。唯一投递键由 `subscription_id + scheduled_for` 派生;同一键重复任务
返回已有结果,不再次调用飞书。
### 忘记我
首次命令仅保存哈希确认码与短期过期时间。确认后在一个事务内删除个人规则/记忆、偏好、
兴趣、会话、订阅及投递中的个人内容,删除身份映射;相关审计 actor/target 替换为随机
匿名标识,并清空目标、请求、响应和请求 ID。若被删除身份仍属于初始管理员配置仅保留
不可逆 bootstrap tombstone 防止重新授予;用户再次联系时按新普通用户注册。删除成功
确认消息不再额外写入包含目标摘要或提供方响应的发送审计。
## 错误处理
- 未验证事件、缺身份、停用用户、权限不足:拒绝且只审计最小元数据。
- AI 未配置或 `noop`返回“AI 当前不可用”,不创建虚假回答、历史、偏好或记忆。
- 计划解析失败、额度超限、手填群 id使用可操作示例响应且不部分写入。
- 飞书 429/5xx/超时及业务非零码:分类为可重试;其他 4xx 为最终失败。
- 缺少飞书凭据且存在启用订阅readiness 返回 degraded不影响基础 health。
- 商店应用缺少可用 `app_ticket` 或默认租户时 readiness 返回 degraded自建应用若启用订阅
涉及多个租户时 readiness 返回 degraded避免把单租户令牌错误用于其他租户。
- 数据库竞争:依赖唯一约束兜底;冲突后回滚到保存点并读取已存在投递。
## 测试策略
- 单元测试:身份解析、权限矩阵、敏感偏好过滤、上下文顺序、会话裁剪、计划解析、
安静时段、下一执行时间和重试分类。
- 服务测试:两租户/两用户隔离、最后管理员保护、群订阅绑定、忘记我级联与匿名审计。
- 并发/幂等测试:多扫描器只产生一条投递、相同 UUID 不重复发送、业务非零码不成功。
- API/事件测试:验签失败不注册、内部 API Key 保护、正文 actor 不参与权限。
- 多租户认证测试A/B 租户令牌缓存隔离、`app_ticket` 只能由已验证事件更新、文本/卡片/
图片/订阅均使用目标租户、固定群任务使用默认租户。
- 迁移测试Alembic head 与元数据一致,旧规则/记忆/自选按既定策略迁移。
- 回归测试:现有固定群报表调度与现有内部接口继续工作。

View File

@@ -0,0 +1,213 @@
# 需求文档
## 简介
本功能为飞书用户提供基于飞书账号身份的安全问答、个人规则、长期记忆、
兴趣偏好和定时推送能力。飞书账号是终端用户权限的唯一来源;内部 API Key
继续用于服务间认证,不得代表或伪造终端用户。
本功能只允许写入平台自身的身份、权限、偏好、订阅、记忆、审计和投递状态。
现有业务 MySQL 必须保持 SELECT-only不得增加业务数据回写、审批、付款、
绩效定级、交易或其他越权动作。
## 已确认边界
- 单个飞书应用可以服务一个或多个飞书租户。
- 飞书用户身份使用 `tenant_key + open_id` 唯一确定。
- `union_id` 和飞书 `user_id` 可以作为关联标识,但不得单独作为默认权限主键。
- `chat_id` 只表示会话或群推送目标,不表示用户身份。
- 个人数据默认不共享;公司级规则必须经过管理员权限控制。
- 用户规则和兴趣属于低信任数据,不能覆盖系统安全、只读、权限和审计规则。
- 本阶段不建设终端用户 Web 页面,不同步修改飞书通讯录,也不回写任何业务系统。
## 需求列表
### 需求 1飞书账号身份认证
**用户故事:** 作为飞书用户,我希望系统准确识别我的飞书账号,以便所有问答、
规则和订阅都归属于我本人。
#### 验收条件
1. WHEN 系统收到已经通过飞书验证的 webhook 或长连接消息事件 THEN 系统 SHALL
从事件中提取 `tenant_key` 和发送者 `open_id`,并解析为唯一用户身份。
2. IF 消息事件缺少 `tenant_key``open_id` 或未通过飞书来源验证 THEN 系统 SHALL
拒绝执行用户命令,且不得创建任何个人数据。
3. WHEN 一个合法飞书账号首次与机器人交互 THEN 系统 SHALL 为其建立默认普通用户身份,
且不得自动授予管理权限。
4. WHERE 不同租户出现相同 `open_id` THEN 系统 SHALL 将其识别为不同用户。
5. WHEN 系统处理群聊消息 THEN 系统 SHALL 以消息发送者账号作为用户身份,而不是以
`chat_id` 或群成员身份作为用户身份。
6. IF 飞书用户被停用 THEN 系统 SHALL 拒绝其所有受保护命令并记录拒绝审计。
### 需求 2基于飞书账号的角色与权限
**用户故事:** 作为管理员,我希望权限绑定到飞书账号,以便普通用户只能操作自己的数据,
而受信任人员才能使用公司级能力。
#### 验收条件
1. WHEN 新飞书用户首次注册 THEN 系统 SHALL 仅授予普通用户角色。
2. WHEN 用户执行命令 THEN 系统 SHALL 根据该飞书身份的有效角色和权限决定是否允许执行。
3. IF 普通用户尝试创建、修改或停用公司级规则 THEN 系统 SHALL 拒绝操作并记录审计。
4. IF 用户尝试访问未授权的公司、项目、财务、风险或审计能力 THEN 系统 SHALL
返回不泄露受保护数据的拒绝响应。
5. WHEN 内部服务管理用户角色或状态 THEN 系统 SHALL 要求有效的内部服务认证,
且角色变更 SHALL 记录操作者、目标飞书用户、变更前后状态和时间。
6. IF 请求正文提供 `actor``open_id` 或其他可伪造身份字段 THEN 系统 SHALL
忽略这些字段作为权限依据。
### 需求 3个人规则和记忆隔离
**用户故事:** 作为飞书用户,我希望我的规则和历史记忆只影响我自己的回答,
以免其他用户看到或受其影响。
#### 验收条件
1. WHEN 用户创建规则、偏好或 AI 记忆 THEN 系统 SHALL 将记录关联到当前飞书用户所有者。
2. WHEN 系统列出、召回、更新、启停或删除个人规则与记忆 THEN 系统 SHALL
强制按当前用户所有者过滤。
3. IF 用户提交其他用户的数据编号 THEN 系统 SHALL 返回未找到或无权限,
且不得泄露该记录是否存在。
4. WHEN 两个用户保存相同内容 THEN 系统 SHALL 分别保存或去重到各自所有者范围内,
不得复用另一用户的记录。
5. WHEN 用户在群聊中创建个人规则或产生记忆 THEN 系统 SHALL 仍将其仅归属于发送者。
6. WHERE 公司级规则被应用 THEN 系统 SHALL 仅使用已经启用且具有管理员来源的公司规则。
7. WHEN 迁移现有无所有者的规则和记忆 THEN 系统 SHALL 不得把旧数据自动暴露给任意个人;
无法安全归属的数据 SHALL 进入停用或待审状态。
### 需求 4个人规则、偏好和兴趣管理
**用户故事:** 作为飞书用户,我希望通过飞书告诉机器人我的规则、表达偏好和兴趣,
并能随时查看或撤销,以便后续交流符合我的要求。
#### 验收条件
1. WHEN 普通用户发送学习规则命令 THEN 系统 SHALL 默认创建个人规则,而不是公司规则。
2. WHEN 有权限的管理员明确发送公司规则命令 THEN 系统 SHALL 创建公司级规则并记录审计。
3. WHEN 用户设置语言、语气、内容详略、关注主题或其他偏好 THEN 系统 SHALL
以结构化个人偏好保存并关联当前用户。
4. WHEN 用户添加或移除兴趣 THEN 系统 SHALL 更新当前用户的兴趣数据,
且现有个人股票自选 SHALL 作为市场兴趣来源之一。
5. WHEN 普通问答中出现语言、语气、详略或内容主题等白名单偏好线索且真实 AI
提供方可用 THEN 系统 SHALL 静默提取并永久保存非敏感偏好IF 内容涉及密钥、
健康、宗教、政治、性取向、绩效或财务秘密 THEN 系统 SHALL 拒绝保存。
6. WHEN 用户请求查看、启停、修改或删除个人规则与偏好 THEN 系统 SHALL
仅操作当前用户的数据并返回明确结果。
7. IF 用户规则与安全、权限、只读或公司规则冲突 THEN 系统 SHALL 忽略冲突部分并保留审计。
### 需求 5个性化 AI 问答
**用户故事:** 作为飞书用户,我希望 AI 根据我的规则、偏好、兴趣和相关记忆回答,
同时不混入其他用户的信息。
#### 验收条件
1. WHEN 用户发起 AI 问答 THEN 系统 SHALL 按当前飞书身份加载公司规则、个人规则、
个人偏好、个人兴趣和当前用户的相关记忆。
2. WHEN 系统组装 AI 上下文 THEN 系统 SHALL 按照“系统安全与只读规则、公司规则、
个人明确规则、当前请求、偏好与兴趣、相关记忆”的优先顺序处理。
3. IF AI 提供方支持会话标识 THEN 系统 SHALL 为不同飞书用户使用隔离的会话标识,
不得复用全局用户会话。
4. IF AI 提供方不可用或仍为占位提供方 THEN 系统 SHALL 返回明确的不可用响应,
不得把占位内容当作真实个性化答案。
5. WHEN 问答来自群聊 THEN 系统 SHALL 回复原会话,但个人规则、偏好和记忆仍只属于发送者。
6. IF 个人规则试图要求调用未授权工具、修改业务数据或绕过安全控制 THEN 系统 SHALL
拒绝执行该要求。
### 需求 6个人推送订阅
**用户故事:** 作为飞书用户,我希望按自己的兴趣和时间订阅消息,并能暂停或退订,
以便只收到需要的内容。
#### 验收条件
1. WHEN 用户创建订阅 THEN 系统 SHALL 保存订阅主题、飞书接收身份、计划时间、时区、
安静时段、启用状态和用户同意时间。
2. WHEN 用户未明确创建或同意订阅 THEN 系统 SHALL 不得主动向该用户发送个人推送。
3. WHEN 用户查看订阅 THEN 系统 SHALL 仅返回当前用户的订阅。
4. WHEN 用户暂停、恢复或退订 THEN 系统 SHALL 立即更新当前用户订阅状态并记录审计。
5. IF 用户尝试修改其他用户订阅 THEN 系统 SHALL 拒绝且不泄露目标订阅信息。
6. WHEN 订阅主题支持个人兴趣过滤 THEN 系统 SHALL 使用当前用户明确保存的兴趣生成内容。
7. WHILE 当前时间位于用户安静时段内 THEN 系统 SHALL 延后非紧急推送,不得直接发送。
8. IF 订阅时间、时区、主题或接收目标无效 THEN 系统 SHALL 拒绝创建并返回可操作的错误说明。
9. WHEN 用户以受控中文时间语法创建订阅且解析成功 THEN 系统 SHALL 立即启用订阅,
返回标准化计划、下次执行时间和暂停命令。
10. IF 订阅间隔短于 15 分钟、用户已有 50 个启用订阅或当日投递将超过 96 条
THEN 系统 SHALL 拒绝创建或跳过投递并说明限制。
11. WHEN 普通用户创建订阅 THEN 系统 SHALL 固定绑定其本人 `open_id`
WHEN 管理员在群内创建群订阅 THEN 系统 SHALL 仅绑定当前事件的 `chat_id`
且 SHALL 拒绝用户手工提供原始 `chat_id`
### 需求 7可靠的个性化定时投递
**用户故事:** 作为订阅用户,我希望消息按时且不重复地送达,并在临时失败后安全重试。
#### 验收条件
1. WHEN 个人订阅到期 THEN 系统 SHALL 通过持久化订阅状态发现任务并生成投递请求,
不得仅依赖进程内临时任务。
2. WHEN 系统发送个人消息 THEN 系统 SHALL 默认使用当前用户的飞书 `open_id`
作为接收目标。
3. WHEN 系统处理同一用户、同一订阅和同一时间窗口 THEN 系统 SHALL 只创建一次有效投递。
4. IF 飞书返回 HTTP 错误、限流响应或非零业务状态码 THEN 系统 SHALL 将投递标记为失败,
不得记录为成功。
5. IF 投递失败属于可重试错误 THEN 系统 SHALL 按受限退避策略重试;
超过最大次数后 SHALL 进入最终失败状态。
6. WHEN 投递成功 THEN 系统 SHALL 保存飞书响应标识、发送时间和最终状态。
7. IF 多个调度器或工作进程同时扫描同一订阅 THEN 系统 SHALL 防止重复生成或重复发送消息。
8. WHILE `READ_ONLY_MODE=true` THEN 系统 SHALL 允许平台自身的订阅与投递状态写入,
但 SHALL 保持旧业务数据库只读。
### 需求 8飞书自助命令
**用户故事:** 作为飞书用户,我希望直接通过机器人管理个人规则、偏好和订阅,
无需访问后台页面。
#### 验收条件
1. WHEN 用户请求帮助 THEN 系统 SHALL 返回其当前权限允许使用的命令说明。
2. WHEN 用户管理个人规则 THEN 系统 SHALL 支持创建、查看、修改、启停和删除。
3. WHEN 用户管理偏好或兴趣 THEN 系统 SHALL 支持设置、查看和删除。
4. WHEN 用户管理订阅 THEN 系统 SHALL 支持创建、查看、暂停、恢复和退订。
5. WHEN 管理员执行公司级命令 THEN 系统 SHALL 在执行前验证其飞书账号权限。
6. IF 命令格式错误 THEN 系统 SHALL 返回示例格式,且不得产生部分写入。
7. IF 命令涉及敏感信息、密钥或禁止内容 THEN 系统 SHALL 拒绝保存并给出安全提示。
8. WHEN 管理员通过飞书管理用户角色或状态 THEN 系统 SHALL 只接受当前验证事件中的
结构化 `@用户` 身份,不得接受文本伪造的 `open_id`
9. IF 操作会停用或降级最后一个有效管理员 THEN 系统 SHALL 拒绝操作。
### 需求 9隐私、审计和用户控制
**用户故事:** 作为飞书用户和审计人员,我希望个性化数据受到保护且关键操作可追踪。
#### 验收条件
1. WHEN 系统认证用户、拒绝权限、修改规则、修改偏好、修改订阅或执行投递 THEN 系统 SHALL
记录必要的审计元数据。
2. WHEN 系统记录审计或错误 THEN 系统 SHALL 避免保存访问令牌、密钥和不必要的完整个人问答内容。
3. WHEN 用户请求删除个人画像和记忆 THEN 系统 SHALL 删除或匿名化可删除的个人数据,
停止其订阅,并保留满足审计要求的最小不可逆证据。
4. WHEN 用户请求查看自己的个性化数据 THEN 系统 SHALL 返回其规则、偏好、兴趣和订阅摘要。
5. IF 内部服务查询个人数据 THEN 系统 SHALL 根据服务权限限制数据范围并记录访问审计。
6. WHEN 用户发送“忘记我” THEN 系统 SHALL 返回短时有效的一次性确认码;
WHEN 同一用户发送“确认忘记我 <>”且验证码有效 THEN 系统 SHALL 在一个事务中
删除其个人数据、停用订阅、移除身份映射并将审计主体替换为随机匿名标识。
### 需求 10兼容性、迁移与验证
**用户故事:** 作为维护人员,我希望现有固定群推送和内部 API 保持可用,
同时安全升级到飞书用户权限体系。
#### 验收条件
1. WHEN 数据库升级 THEN 系统 SHALL 通过 Alembic 创建身份、权限、偏好、订阅和投递所需结构。
2. WHEN 升级现有数据库 THEN 系统 SHALL 保留已有审计、报表推送和业务只读数据。
3. WHEN 现有固定群定时任务运行 THEN 系统 SHALL 保持原有功能,且不得被误认为个人订阅。
4. WHEN 内部 API 使用 API Key 调用 THEN 系统 SHALL 保持服务级认证,
但不得通过请求参数伪造飞书终端用户权限。
5. WHEN 自动化测试运行 THEN 系统 SHALL 覆盖两个不同飞书用户的数据隔离、
越权拒绝、角色权限、个人问答上下文、订阅生命周期、安静时段、幂等投递、
飞书非零业务码和数据库迁移一致性。
6. WHEN 完整验证运行 THEN Ruff、Python 编译检查、Alembic 元数据一致性测试和全部 pytest
测试 SHALL 通过。

View File

@@ -0,0 +1,70 @@
# 实现任务
- [x] 1. 建立飞书用户身份与权限域
- 新增 `app/modules/feishu_users/` 的模型、schema、principal、身份与管理服务。
- 实现首次注册、初始管理员引导、角色/状态修改、最后管理员保护与审计。
- 增加用户管理 API 和身份/权限测试。
- _Requirements: 1, 2, 8.5, 8.8, 8.9, 9.1, 10.4_
- [x] 2. 将验证后的飞书事件接入用户主体
- 扩展 webhook/长连接事件提取 `tenant_key`、sender ids、chat type 和 mentions。
- 只有验证后的事件才能解析或创建 `FeishuPrincipal`;群聊始终使用发送者身份。
- 把 principal 传入命令处理并在停用/缺身份时安全拒绝。
- _Requirements: 1, 2.2, 2.4, 8.1, 9.1_
- [x] 3. 实现个人规则、偏好、兴趣和记忆隔离
- 新增 `app/modules/personalization/` 偏好模型与服务。
- 为 AI 规则/记忆和市场自选增加 owner按 owner 查询、去重和安全认领。
- 将现有 AI rule API 固定为公司规则入口;飞书个人/公司规则命令分别授权。
- 实现个人规则、偏好、兴趣 CRUD 命令及跨用户隔离测试。
- _Requirements: 3, 4, 8.2, 8.3, 8.5, 8.7, 9.4_
- [x] 4. 实现会话与个性化 AI 问答
- 新增会话和消息模型、20 轮裁剪、30 天清理与重置命令。
- 按既定优先级组装公司规则、个人规则、请求、偏好/兴趣、记忆和会话历史。
- 隔离 provider session`noop`/AI 不可用时不写历史、偏好或记忆。
- 实现白名单自动偏好提取与敏感画像拒绝。
- _Requirements: 3, 4.5, 5, 8.3, 8.7, 9.2_
- [x] 5. 实现订阅与受控中文计划解析
- 新增订阅/投递模型、schema 和服务。
- 支持单次、每天、工作日、每周、每月和间隔计划以及 IANA 时区。
- 实现 15 分钟下限、50 个启用订阅、每日 96 条、安静时段。
- 实现私聊固定 open_id、管理员当前群绑定以及订阅生命周期命令/API。
- _Requirements: 6, 8.4, 8.6, 8.7, 10.4_
- [x] 6. 实现持久化扫描、幂等投递和重试
- 每分钟扫描到期订阅,通过行锁和唯一投递键领取并推进下一执行时间。
- 为 Feishu client 增加 `open_id`、消息 UUID 和业务状态校验。
- 实现个人/群订阅上下文边界、1/5/15 分钟重试和无 Celery 持久化恢复。
- 接入 scheduler、Celery task/queue helper 并保持固定群报表任务不变。
- _Requirements: 6.7, 7, 10.3_
- [x] 7. 实现用户数据查看、忘记我和运行可观测性
- 实现 `我的数据`、二次确认、事务删除和审计匿名化。
- 增加用户/订阅/投递查询 API、活跃用户和投递状态指标。
- 启用订阅但缺少飞书凭据时 readiness 返回 degraded。
- _Requirements: 6.2, 9, 10.2, 10.4_
- [x] 8. 完成配置、Alembic 迁移与全量验证
- 增加安全关闭的功能开关、管理员身份配置并更新 `.env.example`
- 创建迁移并处理旧公司规则、旧自动记忆和旧自选延迟认领。
- 补齐身份、隔离、权限、计划、并发、投递、删除和迁移回归测试。
- 运行 Ruff、compileall、完整 pytest 和迁移一致性检查。
- _Requirements: 3.7, 10_
```mermaid
flowchart LR
T1["1 身份权限"] --> T2["2 事件主体"]
T1 --> T3["3 个人数据隔离"]
T2 --> T4["4 个性化问答"]
T3 --> T4
T1 --> T5["5 订阅与计划"]
T5 --> T6["6 扫描投递"]
T3 --> T7["7 删除与可观测性"]
T5 --> T7
T2 --> T8["8 迁移与验证"]
T4 --> T8
T6 --> T8
T7 --> T8
```