14 KiB
14 KiB
积分系统与广告服务配置指南
1. 积分系统设计
1.1 积分获取方式
- 观看广告: 每次完整观看激励视频广告获得5-20积分
- 订阅赠送: 包月用户每月自动获得积分
- 新用户奖励: 注册即送100积分
- 每日签到: 连续签到获得递增积分奖励
- 分享奖励: 分享作品获得积分
1.2 积分消耗规则
- 图片生成: 基础10积分,高质量15积分
- 视频生成: 基础50积分,高质量75积分
- 高级功能: 特殊滤镜、风格转换等
2. 积分服务实现
2.1 积分管理服务
// 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<UserCredit>,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
// 获取用户当前积分余额
async getUserBalance(userId: string, platform: PlatformType): Promise<number> {
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<boolean> {
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<UserCredit> {
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<UserCredit> {
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<UserCredit> {
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<UserCredit> {
const bonusAmount = 100; // 新用户奖励100积分
return this.addCredits(
userId,
platform,
bonusAmount,
CreditSource.MANUAL,
'新用户注册奖励',
'new_user_bonus'
);
}
// 每日签到奖励
async grantDailyCheckIn(userId: string, platform: PlatformType, consecutiveDays: number): Promise<UserCredit> {
// 连续签到奖励递增: 第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 广告服务实现
// 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<AdWatch>,
private readonly creditService: CreditService,
) {}
// 开始观看广告
async startAdWatch(
userId: string,
platform: PlatformType,
adType: AdType,
adId: string,
adUnitId?: string
): Promise<AdWatch> {
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<AdWatch> {
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<AdWatch> {
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<boolean> {
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 积分相关接口
// 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 广告相关接口
// 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. 环境配置
# 积分系统配置
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
这个积分和广告系统提供了完整的积分管理、广告观看奖励、每日限制等功能,支持多种广告类型和灵活的奖励机制。