feat: 付费体系重构 P0 - 配额独立化/简历付费下载/PDF生成
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common'
|
||||
import { InjectModel } from '@nestjs/mongoose'
|
||||
import { Model } from 'mongoose'
|
||||
import { User, UserDocument } from './user.schema'
|
||||
|
||||
const FREE_OPTIMIZE_LIMIT = 3
|
||||
|
||||
@Injectable()
|
||||
export class QuotaService {
|
||||
constructor(
|
||||
@InjectModel(User.name) private userModel: Model<UserDocument>,
|
||||
) {}
|
||||
|
||||
async checkAndDeductInterview(userId: string) {
|
||||
const user = await this.userModel.findById(userId).exec()
|
||||
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||
if (user.plan !== 'free') return
|
||||
|
||||
if ((user.interviewCredits || 0) <= 0) {
|
||||
throw new HttpException('面试次数已用完,请购买面试次数或开通会员', HttpStatus.FORBIDDEN)
|
||||
}
|
||||
user.interviewCredits = (user.interviewCredits || 0) - 1
|
||||
user.interviewCount = (user.interviewCount || 0) + 1
|
||||
await user.save()
|
||||
}
|
||||
|
||||
async checkAndDeductOptimize(userId: string) {
|
||||
const user = await this.userModel.findById(userId).exec()
|
||||
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||
if (user.plan !== 'free') return
|
||||
|
||||
// 优先扣付费额度
|
||||
if ((user.resumeOptimizeCredits || 0) > 0) {
|
||||
user.resumeOptimizeCredits = (user.resumeOptimizeCredits || 0) - 1
|
||||
await user.save()
|
||||
return
|
||||
}
|
||||
|
||||
// 免费额度
|
||||
if ((user.freeOptimizeUsed || 0) < FREE_OPTIMIZE_LIMIT) {
|
||||
user.freeOptimizeUsed = (user.freeOptimizeUsed || 0) + 1
|
||||
await user.save()
|
||||
return
|
||||
}
|
||||
|
||||
throw new HttpException('简历优化次数已用完,请购买优化次数或开通会员', HttpStatus.FORBIDDEN)
|
||||
}
|
||||
|
||||
async checkDownload(userId: string, resume: { paidDownload?: boolean }): Promise<boolean> {
|
||||
const user = await this.userModel.findById(userId).exec()
|
||||
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||
|
||||
if (resume.paidDownload) return true
|
||||
if ((user.resumeDownloadCredits || 0) > 0) return true
|
||||
return false
|
||||
}
|
||||
|
||||
async deductDownload(userId: string, resume: { paidDownload?: boolean; _id?: any }) {
|
||||
const user = await this.userModel.findById(userId).exec()
|
||||
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||
|
||||
if (resume.paidDownload) return
|
||||
if ((user.resumeDownloadCredits || 0) > 0) {
|
||||
user.resumeDownloadCredits = (user.resumeDownloadCredits || 0) - 1
|
||||
await user.save()
|
||||
}
|
||||
}
|
||||
|
||||
async grantCredits(userId: string, type: 'interview' | 'optimize' | 'download', amount: number) {
|
||||
const user = await this.userModel.findById(userId).exec()
|
||||
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||
|
||||
if (type === 'interview') user.interviewCredits = (user.interviewCredits || 0) + amount
|
||||
else if (type === 'optimize') user.resumeOptimizeCredits = (user.resumeOptimizeCredits || 0) + amount
|
||||
else if (type === 'download') user.resumeDownloadCredits = (user.resumeDownloadCredits || 0) + amount
|
||||
|
||||
await user.save()
|
||||
}
|
||||
|
||||
async setPlanQuota(userId: string, plan: string, credits: { interview: number; resumeOptimize: number; resumeDownload: number }) {
|
||||
const user = await this.userModel.findById(userId).exec()
|
||||
if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
|
||||
|
||||
user.remaining = 999
|
||||
user.interviewCredits = credits.interview
|
||||
user.resumeOptimizeCredits = credits.resumeOptimize
|
||||
user.resumeDownloadCredits = credits.resumeDownload
|
||||
user.freeOptimizeUsed = FREE_OPTIMIZE_LIMIT // 会员不再消耗免费次数
|
||||
|
||||
await user.save()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user