using Medallion.Threading; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using SqlSugar; using Volo.Abp.Application.Services; using Volo.Abp.Caching; using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask; using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities.Chat; using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services; /// /// 每日任务服务 /// [Authorize] public class DailyTaskService : ApplicationService { private readonly ISqlSugarRepository _dailyTaskRepository; private readonly ISqlSugarRepository _messageRepository; private readonly ISqlSugarRepository _premiumPackageRepository; private readonly ILogger _logger; private readonly IDistributedCache _taskConfigCache; private IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetRequiredService(); private readonly ISqlSugarRepository _aiModelRepository; private const string TaskConfigCacheKey = "AiHub:DailyTaskConfig"; // 默认任务配置(当Redis中没有配置时使用) private static readonly List DefaultTaskConfigs = new() { new DailyTaskConfigItem { Level = 1, RequiredTokens = 10000000, RewardTokens = 1000000, Name = "尊享包1000w token任务", Description = "累积使用尊享包 1000w token" }, new DailyTaskConfigItem { Level = 2, RequiredTokens = 30000000, RewardTokens = 2000000, Name = "尊享包3000w token任务", Description = "累积使用尊享包 3000w token" } }; public DailyTaskService( ISqlSugarRepository dailyTaskRepository, ISqlSugarRepository messageRepository, ISqlSugarRepository premiumPackageRepository, ILogger logger, ISqlSugarRepository aiModelRepository, IDistributedCache taskConfigCache) { _dailyTaskRepository = dailyTaskRepository; _messageRepository = messageRepository; _premiumPackageRepository = premiumPackageRepository; _logger = logger; _aiModelRepository = aiModelRepository; _taskConfigCache = taskConfigCache; } /// /// 获取今日任务状态 /// /// public async Task GetTodayTaskStatusAsync() { var userId = CurrentUser.GetId(); var today = DateTime.Today; // 1. 获取任务配置 var taskConfigs = await GetTaskConfigsAsync(); // 2. 统计今日尊享包Token消耗量 var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today); // 3. 查询今日已领取的任务 var claimedTasks = await _dailyTaskRepository._DbQueryable .Where(x => x.UserId == userId && x.TaskDate == today) .Select(x => new { x.TaskLevel, x.IsRewarded }) .ToListAsync(); // 4. 构建任务列表 var tasks = new List(); foreach (var config in taskConfigs) { var claimed = claimedTasks.FirstOrDefault(x => x.TaskLevel == config.Level); int status; if (claimed != null && claimed.IsRewarded) { status = 2; // 已领取 } else if (todayConsumed >= config.RequiredTokens) { status = 1; // 可领取 } else { status = 0; // 未完成 } var progress = todayConsumed >= config.RequiredTokens ? 100 : Math.Round((decimal)todayConsumed / config.RequiredTokens * 100, 2); tasks.Add(new DailyTaskItem { Level = config.Level, Name = config.Name, Description = config.Description, RequiredTokens = config.RequiredTokens, RewardTokens = config.RewardTokens, Status = status, Progress = progress }); } return new DailyTaskStatusOutput { TodayConsumedTokens = todayConsumed, Tasks = tasks }; } /// /// 领取任务奖励 /// /// /// public async Task ClaimTaskRewardAsync(ClaimTaskRewardInput input) { var userId = CurrentUser.GetId(); //自旋等待,防抖 await using var handle = await DistributedLock.AcquireLockAsync($"Yi:AiHub:ClaimTaskRewardLock:{userId}"); var today = DateTime.Today; // 1. 获取任务配置 var taskConfigs = await GetTaskConfigsAsync(); var taskConfig = taskConfigs.FirstOrDefault(x => x.Level == input.TaskLevel); // 2. 验证任务等级 if (taskConfig == null) { throw new UserFriendlyException($"无效的任务等级: {input.TaskLevel}"); } // 3. 检查是否已领取 var existingRecord = await _dailyTaskRepository._DbQueryable .Where(x => x.UserId == userId && x.TaskDate == today && x.TaskLevel == input.TaskLevel) .FirstAsync(); if (existingRecord != null) { throw new UserFriendlyException("今日该任务奖励已领取,请明天再来!"); } // 4. 验证今日Token消耗是否达标 var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today); if (todayConsumed < taskConfig.RequiredTokens) { throw new UserFriendlyException( $"Token消耗未达标!需要 {taskConfig.RequiredTokens / 10000}w,当前 {todayConsumed / 10000}w"); } // 5. 创建奖励包 var premiumPackage = new PremiumPackageAggregateRoot(userId, taskConfig.RewardTokens, $"每日任务:{taskConfig.Name}") { PurchaseAmount = 0, Remark = $"{today:yyyy-MM-dd} 每日任务奖励" }; await _premiumPackageRepository.InsertAsync(premiumPackage); // 6. 记录领取记录 var record = new DailyTaskRewardRecordAggregateRoot(userId, input.TaskLevel, today, taskConfig.RewardTokens) { Remark = $"完成任务{input.TaskLevel},名称:{taskConfig.Name},消耗 {todayConsumed / 10000}w token" }; await _dailyTaskRepository.InsertAsync(record); _logger.LogInformation( $"用户 {userId} 领取每日任务 {input.TaskLevel} 奖励成功,获得 {taskConfig.RewardTokens / 10000}w tokens"); } /// /// 获取今日尊享包Token消耗量 /// /// 用户ID /// 今日日期 /// 消耗的Token总数 private async Task GetTodayPremiumTokenConsumptionAsync(Guid userId, DateTime today) { var tomorrow = today.AddDays(1); // 查询今日所有使用尊享包模型的消息(role=system 表示消耗) // 先获取所有尊享模型的ModelId列表 var premiumModelIds = await _aiModelRepository._DbQueryable .Where(x => x.IsPremium) .Select(x => x.ModelId) .ToListAsync(); var totalTokens = await _messageRepository._DbQueryable .Where(x => x.UserId == userId) .Where(x => x.Role == "system") // system角色表示实际消耗 .Where(x => premiumModelIds.Contains(x.ModelId)) // 尊享包模型 .Where(x => x.CreationTime >= today && x.CreationTime < tomorrow) .SumAsync(x => x.TokenUsage.TotalTokenCount); return totalTokens; } /// /// 从Redis获取任务配置,如果不存在则写入默认配置 /// private async Task> GetTaskConfigsAsync() { var cacheData = await _taskConfigCache.GetOrAddAsync( TaskConfigCacheKey, () => Task.FromResult(new DailyTaskConfigCacheDto { Tasks = DefaultTaskConfigs }), () => new DistributedCacheEntryOptions { // 不设置过期时间,永久缓存,需要手动更新 } ); return cacheData?.Tasks ?? DefaultTaskConfigs; } }