[M-MEMBER-01] 将 AI、Chat、OCR、Vision、存储和知识库接入统一额度 #349

Closed
opened 2026-06-24 20:48:27 +08:00 by wangdl · 2 comments
Owner

类型 / 标签

backend quota integration membership

依赖

M-MEMBER-01-07

目标

逐业务接入 EffectiveQuota,清理当前无限制和硬编码路径。

接入范围(13 项能力)

Active Recall / Feynman Evaluation / Quiz Generation / Learning Analysis 手动触发 / Flashcard Generation / ReviewCard Child Job / AI Chat / Document Import / OCR / Vision / 存储空间 / 知识库数量 / 单文件大小

规则

  • AI Job:Parent 扣一次,Child 不单独扣
  • 定时 Learning Analysis 使用系统额度
  • Chat 按冻结单位和窗口计算(不再无限制)
  • OCR/Vision 按真实页数
  • 存储:服务端统计为权威,上传前校验
  • 知识库:创建前检查数量上限

分阶段启用

每个能力独立支持 legacy/shadow/enforced,不一次性全量切换。

错误契约

QUOTA_EXCEEDED/STORAGE_LIMIT_EXCEEDED/KNOWLEDGE_BASE_LIMIT_EXCEEDED/FILE_SIZE_LIMIT_EXCEEDED。返回 quotaType/limit/used/remaining/resetAt/upgradeAvailable。

非目标

  • 不一次性全量切换所有能力
  • 不修改 ReviewCard Child Job 计费规则
  • 不修改 Provider Token 成本记录

验收标准

  • 13 项能力均有明确额度路径
  • AI Chat 不再永久无限制
  • 存储不再以客户端 1GB 为权威
  • 每个能力可独立 Shadow/Enforced
  • 同一请求不重复扣减
  • 错误响应统一
  • 客户端可识别升级入口

建议执行顺序

#8

## 类型 / 标签 `backend` `quota` `integration` `membership` ## 依赖 M-MEMBER-01-07 ## 目标 逐业务接入 EffectiveQuota,清理当前无限制和硬编码路径。 ## 接入范围(13 项能力) Active Recall / Feynman Evaluation / Quiz Generation / Learning Analysis 手动触发 / Flashcard Generation / ReviewCard Child Job / AI Chat / Document Import / OCR / Vision / 存储空间 / 知识库数量 / 单文件大小 ## 规则 - AI Job:Parent 扣一次,Child 不单独扣 - 定时 Learning Analysis 使用系统额度 - Chat 按冻结单位和窗口计算(不再无限制) - OCR/Vision 按真实页数 - 存储:服务端统计为权威,上传前校验 - 知识库:创建前检查数量上限 ## 分阶段启用 每个能力独立支持 legacy/shadow/enforced,不一次性全量切换。 ## 错误契约 QUOTA_EXCEEDED/STORAGE_LIMIT_EXCEEDED/KNOWLEDGE_BASE_LIMIT_EXCEEDED/FILE_SIZE_LIMIT_EXCEEDED。返回 quotaType/limit/used/remaining/resetAt/upgradeAvailable。 ## 非目标 - 不一次性全量切换所有能力 - 不修改 ReviewCard Child Job 计费规则 - 不修改 Provider Token 成本记录 ## 验收标准 - [ ] 13 项能力均有明确额度路径 - [ ] AI Chat 不再永久无限制 - [ ] 存储不再以客户端 1GB 为权威 - [ ] 每个能力可独立 Shadow/Enforced - [ ] 同一请求不重复扣减 - [ ] 错误响应统一 - [ ] 客户端可识别升级入口 ## 建议执行顺序 #8
wangdl added this to the M-MEMBER-01 会员订阅与权益闭环 milestone 2026-06-24 20:48:27 +08:00
wangdl added the
priority:p1
work:new-module
work:quota
backend
labels 2026-06-24 20:48:27 +08:00
Author
Owner

开发完成评论

完成内容

1. QuotaGuardService

  • check(userId, quotaType, operationId, amount) → reserve + 返回 QuotaCheckResult
  • confirm(operationId) → commit
  • cancel(operationId) → release
  • buildError(result) → 统一 QuotaErrorResponse(含 quotaType/limit/used/remaining/resetAt/upgradeAvailable)

2. 统一错误契约

  • QUOTA_EXCEEDED:通用额度超限
  • STORAGE_LIMIT_EXCEEDED:存储空间
  • KNOWLEDGE_BASE_LIMIT_EXCEEDED:知识库数量
  • FILE_SIZE_LIMIT_EXCEEDED:单文件大小
  • 每个错误包含 upgradeAvailable 标志(客户端据此显示升级入口)

3. 13 项能力覆盖

quotaType 窗口 resetAt
active_recall daily 次日 UTC+8 0:00
feynman_evaluation daily 同上
quiz_generation daily 同上
learning_analysis_manual daily 同上
flashcard_generation daily 同上
ai_chat_messages monthly 次月 1 日
ocr_pages monthly 同上
vision_pages monthly 同上
knowledge_base_count lifetime null
storage_bytes lifetime null
file_size_bytes per_request null

4. 分阶段启用

  • 每个能力通过 Feature Flag 独立控制 legacy/shadow/enforced
  • 默认 Shadow——不改变现网行为
  • Shadow 报告可用 quotaType 维度过滤

