feat: 完成token功能

This commit is contained in:
chenchun
2025-11-27 19:01:16 +08:00
parent 02a5f69958
commit b78ecf27d5
16 changed files with 643 additions and 128 deletions

View File

@@ -135,7 +135,7 @@ public class AiChatService : ApplicationService
//ai网关代理httpcontext
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
CurrentUser.Id, sessionId, cancellationToken);
CurrentUser.Id, sessionId, null, cancellationToken);
}
@@ -172,6 +172,6 @@ public class AiChatService : ApplicationService
//ai网关代理httpcontext
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
CurrentUser.Id, null, cancellationToken);
CurrentUser.Id, null, null, cancellationToken);
}
}

View File

@@ -1,55 +1,216 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Token;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
/// <summary>
/// Token服务
/// </summary>
[Authorize]
public class TokenService : ApplicationService
{
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
private readonly TokenManager _tokenManager;
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tokenRepository"></param>
/// <param name="tokenManager"></param>
public TokenService(ISqlSugarRepository<TokenAggregateRoot> tokenRepository, TokenManager tokenManager)
public TokenService(
ISqlSugarRepository<TokenAggregateRoot> tokenRepository,
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository)
{
_tokenRepository = tokenRepository;
_tokenManager = tokenManager;
_usageStatisticsRepository = usageStatisticsRepository;
}
/// <summary>
/// 获取token
/// 获取当前用户的Token列表
/// </summary>
/// <returns></returns>
[Authorize]
public async Task<TokenOutput> GetAsync()
[HttpGet("token/list")]
public async Task<List<TokenListOutput>> GetListAsync()
{
return new TokenOutput
var userId = CurrentUser.GetId();
var tokens = await _tokenRepository._DbQueryable
.Where(x => x.UserId == userId)
.OrderByDescending(x => x.CreationTime)
.ToListAsync();
if (!tokens.Any())
{
ApiKey = await _tokenManager.GetAsync(CurrentUser.GetId())
};
return new List<TokenListOutput>();
}
// 获取尊享包模型ID列表
var premiumModelIds = PremiumPackageConst.ModeIds;
// 批量查询所有Token的尊享包已使用额度
var tokenIds = tokens.Select(t => t.Id).ToList();
var usageStats = await _usageStatisticsRepository._DbQueryable
.Where(x => x.UserId == userId && tokenIds.Contains(x.TokenId) && premiumModelIds.Contains(x.ModelId))
.GroupBy(x => x.TokenId)
.Select(g => new
{
TokenId = g.TokenId,
UsedQuota = SqlFunc.AggregateSum(g.TotalTokenCount)
})
.ToListAsync();
var result = tokens.Select(t =>
{
var usedQuota = usageStats.FirstOrDefault(u => u.TokenId == t.Id)?.UsedQuota ?? 0;
return new TokenListOutput
{
Id = t.Id,
Name = t.Name,
ApiKey = t.Token,
ExpireTime = t.ExpireTime,
PremiumQuotaLimit = t.PremiumQuotaLimit,
PremiumUsedQuota = usedQuota,
IsDisabled = t.IsDisabled,
CreationTime = t.CreationTime
};
}).ToList();
return result;
}
/// <summary>
/// 创建token
/// 创建Token
/// </summary>
/// <exception cref="UserFriendlyException"></exception>
[Authorize]
public async Task CreateAsync()
[HttpPost("token")]
public async Task<TokenListOutput> CreateAsync([FromBody] TokenCreateInput input)
{
var userId = CurrentUser.GetId();
// 检查用户是否为VIP
if (!CurrentUser.IsAiVip())
{
throw new UserFriendlyException("充值成为Vip畅享第三方token服务");
}
await _tokenManager.CreateAsync(CurrentUser.GetId());
// 检查名称是否重复
var exists = await _tokenRepository._DbQueryable
.AnyAsync(x => x.UserId == userId && x.Name == input.Name);
if (exists)
{
throw new UserFriendlyException($"名称【{input.Name}】已存在,请使用其他名称");
}
var token = new TokenAggregateRoot(userId, input.Name)
{
ExpireTime = input.ExpireTime,
PremiumQuotaLimit = input.PremiumQuotaLimit
};
await _tokenRepository.InsertAsync(token);
return new TokenListOutput
{
Id = token.Id,
Name = token.Name,
ApiKey = token.Token,
ExpireTime = token.ExpireTime,
PremiumQuotaLimit = token.PremiumQuotaLimit,
PremiumUsedQuota = 0,
IsDisabled = token.IsDisabled,
CreationTime = token.CreationTime
};
}
}
/// <summary>
/// 编辑Token
/// </summary>
[HttpPut("token")]
public async Task UpdateAsync([FromBody] TokenUpdateInput input)
{
var userId = CurrentUser.GetId();
var token = await _tokenRepository._DbQueryable
.FirstAsync(x => x.Id == input.Id && x.UserId == userId);
if (token is null)
{
throw new UserFriendlyException("Token不存在或无权限操作");
}
// 检查名称是否重复(排除自己)
var exists = await _tokenRepository._DbQueryable
.AnyAsync(x => x.UserId == userId && x.Name == input.Name && x.Id != input.Id);
if (exists)
{
throw new UserFriendlyException($"名称【{input.Name}】已存在,请使用其他名称");
}
token.Name = input.Name;
token.ExpireTime = input.ExpireTime;
token.PremiumQuotaLimit = input.PremiumQuotaLimit;
await _tokenRepository.UpdateAsync(token);
}
/// <summary>
/// 删除Token
/// </summary>
[HttpDelete("token/{id}")]
public async Task DeleteAsync(Guid id)
{
var userId = CurrentUser.GetId();
var token = await _tokenRepository._DbQueryable
.FirstAsync(x => x.Id == id && x.UserId == userId);
if (token is null)
{
throw new UserFriendlyException("Token不存在或无权限操作");
}
await _tokenRepository.DeleteAsync(token);
}
/// <summary>
/// 启用Token
/// </summary>
[HttpPost("token/{id}/enable")]
public async Task EnableAsync(Guid id)
{
var userId = CurrentUser.GetId();
var token = await _tokenRepository._DbQueryable
.FirstAsync(x => x.Id == id && x.UserId == userId);
if (token is null)
{
throw new UserFriendlyException("Token不存在或无权限操作");
}
token.Enable();
await _tokenRepository.UpdateAsync(token);
}
/// <summary>
/// 禁用Token
/// </summary>
[HttpPost("token/{id}/disable")]
public async Task DisableAsync(Guid id)
{
var userId = CurrentUser.GetId();
var token = await _tokenRepository._DbQueryable
.FirstAsync(x => x.Id == id && x.UserId == userId);
if (token is null)
{
throw new UserFriendlyException("Token不存在或无权限操作");
}
token.Disable();
await _tokenRepository.UpdateAsync(token);
}
}

