feat: 支持非流式传输

This commit is contained in:
ccnetcore
2025-07-09 21:52:00 +08:00
parent 716c344780
commit c5a9b9a15f
9 changed files with 365 additions and 48 deletions

View File

@@ -58,5 +58,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
/// 是否启用SaaS多租户 /// 是否启用SaaS多租户
/// </summary> /// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false; public bool EnabledSaasMultiTenancy { get; set; } = false;
/// <summary>
/// 并发乐观锁异常,否则不处理
/// </summary>
public bool EnabledConcurrencyException { get; set; } = true;
} }
} }

View File

@@ -2,11 +2,13 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Nito.AsyncEx; using Nito.AsyncEx;
using SqlSugar; using SqlSugar;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq; using Volo.Abp.Linq;
@@ -24,6 +26,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider; private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
/// <summary> /// <summary>
/// 异步查询执行器 /// 异步查询执行器
/// </summary> /// </summary>
@@ -407,17 +412,26 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{ {
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新 if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
{ {
try if (Options is not null && Options.EnabledConcurrencyException)
{
try
{
int num = await (await GetDbSimpleClientAsync())
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
return num > 0;
}
catch (VersionExceptions ex)
{
throw new AbpDbConcurrencyException(
$"{ex.Message}[更新失败ConcurrencyStamp不是最新版本],entityInfo{updateObj}", ex);
}
}
else
{ {
int num = await (await GetDbSimpleClientAsync()) int num = await (await GetDbSimpleClientAsync())
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true); .Context.Updateable(updateObj).ExecuteCommandAsync();
return num > 0; return num > 0;
} }
catch (VersionExceptions ex)
{
throw new AbpDbConcurrencyException(
$"{ex.Message}[更新失败ConcurrencyStamp不是最新版本],entityInfo{updateObj}", ex);
}
} }
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj); return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
@@ -441,8 +455,8 @@ namespace Yi.Framework.SqlSugarCore.Repositories
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>, public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>,
IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new() IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{ {
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base( public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider) : base(
sugarDbContextProvider) dbContextProvider)
{ {
} }

View File

@@ -0,0 +1,115 @@
using Newtonsoft.Json;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi;
public class ChatCompletionsOutput
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("object")]
public string Object { get; set; }
[JsonProperty("created_at")]
public long CreatedAt { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("error")]
public object Error { get; set; }
[JsonProperty("incomplete_details")]
public object IncompleteDetails { get; set; }
[JsonProperty("instructions")]
public object Instructions { get; set; }
[JsonProperty("max_output_tokens")]
public object MaxOutputTokens { get; set; }
[JsonProperty("model")]
public string Model { get; set; }
[JsonProperty("output")]
public List<Output> Output { get; set; }
[JsonProperty("parallel_tool_calls")]
public bool ParallelToolCalls { get; set; }
[JsonProperty("previous_response_id")]
public object PreviousResponseId { get; set; }
[JsonProperty("reasoning")]
public Reasoning Reasoning { get; set; }
[JsonProperty("store")]
public bool Store { get; set; }
[JsonProperty("temperature")]
public double Temperature { get; set; }
[JsonProperty("text")]
public Text Text { get; set; }
[JsonProperty("tool_choice")]
public string ToolChoice { get; set; }
[JsonProperty("tools")]
public List<object> Tools { get; set; }
[JsonProperty("top_p")]
public double TopP { get; set; }
[JsonProperty("truncation")]
public string Truncation { get; set; }
[JsonProperty("usage")]
public Usage Usage { get; set; }
[JsonProperty("user")]
public object User { get; set; }
[JsonProperty("metadata")]
public Dictionary<string, object> Metadata { get; set; }
}
public class Output
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("role")]
public string Role { get; set; }
[JsonProperty("content")]
public List<Content> Content { get; set; }
}
public class Content
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("text")]
public string Text { get; set; }
[JsonProperty("annotations")]
public List<object> Annotations { get; set; }
}
public class Reasoning
{
[JsonProperty("effort")]
public object Effort { get; set; }
[JsonProperty("summary")]
public object Summary { get; set; }
}
public class Text
{
[JsonProperty("format")]
public Format Format { get; set; }
}
public class Format
{
[JsonProperty("type")]
public string Type { get; set; }
}
public class Usage
{
[JsonProperty("input_tokens")]
public int InputTokens { get; set; }
[JsonProperty("input_tokens_details")]
public InputTokensDetails InputTokensDetails { get; set; }
[JsonProperty("output_tokens")]
public int OutputTokens { get; set; }
[JsonProperty("output_tokens_details")]
public OutputTokensDetails OutputTokensDetails { get; set; }
[JsonProperty("total_tokens")]
public int TotalTokens { get; set; }
}
public class InputTokensDetails
{
[JsonProperty("cached_tokens")]
public int CachedTokens { get; set; }
}
public class OutputTokensDetails
{
[JsonProperty("reasoning_tokens")]
public int ReasoningTokens { get; set; }
}

