feat: 付费体系重构 P0 - 配额独立化/简历付费下载/PDF生成

This commit is contained in:
yuzhiran
2026-06-12 09:31:11 +08:00
parent 5d407b4f79
commit 065fe7a186
23 changed files with 965 additions and 106 deletions
+19 -12
View File
@@ -8,6 +8,7 @@ import { User, UserDocument } from '../user/user.schema'
import { Interview, InterviewDocument } from '../interview/interview.schema'
import { PaymentOrder, PaymentOrderDocument } from '../payment/payment-order.schema'
import { SiteConfig, SiteConfigDocument } from '../schemas/site-config.schema'
import { QuotaService } from '../user/quota.service'
import { WechatPayService } from '../payment/wechat-pay.service'
const VIP_DURATION_DAYS = 30
@@ -20,6 +21,7 @@ export class AdminController {
@InjectModel(Interview.name) private interviewModel: Model<InterviewDocument>,
@InjectModel(PaymentOrder.name) private orderModel: Model<PaymentOrderDocument>,
@InjectModel(SiteConfig.name) private configModel: Model<SiteConfigDocument>,
private quotaService: QuotaService,
private wechatPay: WechatPayService,
) {}
@@ -82,8 +84,7 @@ export class AdminController {
expireAt.setDate(expireAt.getDate() + VIP_DURATION_DAYS)
user.plan = 'growth'
user.vipExpireAt = expireAt
user.remaining = 999
await user.save()
await this.quotaService.setPlanQuota(targetUserId, 'growth', { interview: 999, resumeOptimize: 20, resumeDownload: 10 })
return { success: true, plan: 'growth', expireAt }
}
@@ -126,14 +127,20 @@ export class AdminController {
order.wxTransactionId = wxResult?.transaction_id || ''
order.paidAt = new Date()
await order.save()
const user = await this.userModel.findById(order.userId).exec()
if (user && user.plan === 'free') {
const expireAt = new Date()
expireAt.setDate(expireAt.getDate() + VIP_DURATION_DAYS)
user.plan = 'growth'
user.vipExpireAt = expireAt
user.remaining = 999
await user.save()
if (order.type === 'membership') {
const user = await this.userModel.findById(order.userId).exec()
if (user && user.plan === 'free') {
const expireAt = new Date()
expireAt.setDate(expireAt.getDate() + VIP_DURATION_DAYS)
user.plan = 'growth'
user.vipExpireAt = expireAt
await this.quotaService.setPlanQuota(order.userId, 'growth', { interview: 999, resumeOptimize: 20, resumeDownload: 10 })
}
} else {
const credits = { interview: 1, optimize: 1, download: 1 }[order.type]
if (credits) {
await this.quotaService.grantCredits(order.userId, order.type as any, credits)
}
}
}
return { order, wxResult }
@@ -179,8 +186,8 @@ const DEFAULT_CONFIG = {
optimize: { dailyFreeLimit: 2 },
price: { monthly: 1990 },
plans: {
free: { name: '免费版', price: 0, features: ['每日 3 次 AI 模拟面试', '每场最多 5 轮 AI 对话', '基础面试报告', '简历诊断', '简历优化'] },
growth: { name: '成长版', price: 1990, features: ['免费版全部权益', '无限面试次数', '每场最多 10 轮 AI 对话', '详细面试报告(四维评分)', '进步轨迹雷达图 + 打卡', '参考回答思路', '公司真题库'] },
free: { name: '免费版', price: 0, features: ['AI 模拟面试 1 次(体验)', '每场最多 5 轮 AI 对话', '基础面试报告', '简历优化(限 3 次)'] },
growth: { name: '成长版', price: 1990, features: ['免费版全部权益', 'AI 数字人面试无限次', '每场最多 10 轮 AI 对话', '详细面试报告(四维评分)', '进步轨迹雷达图 + 打卡', '参考回答思路', '公司真题库', '简历优化 20 次/月', '简历下载 10 次/月'] },
},
}
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'
import { MongooseModule } from '@nestjs/mongoose'
import { AdminController } from './admin.controller'
import { User, UserSchema } from '../user/user.schema'
import { UserModule } from '../user/user.module'
import { Interview, InterviewSchema } from '../interview/interview.schema'
import { PaymentOrder, PaymentOrderSchema } from '../payment/payment-order.schema'
import { WechatPayService } from '../payment/wechat-pay.service'
@@ -16,6 +17,7 @@ import { SiteConfig, SiteConfigSchema } from '../schemas/site-config.schema'
{ name: PaymentOrder.name, schema: PaymentOrderSchema },
{ name: SiteConfig.name, schema: SiteConfigSchema },
]),
UserModule,
],
controllers: [AdminController],
providers: [WechatPayService, AdminGuard],