View File

@@ -76,12 +76,12 @@ public class FileMasterService : ApplicationService
if (input.Stream == true)
{
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
userId, null, cancellationToken);
userId, null, null, cancellationToken);
}
else
{
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
null,
null, null,
cancellationToken);
}
}

View File

@@ -55,7 +55,9 @@ public class OpenApiService : ApplicationService
{
//前面都是校验,后面才是真正的调用
var httpContext = this._httpContextAccessor.HttpContext;
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
var tokenValidation = await _tokenManager.ValidateTokenAsync(GetTokenByHttpContext(httpContext), input.Model);
var userId = tokenValidation.UserId;
var tokenId = tokenValidation.TokenId;
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
//如果是尊享包服务,需要校验是是否尊享包足够
@@ -68,17 +70,17 @@ public class OpenApiService : ApplicationService
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
}
}
//ai网关代理httpcontext
if (input.Stream == true)
{
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
userId, null, cancellationToken);
userId, null, tokenId, cancellationToken);
}
else
{
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
null,
null, tokenId,
cancellationToken);
}
}
@@ -93,9 +95,11 @@ public class OpenApiService : ApplicationService
{
var httpContext = this._httpContextAccessor.HttpContext;
Intercept(httpContext);
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
var tokenValidation = await _tokenManager.ValidateTokenAsync(GetTokenByHttpContext(httpContext), input.Model);
var userId = tokenValidation.UserId;
var tokenId = tokenValidation.TokenId;
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
await _aiGateWayManager.CreateImageForStatisticsAsync(httpContext, userId, null, input);
await _aiGateWayManager.CreateImageForStatisticsAsync(httpContext, userId, null, input, tokenId);
}
/// <summary>
@@ -108,9 +112,11 @@ public class OpenApiService : ApplicationService
{
var httpContext = this._httpContextAccessor.HttpContext;
Intercept(httpContext);
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
var tokenValidation = await _tokenManager.ValidateTokenAsync(GetTokenByHttpContext(httpContext), input.Model);
var userId = tokenValidation.UserId;
var tokenId = tokenValidation.TokenId;
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
await _aiGateWayManager.EmbeddingForStatisticsAsync(httpContext, userId, null, input);
await _aiGateWayManager.EmbeddingForStatisticsAsync(httpContext, userId, null, input, tokenId);
}
@@ -151,7 +157,9 @@ public class OpenApiService : ApplicationService
{
//前面都是校验,后面才是真正的调用
var httpContext = this._httpContextAccessor.HttpContext;
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
var tokenValidation = await _tokenManager.ValidateTokenAsync(GetTokenByHttpContext(httpContext), input.Model);
var userId = tokenValidation.UserId;
var tokenId = tokenValidation.TokenId;
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
// 验证用户是否为VIP
@@ -178,12 +186,12 @@ public class OpenApiService : ApplicationService
if (input.Stream)
{
await _aiGateWayManager.AnthropicCompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
userId, null, cancellationToken);
userId, null, tokenId, cancellationToken);
}
else
{
await _aiGateWayManager.AnthropicCompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
null,
null, tokenId,
cancellationToken);
}
}

