From 46bc48d1c139e7f2d29b6592020dc1b16b73b08a Mon Sep 17 00:00:00 2001 From: chenchun Date: Thu, 25 Dec 2025 18:01:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=97=A5=E6=9C=9F=E5=90=84=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?Token=E7=BB=9F=E8=AE=A1=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 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。 --- .../Services/AiAccountService.cs | 97 ++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs index 3d4daf5c..83833ebc 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs @@ -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 _userRepository; private ISqlSugarRepository _rechargeRepository; private ISqlSugarRepository _premiumPackageRepository; - + private ISqlSugarRepository _messageRepository; public AiAccountService( IAccountService accountService, ISqlSugarRepository userRepository, ISqlSugarRepository rechargeRepository, - ISqlSugarRepository premiumPackageRepository) + ISqlSugarRepository premiumPackageRepository, ISqlSugarRepository messageRepository) { _accountService = accountService; _userRepository = userRepository; _rechargeRepository = rechargeRepository; _premiumPackageRepository = premiumPackageRepository; + _messageRepository = messageRepository; } /// @@ -148,4 +153,90 @@ public class AiAccountService : ApplicationService return result; } -} \ No newline at end of file + + public class TokenStatisticsInput + { + /// + /// 指定日期(当天零点) + /// + public DateTime Date { get; set; } + + /// + /// 模型Id -> 1亿Token成本(RMB) + /// + public Dictionary ModelCosts { get; set; } = new(); + } + + /// + /// 获取指定日期各模型Token统计 + /// + [Authorize] + [HttpPost("account/token-statistics")] + public async Task 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(); + } +}