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; using Yi.Framework.AiHub.Domain.Extensions; namespace Yi.Framework.AiHub.Application.Services; public class AiAccountService : ApplicationService { private IAccountService _accountService; private ISqlSugarRepository _userRepository; private ISqlSugarRepository _rechargeRepository; private ISqlSugarRepository _premiumPackageRepository; private ISqlSugarRepository _messageRepository; public AiAccountService( IAccountService accountService, ISqlSugarRepository userRepository, ISqlSugarRepository rechargeRepository, ISqlSugarRepository premiumPackageRepository, ISqlSugarRepository messageRepository) { _accountService = accountService; _userRepository = userRepository; _rechargeRepository = rechargeRepository; _premiumPackageRepository = premiumPackageRepository; _messageRepository = messageRepository; } /// /// 获取ai用户信息 /// /// [Authorize] [HttpGet("account/ai")] public async Task GetAsync() { var userId = CurrentUser.GetId(); var userAccount = await _accountService.GetAsync(null, null, userId: CurrentUser.GetId()); var output = userAccount.Adapt(); // 是否绑定服务号 output.IsBindFuwuhao = await _userRepository.IsAnyAsync(x => userId == x.UserId); // 是否为VIP用户 output.IsVip = CurrentUser.IsAiVip(); // 获取VIP到期时间 if (output.IsVip) { var recharges = await _rechargeRepository._DbQueryable .Where(x => x.UserId == userId) .ToListAsync(); if (recharges.Any()) { // 如果有任何一个充值记录的过期时间为null,说明是永久VIP if (recharges.Any(x => !x.ExpireDateTime.HasValue)) { output.VipExpireTime = null; // 永久VIP } else { // 取最大的过期时间 output.VipExpireTime = recharges .Where(x => x.ExpireDateTime.HasValue) .Max(x => x.ExpireDateTime); } } } return output; } /// /// 获取利润统计数据 /// /// 当前成本(RMB) /// [Authorize] [HttpGet("account/profit-statistics")] public async Task GetProfitStatisticsAsync([FromQuery] decimal currentCost) { if (CurrentUser.UserName != "Guo" && CurrentUser.UserName != "cc") { throw new UserFriendlyException("您暂无权限访问"); } // 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 ? 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 System.Globalization.CultureInfo("zh-CN")); string weekDay = dayOfWeek switch { "星期一" => "周1", "星期二" => "周2", "星期三" => "周3", "星期四" => "周4", "星期五" => "周5", "星期六" => "周6", "星期日" => "周日", _ => dayOfWeek }; var result = $@"{today:M月d日} {weekDay} 尊享包已消耗({totalUsedTokens / 100000000m:F2}亿){totalUsedTokens} 尊享包剩余库存({totalRemainingTokens / 100000000m:F2}亿){totalRemainingTokens} 当前成本:{currentCost:F2}RMB 1亿Token成本:{costPerHundredMillion:F2} RMB=1亿 Token 总成本:{totalCost:F2} RMB 总收益:{totalRevenue:F2}RMB 利润率: {profitRate:F1}% 按200售价来算,成本在{costAt200Price:F2}"; return result; } 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(); } }