feat: 添加账号删除功能

This commit is contained in:
qzl
2026-04-10 10:40:44 +08:00
parent 17a1303f00
commit 46513829cd
30 changed files with 1510 additions and 664 deletions
+131
View File
@@ -0,0 +1,131 @@
你是一个资深系统设计与代码分析助手。你的任务不是立刻编写代码,而是先深入理解当前项目中与“通知系统”相关的现有实现,然后基于现有代码结构输出一份可靠、可落地的实现方案。
本次任务聚焦于 App 通知系统设计,重点包括但不限于:
- 通知中心(notification list / inbox
- 用户通知存储
- 已读 / 已看状态
- 推送触达状态
- 前台实时通知
- 后台/离线系统推送
- 新版本通知、活动通知等业务通知类型
- Flutter 客户端、后端服务、Supabase 之间的协作方式
你的首要目标是“理解现状并制定方案”,而不是直接进入编码。
请严格遵循以下工作方式:
1. 先理解现有代码,再做设计
- 主动查找项目中与通知相关的现有代码、模块、接口、表结构、状态管理、服务封装和配置文件。
- 特别关注以下内容:
- Flutter 端是否已有本地通知、消息中心、badge、页面入口、深链跳转
- 后端是否已有 notification / message / event / push / reminder 等相关模型、接口或 service
- Supabase 中是否已有相关表、RLS、Realtime、设备 token 存储
- 是否已有 APNs / FCM / flutter_local_notifications / Firebase Messaging / Supabase Realtime 等集成痕迹
- 是否已有版本检查机制、活动通知机制、用户收件箱机制
- 不要假设项目是空白的。必须优先复用现有架构与已有能力。
2. 基于现有架构设计,而不是脱离项目另起炉灶
- 方案必须尽量贴合当前项目的技术栈、目录结构、分层方式、命名风格和已有约束。
- 优先考虑如何在现有模块上扩展,而不是重新设计一整套无关架构。
- 如果当前实现存在明显缺陷或冲突,可以指出问题,但仍要给出“在现有基础上渐进演进”的方案。
3. 明确区分几个概念
你在分析和设计时,必须区分以下概念,避免混淆:
- 应用内通知记录
- 系统推送通知
- 前台实时同步
- 本地通知
- 已看状态
- 已读状态
- 推送是否成功
- 用户是否真正查看
4. 方案输出要覆盖的核心问题
在最终方案中,至少要回答以下问题:
- 现有代码里已经有什么,缺什么
- 通知数据应该如何建模
- 是否需要 notifications / notification_receipts / user_push_devices 等表
- 已读、已看、点击、删除等状态如何设计
- Flutter 端如何读取通知列表、显示未读数、更新已读状态
- Supabase Realtime 在这个项目里适合承担什么职责
- APNs / FCM 或其他推送通道应该如何接入
- 后端应该如何组织通知写入、fanout、推送发送、状态回写
- 新版本通知与活动通知如何落地
- 如何保证权限安全,例如 RLS、用户只能访问自己的通知
- 如何分阶段实施,避免一次性改动过大
5. 输出必须先分析,后给建议
不要一上来直接写“建议这样做”。
你必须先给出:
- 当前代码现状梳理
- 已有能力
- 缺失点
- 架构约束
然后再给出推荐方案。
6. 不直接修改代码
- 本轮目标是产出实现方案,而不是直接提交代码。
- 除非我明确要求,否则不要直接创建文件、修改代码或生成迁移。
- 可以提出建议的文件改动点,但不要直接实现。
请按以下结构输出:
# 1. 需求理解
- 这次通知系统要解决的核心问题
- 涉及的通知类型
- 系统边界(Flutter / Backend / Supabase / Push Provider
# 2. 现有代码调研结果
- 已发现的相关模块
- 已有能力
- 可复用部分
- 当前缺口
- 潜在冲突或风险
# 3. 当前架构判断
- 当前项目更适合采用什么通知架构
- 为什么
- 哪些方案不适合当前项目
# 4. 推荐实现方案
至少包括:
- 数据模型设计
- 状态字段设计
- 客户端交互流程
- 服务端处理流程
- 实时通知与系统推送的职责划分
- 已读/已看/触达状态方案
- 版本通知与活动通知方案
- 权限与安全策略
# 5. 分阶段落地计划
请拆分为多个阶段,例如:
- 第一阶段:最小可用通知中心
- 第二阶段:接入系统推送
- 第三阶段:完善版本通知/活动通知/统计能力
每个阶段说明:
- 目标
- 改动范围
- 主要任务
- 依赖项
- 风险点
- 验收标准
# 6. 建议改动清单
- 建议新增或修改的表
- 建议新增或修改的后端模块
- 建议新增或修改的 Flutter 模块
- 建议新增的接口 / RPC / service
- 建议新增的配置项
# 7. 最终推荐
- 推荐采用的总体方案
- 推荐原因
- 不确定点
- 实施优先级排序
额外要求:
- 如果代码库中已经存在通知、提醒、消息、推送等相近实现,优先尝试整合,而不是重复建设。
- 如果某些信息无法从当前代码中确认,要明确写出“不确定项”和“推断依据”。
- 方案必须可执行、可渐进落地,避免空泛。
- 优先给出最贴合当前代码库的设计,不要输出与项目现状脱节的理想化架构。
@@ -0,0 +1,419 @@
# 积分审计与注册赠分策略改造计划(gstack / plan-eng-review
## 1. 目标与结论
本计划解决三个问题:
1. 用户删除账号后,积分与成本审计数据不能随业务数据一起丢失。
2. 同邮箱重复注册时,不应再次拿到注册赠分。
3. 积分消耗审计必须记录真实 `input_tokens` / `output_tokens` / `cost`,不能再写占位值。
4. LLM 失败/取消时若平台已产生真实成本,该成本不转嫁用户积分,但必须进入审计账本。
结论:采用 **双账本 + 资格账本**
- 保留业务账本:`user_points``points_ledger`(在线业务能力)
- 新增审计账本:`points_audit_ledger`(不可变审计)
- 新增资格账本:`register_bonus_claims`(注册奖励去重)
- 注册赠分策略从 DB trigger 移出,改为应用层策略(配置驱动)
---
## 2. 系统边界
### 2.1 业务域(可删除)
- `user_points`:余额视图
- `points_ledger`:业务流水
- `messages` / `sessions`:会话与消息
### 2.2 审计域(不可级联删除)
- `points_audit_ledger`:审计流水,保留用户快照和成本快照(含用户承担/平台承担归属)
- `register_bonus_claims`:注册奖励领取资格记录
### 2.3 策略域(应用层)
- `register_bonus_points` 配置项(默认 60
- `register_bonus_hmac_key` 配置项(环境变量注入)
- 首登赠分是否发放由服务层决定,不写死在数据库 trigger
---
## 3. 现状问题(基于当前代码)
1. 注册赠分写死在 DB trigger。
当前函数:`public.initialize_profile_and_invite_code_on_signup()`,历史上出现过 100/60 改动漂移。
2. 积分消费审计写占位值。
`backend/src/v1/points/service.py` 中,`consume_successful_run_points` 写入 `input_tokens=0``output_tokens=0``cost=0`
3. 删号会丢审计线索。
当前业务删除路径会清理业务数据,缺少独立审计账本保留策略。
---
## 4. 数据模型(按项目风格精简命名)
说明:不引入陌生“模板字段”,沿用当前 `points_ledger` 命名风格。
### 4.1 新表:`points_audit_ledger`
- `id` UUID PK
- `event_id` VARCHAR(64) UNIQUE NOT NULL
- `user_id_snapshot` UUID NULL
- `user_email_snapshot` TEXT NULL
- `change_type` VARCHAR(16) NOT NULL
- `biz_type` VARCHAR(16) NULL
- `biz_id` UUID NULL
- `direction` SMALLINT NOT NULL
- `amount` BIGINT NOT NULL
- `balance_after` BIGINT NOT NULL
- `billed_to` VARCHAR(16) NOT NULL -- `user` | `platform`
- `run_id` VARCHAR(128) NULL
- `request_id` VARCHAR(128) NULL
- `input_tokens` INTEGER NOT NULL DEFAULT 0
- `output_tokens` INTEGER NOT NULL DEFAULT 0
- `cost` NUMERIC(12,6) NOT NULL DEFAULT 0
- `metadata` JSONB NOT NULL DEFAULT '{}'
- `created_at` TIMESTAMPTZ NOT NULL DEFAULT now()
- `updated_at` TIMESTAMPTZ NOT NULL DEFAULT now()
索引建议:
- `uq_points_audit_ledger_event_id`
- `ix_points_audit_ledger_user_id_created_at` (`user_id_snapshot`, `created_at DESC`)
- `ix_points_audit_ledger_change_type_created_at` (`change_type`, `created_at DESC`)
### 4.2 新表:`register_bonus_claims`
- `id` UUID PK
- `email_hash` VARCHAR(64) UNIQUE NOT NULL
- `user_email_snapshot` TEXT NOT NULL
- `first_user_id` UUID NULL
- `grant_event_id` VARCHAR(64) UNIQUE NOT NULL
- `created_at` TIMESTAMPTZ NOT NULL DEFAULT now()
- `updated_at` TIMESTAMPTZ NOT NULL DEFAULT now()
注:`email_hash` 由标准化邮箱(trim + lower)计算(HMAC-SHA256key 来自 `register_bonus_hmac_key`)。
---
## 5. 数据流设计
### 5.1 注册赠分流程(应用层,非 trigger)
```text
[用户首登/注册完成]
-> PointsPolicyService.load(register_bonus_points)
-> normalize(email) -> email_hash
-> INSERT register_bonus_claims(email_hash, ...)
- 成功: 继续发放积分
- 唯一冲突: 说明历史已领取,跳过发放
-> 更新 user_points
-> 写 points_ledger
-> 写 points_audit_ledger
-> commit
```
### 5.2 运行消耗积分流程(写真实成本)
```text
[run completed]
-> 从持久化消息/会话聚合真实 usage
(input_tokens, output_tokens, cost)
-> PointsService.consume_successful_run_points(...)
-> 更新 user_points
-> 写 points_ledger
-> 写 points_audit_ledger(真实 usage)
-> commit
```
### 5.3 运行失败/取消但平台发生成本流程(不扣用户,记平台账)
```text
[run failed/canceled]
-> 从持久化消息/事件聚合真实 usage
-> 若 cost > 0:
- 不调用用户扣分
- 写 points_audit_ledger(
direction=0,
amount=0,
billed_to='platform',
input_tokens/output_tokens/cost=真实值,
metadata.reason='run_failed_or_canceled_platform_billed'
)
-> commit
```
### 5.4 删除账号流程
```text
[delete account]
-> 删除 user_points / points_ledger / sessions / messages / profile / auth
-> 保留 points_audit_ledger / register_bonus_claims
```
---
## 6. 失败模式与处理
### 6.1 双写不一致(P0
- 场景:`points_ledger` 写成功,`points_audit_ledger` 写失败。
- 策略:同事务写入,任一失败全部回滚。
### 6.2 并发重复注册(P0
- 场景:同邮箱并发首登,发放多次。
- 策略:`register_bonus_claims.email_hash` 唯一约束 + 冲突即跳过。
### 6.3 邮箱规范化不一致(P1)
- 场景:`User@A.com``user@a.com` 被当成不同人。
- 策略:统一 normalizetrim + lower)后再 hash。
### 6.4 成本快照缺失(P1
- 场景:run 成功但 usage 聚合取不到,写入 0。
- 策略:
- 业务是否扣分与成本写入解耦:允许扣分,但审计需标记 `metadata.usage_missing=true`
- 记录 warning 日志并纳入告警指标
### 6.5 失败/取消真实成本归属(P0)
- 场景:LLM 回调失败或用户取消,但上游已计费。
- 策略:
- 不扣用户积分(`user_points``points_ledger`不变)
- 审计账本强制落一条平台承担记录(`billed_to='platform'`
- 该记录必须包含真实 `input_tokens` / `output_tokens` / `cost`
---
## 7. 信任边界与安全
1. `user_email_snapshot` 必须来自服务端认证上下文,不接受客户端传入。
2. `input_tokens/output_tokens/cost` 必须来自服务端持久化记录,不接受客户端上报。
3. 审计表只允许后端 service-role 写入,不暴露客户端写接口。
4. `register_bonus_claims` 不应被普通业务接口更新/删除。
5. `register_bonus_hmac_key` 仅后端可读,不下发客户端,不写日志。
---
## 8. 实施步骤(最小改动优先)
### Phase 1: 协议与配置
- 更新协议文档:
- `docs/protocols/common/user-points-chat-data-protocol.md`
- 新增“审计留存与注册奖励策略”章节
- 新增配置:`register_bonus_points`(默认 60
### Phase 2: 数据库迁移
- 新增表:`points_audit_ledger`
- 新增表:`register_bonus_claims`
- 不改现有 `points_ledger``user_points` 结构
### Phase 3: 服务层改造
- 移除 trigger 中注册送分逻辑(trigger 只保留 profile/invite 初始化)
- 在应用层增加注册奖励发放逻辑(带资格检查)
- 在积分消费路径改造为真实 usage 写审计
- 在失败/取消路径增加平台承担成本审计(不扣用户)
### Phase 4: 删除链路校验
- 删除账号后验证业务表清理
- 验证审计表与资格表仍可查
---
## 9. 测试覆盖计划
### 9.0 P0 测试门槛(实现前锁定)
以下测试为上线前阻断项,任一缺失不得合并:
1. **幂等回放**:同一 `event_id` 重放不重复写 `points_audit_ledger`
2. **注册送分去重**:同邮箱(normalize 后)重复注册不重复发放积分。
3. **事务一致性**:业务账本写入成功但审计写入失败时,整体回滚。
4. **删除后重注册**:删号后同邮箱重注册仍不再发放首登奖励。
5. **失败/取消审计**:run 失败与取消场景写审计但不扣积分。
6. **成本归属**:失败/取消且 `cost>0` 的记录必须为 `billed_to='platform'`
### 9.1 单元测试
- 邮箱 normalize/hash 一致性
- 注册奖励配置读取与默认值
- usage 聚合函数(含空值和异常值)
### 9.2 集成测试
- 首次注册发放奖励成功
- 同邮箱重复注册不再发放
- 并发注册仅一次成功发放
- 消费积分写入真实 tokens/cost 审计
- 失败/取消且平台发生成本时,写平台承担审计且不扣用户积分
- 删号后审计数据保留
### 9.3 回归测试
- 现有积分余额查询和扣分逻辑不回归
- 邀请码流程不回归
---
## 10. 文件级改造清单
### 数据库 / 模型
- `backend/alembic/versions/*` 新增迁移:创建两张新表
- `backend/src/models/points_audit_ledger.py` 新增
- `backend/src/models/register_bonus_claims.py` 新增
### 积分服务与仓储
- `backend/src/v1/points/repository.py`:新增审计写入、资格检查方法
- `backend/src/v1/points/service.py`
- 新增注册奖励发放入口(配置驱动)
- 消费路径写真实 usage 审计
- 失败/取消路径写平台承担成本审计
### 运行时调用链
- `backend/src/core/agentscope/runtime/tasks.py`
- 在扣分点传入真实 usage(或可计算上下文)
- 在 run 异常/取消路径传入 usage 并落平台承担审计
### 协议文档
- `docs/protocols/common/user-points-chat-data-protocol.md` 更新
---
## 11. 取舍说明
### 为什么不直接改 `points_ledger` 为审计表
- 会把在线业务与审计诉求耦合在一张表,后续权限和迁移风险高。
- 当前最小改动方案是新增审计表,保持业务链路稳定。
### 为什么保留 `event_id`
- `id` 是技术主键,只保证行唯一。
- `event_id` 是业务幂等键,防重放、防重试重复记账、支持跨表对账。
---
## 12. 未决事项
1. `user_email_snapshot` 是否明文存储,还是仅内部可解密存储。
2. 审计数据保留时长(默认建议至少 1 年)。
3. 成本单位与精度是否统一沿用 `NUMERIC(12,6)`
---
## 13. PR 拆分与执行顺序(可直接实现)
### PR1:数据库与协议落地(不改业务行为)
目标:先建立新数据边界,不改变线上积分逻辑。
改动范围:
- `backend/alembic/versions/*`:新增迁移,创建 `points_audit_ledger``register_bonus_claims`
- `backend/src/models/points_audit_ledger.py`:新增模型
- `backend/src/models/register_bonus_claims.py`:新增模型
- `docs/protocols/common/user-points-chat-data-protocol.md`:补审计与注册送分策略契约
验收标准:
- 迁移可执行、可回滚
- 新表索引与唯一约束生效
- 协议文档与表结构一致
测试要求:
- 迁移 smoke test
- 约束与索引存在性校验
### PR2:注册送分策略迁移到应用层(去 trigger 固化)
目标:把注册送分从 DB trigger 移到应用层唯一触发点(注册回调)。
改动范围:
- `backend/src/v1/points/service.py`:新增注册奖励发放入口与资格校验
- `backend/src/v1/points/repository.py`:新增 `register_bonus_claims` 检查/写入
- `backend/src/core/config/settings.py`(或等价配置入口):新增 `register_bonus_points`
- `backend/src/core/config/settings.py`(或等价配置入口):新增 `register_bonus_hmac_key`
- 相关注册回调调用链文件:接入 `grant_register_bonus_if_eligible(...)`
- 迁移调整:更新 trigger,移除注册送分写入逻辑,仅保留 profile/invite 初始化
验收标准:
- 新注册触发一次赠分
- 同邮箱重复注册不再赠分
- 配置变更可控制赠分值(默认 60
- 邮箱哈希稳定且不可逆(同邮箱同哈希,不暴露明文)
测试要求:
- 并发注册去重测试(唯一约束 + 冲突路径)
- 删除账号后同邮箱重注册不赠分
- event_id 幂等回放不重复发放
- 缺失 `register_bonus_hmac_key` 时服务启动失败(fail fast)
### PR3:真实成本审计与删除链路联调
目标:将 run 真实 usage 写入审计,并覆盖成功/失败/取消三种对话轮次;失败/取消场景发生真实成本时记平台承担。
改动范围:
- `backend/src/v1/points/service.py`:消费路径审计写入(真实 tokens/cost
- `backend/src/v1/points/repository.py`:新增 `append_audit_ledger(...)`
- `backend/src/core/agentscope/runtime/tasks.py`:传递该轮次必要上下文
- 账号删除服务链路:确认保留 `points_audit_ledger/register_bonus_claims`
验收标准:
- 成功对话:扣分 + 审计
- 失败/取消对话:不扣分 + 审计(若有成本则 `billed_to='platform'`
- 审计中的 `input_tokens/output_tokens/cost` 为真实值,不再占位 0
测试要求:
- 成功/失败/取消三路径集成测试
- 事务一致性测试(业务写成功 + 审计写失败 -> 回滚)
- 删除后审计保留验证
- 失败/取消 + `cost>0` 平台承担场景回归测试
### PR4:观测与运维保障(建议同迭代完成)
目标:避免审计静默失真。
改动范围:
- 指标与日志:
- `points_audit_write_failed_total`
- `points_usage_missing_total`
- 告警阈值:连续失败或短时突增告警
- 运维文档:异常重放与人工核对流程
验收标准:
- 审计写入失败可被监控发现
- usage 缺失可被监控发现并可追溯到事件
---
## 14. 实施完成定义(DoD
满足以下全部条件才算完成:
1. 计划中的 P0 测试门槛全部通过。
2. 注册赠分不再依赖 DB trigger 写死值。
3. `points_audit_ledger` 记录真实 usage,覆盖成功/失败/取消。
3. `points_audit_ledger` 记录真实 usage,覆盖成功/失败/取消;失败/取消有真实成本时归属 `platform`
4. 删除账号后业务数据清理,审计与资格数据保留。
5. 关键失败有指标与告警,不允许静默失败。