feat: 完成openapi改造

This commit is contained in:
ccnetcore
2025-07-03 22:31:39 +08:00
parent 0a0e0bca10
commit 15be047371
8 changed files with 238 additions and 10 deletions

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -8,10 +9,13 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using OpenAI.Chat; using OpenAI.Chat;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAiDto; using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAiDto;
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Dtos; using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services; namespace Yi.Framework.AiHub.Application.Services;
@@ -19,18 +23,34 @@ public class OpenApiService : ApplicationService
{ {
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<OpenApiService> _logger; private readonly ILogger<OpenApiService> _logger;
private readonly TokenManager _tokenManager;
private readonly AiMessageManager _aiMessageManager;
private readonly UsageStatisticsManager _usageStatisticsManager;
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger) public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
TokenManager tokenManager, AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager)
{ {
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_logger = logger; _logger = logger;
_tokenManager = tokenManager;
_aiMessageManager = aiMessageManager;
_usageStatisticsManager = usageStatisticsManager;
} }
/// <summary>
/// 对话
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("/v1/chat/completions")] [HttpPost("/v1/chat/completions")]
public async Task ChatCompletionsAsync(ChatCompletionsInput input, CancellationToken cancellationToken) public async Task ChatCompletionsAsync(ChatCompletionsInput input, CancellationToken cancellationToken)
{ {
//前面都是校验,后面才是真正的调用 //前面都是校验,后面才是真正的调用
var httpContext = this._httpContextAccessor.HttpContext; var httpContext = this._httpContextAccessor.HttpContext;
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
var response = httpContext.Response; var response = httpContext.Response;
// 设置响应头,声明是 SSE 流 // 设置响应头,声明是 SSE 流
response.ContentType = "text/event-stream"; response.ContentType = "text/event-stream";
@@ -125,6 +145,26 @@ public class OpenApiService : ApplicationService
isComplete = true; isComplete = true;
await outputTask; await outputTask;
await _aiMessageManager.CreateUserMessageAsync(userId, null,
new MessageInputDto
{
Content = input.Messages.LastOrDefault()
.Content,
ModelId = input.Model,
TokenUsage = tokenUsage,
});
await _aiMessageManager.CreateSystemMessageAsync(userId, null,
new MessageInputDto
{
Content = backupSystemContent.ToString(),
ModelId = input.Model,
TokenUsage = tokenUsage
});
await _usageStatisticsManager.SetUsageAsync(userId, input.Model, tokenUsage.InputTokenCount,
tokenUsage.OutputTokenCount);
} }
private SendMessageOutputDto MapToMessage(string modelId, string content) private SendMessageOutputDto MapToMessage(string modelId, string content)
@@ -204,4 +244,18 @@ public class OpenApiService : ApplicationService
return output; return output;
} }
private string? GetTokenByHttpContext(HttpContext httpContext)
{
// 获取Authorization头
string authHeader = httpContext.Request.Headers["Authorization"];
// 检查是否有Bearer token
if (authHeader != null && authHeader.StartsWith("Bearer "))
{
return authHeader.Substring("Bearer ".Length).Trim();
}
return null;
}
} }

View File

@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
public class TokenService : ApplicationService
{
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
private readonly TokenManager _tokenManager;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tokenRepository"></param>
/// <param name="tokenManager"></param>
public TokenService(ISqlSugarRepository<TokenAggregateRoot> tokenRepository, TokenManager tokenManager)
{
_tokenRepository = tokenRepository;
_tokenManager = tokenManager;
}
/// <summary>
/// 获取token
/// </summary>
/// <returns></returns>
[Authorize]
public async Task<string?> GetAsync()
{
return await _tokenManager.GetAsync(CurrentUser.GetId());
}
/// <summary>
/// 创建token
/// </summary>
/// <exception cref="UserFriendlyException"></exception>
[Authorize]
public async Task CreateAsync()
{
if (!CurrentUser.Roles.Contains("YiXinAi-Vip") && CurrentUser.UserName != "cc")
{
throw new UserFriendlyException("充值成为Vip畅想第三方token服务");
}
await _tokenManager.CreateAsync(CurrentUser.GetId());
}
}

View File

@@ -0,0 +1,7 @@
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
public enum MessageTypeEnum
{
Web = 1,
Api = 2
}

View File

@@ -7,7 +7,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Consts\" /> <Folder Include="Consts\" />
<Folder Include="Enums\" />
<Folder Include="Etos\" /> <Folder Include="Etos\" />
</ItemGroup> </ItemGroup>

View File