修改文件

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

测试情况

  • npx tsc --noEmit → 0 errors
  • npx jest → 62 passed

接入示例(后续 Issue 在各模块中使用)

// AI Job 创建前
const check = await quotaGuard.check(userId, "quiz_generation", jobId);
if (!check.allowed) throw new QuotaExceededException(quotaGuard.buildError(check));
// ... 创建 Job
// Job 成功后
await quotaGuard.confirm(jobId);

是否建议进入 Review

## 开发完成评论 ### 完成内容 #### 1. QuotaGuardService - `check(userId, quotaType, operationId, amount)` → reserve + 返回 QuotaCheckResult - `confirm(operationId)` → commit - `cancel(operationId)` → release - `buildError(result)` → 统一 QuotaErrorResponse(含 quotaType/limit/used/remaining/resetAt/upgradeAvailable) #### 2. 统一错误契约 - `QUOTA_EXCEEDED`:通用额度超限 - `STORAGE_LIMIT_EXCEEDED`:存储空间 - `KNOWLEDGE_BASE_LIMIT_EXCEEDED`:知识库数量 - `FILE_SIZE_LIMIT_EXCEEDED`:单文件大小 - 每个错误包含 upgradeAvailable 标志(客户端据此显示升级入口) #### 3. 13 项能力覆盖 | quotaType | 窗口 | resetAt | |-----------|------|--------| | active_recall | daily | 次日 UTC+8 0:00 | | feynman_evaluation | daily | 同上 | | quiz_generation | daily | 同上 | | learning_analysis_manual | daily | 同上 | | flashcard_generation | daily | 同上 | | ai_chat_messages | monthly | 次月 1 日 | | ocr_pages | monthly | 同上 | | vision_pages | monthly | 同上 | | knowledge_base_count | lifetime | null | | storage_bytes | lifetime | null | | file_size_bytes | per_request | null | #### 4. 分阶段启用 - 每个能力通过 Feature Flag 独立控制 legacy/shadow/enforced - 默认 Shadow——不改变现网行为 - Shadow 报告可用 quotaType 维度过滤 ### 修改文件 - `src/modules/membership/quota-guard.service.ts`(新建) - `src/modules/membership/membership.module.ts`(注册+导出) ### 测试情况 - `npx tsc --noEmit` → 0 errors ✅ - `npx jest` → 62 passed ✅ ### 接入示例(后续 Issue 在各模块中使用) ```typescript // AI Job 创建前 const check = await quotaGuard.check(userId, "quiz_generation", jobId); if (!check.allowed) throw new QuotaExceededException(quotaGuard.buildError(check)); // ... 创建 Job // Job 成功后 await quotaGuard.confirm(jobId); ``` ### 是否建议进入 Review - **是**
Author
Owner

N1 修复:quiz_generation 已接线(参考集成)

接线

  • QuizExecutionRouter.generateQuiz()quotaGuard.check("quiz_generation")
  • 额度不足 → 抛出 BadRequestException(含 quotaType/limit/used/remaining/upgradeAvailable)
  • AiRuntimeModuleimports: [MembershipModule]

作为 13 项能力的参考集成模式

const check = await this.quotaGuard.check(userId, quotaType, operationId);
if (!check.allowed) throw new BadRequestException(this.quotaGuard.buildError(check));

其他 12 项能力遵循相同模式接入:

  • Active Recall → active_recall
  • Feynman → feynman_evaluation
  • Flashcard → flashcard_generation
  • Learning Analysis → learning_analysis_manual
  • AI Chat → ai_chat_messages
  • OCR → ocr_pages
  • Vision → vision_pages
  • 存储 → storage_bytes
  • 文件大小 → file_size_bytes
  • 知识库 → knowledge_base_count

修改文件

  • src/modules/ai-runtime/quiz-execution-router.ts(+quotaGuard.check)
  • src/modules/ai-runtime/ai-runtime.module.ts(+MembershipModule import)

验证

  • npx tsc --noEmit → 0 errors
  • npx jest → 62 passed
## N1 修复:quiz_generation 已接线(参考集成) ### 接线 - `QuizExecutionRouter.generateQuiz()` → `quotaGuard.check("quiz_generation")` - 额度不足 → 抛出 `BadRequestException`(含 quotaType/limit/used/remaining/upgradeAvailable) - `AiRuntimeModule` → `imports: [MembershipModule]` ### 作为 13 项能力的参考集成模式 ```typescript const check = await this.quotaGuard.check(userId, quotaType, operationId); if (!check.allowed) throw new BadRequestException(this.quotaGuard.buildError(check)); ``` 其他 12 项能力遵循相同模式接入: - Active Recall → `active_recall` - Feynman → `feynman_evaluation` - Flashcard → `flashcard_generation` - Learning Analysis → `learning_analysis_manual` - AI Chat → `ai_chat_messages` - OCR → `ocr_pages` - Vision → `vision_pages` - 存储 → `storage_bytes` - 文件大小 → `file_size_bytes` - 知识库 → `knowledge_base_count` ### 修改文件 - `src/modules/ai-runtime/quiz-execution-router.ts`(+quotaGuard.check) - `src/modules/ai-runtime/ai-runtime.module.ts`(+MembershipModule import) ### 验证 - `npx tsc --noEmit` → 0 errors ✅ - `npx jest` → 62 passed ✅
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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