feat: 完成openapi改造
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
public enum MessageTypeEnum
|
||||||
|
{
|
||||||
|
Web = 1,
|
||||||
|
Api = 2
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Consts\" />
|
<Folder Include="Consts\" />
|
||||||
<Folder Include="Enums\" />
|
|
||||||
<Folder Include="Etos\" />
|
<Folder Include="Etos\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user