View File

@@ -1,6 +1,6 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos; namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class SendMessageOutputDto public class SendMessageStreamOutputDto
{ {
/// <summary> /// <summary>
/// 唯一标识符 /// 唯一标识符

View File

@@ -55,6 +55,7 @@ public class OpenApiService : ApplicationService
} }
} }
//是否使用流式传输
if (input.Stream) if (input.Stream)
{ {
//ai网关代理httpcontext //ai网关代理httpcontext
@@ -64,8 +65,9 @@ public class OpenApiService : ApplicationService
} }
else else
{ {
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext,input.Model, history, userId, null, await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input.Model,
cancellationToken); history, userId, null,
cancellationToken);
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -62,7 +63,8 @@ public class AzureRestChatService : IChatService
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
throw new UserFriendlyException($"当前模型不可用:{aiModelDescribe.ModelId},状态码:{response.StatusCode},原因:{response.ReasonPhrase}"); throw new UserFriendlyException(
$"当前模型不可用:{aiModelDescribe.ModelId},状态码:{response.StatusCode},原因:{response.ReasonPhrase}");
} }
// 确认响应成功 // 确认响应成功
// response.EnsureSuccessStatusCode(); // response.EnsureSuccessStatusCode();
@@ -76,11 +78,11 @@ public class AzureRestChatService : IChatService
var result = new CompleteChatResponse(); var result = new CompleteChatResponse();
try try
{ {
var jsonObj = MapToJObject(line); var jsonObj = MapToStreamJObject(line);
if (jsonObj is not null) if (jsonObj is not null)
{ {
var content = GetContent(jsonObj); var content = GetStreamContent(jsonObj);
var tokenUsage = GetTokenUsage(jsonObj); var tokenUsage = GetStreamTokenUsage(jsonObj);
result = new CompleteChatResponse result = new CompleteChatResponse
{ {
TokenUsage = tokenUsage, TokenUsage = tokenUsage,
@@ -98,28 +100,85 @@ public class AzureRestChatService : IChatService
} }
} }
public Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages, CancellationToken cancellationToken) public async Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
List<ChatMessage> messages, CancellationToken cancellationToken)
{ {
throw new NotImplementedException("暂未实现"); // 设置API URL
var apiUrl = $"{aiModelDescribe.Endpoint}";
// 准备请求内容
var requestBody = new
{
messages = messages.Select(x => new
{
role = x.GetRoleAsString(),
content = x.Content.FirstOrDefault()?.Text
}).ToList(),
stream = false,
// max_tokens = 2048,
// temperature = 0.8,
// top_p = 0.1,
// presence_penalty = 0,
// frequency_penalty = 0,
model = aiModelDescribe.ModelId
};
// 序列化请求内容为JSON
string jsonBody = JsonConvert.SerializeObject(requestBody);
using var httpClient = new HttpClient()
{
//10分钟超时
Timeout = TimeSpan.FromSeconds(600)
};
// 设置请求头
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {aiModelDescribe.ApiKey}");
// 其他头信息如Content-Type在StringContent中设置
// 构造 POST 请求
var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
// 设置请求内容(示例)
request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
// 发送POST请求
HttpResponseMessage response =
await httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
throw new UserFriendlyException(
$"当前模型不可用:{aiModelDescribe.ModelId},状态码:{response.StatusCode},原因:{response.ReasonPhrase}");
}
// 确认响应成功
// response.EnsureSuccessStatusCode();
// 读取响应内容
var responseStr = await response.Content.ReadAsStringAsync(cancellationToken);
var jObject = MapToJObject(responseStr);
var content = GetContent(jObject);
var usage = GetTokenUsage(jObject);
var result = new CompleteChatResponse
{
TokenUsage = usage,
IsFinish = true,
Content = content
};
return result;
} }
private JObject? MapToJObject(string line) private JObject? MapToJObject(string body)
{ {
if (line == "data: [DONE]"||string.IsNullOrWhiteSpace(line) ) if (string.IsNullOrWhiteSpace(body))
{
return null; return null;
} return JObject.Parse(body);
if (string.IsNullOrWhiteSpace(line))
return null;
string prefix = "data: ";
line = line.Substring(prefix.Length);
return JObject.Parse(line);
} }
private string? GetContent(JObject? jsonObj) private string? GetContent(JObject? jsonObj)
{ {
var contentToken = jsonObj.SelectToken("choices[0].delta.content"); var contentToken = jsonObj.SelectToken("choices[0].message.content");
if (contentToken != null && contentToken.Type != JTokenType.Null) if (contentToken != null && contentToken.Type != JTokenType.Null)
{ {
return contentToken.ToString(); return contentToken.ToString();
@@ -157,4 +216,60 @@ public class AzureRestChatService : IChatService
return null; return null;
} }
private JObject? MapToStreamJObject(string line)
{
if (line == "data: [DONE]" || string.IsNullOrWhiteSpace(line))
{
return null;
}
if (string.IsNullOrWhiteSpace(line))
return null;
string prefix = "data: ";
line = line.Substring(prefix.Length);
return JObject.Parse(line);
}
private string? GetStreamContent(JObject? jsonObj)
{
var contentToken = jsonObj.SelectToken("choices[0].delta.content");
if (contentToken != null && contentToken.Type != JTokenType.Null)
{
return contentToken.ToString();
}
return null;
}
private TokenUsage? GetStreamTokenUsage(JObject? jsonObj)
{
var usage = jsonObj.SelectToken("usage");
if (usage is not null && usage.Type != JTokenType.Null)
{
var result = new TokenUsage();
var completionTokens = usage["completion_tokens"];
if (completionTokens is not null && completionTokens.Type != JTokenType.Null)
{
result.OutputTokenCount = completionTokens.ToObject<int>();
}
var promptTokens = usage["prompt_tokens"];
if (promptTokens is not null && promptTokens.Type != JTokenType.Null)
{
result.InputTokenCount = promptTokens.ToObject<int>();
}
var totalTokens = usage["total_tokens"];
if (totalTokens is not null && totalTokens.Type != JTokenType.Null)
{
result.TotalTokenCount = totalTokens.ToObject<int>();
}
return result;
}
return null;
}
} }