@@ -3,6 +3,7 @@ using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Yi.Framework.AiHub.Domain.Entities.ValueObjects; using Yi.Framework.AiHub.Domain.Entities.ValueObjects;
using Yi.Framework.AiHub.Domain.Shared.Dtos; using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Domain.Entities.Chat; namespace Yi.Framework.AiHub.Domain.Entities.Chat;
@@ -17,7 +18,7 @@ public class MessageAggregateRoot : FullAuditedAggregateRoot<Guid>
{ {
} }
public MessageAggregateRoot(Guid userId, Guid sessionId, string content, string role, string modelId, public MessageAggregateRoot(Guid userId, Guid? sessionId, string content, string role, string modelId,
TokenUsage? tokenUsage) TokenUsage? tokenUsage)
{ {
UserId = userId; UserId = userId;
@@ -27,13 +28,14 @@ public class MessageAggregateRoot : FullAuditedAggregateRoot<Guid>
ModelId = modelId; ModelId = modelId;
if (tokenUsage is not null) if (tokenUsage is not null)
{ {
this.TokenUsage = tokenUsage.Adapt<TokenUsageValueObject>(); this.TokenUsage = tokenUsage.Adapt<TokenUsageValueObject>();
} }
this.MessageType = sessionId is null ? MessageTypeEnum.Api : MessageTypeEnum.Web;
} }
public Guid UserId { get; set; } public Guid UserId { get; set; }
public Guid SessionId { get; set; } public Guid? SessionId { get; set; }
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)] [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string Content { get; set; } public string Content { get; set; }
@@ -42,7 +44,7 @@ public class MessageAggregateRoot : FullAuditedAggregateRoot<Guid>
public string ModelId { get; set; } public string ModelId { get; set; }
public string? Remark { get; set; } public string? Remark { get; set; }
[SugarColumn(IsOwnsOne = true)] [SugarColumn(IsOwnsOne = true)] public TokenUsageValueObject TokenUsage { get; set; } = new TokenUsageValueObject();
public TokenUsageValueObject TokenUsage { get; set; }= new TokenUsageValueObject();
public MessageTypeEnum MessageType { get; set; }
} }

View File

@@ -0,0 +1,52 @@
using System.Text;
using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing;
namespace Yi.Framework.AiHub.Domain.Entities.OpenApi;
[SugarTable("Ai_Token")]
public class TokenAggregateRoot : FullAuditedAggregateRoot<Guid>
{
public TokenAggregateRoot()
{
}
public TokenAggregateRoot(Guid userId)
{
this.UserId = userId;
this.Token = GenerateToken();
}
public string Token { get; set; }
public Guid UserId { get; set; }
/// <summary>
/// 重置token
/// </summary>
public void ResetToken()
{
this.Token = GenerateToken();
}
private string GenerateToken(int length = 36)
{
// 定义可能的字符集:大写字母、小写字母和数字
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// 创建随机数生成器
Random random = new Random();
// 使用StringBuilder高效构建字符串
StringBuilder sb = new StringBuilder(length);
// 生成指定长度的随机字符串
for (int i = 0; i < length; i++)
{
// 从字符集中随机选择一个字符
int index = random.Next(chars.Length);
sb.Append(chars[index]);
}
return "yi-" + sb.ToString();
}
}

View File

@@ -23,7 +23,7 @@ public class AiMessageManager : DomainService
/// <param name="userId"></param> /// <param name="userId"></param>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
public async Task CreateSystemMessageAsync(Guid userId, Guid sessionId, MessageInputDto input) public async Task CreateSystemMessageAsync(Guid userId, Guid? sessionId, MessageInputDto input)
{ {
input.Role = "system"; input.Role = "system";
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId,input.TokenUsage); var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId,input.TokenUsage);
@@ -37,7 +37,7 @@ public class AiMessageManager : DomainService
/// <param name="userId"></param> /// <param name="userId"></param>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
public async Task CreateUserMessageAsync(Guid userId, Guid sessionId, MessageInputDto input) public async Task CreateUserMessageAsync(Guid userId, Guid? sessionId, MessageInputDto input)
{ {
input.Role = "user"; input.Role = "user";
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId,input.TokenUsage); var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId,input.TokenUsage);

View File

@@ -0,0 +1,64 @@
using Volo.Abp.Domain.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Domain.Managers;
public class TokenManager : DomainService
{
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
public TokenManager(ISqlSugarRepository<TokenAggregateRoot> tokenRepository)
{
_tokenRepository = tokenRepository;
}
public async Task<string?> GetAsync(Guid userId)
{
var entity = await _tokenRepository._DbQueryable.FirstAsync(x => x.UserId == userId);
if (entity is not null)
{
return entity.Token;
}
else
{
return null;
}
}
public async Task CreateAsync(Guid userId)
{
var entity = await _tokenRepository._DbQueryable.FirstAsync(x => x.UserId == userId);
if (entity is not null)
{
entity.ResetToken();
await _tokenRepository.UpdateAsync(entity);
}
else
{
var token = new TokenAggregateRoot(userId);
await _tokenRepository.InsertAsync(token);
}
}
public async Task<Guid> GetUserIdAsync(string? token)
{
if (token is null)
{
throw new UserFriendlyException("当前请求未包含token", "401");
}
if (token.StartsWith("yi-"))
{
var entity = await _tokenRepository._DbQueryable.Where(x => x.Token == token).FirstAsync();
if (entity is null)
{
throw new UserFriendlyException("当前请求token无效", "401");
}
return entity.UserId;
}
throw new UserFriendlyException("当前请求token非法", "401");
}
}