feat: 兼容claude格式
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||
|
||||
/// <summary>
|
||||
/// 模型Token统计DTO
|
||||
/// </summary>
|
||||
public class ModelTokenStatisticsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 模型ID
|
||||
/// </summary>
|
||||
public string ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型名称
|
||||
/// </summary>
|
||||
public string ModelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Token消耗量
|
||||
/// </summary>
|
||||
public long Tokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Token消耗量(万)
|
||||
/// </summary>
|
||||
public decimal TokensInWan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用次数
|
||||
/// </summary>
|
||||
public long Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成本(RMB)
|
||||
/// </summary>
|
||||
public decimal Cost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 1亿Token成本(RMB)
|
||||
/// </summary>
|
||||
public decimal CostPerHundredMillion { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||
|
||||
/// <summary>
|
||||
/// 利润统计输入
|
||||
/// </summary>
|
||||
public class ProfitStatisticsInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前成本(RMB)
|
||||
/// </summary>
|
||||
public decimal CurrentCost { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||
|
||||
/// <summary>
|
||||
/// 利润统计输出
|
||||
/// </summary>
|
||||
public class ProfitStatisticsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 日期
|
||||
/// </summary>
|
||||
public string Date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尊享包已消耗Token数(单位:个)
|
||||
/// </summary>
|
||||
public long TotalUsedTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尊享包已消耗Token数(单位:亿)
|
||||
/// </summary>
|
||||
public decimal TotalUsedTokensInHundredMillion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尊享包剩余库存Token数(单位:个)
|
||||
/// </summary>
|
||||
public long TotalRemainingTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尊享包剩余库存Token数(单位:亿)
|
||||
/// </summary>
|
||||
public decimal TotalRemainingTokensInHundredMillion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前成本(RMB)
|
||||
/// </summary>
|
||||
public decimal CurrentCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 1亿Token成本(RMB)
|
||||
/// </summary>
|
||||
public decimal CostPerHundredMillion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总成本(RMB)
|
||||
/// </summary>
|
||||
public decimal TotalCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总收益(RMB)
|
||||
/// </summary>
|
||||
public decimal TotalRevenue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 利润率(%)
|
||||
/// </summary>
|
||||
public decimal ProfitRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按200售价计算的成本(RMB)
|
||||
/// </summary>
|
||||
public decimal CostAt200Price { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||
|
||||
/// <summary>
|
||||
/// Token统计输入
|
||||
/// </summary>
|
||||
public class TokenStatisticsInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定日期(当天零点)
|
||||
/// </summary>
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||
|
||||
/// <summary>
|
||||
/// Token统计输出
|
||||
/// </summary>
|
||||
public class TokenStatisticsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 日期
|
||||
/// </summary>
|
||||
public string Date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型统计列表
|
||||
/// </summary>
|
||||
public List<ModelTokenStatisticsDto> ModelStatistics { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||
|
||||
/// <summary>
|
||||
/// 系统使用量统计服务接口
|
||||
/// </summary>
|
||||
public interface ISystemUsageStatisticsService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取利润统计数据
|
||||
/// </summary>
|
||||
Task<ProfitStatisticsOutput> GetProfitStatisticsAsync(ProfitStatisticsInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定日期各模型Token统计
|
||||
/// </summary>
|
||||
Task<TokenStatisticsOutput> GetTokenStatisticsAsync(TokenStatisticsInput input);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user