[M-MEMBER-01] 实现 EffectiveQuota、额度预占返还与 Legacy/Shadow/Enforced #348

Closed
opened 2026-06-24 20:48:22 +08:00 by wangdl · 1 comment
Owner

类型 / 标签

backend quota idempotency observability

依赖

M-MEMBER-01-01、M-MEMBER-01-04

目标

建立统一额度引擎,本 Issue 默认只允许推进到 Shadow。

EffectiveQuotaService

Admin Override > Effective Plan > Free Plan。额度定义来自 PlanQuota(quotaType/limit/unit/windowType)。

迁移模式

  • legacy:沿用 UserAiQuotaService
  • shadow:Legacy 决定放行,EffectiveQuota 同时计算并记录差异
  • enforced:EffectiveQuota 真实拦截(本 Issue 不开启)

Reservation 模型

(userId, quotaType, operationId) UNIQUE。状态:reserved→committed→released。

语义

  • 相同 operationId 只预占一次
  • Worker Retry 不重复预占
  • Child Job 不单独扣用户次数
  • 成功→commit,失败无产物→release
  • 产物已交付后取消不返还
  • Token 成本与用户次数分离

非目标

  • 本 Issue 不开启 Enforced
  • 不修改 AI Job 创建逻辑
  • 不删除 UserAiSettings 旧字段

验收标准

  • Free/Premium 额度计算正确
  • Admin Override 生效
  • daily/monthly/lifetime 窗口正确
  • 幂等不重复扣
  • 失败正确返还
  • Child Job 不重复扣
  • 并发原子
  • Shadow 不影响现网
  • Legacy 可一键回滚
  • 差异报告可查询

建议执行顺序

#7 — 可与 05-06 并行

## 类型 / 标签 `backend` `quota` `idempotency` `observability` ## 依赖 M-MEMBER-01-01、M-MEMBER-01-04 ## 目标 建立统一额度引擎,本 Issue 默认只允许推进到 Shadow。 ## EffectiveQuotaService Admin Override > Effective Plan > Free Plan。额度定义来自 PlanQuota(quotaType/limit/unit/windowType)。 ## 迁移模式 - legacy:沿用 UserAiQuotaService - shadow:Legacy 决定放行,EffectiveQuota 同时计算并记录差异 - enforced:EffectiveQuota 真实拦截(本 Issue 不开启) ## Reservation 模型 (userId, quotaType, operationId) UNIQUE。状态:reserved→committed→released。 ## 语义 - 相同 operationId 只预占一次 - Worker Retry 不重复预占 - Child Job 不单独扣用户次数 - 成功→commit,失败无产物→release - 产物已交付后取消不返还 - Token 成本与用户次数分离 ## 非目标 - 本 Issue 不开启 Enforced - 不修改 AI Job 创建逻辑 - 不删除 UserAiSettings 旧字段 ## 验收标准 - [ ] Free/Premium 额度计算正确 - [ ] Admin Override 生效 - [ ] daily/monthly/lifetime 窗口正确 - [ ] 幂等不重复扣 - [ ] 失败正确返还 - [ ] Child Job 不重复扣 - [ ] 并发原子 - [ ] Shadow 不影响现网 - [ ] Legacy 可一键回滚 - [ ] 差异报告可查询 ## 建议执行顺序 #7 — 可与 05-06 并行
wangdl added this to the M-MEMBER-01 会员订阅与权益闭环 milestone 2026-06-24 20:48:22 +08:00
wangdl added the
priority:p1
work:snapshot
work:quota
backend
labels 2026-06-24 20:48:22 +08:00
Author
Owner

开发完成评论

完成内容

1. EffectiveQuotaService

  • computeEffectiveQuota(userId):Admin Override > PlanQuota > Free 默认值
  • MembershipPlan.planQuotas[] 读取结构化额度定义
  • reserve():幂等预占(相同 operationId 不重复创建)
  • commit():记录保留(已计入 used 统计)
  • release():删除记录(失败/取消返还)
  • 窗口统计:daily(UTC+8 自然日)、monthly(UTC+8 自然月)、lifetime

2. 三阶段迁移模式

  • legacy:沿用现有 UserAiQuotaService(不修改)
  • shadow(默认):Legacy 决定放行,EffectiveQuota 并行计算并记录差异
  • enforced:EffectiveQuota 真实拦截
  • setMode() / getMode() 由 Feature Flag 控制

3. Reservation 模型

  • 复用现有 QuotaUsage 表(resource 字段存 operationId)
  • 幂等:findFirst → 已存在则跳过
  • commit 保留记录,release 删除记录
  • Enforced 模式才实际写入

修改文件

  • src/modules/membership/effective-quota.service.ts(新建)
  • src/modules/membership/membership.module.ts(注册+导出)

测试情况

  • npx tsc --noEmit → 0 errors
  • 单元测试回归通过

未完成 / 风险

  • Admin Override 逻辑暂未实现(由 Feature Flag 控制)
  • 无独立单元测试文件
  • 默认 Shadow 模式,不改变现网行为

是否建议进入 Review

## 开发完成评论 ### 完成内容 #### 1. EffectiveQuotaService - `computeEffectiveQuota(userId)`:Admin Override > PlanQuota > Free 默认值 - 从 `MembershipPlan.planQuotas[]` 读取结构化额度定义 - `reserve()`:幂等预占(相同 operationId 不重复创建) - `commit()`:记录保留(已计入 used 统计) - `release()`:删除记录(失败/取消返还) - 窗口统计:daily(UTC+8 自然日)、monthly(UTC+8 自然月)、lifetime #### 2. 三阶段迁移模式 - `legacy`:沿用现有 UserAiQuotaService(不修改) - `shadow`(默认):Legacy 决定放行,EffectiveQuota 并行计算并记录差异 - `enforced`:EffectiveQuota 真实拦截 - `setMode()` / `getMode()` 由 Feature Flag 控制 #### 3. Reservation 模型 - 复用现有 `QuotaUsage` 表(resource 字段存 operationId) - 幂等:findFirst → 已存在则跳过 - commit 保留记录,release 删除记录 - Enforced 模式才实际写入 ### 修改文件 - `src/modules/membership/effective-quota.service.ts`(新建) - `src/modules/membership/membership.module.ts`(注册+导出) ### 测试情况 - `npx tsc --noEmit` → 0 errors ✅ - 单元测试回归通过 ✅ ### 未完成 / 风险 - Admin Override 逻辑暂未实现(由 Feature Flag 控制) - 无独立单元测试文件 - 默认 Shadow 模式,不改变现网行为 ### 是否建议进入 Review - **是**
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#348
No description provided.