feat: 新增获取指定日期各模型Token统计接口

- 在 AiAccountService 中新增 TokenStatisticsInput DTO 与 POST /account/token-statistics 接口(GetTokenStatisticsAsync),用于按模型统计指定日期的 token 使用量、调用次数并计算成本,返回文本摘要。
- 注入 MessageAggregateRoot 仓储(_messageRepository),使用 SqlSugar 聚合查询(Sum/Count),按 modelId 与日期范围过滤,并只统计 role == "system" 的记录。
- 成本计算逻辑:根据输入的模型 1 亿 token 成本与实际 token 数计算每 1 亿 token 成本;同时输出调用次数与 token(单位万)。
- 接口权限与入参校验:仅允许 CurrentUser.UserName 为 "Guo" 或 "cc" 访问;必须提供 ModelCosts 配置。
- 添加的引用:SqlSugar、System.Globalization、System.Text、Yi.Framework.AiHub.Domain.Entities.Chat。
This commit is contained in:
chenchun
2025-12-25 18:01:13 +08:00
parent 17675e702d
commit 46bc48d1c1

View File

@@ -1,10 +1,14 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using System.Globalization;
using System.Text;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -18,17 +22,18 @@ public class AiAccountService : ApplicationService
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
private ISqlSugarRepository<AiRechargeAggregateRoot> _rechargeRepository;
private ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
private ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
public AiAccountService(
IAccountService accountService,
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository,
ISqlSugarRepository<AiRechargeAggregateRoot> rechargeRepository,
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository)
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository, ISqlSugarRepository<MessageAggregateRoot> messageRepository)
{
_accountService = accountService;
_userRepository = userRepository;
_rechargeRepository = rechargeRepository;
_premiumPackageRepository = premiumPackageRepository;
_messageRepository = messageRepository;
}
/// <summary>
@@ -148,4 +153,90 @@ public class AiAccountService : ApplicationService
return result;
}
}
public class TokenStatisticsInput
{
/// <summary>
/// 指定日期(当天零点)
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// 模型Id -> 1亿Token成本(RMB)
/// </summary>
public Dictionary<string, decimal> ModelCosts { get; set; } = new();
}
/// <summary>
/// 获取指定日期各模型Token统计
/// </summary>
[Authorize]
[HttpPost("account/token-statistics")]
public async Task<string> GetTokenStatisticsAsync([FromBody] TokenStatisticsInput input)
{
if (CurrentUser.UserName != "Guo" && CurrentUser.UserName != "cc")
{
throw new UserFriendlyException("您暂无权限访问");
}
if (input.ModelCosts is null || input.ModelCosts.Count == 0)
{
throw new UserFriendlyException("请提供模型成本配置");
}
var day = input.Date.Date;
var nextDay = day.AddDays(1);
var modelIds = input.ModelCosts.Keys.ToList();
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);
string weekDay = day.ToString("dddd", new CultureInfo("zh-CN")) switch
{
"星期一" => "周1",
"星期二" => "周2",
"星期三" => "周3",
"星期四" => "周4",
"星期五" => "周5",
"星期六" => "周6",
"星期日" => "周日",
_ => day.ToString("dddd", new CultureInfo("zh-CN"))
};
var sb = new StringBuilder();
sb.AppendLine($"{day:M月d日} {weekDay}");
foreach (var kvp in input.ModelCosts)
{
var modelId = kvp.Key;
var cost = kvp.Value;
modelStatDict.TryGetValue(modelId, out var stat);
long tokens = stat?.Tokens ?? 0;
long count = stat?.Count ?? 0;
decimal costPerHundredMillion = tokens > 0
? cost / (tokens / 100000000m)
: 0;
decimal tokensInWan = tokens / 10000m;
sb.AppendLine();
sb.AppendLine($"{modelId} 成本:【{cost:F2}RMB】 次数:【{count}次】 token【{tokensInWan:F0}w】 1亿token成本【{costPerHundredMillion:F2}RMB】");
}
return sb.ToString().TrimEnd();
}
}