View File

@@ -9,11 +9,13 @@ using Newtonsoft.Json.Serialization;
using OpenAI.Chat; using OpenAI.Chat;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.AiChat; using Yi.Framework.AiHub.Domain.AiChat;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Shared.Dtos; using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using Usage = Yi.Framework.AiHub.Application.Contracts.Dtos.Usage;
namespace Yi.Framework.AiHub.Domain.Managers; namespace Yi.Framework.AiHub.Domain.Managers;
@@ -107,7 +109,7 @@ public class AiGateWayManager : DomainService
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true); await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
var modelDescribe = await GetModelAsync(modelId); var modelDescribe = await GetModelAsync(modelId);
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(modelDescribe.HandlerName); var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(modelDescribe.HandlerName);
var output = await chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken); var data = await chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken);
if (userId is not null) if (userId is not null)
{ {
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId, await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
@@ -115,22 +117,23 @@ public class AiGateWayManager : DomainService
{ {
Content = messages.LastOrDefault().Content.FirstOrDefault()?.Text ?? string.Empty, Content = messages.LastOrDefault().Content.FirstOrDefault()?.Text ?? string.Empty,
ModelId = modelId, ModelId = modelId,
TokenUsage = output.TokenUsage, TokenUsage = data.TokenUsage,
}); });
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId, await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = output.Content, Content = data.Content,
ModelId = modelId, ModelId = modelId,
TokenUsage = output.TokenUsage TokenUsage = data.TokenUsage
}); });
await _usageStatisticsManager.SetUsageAsync(userId.Value, modelId, output.TokenUsage.InputTokenCount, await _usageStatisticsManager.SetUsageAsync(userId.Value, modelId, data.TokenUsage.InputTokenCount,
output.TokenUsage.OutputTokenCount); data.TokenUsage.OutputTokenCount);
} }
var body = JsonConvert.SerializeObject(output, new JsonSerializerSettings var result = MapToChatCompletions(modelId, data.Content);
var body = JsonConvert.SerializeObject(result, new JsonSerializerSettings
{ {
ContractResolver = new CamelCasePropertyNamesContractResolver() ContractResolver = new CamelCasePropertyNamesContractResolver()
}); });
@@ -261,9 +264,9 @@ public class AiGateWayManager : DomainService
} }
private SendMessageOutputDto MapToMessage(string modelId, string content) private SendMessageStreamOutputDto MapToMessage(string modelId, string content)
{ {
var output = new SendMessageOutputDto var output = new SendMessageStreamOutputDto
{ {
Id = "chatcmpl-BotYP3BlN5T4g9YPnW0fBSBvKzXdd", Id = "chatcmpl-BotYP3BlN5T4g9YPnW0fBSBvKzXdd",
Object = "chat.completion.chunk", Object = "chat.completion.chunk",
@@ -338,4 +341,75 @@ public class AiGateWayManager : DomainService
return output; return output;
} }
private ChatCompletionsOutput MapToChatCompletions(string modelId, string content)
{
return new ChatCompletionsOutput
{
Id = "resp_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b",
Object = "response",
CreatedAt = 1741476542,
Status = "completed",
Error = null,
IncompleteDetails = null,
Instructions = null,
MaxOutputTokens = null,
Model = modelId,
Output = new List<Output>()
{
new Output
{
Type = "message",
Id = "msg_67ccd2bf17f0819081ff3bb2cf6508e60bb6a6b452d3795b",
Status = "completed",
Role = "assistant",
Content = new List<Content>
{
new Content
{
Type = "output_text",
Text = content,
Annotations = new List<object>()
}
}
}
},
ParallelToolCalls = true,
PreviousResponseId = null,
Reasoning = new Reasoning
{
Effort = null,
Summary = null
},
Store = true,
Temperature = 0,
Text = new Text
{
Format = new Format
{
Type = "text"
}
},
ToolChoice = "auto",
Tools = new List<object>(),
TopP = 1.0,
Truncation = "disabled",
Usage = new Application.Contracts.Dtos.OpenAi.Usage
{
InputTokens = 0,
InputTokensDetails = new InputTokensDetails
{
CachedTokens = 0
},
OutputTokens = 0,
OutputTokensDetails = new OutputTokensDetails
{
ReasoningTokens = 0
},
TotalTokens = 0
},
User = null,
Metadata = null
};
}
} }

