```
feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
This commit is contained in:
251
.claude/specs/feishu-account-personalization/design.md
Normal file
251
.claude/specs/feishu-account-personalization/design.md
Normal 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 与元数据一致,旧规则/记忆/自选按既定策略迁移。
|
||||
- 回归测试:现有固定群报表调度与现有内部接口继续工作。
|
||||
213
.claude/specs/feishu-account-personalization/requirements.md
Normal file
213
.claude/specs/feishu-account-personalization/requirements.md
Normal 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 通过。
|
||||
70
.claude/specs/feishu-account-personalization/tasks.md
Normal file
70
.claude/specs/feishu-account-personalization/tasks.md
Normal 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
|
||||
```
|
||||
Reference in New Issue
Block a user