| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- import { prisma } from '../../models';
- import { MemberLevel } from '../../types';
- // ============================================
- // TTS服务商成本配置(2026-04-12 更新)
- // ============================================
- export const TTS_COST_CONFIG = {
- // TTS服务商配置
- providers: {
- // 免费方案(科大讯飞基础版)
- iflytek_free: {
- name: '科大讯飞-基础版',
- costPerThousand: 0, // 免费
- quality: 'standard',
- voiceOptions: 10,
- freeQuota: 100000, // 10万/月(永久)
- novelRating: 3 // ★★★☆☆
- },
- // 性价比方案(百度基础版)
- baidu_standard: {
- name: '百度智能云-基础版',
- costPerThousand: 3.0, // ¥3/千字符
- quality: 'standard',
- voiceOptions: 20,
- freeQuota: 2000000, // 200万/月(首年)
- novelRating: 4 // ★★★★☆
- },
- // 高质量方案(百度精品版)
- baidu_premium: {
- name: '百度智能云-精品版',
- costPerThousand: 15.0, // ¥15/千字符
- quality: 'premium',
- voiceOptions: 50,
- freeQuota: 0,
- novelRating: 5 // ★★★★★
- },
- // 阿里云方案
- aliyun_plus: {
- name: '阿里云Qwen3-Plus',
- costPerThousand: 12.0, // ¥12/千字符
- quality: 'high',
- voiceOptions: 30,
- freeQuota: 0,
- novelRating: 5 // ★★★★★
- },
- // 企业方案(火山引擎)
- volcano_premium: {
- name: '火山引擎豆包-标准版',
- costPerThousand: 50.0, // ¥50/千字符
- quality: 'premium',
- voiceOptions: 40,
- freeQuota: 20000, // 2万/应用
- novelRating: 5 // ★★★★★
- }
- },
-
- // AI模型成本配置
- aiModels: {
- deepseek: {
- name: 'DeepSeek V3.2',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 8.0, // ¥8.0/千token
- quality: 'standard',
- default: true
- },
- qwen_plus: {
- name: '通义Qwen Plus',
- inputCost: 4.0, // ¥4.0/千token
- outputCost: 12.0, // ¥12.0/千token
- quality: 'high'
- },
- doubao_pro: {
- name: '豆包 Pro',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 6.0, // ¥6.0/千token
- quality: 'high'
- }
- },
-
- // 各等级默认TTS方案
- planTTSProvider: {
- 0: 'iflytek_free', // 免费版:科大讯飞免费
- 1: 'baidu_standard', // 入门版:百度基础
- 2: 'baidu_premium', // 专业版:百度精品
- 3: 'volcano_premium' // 旗舰版:火山引擎
- },
-
- // 默认AI模型
- defaultAIModel: 'deepseek'
- };
- // ============================================
- // Token成本配置(AI生成成本 + TTS成本)
- // ============================================
- export const TOKEN_COST_CONFIG = {
- // 基础Token成本(DeepSeek V3.2 + 百度基础TTS)
- baseCostPerThousand: 5.0, // AI: ¥2 + TTS: ¥3 = ¥5/千字符
-
- // 超额使用单价
- extraPricePerThousand: 6.0, // 略高于成本价
-
- // 模型选择
- models: {
- deepseek: {
- name: 'DeepSeek V3.2',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 8.0, // ¥8.0/千token
- quality: 'standard',
- default: true
- },
- qwen_plus: {
- name: '通义 Qwen Plus',
- inputCost: 4.0, // ¥4.0/千token
- outputCost: 12.0, // ¥12.0/千token
- quality: 'high',
- extraFee: 1.5 // 每千token额外收费
- },
- doubao_pro: {
- name: '豆包 Pro',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 6.0, // ¥6.0/千token
- quality: 'high'
- }
- }
- };
- // 计算Token生成成本
- export function calculateTokenCost(textLength: number, model: string = 'deepseek'): {
- inputCost: number;
- outputCost: number;
- totalCost: number;
- suggestedPrice: number;
- } {
- const modelConfig = TOKEN_COST_CONFIG.models[model] || TOKEN_COST_CONFIG.models.deepseek;
-
- // 输入成本
- const inputTokens = textLength; // 1字 ≈ 1 token
- const inputCost = (inputTokens / 1000) * modelConfig.inputCost;
-
- // 输出成本(音频合成)
- const outputCost = textLength * 0.001 * modelConfig.outputCost * 0.1;
-
- // 总成本
- const totalCost = inputCost + outputCost;
-
- // 建议售价(成本 × 1.5,保证50%利润)
- const suggestedPrice = totalCost * 1.5;
-
- return {
- inputCost: Math.round(inputCost * 100) / 100,
- outputCost: Math.round(outputCost * 100) / 100,
- totalCost: Math.round(totalCost * 100) / 100,
- suggestedPrice: Math.round(suggestedPrice * 100) / 100
- };
- }
- // 默认套餐配置(基于TTS成本,2026-04-12更新)
- export const DEFAULT_PLANS = [
- {
- name: '免费版',
- level: 0,
- priceMonthly: 0,
- priceYearly: 0,
- description: '适合轻度体验',
- features: JSON.stringify([
- '每天3次生成',
- '每次最多2000字',
- '基础音色5种',
- '标准音质',
- '科大讯飞免费额度(10万/月永久)',
- 'Token成本:¥17.5/月(平台补贴)'
- ]),
- isRecommended: false,
- sortOrder: 0,
- dailyGenerations: 3,
- perGenerationLimit: 2000,
- monthlyTokens: 5000, // AI+基础TTS成本:¥17.5
- yearlyTokens: null,
- voiceOptions: 5,
- audioQuality: 'standard',
- apiAccess: false,
- batchProcessing: false,
- teamManagement: false,
- // 成本分析
- costAnalysis: {
- tokenCost: 17.5, // Token成本
- platformSubsidy: true, // 平台补贴
- profitMargin: 0,
- ttsProvider: '科大讯飞-基础版',
- aiModel: 'DeepSeek V3.2'
- }
- },
- {
- name: '入门版',
- level: 1,
- priceMonthly: 9.9,
- priceYearly: 99,
- description: '适合日常使用',
- features: JSON.stringify([
- '每月10000 Token',
- '每次最多5000字',
- '全部音色',
- '高清音质',
- '百度基础版TTS(¥3/千字符)',
- 'Token成本:¥35/月'
- ]),
- isRecommended: false,
- sortOrder: 1,
- dailyGenerations: 10,
- perGenerationLimit: 5000,
- monthlyTokens: 10000, // AI+基础TTS成本:¥35
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'high',
- apiAccess: false,
- batchProcessing: false,
- teamManagement: false,
- // 成本分析(亏损,用于获客转化)
- costAnalysis: {
- tokenCost: 35.0,
- monthlyPrice: 9.9,
- loss: -25.1, // 每单亏损,获客用
- strategy: '获客转化',
- ttsProvider: '百度智能云-基础版'
- }
- },
- {
- name: '专业版',
- level: 2,
- priceMonthly: 49,
- priceYearly: 490,
- description: '适合内容创作者 ⭐推荐',
- features: JSON.stringify([
- '每月30000 Token',
- '每次最多20000字',
- '全部音色+定制音色',
- '高清音质',
- '百度精品版TTS(¥15/千字符)',
- 'VIP优先队列',
- 'Token成本:¥105/月'
- ]),
- isRecommended: true,
- sortOrder: 2,
- dailyGenerations: -1,
- perGenerationLimit: 20000,
- monthlyTokens: 30000, // AI+精品TTS成本:¥105
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'lossless',
- apiAccess: false,
- batchProcessing: false,
- teamManagement: false,
- // 成本分析(部分补贴,核心盈利)
- costAnalysis: {
- tokenCost: 105.0,
- monthlyPrice: 49,
- loss: -56.0, // 部分补贴
- profitMargin: 0, // 保本微亏
- strategy: '核心转化产品',
- ttsProvider: '百度智能云-精品版'
- }
- },
- {
- name: '旗舰版',
- level: 3,
- priceMonthly: 199,
- priceYearly: 1999,
- description: '适合企业用户',
- features: JSON.stringify([
- '每月100000 Token',
- '每次最多100000字',
- '全部功能',
- '无损音质',
- '火山引擎豆包TTS(¥50/千字符)',
- '专属技术支持',
- '批量处理',
- '团队管理',
- 'Token成本:¥350/月'
- ]),
- isRecommended: false,
- sortOrder: 3,
- dailyGenerations: -1, // 无限制
- perGenerationLimit: 100000,
- monthlyTokens: 100000, // AI+高端TTS成本:¥350
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'lossless',
- apiAccess: true,
- batchProcessing: true,
- teamManagement: true,
- // 成本分析(盈利)
- costAnalysis: {
- tokenCost: 350.0,
- monthlyPrice: 199,
- profit: -151.0, // 仍有亏损,建议涨价
- profitMargin: 0,
- suggestion: '建议定价¥399-499/月',
- ttsProvider: '火山引擎豆包'
- }
- }
- ];
- // 初始化套餐数据
- export async function initializePlans() {
- const existingPlans = await prisma.subscriptionPlan.count();
-
- if (existingPlans === 0) {
- console.log('初始化订阅套餐数据...');
- for (const plan of DEFAULT_PLANS) {
- await prisma.subscriptionPlan.create({ data: plan });
- }
- console.log('订阅套餐初始化完成');
- }
- }
- // 获取所有套餐列表
- export async function getPlans() {
- const plans = await prisma.subscriptionPlan.findMany({
- where: { isActive: true },
- orderBy: { sortOrder: 'asc' }
- });
- return plans.map(plan => ({
- ...plan,
- priceMonthly: Number(plan.priceMonthly),
- priceYearly: Number(plan.priceYearly),
- features: JSON.parse(plan.features || '[]')
- }));
- }
- // 获取单个套餐详情
- export async function getPlanById(planId: number) {
- const plan = await prisma.subscriptionPlan.findUnique({
- where: { id: planId }
- });
- if (!plan) {
- throw new Error('套餐不存在');
- }
- return {
- ...plan,
- priceMonthly: Number(plan.priceMonthly),
- priceYearly: Number(plan.priceYearly),
- features: JSON.parse(plan.features || '[]')
- };
- }
- // 获取用户当前订阅信息
- export async function getUserSubscription(userId: number) {
- const subscription = await prisma.subscription.findFirst({
- where: {
- userId,
- status: 'active',
- endDate: { gt: new Date() }
- },
- include: {
- plan: true
- },
- orderBy: { endDate: 'desc' }
- });
- return subscription;
- }
- // 获取用户Token余额
- export async function getUserTokenBalance(userId: number) {
- let balance = await prisma.tokenBalance.findUnique({
- where: { userId }
- });
- if (!balance) {
- // 初始化Token余额
- balance = await prisma.tokenBalance.create({
- data: {
- userId,
- totalTokens: 10000, // 默认免费额度
- usedTokens: 0
- }
- });
- }
- const remaining = balance.totalTokens - balance.usedTokens;
- return {
- totalTokens: balance.totalTokens,
- usedTokens: balance.usedTokens,
- remainingTokens: remaining,
- resetDate: balance.resetDate,
- isUnlimited: balance.totalTokens === -1
- };
- }
- // 获取Token使用记录
- export async function getTokenUsageList(userId: number, page: number = 1, pageSize: number = 20) {
- const where = { userId };
- const total = await prisma.tokenUsage.count({ where });
-
- const list = await prisma.tokenUsage.findMany({
- where,
- orderBy: { createdAt: 'desc' },
- skip: (page - 1) * pageSize,
- take: pageSize
- });
- return {
- list,
- total,
- page,
- pageSize,
- totalPages: Math.ceil(total / pageSize)
- };
- }
- // 消耗Token
- export async function consumeToken(
- userId: number,
- amount: number,
- type: string,
- contentLength: number,
- description?: string
- ) {
- const balance = await prisma.tokenBalance.findUnique({
- where: { userId }
- });
- if (!balance) {
- throw new Error('用户Token余额不存在');
- }
- if (balance.totalTokens !== -1 && balance.usedTokens + amount > balance.totalTokens) {
- throw new Error('Token余额不足');
- }
- // 更新余额
- await prisma.tokenBalance.update({
- where: { userId },
- data: {
- usedTokens: { increment: amount }
- }
- });
- // 记录使用
- const usage = await prisma.tokenUsage.create({
- data: {
- userId,
- type,
- amount,
- contentLength,
- description
- }
- });
- return usage;
- }
- // 获取用户配额(兼容旧接口)
- export async function getUserQuota(userId: number) {
- const user = await prisma.user.findUnique({
- where: { id: userId }
- });
- if (!user) {
- throw new Error('用户不存在');
- }
- const subscription = await getUserSubscription(userId);
- const tokenBalance = await getUserTokenBalance(userId);
-
- const memberLevel = user.memberLevel as MemberLevel;
- const levelNames = ['免费版', '基础版', '专业版', '旗舰版'];
-
- return {
- level: memberLevel,
- levelName: levelNames[memberLevel] || '免费版',
- isValid: subscription !== null,
- expireAt: subscription?.endDate,
- plan: subscription?.plan ? {
- name: subscription.plan.name,
- features: JSON.parse(subscription.plan.features || '[]')
- } : null,
- tokenBalance,
- limits: subscription?.plan ? {
- dailyGenerations: subscription.plan.dailyGenerations,
- perGenerationLimit: subscription.plan.perGenerationLimit,
- monthlyTokens: subscription.plan.monthlyTokens,
- voiceOptions: subscription.plan.voiceOptions,
- audioQuality: subscription.plan.audioQuality
- } : {
- dailyGenerations: 3,
- perGenerationLimit: 2000,
- monthlyTokens: 10000,
- voiceOptions: 5,
- audioQuality: 'standard'
- }
- };
- }
- // 检查用户配额
- export async function checkQuota(userId: number, requiredTokens: number) {
- const quota = await getUserQuota(userId);
- const balance = quota.tokenBalance;
-
- if (balance.isUnlimited) {
- return { allowed: true, reason: null };
- }
-
- if (balance.remainingTokens < requiredTokens) {
- return {
- allowed: false,
- reason: 'Token余额不足',
- required: requiredTokens,
- remaining: balance.remainingTokens
- };
- }
-
- return { allowed: true, reason: null };
- }
|