Files
Yi.Framework/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/DailyTaskService.cs
ccnetcore e680ac4cac feat: 引入基于 Redis 的每日任务配置缓存
将每日任务配置从硬编码字典改为通过 IDistributedCache 从 Redis 获取
新增默认任务配置作为兜底,在缓存不存在时自动写入
统一任务配置读取逻辑,支持后续动态调整任务等级与奖励
不影响现有任务流程与业务规则,仅增强配置灵活性
2026-01-06 22:13:18 +08:00

230 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}