View File

@@ -9,7 +9,9 @@ using Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
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.OpenApi;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.Ddd.Application.Contracts;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -24,15 +26,18 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository;
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
public UsageStatisticsService(
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository,
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository)
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
ISqlSugarRepository<TokenAggregateRoot> tokenRepository)
{
_messageRepository = messageRepository;
_usageStatisticsRepository = usageStatisticsRepository;
_premiumPackageRepository = premiumPackageRepository;
_tokenRepository = tokenRepository;
}
/// <summary>
@@ -83,13 +88,14 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
{
var userId = CurrentUser.GetId();
// 从UsageStatistics表获取各模型的token消耗统计
// 从UsageStatistics表获取各模型的token消耗统计按ModelId聚合因为同一模型可能有多个TokenId的记录
var modelUsages = await _usageStatisticsRepository._DbQueryable
.Where(x => x.UserId == userId)
.GroupBy(x => x.ModelId)
.Select(x => new
{
x.ModelId,
x.TotalTokenCount
ModelId = x.ModelId,
TotalTokenCount = SqlFunc.AggregateSum(x.TotalTokenCount)
})
.ToListAsync();
@@ -164,4 +170,54 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
return new PagedResultDto<PremiumTokenUsageGetListOutput>(total,
entities.Adapt<List<PremiumTokenUsageGetListOutput>>());
}
/// <summary>
/// 获取当前用户尊享包不同Token用量占比饼图
/// </summary>
/// <returns>各Token的尊享模型用量及占比</returns>
[HttpGet("usage-statistics/premium-token-usage/by-token")]
public async Task<List<TokenPremiumUsageDto>> GetPremiumTokenUsageByTokenAsync()
{
var userId = CurrentUser.GetId();
var premiumModelIds = PremiumPackageConst.ModeIds;
// 从UsageStatistics表获取尊享模型的token消耗统计按TokenId聚合
var tokenUsages = await _usageStatisticsRepository._DbQueryable
.Where(x => x.UserId == userId && premiumModelIds.Contains(x.ModelId))
.GroupBy(x => x.TokenId)
.Select(x => new
{
TokenId = x.TokenId,
TotalTokenCount = SqlFunc.AggregateSum(x.TotalTokenCount)
})
.ToListAsync();
if (!tokenUsages.Any())
{
return new List<TokenPremiumUsageDto>();
}
// 获取用户的所有Token信息用于名称映射
var tokenIds = tokenUsages.Select(x => x.TokenId).ToList();
var tokens = await _tokenRepository._DbQueryable
.Where(x => x.UserId == userId && tokenIds.Contains(x.Id))
.Select(x => new { x.Id, x.Name })
.ToListAsync();
var tokenNameDict = tokens.ToDictionary(x => x.Id, x => x.Name);
// 计算总token数
var totalTokens = tokenUsages.Sum(x => x.TotalTokenCount);
// 计算各Token占比
var result = tokenUsages.Select(x => new TokenPremiumUsageDto
{
TokenId = x.TokenId,
TokenName = x.TokenId == Guid.Empty ? "Web端" : (tokenNameDict.TryGetValue(x.TokenId, out var name) ? name : "未知Token"),
Tokens = x.TotalTokenCount,
Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0
}).OrderByDescending(x => x.Tokens).ToList();
return result;
}
}