using Mapster; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SqlSugar; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics; using Yi.Framework.AiHub.Application.Contracts.IServices; using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities.Chat; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.Ddd.Application.Contracts; using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services; /// /// 使用量统计服务 /// [Authorize] public class UsageStatisticsService : ApplicationService, IUsageStatisticsService { private readonly ISqlSugarRepository _messageRepository; private readonly ISqlSugarRepository _usageStatisticsRepository; private readonly ISqlSugarRepository _premiumPackageRepository; public UsageStatisticsService( ISqlSugarRepository messageRepository, ISqlSugarRepository usageStatisticsRepository, ISqlSugarRepository premiumPackageRepository) { _messageRepository = messageRepository; _usageStatisticsRepository = usageStatisticsRepository; _premiumPackageRepository = premiumPackageRepository; } /// /// 获取当前用户近7天的Token消耗统计 /// /// 每日Token使用量列表 public async Task> GetLast7DaysTokenUsageAsync() { var userId = CurrentUser.GetId(); var endDate = DateTime.Today; var startDate = endDate.AddDays(-6); // 近7天 // 从Message表统计近7天的token消耗 var dailyUsage = await _messageRepository._DbQueryable .Where(x => x.UserId == userId) .Where(x => x.Role == "assistant" || x.Role == "system") .Where(x => x.CreationTime >= startDate && x.CreationTime < endDate.AddDays(1)) .GroupBy(x => x.CreationTime.Date) .Select(g => new { Date = g.CreationTime.Date, Tokens = SqlFunc.AggregateSum(g.TokenUsage.TotalTokenCount) }) .ToListAsync(); // 生成完整的7天数据,包括没有使用记录的日期 var result = new List(); for (int i = 0; i < 7; i++) { var date = startDate.AddDays(i); var usage = dailyUsage.FirstOrDefault(x => x.Date == date); result.Add(new DailyTokenUsageDto { Date = date, Tokens = usage?.Tokens ?? 0 }); } return result.OrderBy(x => x.Date).ToList(); } /// /// 获取当前用户各个模型的Token消耗量及占比 /// /// 模型Token使用量列表 public async Task> GetModelTokenUsageAsync() { var userId = CurrentUser.GetId(); // 从UsageStatistics表获取各模型的token消耗统计 var modelUsages = await _usageStatisticsRepository._DbQueryable .Where(x => x.UserId == userId) .Select(x => new { x.ModelId, x.TotalTokenCount }) .ToListAsync(); if (!modelUsages.Any()) { return new List(); } // 计算总token数 var totalTokens = modelUsages.Sum(x => x.TotalTokenCount); // 计算各模型占比 var result = modelUsages.Select(x => new ModelTokenUsageDto { Model = x.ModelId, Tokens = x.TotalTokenCount, Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0 }).OrderByDescending(x => x.Tokens).ToList(); return result; } /// /// 获取当前用户尊享服务Token用量统计 /// /// 尊享服务Token用量统计 public async Task GetPremiumTokenUsageAsync() { var userId = CurrentUser.GetId(); // 获取尊享包Token信息 var premiumPackages = await _premiumPackageRepository._DbQueryable .Where(x => x.UserId == userId && x.IsActive) .ToListAsync(); var result = new PremiumTokenUsageDto(); if (premiumPackages.Any()) { // 过滤掉已过期、禁用的包,不过滤用量负数的包 var validPackages = premiumPackages .Where(p => p.IsAvailable(false)) .ToList(); result.PremiumTotalTokens = validPackages.Sum(x => x.TotalTokens); result.PremiumUsedTokens = validPackages.Sum(x => x.UsedTokens); result.PremiumRemainingTokens = validPackages.Sum(x => x.RemainingTokens); } return result; } /// /// 获取当前用户尊享服务token用量统计列表 /// /// [HttpGet("usage-statistics/premium-token-usage/list")] public async Task> GetPremiumTokenUsageListAsync( PremiumTokenUsageGetListInput input) { var userId = CurrentUser.GetId(); RefAsync total = 0; // 获取尊享包Token信息 var entities = await _premiumPackageRepository._DbQueryable .Where(x => x.UserId == userId) .WhereIF(input.IsFree == false, x => x.PurchaseAmount > 0) .WhereIF(input.IsFree == true, x => x.PurchaseAmount == 0) .WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) .OrderByDescending(x => x.CreationTime) .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); return new PagedResultDto(total, entities.Adapt>()); } }