# 积分系统与广告服务配置指南 ## 1. 积分系统设计 ### 1.1 积分获取方式 - **观看广告**: 每次完整观看激励视频广告获得5-20积分 - **订阅赠送**: 包月用户每月自动获得积分 - **新用户奖励**: 注册即送100积分 - **每日签到**: 连续签到获得递增积分奖励 - **分享奖励**: 分享作品获得积分 ### 1.2 积分消耗规则 - **图片生成**: 基础10积分,高质量15积分 - **视频生成**: 基础50积分,高质量75积分 - **高级功能**: 特殊滤镜、风格转换等 ## 2. 积分服务实现 ### 2.1 积分管理服务 ```typescript // src/services/credit.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UserCredit, CreditType, CreditSource } from '../entities/user-credit.entity'; import { User } from '../entities/user.entity'; import { PlatformType } from '../entities/platform-user.entity'; @Injectable() export class CreditService { constructor( @InjectRepository(UserCredit) private readonly creditRepository: Repository, @InjectRepository(User) private readonly userRepository: Repository, ) {} // 获取用户当前积分余额 async getUserBalance(userId: string, platform: PlatformType): Promise { const latestRecord = await this.creditRepository.findOne({ where: { userId, platform }, order: { createdAt: 'DESC' }, }); return latestRecord?.balance || 0; } // 检查用户积分是否足够 async checkBalance(userId: string, platform: PlatformType, requiredAmount: number): Promise { const currentBalance = await this.getUserBalance(userId, platform); return currentBalance >= requiredAmount; } // 增加积分 async addCredits( userId: string, platform: PlatformType, amount: number, source: CreditSource, description?: string, referenceId?: string ): Promise { const currentBalance = await this.getUserBalance(userId, platform); const newBalance = currentBalance + amount; const creditRecord = this.creditRepository.create({ userId, platform, type: CreditType.REWARD, source, amount, balance: newBalance, description, referenceId, }); return this.creditRepository.save(creditRecord); } // 消耗积分 async consumeCredits( userId: string, platform: PlatformType, amount: number, source: CreditSource, referenceId?: string ): Promise { const currentBalance = await this.getUserBalance(userId, platform); if (currentBalance < amount) { throw new Error('积分不足'); } const newBalance = currentBalance - amount; const creditRecord = this.creditRepository.create({ userId, platform, type: CreditType.CONSUME, source, amount: -amount, // 负数表示扣除 balance: newBalance, description: `消耗积分: ${source}`, referenceId, }); return this.creditRepository.save(creditRecord); } // 退还积分 async refundCredits( userId: string, platform: PlatformType, amount: number, source: CreditSource, referenceId?: string ): Promise { const currentBalance = await this.getUserBalance(userId, platform); const newBalance = currentBalance + amount; const creditRecord = this.creditRepository.create({ userId, platform, type: CreditType.REFUND, source, amount, balance: newBalance, description: `积分退还: ${source}`, referenceId, }); return this.creditRepository.save(creditRecord); } // 获取积分历史记录 async getCreditHistory( userId: string, platform: PlatformType, page: number = 1, limit: number = 20 ) { const [records, total] = await this.creditRepository.findAndCount({ where: { userId, platform }, order: { createdAt: 'DESC' }, skip: (page - 1) * limit, take: limit, }); return { records, total, page, limit, totalPages: Math.ceil(total / limit), }; } // 新用户注册奖励 async grantNewUserBonus(userId: string, platform: PlatformType): Promise { const bonusAmount = 100; // 新用户奖励100积分 return this.addCredits( userId, platform, bonusAmount, CreditSource.MANUAL, '新用户注册奖励', 'new_user_bonus' ); } // 每日签到奖励 async grantDailyCheckIn(userId: string, platform: PlatformType, consecutiveDays: number): Promise { // 连续签到奖励递增: 第1天5积分,第2天10积分,第7天及以上20积分 let bonusAmount = 5; if (consecutiveDays >= 7) { bonusAmount = 20; } else if (consecutiveDays >= 3) { bonusAmount = 15; } else if (consecutiveDays >= 2) { bonusAmount = 10; } return this.addCredits( userId, platform, bonusAmount, CreditSource.MANUAL, `每日签到奖励 (连续${consecutiveDays}天)`, `daily_checkin_${consecutiveDays}` ); } } ``` ### 2.2 广告服务实现 ```typescript // src/services/ad.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AdWatch, AdType, AdStatus } from '../entities/ad-watch.entity'; import { CreditService } from './credit.service'; import { PlatformType } from '../entities/platform-user.entity'; import { CreditSource } from '../entities/user-credit.entity'; @Injectable() export class AdService { constructor( @InjectRepository(AdWatch) private readonly adWatchRepository: Repository, private readonly creditService: CreditService, ) {} // 开始观看广告 async startAdWatch( userId: string, platform: PlatformType, adType: AdType, adId: string, adUnitId?: string ): Promise { const adWatch = this.adWatchRepository.create({ userId, platform, adType, adId, adUnitId, status: AdStatus.STARTED, }); return this.adWatchRepository.save(adWatch); } // 完成观看广告 async completeAdWatch( adWatchId: string, watchDuration: number, adData?: any ): Promise<{ adWatch: AdWatch; creditReward: number }> { const adWatch = await this.adWatchRepository.findOne({ where: { id: adWatchId }, }); if (!adWatch) { throw new Error('广告观看记录不存在'); } if (adWatch.status !== AdStatus.STARTED) { throw new Error('广告状态异常'); } // 计算奖励积分 const creditReward = this.calculateAdReward(adWatch.adType, watchDuration); // 更新广告观看记录 await this.adWatchRepository.update(adWatchId, { status: AdStatus.COMPLETED, watchDuration, rewardCredits: creditReward, adData, }); // 发放积分奖励 if (creditReward > 0) { await this.creditService.addCredits( adWatch.userId, adWatch.platform, creditReward, CreditSource.AD_WATCH, `观看${adWatch.adType}广告奖励`, adWatchId ); } const updatedAdWatch = await this.adWatchRepository.findOne({ where: { id: adWatchId }, }); return { adWatch: updatedAdWatch, creditReward, }; } // 跳过广告 async skipAdWatch(adWatchId: string, watchDuration: number): Promise { await this.adWatchRepository.update(adWatchId, { status: AdStatus.SKIPPED, watchDuration, rewardCredits: 0, // 跳过广告无奖励 }); return this.adWatchRepository.findOne({ where: { id: adWatchId } }); } // 广告观看失败 async failAdWatch(adWatchId: string, errorMessage?: string): Promise { await this.adWatchRepository.update(adWatchId, { status: AdStatus.FAILED, adData: { error: errorMessage }, }); return this.adWatchRepository.findOne({ where: { id: adWatchId } }); } // 计算广告奖励积分 private calculateAdReward(adType: AdType, watchDuration: number): number { // 不同类型广告的基础奖励 const baseRewards = { [AdType.REWARDED]: 15, // 激励视频广告奖励最高 [AdType.INTERSTITIAL]: 8, // 插屏广告中等奖励 [AdType.BANNER]: 3, // 横幅广告奖励较低 [AdType.NATIVE]: 5, // 原生广告奖励较低 }; const baseReward = baseRewards[adType] || 5; // 根据观看时长调整奖励 if (adType === AdType.REWARDED) { // 激励视频需要观看完整才有奖励 const minWatchTime = 15; // 最少观看15秒 if (watchDuration < minWatchTime) { return 0; } } return baseReward; } // 获取用户广告观看历史 async getUserAdHistory( userId: string, platform: PlatformType, page: number = 1, limit: number = 20 ) { const [records, total] = await this.adWatchRepository.findAndCount({ where: { userId, platform }, order: { createdAt: 'DESC' }, skip: (page - 1) * limit, take: limit, }); return { records, total, page, limit, totalPages: Math.ceil(total / limit), }; } // 获取今日广告观看统计 async getTodayAdStats(userId: string, platform: PlatformType) { const today = new Date(); today.setHours(0, 0, 0, 0); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); const stats = await this.adWatchRepository .createQueryBuilder('ad_watch') .select('ad_watch.adType', 'adType') .addSelect('ad_watch.status', 'status') .addSelect('COUNT(*)', 'count') .addSelect('SUM(ad_watch.rewardCredits)', 'totalRewards') .where('ad_watch.userId = :userId', { userId }) .andWhere('ad_watch.platform = :platform', { platform }) .andWhere('ad_watch.createdAt >= :today', { today }) .andWhere('ad_watch.createdAt < :tomorrow', { tomorrow }) .groupBy('ad_watch.adType, ad_watch.status') .getRawMany(); return stats; } // 检查广告观看限制 async checkAdWatchLimit(userId: string, platform: PlatformType, adType: AdType): Promise { const today = new Date(); today.setHours(0, 0, 0, 0); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); // 每日观看限制 const dailyLimits = { [AdType.REWARDED]: 20, // 激励视频每日最多20次 [AdType.INTERSTITIAL]: 10, // 插屏广告每日最多10次 [AdType.BANNER]: 50, // 横幅广告每日最多50次 [AdType.NATIVE]: 30, // 原生广告每日最多30次 }; const todayCount = await this.adWatchRepository.count({ where: { userId, platform, adType, status: AdStatus.COMPLETED, createdAt: { $gte: today, $lt: tomorrow, } as any, }, }); return todayCount < (dailyLimits[adType] || 10); } } ``` ## 3. API接口实现 ### 3.1 积分相关接口 ```typescript // src/controllers/credit.controller.ts @ApiTags('💰 积分系统') @Controller('credits') export class CreditController { constructor(private readonly creditService: CreditService) {} @Get('balance') @ApiOperation({ summary: '获取用户积分余额' }) async getBalance(@CurrentUser() user: any) { const balance = await this.creditService.getUserBalance(user.id, user.platform); return { balance }; } @Get('history') @ApiOperation({ summary: '获取积分历史记录' }) async getHistory( @CurrentUser() user: any, @Query('page') page: number = 1, @Query('limit') limit: number = 20 ) { return this.creditService.getCreditHistory(user.id, user.platform, page, limit); } @Post('daily-checkin') @ApiOperation({ summary: '每日签到' }) async dailyCheckIn(@CurrentUser() user: any) { // 这里需要实现签到逻辑,计算连续签到天数 const consecutiveDays = 1; // 简化示例 return this.creditService.grantDailyCheckIn(user.id, user.platform, consecutiveDays); } } ``` ### 3.2 广告相关接口 ```typescript // src/controllers/ad.controller.ts @ApiTags('📺 广告系统') @Controller('ads') export class AdController { constructor(private readonly adService: AdService) {} @Post('watch/start') @ApiOperation({ summary: '开始观看广告' }) async startWatch(@CurrentUser() user: any, @Body() startAdDto: StartAdWatchDto) { return this.adService.startAdWatch( user.id, user.platform, startAdDto.adType, startAdDto.adId, startAdDto.adUnitId ); } @Post('watch/:adWatchId/complete') @ApiOperation({ summary: '完成观看广告' }) async completeWatch( @Param('adWatchId') adWatchId: string, @Body() completeAdDto: CompleteAdWatchDto ) { return this.adService.completeAdWatch( adWatchId, completeAdDto.watchDuration, completeAdDto.adData ); } @Get('stats/today') @ApiOperation({ summary: '获取今日广告观看统计' }) async getTodayStats(@CurrentUser() user: any) { return this.adService.getTodayAdStats(user.id, user.platform); } } ``` ## 4. 环境配置 ```env # 积分系统配置 NEW_USER_BONUS_CREDITS=100 DAILY_CHECKIN_BASE_CREDITS=5 MAX_DAILY_CHECKIN_CREDITS=20 # 广告奖励配置 REWARDED_AD_CREDITS=15 INTERSTITIAL_AD_CREDITS=8 BANNER_AD_CREDITS=3 NATIVE_AD_CREDITS=5 # 广告观看限制 DAILY_REWARDED_AD_LIMIT=20 DAILY_INTERSTITIAL_AD_LIMIT=10 DAILY_BANNER_AD_LIMIT=50 DAILY_NATIVE_AD_LIMIT=30 # AI生成积分消耗 IMAGE_GENERATION_CREDITS=10 VIDEO_GENERATION_CREDITS=50 HIGH_QUALITY_MULTIPLIER=1.5 ``` 这个积分和广告系统提供了完整的积分管理、广告观看奖励、每日限制等功能,支持多种广告类型和灵活的奖励机制。