fix(quota): remove duplicate quiz_generation reserve in router
Some checks failed
Deploy API Server / build-and-unit (push) Successful in 39s
Deploy API Server / current-integration (push) Successful in 33s
Deploy API Server / backward-compat (push) Successful in 0s
Deploy API Server / deploy (push) Has been cancelled

Quota reserve is now centralized in UserAiController.createAnalysisJob.
Router no longer needs QuotaGuardService injection.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-26 20:19:39 +08:00
parent 35e134d9e6
commit 6a5ab31ddd
2 changed files with 2 additions and 15 deletions

View File

@ -4,7 +4,6 @@ import { FeatureFlagService } from '../config/feature-flag.service';
import { QuizGenerationSnapshotBuilder } from '../ai-job/quiz-generation-snapshot-builder'; import { QuizGenerationSnapshotBuilder } from '../ai-job/quiz-generation-snapshot-builder';
import { AiJobCreationService } from '../ai-job/ai-job-creation.service'; import { AiJobCreationService } from '../ai-job/ai-job-creation.service';
import { UserAiService } from './user-ai.service'; import { UserAiService } from './user-ai.service';
import { QuotaGuardService } from '../membership/quota-guard.service';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
function dto(overrides?: any) { function dto(overrides?: any) {
@ -42,10 +41,6 @@ describe('QuizExecutionRouter', () => {
})), })),
}; };
mockLegacy = { createAnalysisJob: jest.fn() }; mockLegacy = { createAnalysisJob: jest.fn() };
const mockQuotaGuard = {
check: jest.fn().mockResolvedValue({ allowed: true, quotaType: 'quiz_generation', limit: 30, used: 0, remaining: 30, resetAt: null, upgradeAvailable: false }),
buildError: jest.fn().mockReturnValue({ errorCode: 'QUOTA_EXCEEDED', message: '', quotaType: 'quiz_generation', limit: 30, used: 30, remaining: 0, resetAt: null, upgradeAvailable: true }),
};
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [ providers: [
@ -54,7 +49,6 @@ describe('QuizExecutionRouter', () => {
{ provide: QuizGenerationSnapshotBuilder, useValue: mockSnapshot }, { provide: QuizGenerationSnapshotBuilder, useValue: mockSnapshot },
{ provide: AiJobCreationService, useValue: mockCreation }, { provide: AiJobCreationService, useValue: mockCreation },
{ provide: UserAiService, useValue: mockLegacy }, { provide: UserAiService, useValue: mockLegacy },
{ provide: QuotaGuardService, useValue: mockQuotaGuard },
], ],
}).compile(); }).compile();

View File

@ -1,4 +1,4 @@
import { Injectable, Logger, BadRequestException } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { FeatureFlagService } from '../config/feature-flag.service'; import { FeatureFlagService } from '../config/feature-flag.service';
import { QuizGenerationSnapshotBuilder } from '../ai-job/quiz-generation-snapshot-builder'; import { QuizGenerationSnapshotBuilder } from '../ai-job/quiz-generation-snapshot-builder';
@ -6,7 +6,6 @@ import type { QuizGenerationSnapshotInput } from '../ai-job/quiz-generation-snap
import { AiJobCreationService } from '../ai-job/ai-job-creation.service'; import { AiJobCreationService } from '../ai-job/ai-job-creation.service';
import { UserAiService } from './user-ai.service'; import { UserAiService } from './user-ai.service';
import type { CreateAnalysisJobDto } from './user-ai.dto'; import type { CreateAnalysisJobDto } from './user-ai.dto';
import { QuotaGuardService } from '../membership/quota-guard.service';
/** /**
* M-AI-07-05 / M-AI-07-CLEANUP: Quiz Execution Router * M-AI-07-05 / M-AI-07-CLEANUP: Quiz Execution Router
@ -35,16 +34,10 @@ export class QuizExecutionRouter {
private readonly snapshotBuilder: QuizGenerationSnapshotBuilder, private readonly snapshotBuilder: QuizGenerationSnapshotBuilder,
private readonly creationService: AiJobCreationService, private readonly creationService: AiJobCreationService,
private readonly legacyService: UserAiService, private readonly legacyService: UserAiService,
private readonly quotaGuard: QuotaGuardService,
) {} ) {}
async generateQuiz(userId: string, dto: CreateAnalysisJobDto) { async generateQuiz(userId: string, dto: CreateAnalysisJobDto) {
// M-MEMBER-01-08: quiz_generation 接入统一额度(参考集成) // M-MEMBER-01-CLOSE-01A: 额度预占已由 UserAiController 集中处理,此处不再重复 reserve
const quotaOpId = dto.idempotencyKey ?? `quiz_gen:${dto.targetType}:${dto.targetId}:${Date.now()}`;
const check = await this.quotaGuard.check(userId, 'quiz_generation', quotaOpId);
if (!check.allowed) {
throw new BadRequestException(this.quotaGuard.buildError(check));
}
const useUnified = await this.shouldUseUnified(userId); const useUnified = await this.shouldUseUnified(userId);