将每日任务配置从硬编码字典改为通过 IDistributedCache 从 Redis 获取 新增默认任务配置作为兜底,在缓存不存在时自动写入 统一任务配置读取逻辑,支持后续动态调整任务等级与奖励 不影响现有任务流程与业务规则,仅增强配置灵活性
230 lines
8.8 KiB
C#
230 lines
8.8 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 每日任务服务
|
||
/// </summary>
|
||
[Authorize]
|
||
public class DailyTaskService : ApplicationService
|
||
{
|
||
private readonly ISqlSugarRepository<DailyTaskRewardRecordAggregateRoot> _dailyTaskRepository;
|
||
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
||
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||
private readonly ILogger<DailyTaskService> _logger;
|
||
private readonly IDistributedCache<DailyTaskConfigCacheDto> _taskConfigCache;
|
||
private IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetRequiredService<IDistributedLockProvider>();
|
||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||
|
||
private const string TaskConfigCacheKey = "AiHub:DailyTaskConfig";
|
||
|
||
// 默认任务配置(当Redis中没有配置时使用)
|
||
private static readonly List<DailyTaskConfigItem> 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<DailyTaskRewardRecordAggregateRoot> dailyTaskRepository,
|
||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
||
ILogger<DailyTaskService> logger,
|
||
ISqlSugarRepository<AiModelEntity> aiModelRepository,
|
||
IDistributedCache<DailyTaskConfigCacheDto> taskConfigCache)
|
||
{
|
||
_dailyTaskRepository = dailyTaskRepository;
|
||
_messageRepository = messageRepository;
|
||
_premiumPackageRepository = premiumPackageRepository;
|
||
_logger = logger;
|
||
_aiModelRepository = aiModelRepository;
|
||
_taskConfigCache = taskConfigCache;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取今日任务状态
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public async Task<DailyTaskStatusOutput> 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<DailyTaskItem>();
|
||
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
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 领取任务奖励
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
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");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取今日尊享包Token消耗量
|
||
/// </summary>
|
||
/// <param name="userId">用户ID</param>
|
||
/// <param name="today">今日日期</param>
|
||
/// <returns>消耗的Token总数</returns>
|
||
private async Task<long> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从Redis获取任务配置,如果不存在则写入默认配置
|
||
/// </summary>
|
||
private async Task<List<DailyTaskConfigItem>> GetTaskConfigsAsync()
|
||
{
|
||
var cacheData = await _taskConfigCache.GetOrAddAsync(
|
||
TaskConfigCacheKey,
|
||
() => Task.FromResult(new DailyTaskConfigCacheDto { Tasks = DefaultTaskConfigs }),
|
||
() => new DistributedCacheEntryOptions
|
||
{
|
||
// 不设置过期时间,永久缓存,需要手动更新
|
||
}
|
||
);
|
||
|
||
return cacheData?.Tasks ?? DefaultTaskConfigs;
|
||
}
|
||
} |