2025-12-19 16:13:23 +08:00
|
|
|
|
using Medallion.Threading;
|
2025-10-18 17:34:46 +08:00
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2026-01-06 22:13:18 +08:00
|
|
|
|
using Microsoft.Extensions.Caching.Distributed;
|
2025-10-18 17:34:46 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
|
using Volo.Abp.Application.Services;
|
2026-01-06 22:13:18 +08:00
|
|
|
|
using Volo.Abp.Caching;
|
2025-10-18 17:34:46 +08:00
|
|
|
|
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;
|
2026-01-01 00:44:02 +08:00
|
|
|
|
using Yi.Framework.AiHub.Domain.Entities.Model;
|
2025-10-18 17:34:46 +08:00
|
|
|
|
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;
|
2026-01-06 22:13:18 +08:00
|
|
|
|
private readonly IDistributedCache<DailyTaskConfigCacheDto> _taskConfigCache;
|
2025-12-19 16:13:23 +08:00
|
|
|
|
private IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetRequiredService<IDistributedLockProvider>();
|
2026-01-01 00:44:02 +08:00
|
|
|
|
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
2026-01-06 22:13:18 +08:00
|
|
|
|
|
|
|
|
|
|
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" }
|
|
|
|
|
|
};
|
2025-10-18 17:34:46 +08:00
|
|
|
|
|
|
|
|
|
|
public DailyTaskService(
|
|
|
|
|
|
ISqlSugarRepository<DailyTaskRewardRecordAggregateRoot> dailyTaskRepository,
|
|
|
|
|
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
|
|
|
|
|
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
2026-01-06 22:13:18 +08:00
|
|
|
|
ILogger<DailyTaskService> logger,
|
|
|
|
|
|
ISqlSugarRepository<AiModelEntity> aiModelRepository,
|
|
|
|
|
|
IDistributedCache<DailyTaskConfigCacheDto> taskConfigCache)
|
2025-10-18 17:34:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
_dailyTaskRepository = dailyTaskRepository;
|
|
|
|
|
|
_messageRepository = messageRepository;
|
|
|
|
|
|
_premiumPackageRepository = premiumPackageRepository;
|
|
|
|
|
|
_logger = logger;
|
2026-01-01 00:44:02 +08:00
|
|
|
|
_aiModelRepository = aiModelRepository;
|
2026-01-06 22:13:18 +08:00
|
|
|
|
_taskConfigCache = taskConfigCache;
|
2025-10-18 17:34:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取今日任务状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<DailyTaskStatusOutput> GetTodayTaskStatusAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = CurrentUser.GetId();
|
|
|
|
|
|
var today = DateTime.Today;
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 1. 获取任务配置
|
|
|
|
|
|
var taskConfigs = await GetTaskConfigsAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 统计今日尊享包Token消耗量
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 3. 查询今日已领取的任务
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var claimedTasks = await _dailyTaskRepository._DbQueryable
|
|
|
|
|
|
.Where(x => x.UserId == userId && x.TaskDate == today)
|
|
|
|
|
|
.Select(x => new { x.TaskLevel, x.IsRewarded })
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 4. 构建任务列表
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var tasks = new List<DailyTaskItem>();
|
2026-01-06 22:13:18 +08:00
|
|
|
|
foreach (var config in taskConfigs)
|
2025-10-18 17:34:46 +08:00
|
|
|
|
{
|
2026-01-06 22:13:18 +08:00
|
|
|
|
var claimed = claimedTasks.FirstOrDefault(x => x.TaskLevel == config.Level);
|
2025-10-18 17:34:46 +08:00
|
|
|
|
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
|
|
|
|
|
|
{
|
2026-01-06 22:13:18 +08:00
|
|
|
|
Level = config.Level,
|
2025-10-18 17:34:46 +08:00
|
|
|
|
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();
|
2025-12-19 16:13:23 +08:00
|
|
|
|
//自旋等待,防抖
|
|
|
|
|
|
await using var handle =
|
|
|
|
|
|
await DistributedLock.AcquireLockAsync($"Yi:AiHub:ClaimTaskRewardLock:{userId}");
|
2026-01-06 22:13:18 +08:00
|
|
|
|
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var today = DateTime.Today;
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 1. 获取任务配置
|
|
|
|
|
|
var taskConfigs = await GetTaskConfigsAsync();
|
|
|
|
|
|
var taskConfig = taskConfigs.FirstOrDefault(x => x.Level == input.TaskLevel);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 验证任务等级
|
|
|
|
|
|
if (taskConfig == null)
|
2025-10-18 17:34:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new UserFriendlyException($"无效的任务等级: {input.TaskLevel}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 3. 检查是否已领取
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var existingRecord = await _dailyTaskRepository._DbQueryable
|
|
|
|
|
|
.Where(x => x.UserId == userId && x.TaskDate == today && x.TaskLevel == input.TaskLevel)
|
|
|
|
|
|
.FirstAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (existingRecord != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new UserFriendlyException("今日该任务奖励已领取,请明天再来!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 4. 验证今日Token消耗是否达标
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
|
|
|
|
|
if (todayConsumed < taskConfig.RequiredTokens)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new UserFriendlyException(
|
|
|
|
|
|
$"Token消耗未达标!需要 {taskConfig.RequiredTokens / 10000}w,当前 {todayConsumed / 10000}w");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 5. 创建奖励包
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var premiumPackage =
|
|
|
|
|
|
new PremiumPackageAggregateRoot(userId, taskConfig.RewardTokens, $"每日任务:{taskConfig.Name}")
|
|
|
|
|
|
{
|
2026-01-06 22:13:18 +08:00
|
|
|
|
PurchaseAmount = 0,
|
2025-10-18 17:34:46 +08:00
|
|
|
|
Remark = $"{today:yyyy-MM-dd} 每日任务奖励"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await _premiumPackageRepository.InsertAsync(premiumPackage);
|
|
|
|
|
|
|
2026-01-06 22:13:18 +08:00
|
|
|
|
// 6. 记录领取记录
|
2025-10-18 17:34:46 +08:00
|
|
|
|
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 表示消耗)
|
2026-01-01 00:44:02 +08:00
|
|
|
|
// 先获取所有尊享模型的ModelId列表
|
|
|
|
|
|
var premiumModelIds = await _aiModelRepository._DbQueryable
|
|
|
|
|
|
.Where(x => x.IsPremium)
|
|
|
|
|
|
.Select(x => x.ModelId)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
2025-10-18 17:34:46 +08:00
|
|
|
|
var totalTokens = await _messageRepository._DbQueryable
|
|
|
|
|
|
.Where(x => x.UserId == userId)
|
|
|
|
|
|
.Where(x => x.Role == "system") // system角色表示实际消耗
|
2026-01-01 00:44:02 +08:00
|
|
|
|
.Where(x => premiumModelIds.Contains(x.ModelId)) // 尊享包模型
|
2025-10-18 17:34:46 +08:00
|
|
|
|
.Where(x => x.CreationTime >= today && x.CreationTime < tomorrow)
|
|
|
|
|
|
.SumAsync(x => x.TokenUsage.TotalTokenCount);
|
|
|
|
|
|
|
|
|
|
|
|
return totalTokens;
|
|
|
|
|
|
}
|
2026-01-06 22:13:18 +08:00
|
|
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
2025-10-18 17:34:46 +08:00
|
|
|
|
}
|