203 lines
7.4 KiB
C#
203 lines
7.4 KiB
C#
using Mapster;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using SqlSugar;
|
|
using System.Globalization;
|
|
using Volo.Abp.Application.Services;
|
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
|
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.SqlSugarCore.Abstractions;
|
|
|
|
namespace Yi.Framework.AiHub.Application.Services;
|
|
|
|
/// <summary>
|
|
/// 系统使用量统计服务实现
|
|
/// </summary>
|
|
[Authorize(Roles = "admin")]
|
|
public class SystemUsageStatisticsService : ApplicationService, ISystemUsageStatisticsService
|
|
{
|
|
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
|
private readonly ISqlSugarRepository<AiRechargeAggregateRoot> _rechargeRepository;
|
|
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
|
private readonly ISqlSugarRepository<AiModelEntity, Guid> _modelRepository;
|
|
|
|
public SystemUsageStatisticsService(
|
|
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
|
ISqlSugarRepository<AiRechargeAggregateRoot> rechargeRepository,
|
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
|
ISqlSugarRepository<AiModelEntity, Guid> modelRepository)
|
|
{
|
|
_premiumPackageRepository = premiumPackageRepository;
|
|
_rechargeRepository = rechargeRepository;
|
|
_messageRepository = messageRepository;
|
|
_modelRepository = modelRepository;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取利润统计数据
|
|
/// </summary>
|
|
[HttpPost("system-statistics/profit")]
|
|
public async Task<ProfitStatisticsOutput> GetProfitStatisticsAsync(ProfitStatisticsInput input)
|
|
{
|
|
// 1. 获取尊享包总消耗和剩余库存
|
|
var premiumPackages = await _premiumPackageRepository._DbQueryable.ToListAsync();
|
|
long totalUsedTokens = premiumPackages.Sum(p => p.UsedTokens);
|
|
long totalRemainingTokens = premiumPackages.Sum(p => p.RemainingTokens);
|
|
|
|
// 2. 计算1亿Token成本
|
|
decimal costPerHundredMillion = totalUsedTokens > 0
|
|
? input.CurrentCost / (totalUsedTokens / 100000000m)
|
|
: 0;
|
|
|
|
// 3. 计算总成本(剩余+已使用的总成本)
|
|
long totalTokens = totalUsedTokens + totalRemainingTokens;
|
|
decimal totalCost = totalTokens > 0
|
|
? (totalTokens / 100000000m) * costPerHundredMillion
|
|
: 0;
|
|
|
|
// 4. 获取总收益(RechargeType=PremiumPackage的充值金额总和)
|
|
decimal totalRevenue = await _rechargeRepository._DbQueryable
|
|
.Where(x => x.RechargeType == Domain.Shared.Enums.RechargeTypeEnum.PremiumPackage)
|
|
.SumAsync(x => x.RechargeAmount);
|
|
|
|
// 5. 计算利润率
|
|
decimal profitRate = totalCost > 0
|
|
? (totalRevenue / totalCost - 1) * 100
|
|
: 0;
|
|
|
|
// 6. 按200售价计算成本
|
|
decimal costAt200Price = totalRevenue > 0
|
|
? (totalCost / totalRevenue) * 200
|
|
: 0;
|
|
|
|
// 7. 格式化日期
|
|
var today = DateTime.Now;
|
|
string dayOfWeek = today.ToString("dddd", new CultureInfo("zh-CN"));
|
|
string weekDay = dayOfWeek switch
|
|
{
|
|
"星期一" => "周1",
|
|
"星期二" => "周2",
|
|
"星期三" => "周3",
|
|
"星期四" => "周4",
|
|
"星期五" => "周5",
|
|
"星期六" => "周6",
|
|
"星期日" => "周日",
|
|
_ => dayOfWeek
|
|
};
|
|
|
|
return new ProfitStatisticsOutput
|
|
{
|
|
Date = $"{today:M月d日} {weekDay}",
|
|
TotalUsedTokens = totalUsedTokens,
|
|
TotalUsedTokensInHundredMillion = totalUsedTokens / 100000000m,
|
|
TotalRemainingTokens = totalRemainingTokens,
|
|
TotalRemainingTokensInHundredMillion = totalRemainingTokens / 100000000m,
|
|
CurrentCost = input.CurrentCost,
|
|
CostPerHundredMillion = costPerHundredMillion,
|
|
TotalCost = totalCost,
|
|
TotalRevenue = totalRevenue,
|
|
ProfitRate = profitRate,
|
|
CostAt200Price = costAt200Price
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取指定日期各模型Token统计
|
|
/// </summary>
|
|
[HttpPost("system-statistics/token")]
|
|
public async Task<TokenStatisticsOutput> GetTokenStatisticsAsync(TokenStatisticsInput input)
|
|
{
|
|
var day = input.Date.Date;
|
|
var nextDay = day.AddDays(1);
|
|
|
|
// 1. 获取所有尊享模型(包含被禁用的),按ModelId去重
|
|
var premiumModels = await _modelRepository._DbQueryable
|
|
.Where(x => x.IsPremium)
|
|
.ToListAsync();
|
|
|
|
if (premiumModels.Count == 0)
|
|
{
|
|
return new TokenStatisticsOutput
|
|
{
|
|
Date = FormatDate(day),
|
|
ModelStatistics = new List<ModelTokenStatisticsDto>()
|
|
};
|
|
}
|
|
|
|
// 按ModelId去重,保留第一个模型的名称
|
|
var distinctModels = premiumModels
|
|
.GroupBy(x => x.ModelId)
|
|
.Select(g => g.First())
|
|
.ToList();
|
|
|
|
var modelIds = distinctModels.Select(x => x.ModelId).ToList();
|
|
|
|
// 2. 查询指定日期内各模型的Token使用统计
|
|
var modelStats = await _messageRepository._DbQueryable
|
|
.Where(x => modelIds.Contains(x.ModelId))
|
|
.Where(x => x.CreationTime >= day && x.CreationTime < nextDay)
|
|
.Where(x => x.Role == "system")
|
|
.GroupBy(x => x.ModelId)
|
|
.Select(x => new
|
|
{
|
|
ModelId = x.ModelId,
|
|
Tokens = SqlFunc.AggregateSum(x.TokenUsage.TotalTokenCount),
|
|
Count = SqlFunc.AggregateCount(x.Id)
|
|
})
|
|
.ToListAsync();
|
|
|
|
var modelStatDict = modelStats.ToDictionary(x => x.ModelId, x => x);
|
|
|
|
// 3. 构建结果列表,使用去重后的模型列表
|
|
var result = new List<ModelTokenStatisticsDto>();
|
|
foreach (var model in distinctModels)
|
|
{
|
|
modelStatDict.TryGetValue(model.ModelId, out var stat);
|
|
long tokens = stat?.Tokens ?? 0;
|
|
long count = stat?.Count ?? 0;
|
|
|
|
// 这里成本设为0,因为需要前端传入或者从配置中获取
|
|
decimal cost = 0;
|
|
decimal costPerHundredMillion = tokens > 0 && cost > 0
|
|
? cost / (tokens / 100000000m)
|
|
: 0;
|
|
|
|
result.Add(new ModelTokenStatisticsDto
|
|
{
|
|
ModelId = model.ModelId,
|
|
ModelName = model.Name,
|
|
Tokens = tokens,
|
|
TokensInWan = tokens / 10000m,
|
|
Count = count,
|
|
Cost = cost,
|
|
CostPerHundredMillion = costPerHundredMillion
|
|
});
|
|
}
|
|
|
|
return new TokenStatisticsOutput
|
|
{
|
|
Date = FormatDate(day),
|
|
ModelStatistics = result
|
|
};
|
|
}
|
|
|
|
private string FormatDate(DateTime date)
|
|
{
|
|
string dayOfWeek = date.ToString("dddd", new CultureInfo("zh-CN"));
|
|
string weekDay = dayOfWeek switch
|
|
{
|
|
"星期一" => "周1",
|
|
"星期二" => "周2",
|
|
"星期三" => "周3",
|
|
"星期四" => "周4",
|
|
"星期五" => "周5",
|
|
"星期六" => "周6",
|
|
"星期日" => "周日",
|
|
_ => dayOfWeek
|
|
};
|
|
return $"{date:M月d日} {weekDay}";
|
|
}
|
|
} |