fix: gravity deducted before AI call, causing deduction on failure
- Split checkAndDeductInterview into checkInterview (read-only) and deductInterview (deduction only) - Restructure interview.create(): check gravity -> AI call -> deduct on success - If AI call fails, gravity is never deducted - Keep old checkAndDeductInterview for backward compatibility
This commit is contained in:
@@ -21,14 +21,19 @@ export class InterviewService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(userId: string, position: string) {
|
async create(userId: string, position: string) {
|
||||||
await this.quotaService.checkAndDeductInterview(userId)
|
// Step 1: 先检查引力值是否足够(不扣除)
|
||||||
|
const cost = await this.quotaService.checkInterview(userId)
|
||||||
|
|
||||||
|
// Step 2: AI 生成第一个问题
|
||||||
const firstQuestion = await this.aiService.call({
|
const firstQuestion = await this.aiService.call({
|
||||||
systemPrompt: `你是一位专业的${position}面试官。请针对校招该岗位提出第一个面试问题,要求具体且有针对性。直接输出问题,不要多余内容。`,
|
systemPrompt: `你是一位专业的${position}面试官。请针对校招该岗位提出第一个面试问题,要求具体且有针对性。直接输出问题,不要多余内容。`,
|
||||||
userMessage: `请为${position}岗位的校招候选人提出第一个面试问题。`,
|
userMessage: `请为${position}岗位的校招候选人提出第一个面试问题。`,
|
||||||
temperature: 0.8,
|
temperature: 0.8,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Step 3: AI 调用成功后,扣除引力值并创建面试记录
|
||||||
|
await this.quotaService.deductInterview(userId, cost)
|
||||||
|
|
||||||
const interview = await this.interviewModel.create({
|
const interview = await this.interviewModel.create({
|
||||||
userId,
|
userId,
|
||||||
position,
|
position,
|
||||||
|
|||||||
@@ -15,6 +15,36 @@ export class QuotaService {
|
|||||||
private pricingService: PricingService,
|
private pricingService: PricingService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/** 仅检查面试引力值是否充足,不扣除(用于先 AI 后扣款模式) */
|
||||||
|
async checkInterview(userId: string): Promise<number> {
|
||||||
|
const user = await this.userModel.findById(userId).exec()
|
||||||
|
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
// 迁移旧字段到 gravity
|
||||||
|
if ((user.gravity ?? 0) <= 0 && this.hasOldCredits(user)) {
|
||||||
|
await this.migrateOldCredits(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rates = (await this.pricingService.getConfig()).gravityRates
|
||||||
|
const cost = rates.interviewPerUse
|
||||||
|
|
||||||
|
// 检查 gravity 或 shareCredits 是否足够
|
||||||
|
const gravOk = (user.gravity ?? 0) >= cost
|
||||||
|
const shareOk = (user.shareCredits ?? 0) > 0
|
||||||
|
if (!gravOk && !shareOk) {
|
||||||
|
throw new HttpException('引力值不足,请充值或分享获取', HttpStatus.FORBIDDEN)
|
||||||
|
}
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 扣除面试引力值(在 AI 调用成功后调用) */
|
||||||
|
async deductInterview(userId: string, cost: number) {
|
||||||
|
const result = await this.deductGravityOrFallback(userId, cost)
|
||||||
|
if (!result) {
|
||||||
|
throw new HttpException('引力值不足', HttpStatus.FORBIDDEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 检查并扣除面试引力值(所有计划统一走引力值) */
|
/** 检查并扣除面试引力值(所有计划统一走引力值) */
|
||||||
async checkAndDeductInterview(userId: string) {
|
async checkAndDeductInterview(userId: string) {
|
||||||
const user = await this.userModel.findById(userId).exec()
|
const user = await this.userModel.findById(userId).exec()
|
||||||
|
|||||||
Reference in New Issue
Block a user