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.Entities.Model; using Yi.Framework.AiHub.Domain.Entities.OpenApi; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; 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; private readonly ISqlSugarRepository _tokenRepository; private readonly ModelManager _modelManager; public UsageStatisticsService( ISqlSugarRepository messageRepository, ISqlSugarRepository usageStatisticsRepository, ISqlSugarRepository premiumPackageRepository, ISqlSugarRepository tokenRepository, ModelManager modelManager) { _messageRepository = messageRepository; _usageStatisticsRepository = usageStatisticsRepository; _premiumPackageRepository = premiumPackageRepository; _tokenRepository = tokenRepository; _modelManager = modelManager; } /// /// 获取当前用户近7天的Token消耗统计 /// /// 每日Token使用量列表 public async Task> GetLast7DaysTokenUsageAsync([FromQuery]UsageStatisticsGetInput input) { 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 == "system") .Where(x => x.CreationTime >= startDate && x.CreationTime < endDate.AddDays(1)) .WhereIF(input.TokenId.HasValue,x => x.TokenId == input.TokenId) .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([FromQuery]UsageStatisticsGetInput input) { var userId = CurrentUser.GetId(); // 从UsageStatistics表获取各模型的token消耗统计(按ModelId聚合,因为同一模型可能有多个TokenId的记录) var modelUsages = await _usageStatisticsRepository._DbQueryable .Where(x => x.UserId == userId) .WhereIF(input.TokenId.HasValue,x => x.TokenId == input.TokenId) .GroupBy(x => x.ModelId) .Select(x => new { ModelId = x.ModelId, TotalTokenCount = SqlFunc.AggregateSum(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>()); } /// /// 获取当前用户尊享包不同Token用量占比(饼图) /// /// 各Token的尊享模型用量及占比 [HttpGet("usage-statistics/premium-token-usage/by-token")] public async Task> GetPremiumTokenUsageByTokenAsync() { var userId = CurrentUser.GetId(); // 通过ModelManager获取所有尊享模型的ModelId列表 var premiumModelIds = await _modelManager.GetPremiumModelIdsAsync(); // 从UsageStatistics表获取尊享模型的token消耗统计(按TokenId聚合) var tokenUsages = await _usageStatisticsRepository._DbQueryable .Where(x => x.UserId == userId && premiumModelIds.Contains(x.ModelId)) .GroupBy(x => x.TokenId) .Select(x => new { TokenId = x.TokenId, TotalTokenCount = SqlFunc.AggregateSum(x.TotalTokenCount) }) .ToListAsync(); if (!tokenUsages.Any()) { return new List(); } // 获取用户的所有Token信息用于名称映射 var tokenIds = tokenUsages.Select(x => x.TokenId).ToList(); var tokens = await _tokenRepository._DbQueryable .Where(x => x.UserId == userId && tokenIds.Contains(x.Id)) .Select(x => new { x.Id, x.Name }) .ToListAsync(); var tokenNameDict = tokens.ToDictionary(x => x.Id, x => x.Name); // 计算总token数 var totalTokens = tokenUsages.Sum(x => x.TotalTokenCount); // 计算各Token占比 var result = tokenUsages.Select(x => new TokenPremiumUsageDto { TokenId = x.TokenId, TokenName = x.TokenId == Guid.Empty ? "默认" : (tokenNameDict.TryGetValue(x.TokenId, out var name) ? name : "其他"), Tokens = x.TotalTokenCount, Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0 }).OrderByDescending(x => x.Tokens).ToList(); return result; } /// /// 获取当前用户近24小时每小时Token消耗统计(柱状图) /// /// 每小时Token使用量列表,包含各模型堆叠数据 public async Task> GetLast24HoursTokenUsageAsync([FromQuery]UsageStatisticsGetInput input) { var userId = CurrentUser.GetId(); var now = DateTime.Now; var startTime = now.AddHours(-23); // 滚动24小时,从23小时前到现在 var startHour = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); // 从Message表查询近24小时的数据,只选择需要的字段 var messages = await _messageRepository._DbQueryable .Where(x => x.UserId == userId) .Where(x => x.Role == "system") .Where(x => x.CreationTime >= startHour) .WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId) .Select(x => new { x.CreationTime, x.ModelId, x.TokenUsage.TotalTokenCount }) .ToListAsync(); // 在内存中按小时和模型分组统计 var hourlyGrouped = messages .GroupBy(x => new { Hour = new DateTime(x.CreationTime.Year, x.CreationTime.Month, x.CreationTime.Day, x.CreationTime.Hour, 0, 0), x.ModelId }) .Select(g => new { g.Key.Hour, g.Key.ModelId, Tokens = g.Sum(x => x.TotalTokenCount) }) .ToList(); // 生成完整的24小时数据 var result = new List(); for (int i = 0; i < 24; i++) { var hour = startHour.AddHours(i); var hourData = hourlyGrouped.Where(x => x.Hour == hour).ToList(); var modelBreakdown = hourData.Select(x => new ModelTokenBreakdownDto { ModelId = x.ModelId, Tokens = x.Tokens }).ToList(); result.Add(new HourlyTokenUsageDto { Hour = hour, TotalTokens = modelBreakdown.Sum(x => x.Tokens), ModelBreakdown = modelBreakdown }); } return result; } /// /// 获取当前用户今日各模型使用量统计(卡片列表) /// /// 模型今日使用量列表,包含使用次数和总Token public async Task> GetTodayModelUsageAsync([FromQuery]UsageStatisticsGetInput input) { var userId = CurrentUser.GetId(); var todayStart = DateTime.Today; // 今天凌晨0点 var tomorrowStart = todayStart.AddDays(1); // 从Message表查询今天的数据,只选择需要的字段 var messages = await _messageRepository._DbQueryable .Where(x => x.UserId == userId) .Where(x => x.Role == "system") .Where(x => x.CreationTime >= todayStart && x.CreationTime < tomorrowStart) .WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId) .Select(x => new { x.ModelId, x.TokenUsage.TotalTokenCount }) .ToListAsync(); // 在内存中按模型分组统计 var modelStats = messages .GroupBy(x => x.ModelId) .Select(g => new ModelTodayUsageDto { ModelId = g.Key, UsageCount = g.Count(), TotalTokens = g.Sum(x => x.TotalTokenCount) }) .OrderByDescending(x => x.TotalTokens) .ToList(); return modelStats; } }