为 GetTodayModelUsage 接口补充模型图标数据,新增 ModelTodayUsageDto.IconUrl 字段 通过 ModelManager 查询已启用模型的 IconUrl 并映射到结果中 同时统一部分代码格式,提升可读性
354 lines
14 KiB
C#
354 lines
14 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 使用量统计服务
|
||
/// </summary>
|
||
[Authorize]
|
||
public class UsageStatisticsService : ApplicationService, IUsageStatisticsService
|
||
{
|
||
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
||
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository;
|
||
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
|
||
private readonly ModelManager _modelManager;
|
||
|
||
public UsageStatisticsService(
|
||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository,
|
||
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
||
ISqlSugarRepository<TokenAggregateRoot> tokenRepository,
|
||
ModelManager modelManager)
|
||
{
|
||
_messageRepository = messageRepository;
|
||
_usageStatisticsRepository = usageStatisticsRepository;
|
||
_premiumPackageRepository = premiumPackageRepository;
|
||
_tokenRepository = tokenRepository;
|
||
_modelManager = modelManager;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户近7天的Token消耗统计
|
||
/// </summary>
|
||
/// <returns>每日Token使用量列表</returns>
|
||
public async Task<List<DailyTokenUsageDto>> 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<DailyTokenUsageDto>();
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户各个模型的Token消耗量及占比
|
||
/// </summary>
|
||
/// <returns>模型Token使用量列表</returns>
|
||
public async Task<List<ModelTokenUsageDto>> 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<ModelTokenUsageDto>();
|
||
}
|
||
|
||
// 计算总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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户尊享服务Token用量统计
|
||
/// </summary>
|
||
/// <returns>尊享服务Token用量统计</returns>
|
||
public async Task<PremiumTokenUsageDto> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户尊享服务token用量统计列表
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet("usage-statistics/premium-token-usage/list")]
|
||
public async Task<PagedResultDto<PremiumTokenUsageGetListOutput>> GetPremiumTokenUsageListAsync(
|
||
PremiumTokenUsageGetListInput input)
|
||
{
|
||
var userId = CurrentUser.GetId();
|
||
RefAsync<int> 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<PremiumTokenUsageGetListOutput>(total,
|
||
entities.Adapt<List<PremiumTokenUsageGetListOutput>>());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户尊享包不同Token用量占比(饼图)
|
||
/// </summary>
|
||
/// <returns>各Token的尊享模型用量及占比</returns>
|
||
[HttpGet("usage-statistics/premium-token-usage/by-token")]
|
||
public async Task<List<TokenPremiumUsageDto>> 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<TokenPremiumUsageDto>();
|
||
}
|
||
|
||
// 获取用户的所有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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户近24小时每小时Token消耗统计(柱状图)
|
||
/// </summary>
|
||
/// <returns>每小时Token使用量列表,包含各模型堆叠数据</returns>
|
||
public async Task<List<HourlyTokenUsageDto>> 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<HourlyTokenUsageDto>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前用户今日各模型使用量统计(卡片列表)
|
||
/// </summary>
|
||
/// <returns>模型今日使用量列表,包含使用次数和总Token</returns>
|
||
public async Task<List<ModelTodayUsageDto>> 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();
|
||
|
||
if (modelStats.Count > 0)
|
||
{
|
||
var modelIds = modelStats.Select(x => x.ModelId).ToList();
|
||
var modelDic = await _modelManager._aiModelRepository._DbQueryable.Where(x => modelIds.Contains(x.ModelId))
|
||
.Distinct()
|
||
.Where(x=>x.IsEnabled)
|
||
.ToDictionaryAsync<string>(x => x.ModelId, y => y.IconUrl);
|
||
modelStats.ForEach(x =>
|
||
{
|
||
if (modelDic.TryGetValue(x.ModelId, out var icon))
|
||
{
|
||
x.IconUrl = icon;
|
||
}
|
||
});
|
||
}
|
||
|
||
return modelStats;
|
||
}
|
||
} |