View File

@@ -35,7 +35,7 @@ namespace Yi.Abp.SqlsugarCore
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.AddYiDbContext<YiDbContext>(); context.Services.AddYiDbContext<YiDbContext>(options => { options.EnabledConcurrencyException = false; });
//默认不开放可根据项目需要是否Db直接对外开放 //默认不开放可根据项目需要是否Db直接对外开放
//context.Services.AddTransient(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient); //context.Services.AddTransient(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient);
} }

View File

@@ -9,10 +9,7 @@ using Hangfire.Redis.StackExchange;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using StackExchange.Redis; using StackExchange.Redis;
@@ -23,13 +20,10 @@ using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery; using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Swashbuckle; using Volo.Abp.Swashbuckle;
using Yi.Abp.Application; using Yi.Abp.Application;
@@ -44,7 +38,6 @@ using Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection;
using Yi.Framework.BackgroundWorkers.Hangfire; using Yi.Framework.BackgroundWorkers.Hangfire;
using Yi.Framework.Bbs.Application; using Yi.Framework.Bbs.Application;
using Yi.Framework.Bbs.Application.Extensions; using Yi.Framework.Bbs.Application.Extensions;
using Yi.Framework.Bbs.Domain.Entities.Forum;
using Yi.Framework.ChatHub.Application; using Yi.Framework.ChatHub.Application;
using Yi.Framework.CodeGen.Application; using Yi.Framework.CodeGen.Application;
using Yi.Framework.Core.Json; using Yi.Framework.Core.Json;
@@ -53,7 +46,6 @@ using Yi.Framework.Rbac.Application;
using Yi.Framework.Rbac.Domain.Authorization; using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Shared.Consts; using Yi.Framework.Rbac.Domain.Shared.Consts;
using Yi.Framework.Rbac.Domain.Shared.Options; using Yi.Framework.Rbac.Domain.Shared.Options;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.Stock.Application; using Yi.Framework.Stock.Application;
using Yi.Framework.TenantManagement.Application; using Yi.Framework.TenantManagement.Application;