23 changed files with 54 additions and 5325 deletions
@ -0,0 +1,46 @@ |
|||
<Solution> |
|||
<Folder Name="/framework/" /> |
|||
<Folder Name="/framework/auditing/"> |
|||
<Project Path="../../../aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN.Abp.AuditLogging.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/framework/common/"> |
|||
<Project Path="../../../aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.Wrapper/LINGYUN.Abp.AspNetCore.Wrapper.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/common/LINGYUN.Abp.IdGenerator/LINGYUN.Abp.IdGenerator.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN.Abp.IP2Region.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/common/LINGYUN.Abp.Wrapper/LINGYUN.Abp.Wrapper.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/framework/elasticsearch/"> |
|||
<Project Path="../../../aspnet-core/framework/elasticsearch/LINGYUN.Abp.Elasticsearch/LINGYUN.Abp.Elasticsearch.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/framework/identity/"> |
|||
<Project Path="../../../aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN.Abp.Identity.Session.AspNetCore.csproj" /> |
|||
<Project Path="../../../aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN.Abp.Identity.Session.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/framework/localization/"> |
|||
<Project Path="../../../aspnet-core/framework/localization/LINGYUN.Abp.Localization.CultureMap/LINGYUN.Abp.Localization.CultureMap.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/framework/logging/"> |
|||
<Project Path="../../../aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN.Abp.Logging.Serilog.Elasticsearch.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN.Abp.Logging.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/logging/LINGYUN.Abp.Serilog.Enrichers.Application/LINGYUN.Abp.Serilog.Enrichers.Application.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/logging/LINGYUN.Abp.Serilog.Enrichers.UniqueId/LINGYUN.Abp.Serilog.Enrichers.UniqueId.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/framework/open-api/"> |
|||
<Project Path="../../../aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj" /> |
|||
<Project Path="../../../aspnet-core/framework/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj" /> |
|||
</Folder> |
|||
<Folder Name="/Solution Items/"> |
|||
<File Path="../../../common.props" /> |
|||
<File Path="../../../common.secrets.props" /> |
|||
<File Path="../../../configureawait.props" /> |
|||
<File Path="../../../Directory.Build.props" /> |
|||
<File Path="../../../Directory.Packages.props" /> |
|||
</Folder> |
|||
<Folder Name="/src/"> |
|||
<Project Path="src/LINGYUN.MicroService.Internal.Gateway/LINGYUN.MicroService.Internal.Gateway.csproj" /> |
|||
<Project Path="src/LINGYUN.MicroService.OpenApi.Gateway/LINGYUN.MicroService.OpenApi.Gateway.csproj" /> |
|||
</Folder> |
|||
</Solution> |
|||
@ -1,12 +0,0 @@ |
|||
{ |
|||
"version": 1, |
|||
"isRoot": true, |
|||
"tools": { |
|||
"dotnet-ef": { |
|||
"version": "7.0.12", |
|||
"commands": [ |
|||
"dotnet-ef" |
|||
] |
|||
} |
|||
} |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
using LINGYUN.MicroService.Internal.ApiGateway.Utils; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Ocelot.Multiplexer; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway.Controllers |
|||
{ |
|||
[Area("ApiGateway")] |
|||
[Route("api/ApiGateway/Basic")] |
|||
public class ApiGatewayController : Controller |
|||
{ |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
protected ILoadBalancerFinder LoadBalancerFinder { get; } |
|||
public ApiGatewayController( |
|||
IServiceProvider serviceProvider, |
|||
ILoadBalancerFinder loadBalancerFinder) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
LoadBalancerFinder = loadBalancerFinder; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("Aggregators")] |
|||
public Task<JsonResult> GetAggregatorsAsync() |
|||
{ |
|||
var aggregators = ServiceProvider.GetServices<IDefinedAggregator>(); |
|||
|
|||
var aggregatorDtos = new ListResultDto<string>(aggregators.Select(agg => agg.GetType().Name).ToList()); |
|||
|
|||
return Task.FromResult(Json(aggregatorDtos)); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("LoadBalancers")] |
|||
public async Task<JsonResult> GetLoadBalancersAsync() |
|||
{ |
|||
var loadBalancers = await LoadBalancerFinder.GetLoadBalancersAsync(); |
|||
|
|||
var loadBalancerDtos = new ListResultDto<LoadBalancerDescriptor>(loadBalancers); |
|||
|
|||
return Json(loadBalancerDtos); |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 |
|||
LABEL maintainer="colin.in@foxmail.com" |
|||
WORKDIR /app |
|||
|
|||
COPY . /app |
|||
|
|||
EXPOSE 80/tcp |
|||
VOLUME [ "./app/Logs" ] |
|||
VOLUME [ "./app/Modules" ] |
|||
|
|||
ENTRYPOINT ["dotnet", "LINGYUN.MicroService.Internal.ApiGateway.dll"] |
|||
@ -1,242 +0,0 @@ |
|||
using LINGYUN.Abp.Serilog.Enrichers.Application; |
|||
using LINGYUN.MicroService.Internal.ApiGateway.Localization; |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Cors; |
|||
using Microsoft.AspNetCore.DataProtection; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.AspNetCore.Routing; |
|||
using Microsoft.AspNetCore.Server.Kestrel.Core; |
|||
using Microsoft.Extensions.Caching.StackExchangeRedis; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.IdentityModel.Logging; |
|||
using Microsoft.OpenApi.Models; |
|||
using Ocelot.DependencyInjection; |
|||
using Ocelot.Multiplexer; |
|||
using Ocelot.Provider.Polly; |
|||
using StackExchange.Redis; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Text.Encodings.Web; |
|||
using System.Text.Unicode; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Json.SystemTextJson; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway |
|||
{ |
|||
public partial class InternalApiGatewayModule |
|||
{ |
|||
protected const string DefaultCorsPolicyName = "Default"; |
|||
|
|||
private void PreConfigureApp(IConfiguration configuration) |
|||
{ |
|||
AbpSerilogEnrichersConsts.ApplicationName = "Internal-ApiGateWay"; |
|||
if (configuration.GetValue<bool>("App:ShowPii")) |
|||
{ |
|||
IdentityModelEventSource.ShowPII = true; |
|||
} |
|||
} |
|||
|
|||
//private void PreForwardedHeaders()
|
|||
//{
|
|||
// PreConfigure<AbpForwardedHeadersOptions>(options =>
|
|||
// {
|
|||
// options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
|||
// options.KnownNetworks.Clear();
|
|||
// options.KnownProxies.Clear();
|
|||
// });
|
|||
//}
|
|||
|
|||
private void ConfigureApiGateway(IConfiguration configuration) |
|||
{ |
|||
Configure<InternalApiGatewayOptions>(configuration.GetSection("ApiGateway")); |
|||
} |
|||
|
|||
|
|||
private void ConfigureKestrelServer(IConfiguration configuration, bool isDevelopment = true) |
|||
{ |
|||
// fix: 不限制请求体大小,解决上传文件问题
|
|||
Configure<KestrelServerOptions>(options => |
|||
{ |
|||
options.Limits.MaxRequestBodySize = null; |
|||
options.Limits.MaxRequestBufferSize = null; |
|||
}); |
|||
|
|||
if (!isDevelopment) |
|||
{ |
|||
// Ssl证书
|
|||
var sslOptions = configuration.GetSection("App:SslOptions"); |
|||
if (sslOptions.Exists()) |
|||
{ |
|||
var fileName = sslOptions["FileName"]; |
|||
var password = sslOptions["Password"]; |
|||
Configure<KestrelServerOptions>(options => |
|||
{ |
|||
options.ConfigureEndpointDefaults(cfg => |
|||
{ |
|||
cfg.UseHttps(fileName, password); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ConfigureJsonSerializer() |
|||
{ |
|||
// 统一时间日期格式
|
|||
Configure<AbpJsonOptions>(options => |
|||
{ |
|||
// options.DefaultDateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
|||
}); |
|||
// 中文序列化的编码问题
|
|||
Configure<AbpSystemTextJsonSerializerOptions>(options => |
|||
{ |
|||
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureVirtualFileSystem() |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<InternalApiGatewayModule>("LINGYUN.MicroService.Internal.ApiGateway"); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureLocalization() |
|||
{ |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Languages.Add(new LanguageInfo("en", "en", "English")); |
|||
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); |
|||
|
|||
options.Resources |
|||
.Add<ApiGatewayResource>() |
|||
.AddVirtualJson("/Localization/Resources"); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureMvc(IServiceCollection services) |
|||
{ |
|||
var mvcBuilder = services.AddMvc(); |
|||
mvcBuilder.AddApplicationPart(typeof(InternalApiGatewayModule).Assembly); |
|||
|
|||
Configure<AbpEndpointRouterOptions>(options => |
|||
{ |
|||
options.EndpointConfigureActions.Add(endpointContext => |
|||
{ |
|||
endpointContext.Endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}"); |
|||
endpointContext.Endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureOcelot(IServiceCollection services, IConfiguration configuration) |
|||
{ |
|||
// 需要合并自定义配置取消注释
|
|||
// services.AddSingleton<IFileConfigurationRepository, DiskFileConfigurationAggragatorRepository>();
|
|||
services |
|||
.AddOcelot(configuration) |
|||
.AddPolly() |
|||
.AddSingletonDefinedAggregator<AbpResponseMergeAggregator>(); |
|||
} |
|||
|
|||
private void ConfigureCaching(IConfiguration configuration) |
|||
{ |
|||
Configure<AbpDistributedCacheOptions>(options => |
|||
{ |
|||
// 最好统一命名,不然某个缓存变动其他应用服务有例外发生
|
|||
options.KeyPrefix = "LINGYUN.Abp.Application"; |
|||
// 滑动过期30天
|
|||
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30d); |
|||
// 绝对过期60天
|
|||
options.GlobalCacheEntryOptions.AbsoluteExpiration = DateTimeOffset.Now.AddDays(60d); |
|||
}); |
|||
|
|||
Configure<RedisCacheOptions>(options => |
|||
{ |
|||
var redisConfig = ConfigurationOptions.Parse(options.Configuration); |
|||
options.ConfigurationOptions = redisConfig; |
|||
options.InstanceName = configuration["Redis:InstanceName"]; |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureSwagger(IServiceCollection services) |
|||
{ |
|||
services.AddSwaggerGen( |
|||
options => |
|||
{ |
|||
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Open API Document", Version = "v1" }); |
|||
options.DocInclusionPredicate((docName, description) => true); |
|||
options.CustomSchemaIds(type => type.FullName); |
|||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme |
|||
{ |
|||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", |
|||
Name = "Authorization", |
|||
In = ParameterLocation.Header, |
|||
Scheme = "bearer", |
|||
Type = SecuritySchemeType.Http, |
|||
BearerFormat = "JWT" |
|||
}); |
|||
options.AddSecurityRequirement(new OpenApiSecurityRequirement |
|||
{ |
|||
{ |
|||
new OpenApiSecurityScheme |
|||
{ |
|||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } |
|||
}, |
|||
new string[] { } |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureCors(IServiceCollection services, IConfiguration configuration) |
|||
{ |
|||
services.AddCors(options => |
|||
{ |
|||
options.AddPolicy(DefaultCorsPolicyName, builder => |
|||
{ |
|||
builder |
|||
.WithOrigins( |
|||
configuration["App:CorsOrigins"] |
|||
.Split(",", StringSplitOptions.RemoveEmptyEntries) |
|||
.Select(o => o.RemovePostFix("/")) |
|||
.ToArray() |
|||
) |
|||
.WithAbpExposedHeaders() |
|||
// 引用 LINGYUN.Abp.AspNetCore.Mvc.Wrapper 包时可替换为 WithAbpWrapExposedHeaders
|
|||
.WithExposedHeaders("_AbpWrapResult", "_AbpDontWrapResult") |
|||
.SetIsOriginAllowedToAllowWildcardSubdomains() |
|||
.AllowAnyHeader() |
|||
.AllowAnyMethod() |
|||
.AllowCredentials(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false) |
|||
{ |
|||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) |
|||
.AddJwtBearer(options => |
|||
{ |
|||
options.Authority = configuration["AuthServer:Authority"]; |
|||
options.RequireHttpsMetadata = false; |
|||
options.Audience = configuration["AuthServer:ApiName"]; |
|||
}); |
|||
|
|||
if (!isDevelopment) |
|||
{ |
|||
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); |
|||
services |
|||
.AddDataProtection() |
|||
.SetApplicationName("LINGYUN.Abp.Application") |
|||
.PersistKeysToStackExchangeRedis(redis, "LINGYUN.Abp.Application:DataProtection:Protection-Keys"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,87 +0,0 @@ |
|||
using LINGYUN.Abp.Serilog.Enrichers.Application; |
|||
using LINGYUN.Abp.Serilog.Enrichers.UniqueId; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using Ocelot.Middleware; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Serilog; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Caching.StackExchangeRedis; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway |
|||
{ |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpSerilogEnrichersApplicationModule), |
|||
typeof(AbpSerilogEnrichersUniqueIdModule), |
|||
typeof(AbpCachingStackExchangeRedisModule), |
|||
typeof(AbpAspNetCoreSerilogModule) |
|||
)] |
|||
public partial class InternalApiGatewayModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
PreConfigureApp(configuration); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
ConfigureLocalization(); |
|||
ConfigureJsonSerializer(); |
|||
ConfigureVirtualFileSystem(); |
|||
|
|||
ConfigureCaching(configuration); |
|||
ConfigureApiGateway(configuration); |
|||
|
|||
ConfigureKestrelServer(configuration, hostingEnvironment.IsDevelopment()); |
|||
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); |
|||
|
|||
ConfigureMvc(context.Services); |
|||
ConfigureSwagger(context.Services); |
|||
ConfigureCors(context.Services, configuration); |
|||
ConfigureOcelot(context.Services, configuration); |
|||
} |
|||
|
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var app = context.GetApplicationBuilder(); |
|||
app.UseAuditing(); |
|||
app.UseStaticFiles(); |
|||
app.UseRouting(); |
|||
app.UseCors(DefaultCorsPolicyName); |
|||
app.UseAuthentication(); |
|||
app.UseAbpClaimsMap(); |
|||
app.MapWhen( |
|||
ctx => ctx.Request.Path.ToString().StartsWith("/api/ApiGateway/Basic/"), |
|||
appNext => |
|||
{ |
|||
// 仅针对属于网关自己的控制器进入MVC管道
|
|||
appNext.UseRouting(); |
|||
appNext.UseConfiguredEndpoints(); |
|||
}); |
|||
app.UseSwagger(); |
|||
app.UseSwaggerUI(c => |
|||
{ |
|||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Open API Document"); |
|||
var options = app.ApplicationServices.GetRequiredService<IOptions<InternalApiGatewayOptions>>().Value; |
|||
foreach (var api in options.DownstreamOpenApis) |
|||
{ |
|||
c.SwaggerEndpoint(api.EndPoint, api.Name); |
|||
} |
|||
}); |
|||
// 启用ws协议
|
|||
app.UseWebSockets(); |
|||
app.UseAbpSerilogEnrichers(); |
|||
app.UseOcelot().Wait(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using LINGYUN.MicroService.Internal.ApiGateway.Models; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway |
|||
{ |
|||
public class InternalApiGatewayOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 聚合异常时是否忽略
|
|||
/// </summary>
|
|||
public bool AggregationIgnoreError { get; set; } |
|||
public DownstreamOpenApi[] DownstreamOpenApis { get; set; } |
|||
public InternalApiGatewayOptions() |
|||
{ |
|||
AggregationIgnoreError = true; |
|||
DownstreamOpenApis = new DownstreamOpenApi[0]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<Import Project="..\..\..\..\..\common.props" /> |
|||
<Import Project="..\..\..\..\..\common.secrets.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<RootNamespace>LINGYUN.MicroService.Internal.ApiGateway</RootNamespace> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Ocelot.Provider.Polly" /> |
|||
<PackageReference Include="AgileConfig.Client" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" /> |
|||
<PackageReference Include="Serilog.AspNetCore" /> |
|||
<PackageReference Include="Serilog.Enrichers.Environment" /> |
|||
<PackageReference Include="Serilog.Enrichers.Assembly" /> |
|||
<PackageReference Include="Serilog.Enrichers.Process" /> |
|||
<PackageReference Include="Serilog.Enrichers.Thread" /> |
|||
<PackageReference Include="Serilog.Settings.Configuration" /> |
|||
<PackageReference Include="Serilog.Sinks.Elasticsearch" /> |
|||
<PackageReference Include="Serilog.Sinks.File" /> |
|||
<PackageReference Include="Swashbuckle.AspNetCore" /> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" /> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" /> |
|||
<PackageReference Include="Volo.Abp.Autofac" /> |
|||
<PackageReference Include="Volo.Abp.AspNetCore" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" /> |
|||
<PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\..\aspnet-core\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" /> |
|||
<!--<ProjectReference Include="..\..\..\..\..\aspnet-core\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />--> |
|||
<ProjectReference Include="..\..\..\..\..\aspnet-core\framework\localization\LINGYUN.Abp.Localization.CultureMap\LINGYUN.Abp.Localization.CultureMap.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\..\aspnet-core\framework\logging\LINGYUN.Abp.Serilog.Enrichers.Application\LINGYUN.Abp.Serilog.Enrichers.Application.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\..\aspnet-core\framework\logging\LINGYUN.Abp.Serilog.Enrichers.UniqueId\LINGYUN.Abp.Serilog.Enrichers.UniqueId.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,9 +0,0 @@ |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway.Localization |
|||
{ |
|||
[LocalizationResourceName("InternalApiGateway")] |
|||
public class ApiGatewayResource |
|||
{ |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"NoLoadBalancer": "No Load balancer", |
|||
"RoundRobin": "Round robin", |
|||
"LeastConnection": "Least connection", |
|||
"CookieStickySessions": "Cookie sticky sessions", |
|||
"CustomLoadBalancer": "Custom load balancer - {0}" |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"NoLoadBalancer": "无负载均衡", |
|||
"RoundRobin": "服务器轮询", |
|||
"LeastConnection": "总是空闲服务器", |
|||
"CookieStickySessions": "粘性会话类型", |
|||
"CustomLoadBalancer": "自定义负载均衡器 - {0}" |
|||
} |
|||
} |
|||
@ -1,8 +0,0 @@ |
|||
namespace LINGYUN.MicroService.Internal.ApiGateway.Models |
|||
{ |
|||
public class DownstreamOpenApi |
|||
{ |
|||
public string Name { get; set; } |
|||
public string EndPoint { get; set; } |
|||
} |
|||
} |
|||
@ -1,132 +0,0 @@ |
|||
using LINGYUN.MicroService.Internal.ApiGateway; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Ocelot.Middleware; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Net.Http.Headers; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Http; |
|||
|
|||
namespace Ocelot.Multiplexer |
|||
{ |
|||
public class AbpResponseMergeAggregator : IDefinedAggregator |
|||
{ |
|||
public ILogger<AbpResponseMergeAggregator> Logger { protected get; set; } |
|||
protected InternalApiGatewayOptions Options { get; } |
|||
|
|||
public AbpResponseMergeAggregator( |
|||
IOptions<InternalApiGatewayOptions> options) |
|||
{ |
|||
Options = options.Value; |
|||
|
|||
Logger = NullLogger<AbpResponseMergeAggregator>.Instance; |
|||
} |
|||
|
|||
public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses) |
|||
{ |
|||
return await MapAbpApiDefinitionAggregateContentAsync(responses); |
|||
} |
|||
|
|||
protected virtual async Task<DownstreamResponse> MapAbpApiDefinitionAggregateContentAsync(List<HttpContext> responses) |
|||
{ |
|||
JObject responseObject = null; |
|||
JsonMergeSettings mergeSetting = new JsonMergeSettings |
|||
{ |
|||
MergeArrayHandling = MergeArrayHandling.Union, |
|||
PropertyNameComparison = System.StringComparison.CurrentCultureIgnoreCase |
|||
}; |
|||
foreach (var httpResponse in responses) |
|||
{ |
|||
var errorItems = httpResponse.Items.Errors(); |
|||
var response = httpResponse.Items.DownstreamResponse(); |
|||
|
|||
if (errorItems.Any()) |
|||
{ |
|||
if (!Options.AggregationIgnoreError) |
|||
{ |
|||
var errorItem = errorItems.First(); |
|||
string error = errorItems.Select(x => x.Message).JoinAsString(";"); |
|||
if (httpResponse.Response.Headers.ContainsKey(AbpHttpConsts.AbpErrorFormat)) |
|||
{ |
|||
// header 存在abp错误标头, 可以序列化错误信息
|
|||
error = await response.Content.ReadAsStringAsync(); |
|||
} |
|||
var errorContent = new StringContent(error) |
|||
{ |
|||
Headers = { ContentType = new MediaTypeHeaderValue("application/json") } |
|||
}; |
|||
if (httpResponse.Response.Headers.ContainsKey(AbpHttpConsts.AbpErrorFormat)) |
|||
{ |
|||
errorContent.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); |
|||
} |
|||
|
|||
return new DownstreamResponse( |
|||
errorContent, |
|||
(HttpStatusCode)errorItem.HttpStatusCode, |
|||
httpResponse.Response.Headers.Select(x => new Header(x.Key, x.Value)).ToList(), |
|||
errorItem.Message); |
|||
} |
|||
var route = httpResponse.Items.DownstreamRoute(); |
|||
var service = route.ServiceName ?? route.LoadBalancerKey ?? route.DownstreamPathTemplate.Value; |
|||
Logger.LogWarning($"The downstream service {service} returned an error, which is configured to be ignored"); |
|||
continue; |
|||
} |
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
try |
|||
{ |
|||
var contentObject = JsonConvert.DeserializeObject(content); |
|||
if (responseObject == null) |
|||
{ |
|||
responseObject = JObject.FromObject(contentObject); |
|||
} |
|||
else |
|||
{ |
|||
responseObject.Merge(contentObject, mergeSetting); |
|||
} |
|||
} |
|||
catch (System.Exception ex) |
|||
{ |
|||
Logger.LogError(ex, "An error occurred in the aggregate route request result."); |
|||
return new DownstreamResponse( |
|||
null, |
|||
HttpStatusCode.InternalServerError, |
|||
new List<KeyValuePair<string, IEnumerable<string>>>(), |
|||
content); |
|||
} |
|||
} |
|||
|
|||
if (responseObject == null) |
|||
{ |
|||
return new DownstreamResponse( |
|||
null, |
|||
HttpStatusCode.NotFound, |
|||
new List<KeyValuePair<string, IEnumerable<string>>>(), |
|||
"Not Found"); |
|||
} |
|||
|
|||
var stringContent = new StringContent(responseObject.ToString()) |
|||
{ |
|||
Headers = { ContentType = new MediaTypeHeaderValue("application/json") } |
|||
}; |
|||
|
|||
//if (responses.Any(response => response.Response.Headers.ContainsKey(AbpHttpConsts.AbpErrorFormat)))
|
|||
//{
|
|||
// stringContent.Headers.Add("_AbpErrorFormat", "true");
|
|||
//}
|
|||
|
|||
return new DownstreamResponse( |
|||
stringContent, |
|||
HttpStatusCode.OK, |
|||
new List<KeyValuePair<string, IEnumerable<string>>>(), |
|||
"OK"); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Serilog; |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.IO; |
|||
using Volo.Abp.Modularity.PlugIns; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway; |
|||
|
|||
public class Program |
|||
{ |
|||
public async static Task<int> Main(string[] args) |
|||
{ |
|||
try |
|||
{ |
|||
Log.Information("Starting Internal ApiGateway."); |
|||
|
|||
var builder = WebApplication.CreateBuilder(args); |
|||
|
|||
builder.Host.AddAppSettingsSecretsJson() |
|||
.UseAutofac() |
|||
.ConfigureAppConfiguration((context, config) => |
|||
{ |
|||
var configuration = config.Build(); |
|||
var agileConfigEnabled = configuration["AgileConfig:IsEnabled"]; |
|||
if (agileConfigEnabled.IsNullOrEmpty() || bool.Parse(agileConfigEnabled)) |
|||
{ |
|||
config.AddAgileConfig(new AgileConfig.Client.ConfigClient(configuration)); |
|||
} |
|||
config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: true); |
|||
if (!context.HostingEnvironment.EnvironmentName.IsNullOrWhiteSpace()) |
|||
{ |
|||
config.AddJsonFile($"ocelot.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true); |
|||
} |
|||
}) |
|||
.UseSerilog((context, provider, config) => |
|||
{ |
|||
config.ReadFrom.Configuration(context.Configuration); |
|||
}); |
|||
await builder.AddApplicationAsync<InternalApiGatewayModule>(options => |
|||
{ |
|||
// 搜索 Modules 目录下所有文件作为插件
|
|||
// 取消显示引用所有其他项目的模块,改为通过插件的形式引用
|
|||
var pluginFolder = Path.Combine( |
|||
Directory.GetCurrentDirectory(), "Modules"); |
|||
DirectoryHelper.CreateIfNotExists(pluginFolder); |
|||
options.PlugInSources.AddFolder( |
|||
pluginFolder, |
|||
SearchOption.AllDirectories); |
|||
}); |
|||
var app = builder.Build(); |
|||
await app.InitializeApplicationAsync(); |
|||
await app.RunAsync(); |
|||
return 0; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Log.Fatal(ex, "Host terminated unexpectedly!"); |
|||
Console.WriteLine("Host terminated unexpectedly!"); |
|||
Console.WriteLine(ex.ToString()); |
|||
return 1; |
|||
} |
|||
finally |
|||
{ |
|||
Log.CloseAndFlush(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:51466/", |
|||
"sslPort": 44332 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"LINGYUN.MicroService.Internal.ApiGateway": { |
|||
"commandName": "Project", |
|||
"launchBrowser": false, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://127.0.0.1:30443;http://127.0.0.1:30000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway.Utils |
|||
{ |
|||
public interface ILoadBalancerFinder |
|||
{ |
|||
Task<List<LoadBalancerDescriptor>> GetLoadBalancersAsync(); |
|||
} |
|||
|
|||
public class LoadBalancerDescriptor |
|||
{ |
|||
public string Type { get; } |
|||
public string DisplayName { get; } |
|||
public LoadBalancerDescriptor(string type, string displayName) |
|||
{ |
|||
Type = type; |
|||
DisplayName = displayName; |
|||
} |
|||
} |
|||
} |
|||
@ -1,58 +0,0 @@ |
|||
using LINGYUN.MicroService.Internal.ApiGateway.Localization; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Localization; |
|||
using Ocelot.LoadBalancer.LoadBalancers; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.MicroService.Internal.ApiGateway.Utils |
|||
{ |
|||
public class LoadBalancerFinder : ILoadBalancerFinder, ISingletonDependency |
|||
{ |
|||
private Lazy<List<LoadBalancerDescriptor>> lazyLoadBalancers; |
|||
protected List<LoadBalancerDescriptor> LoadBalancers => lazyLoadBalancers.Value; |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
protected IStringLocalizer Localizer { get; } |
|||
public LoadBalancerFinder( |
|||
IServiceProvider serviceProvider, |
|||
IStringLocalizer<ApiGatewayResource> localizer) |
|||
{ |
|||
Localizer = localizer; |
|||
ServiceProvider = serviceProvider; |
|||
|
|||
lazyLoadBalancers = new Lazy<List<LoadBalancerDescriptor>>(() => FindLocalLoadBalancers()); |
|||
} |
|||
|
|||
public Task<List<LoadBalancerDescriptor>> GetLoadBalancersAsync() |
|||
{ |
|||
return Task.FromResult(LoadBalancers); |
|||
} |
|||
|
|||
protected List<LoadBalancerDescriptor> FindLocalLoadBalancers() |
|||
{ |
|||
List<LoadBalancerDescriptor> loadBalancers = new List<LoadBalancerDescriptor> |
|||
{ |
|||
new LoadBalancerDescriptor(typeof(NoLoadBalancer).Name, Localizer["NoLoadBalancer"]), |
|||
new LoadBalancerDescriptor(typeof(RoundRobin).Name, Localizer["RoundRobin"]), |
|||
new LoadBalancerDescriptor(typeof(LeastConnection).Name, Localizer["LeastConnection"]), |
|||
new LoadBalancerDescriptor(typeof(CookieStickySessions).Name, Localizer["CookieStickySessions"]) |
|||
}; |
|||
|
|||
|
|||
var loadBalancerCreators = ServiceProvider.GetServices<ILoadBalancerCreator>(); |
|||
loadBalancerCreators = loadBalancerCreators |
|||
.Where(lbc => !loadBalancers.Any(l => l.Type.Equals(lbc.Type))) |
|||
.ToArray(); |
|||
|
|||
foreach(var defintedLoadBalancerCreator in loadBalancerCreators) |
|||
{ |
|||
loadBalancers.Add(new LoadBalancerDescriptor(defintedLoadBalancerCreator.Type, Localizer["CustomLoadBalancer", defintedLoadBalancerCreator.Type])); |
|||
} |
|||
|
|||
return loadBalancers; |
|||
} |
|||
} |
|||
} |
|||
@ -1,87 +0,0 @@ |
|||
{ |
|||
"AgileConfig": { |
|||
"IsEnabled": false, |
|||
"env": "DEV", |
|||
"appId": "LINGYUN.Internal.ApiGateway", |
|||
"secret": "1q2w3E*", |
|||
"nodes": "http://127.0.0.1:5000", |
|||
"name": "ApiGateway", |
|||
"tag": "ApiGateway" |
|||
}, |
|||
"App": { |
|||
"CorsOrigins": "http://127.0.0.1:3100", |
|||
"ShowPii": true |
|||
}, |
|||
"ApiGateway": { |
|||
"AppId": "TEST-APP", |
|||
"DownstreamOpenApis": [ |
|||
{ |
|||
"Name": "ApiGateway Admin API", |
|||
"EndPoint": "/apigateway-admin/v1/swagger.json" |
|||
}, |
|||
{ |
|||
"Name": "Backend Admin API", |
|||
"EndPoint": "/admin/v1/swagger.json" |
|||
}, |
|||
{ |
|||
"Name": "Platform API", |
|||
"EndPoint": "/platform/v1/swagger.json" |
|||
}, |
|||
{ |
|||
"Name": "Localization API", |
|||
"EndPoint": "/localization/v1/swagger.json" |
|||
}, |
|||
{ |
|||
"Name": "Messages API", |
|||
"EndPoint": "/messages/v1/swagger.json" |
|||
}, |
|||
{ |
|||
"Name": "IdentityServer Admin API", |
|||
"EndPoint": "/ids-admin/v1/swagger.json" |
|||
}, |
|||
{ |
|||
"Name": "Webhooks API", |
|||
"EndPoint": "/webhooks/v1/swagger.json" |
|||
} |
|||
] |
|||
}, |
|||
"Redis": { |
|||
"Configuration": "127.0.0.1,defaultDatabase=10", |
|||
"InstanceName": "LINGYUN.Abp.Application" |
|||
}, |
|||
"AuthServer": { |
|||
"Authority": "http://host.docker.internal:44385/", |
|||
"ApiName": "lingyun-abp-application", |
|||
"SwaggerClientId": "ApigatewayHostClient", |
|||
"SwaggerClientSecret": "1q2w3e*", |
|||
"ApiSocpes": "lingyun-abp-application" |
|||
}, |
|||
"Serilog": { |
|||
"MinimumLevel": { |
|||
"Default": "Debug", |
|||
"Override": { |
|||
"System": "Warning", |
|||
"Microsoft": "Warning", |
|||
"DotNetCore": "Debug" |
|||
} |
|||
}, |
|||
"WriteTo": [ |
|||
{ |
|||
"Name": "Console", |
|||
"Args": { |
|||
"restrictedToMinimumLevel": "Debug", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
}, |
|||
{ |
|||
"Name": "Elasticsearch", |
|||
"Args": { |
|||
"nodeUris": "http://127.0.0.1:9200", |
|||
"indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", |
|||
"autoRegisterTemplate": true, |
|||
"autoRegisterTemplateVersion": "ESv7" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
} |
|||
@ -1,80 +0,0 @@ |
|||
{ |
|||
"AuthServer": { |
|||
"Authority": "http://127.0.0.1:44385/", |
|||
"ApiName": "lingyun-abp-application", |
|||
"SwaggerClientId": "ApigatewayHostClient", |
|||
"SwaggerClientSecret": "1q2w3e*", |
|||
"ApiSocpes": "lingyun-abp-application" |
|||
}, |
|||
"StringEncryption": { |
|||
"DefaultPassPhrase": "s46c5q55nxpeS8Ra", |
|||
"InitVectorBytes": "s83ng0abvd02js84", |
|||
"DefaultSalt": "sf&5)s3#" |
|||
}, |
|||
"Serilog": { |
|||
"MinimumLevel": { |
|||
"Default": "Debug", |
|||
"Override": { |
|||
"System": "Warning", |
|||
"Microsoft": "Warning" |
|||
} |
|||
}, |
|||
"Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId", "WithEnvironmentName", "WithMachineName", "WithApplicationName", "WithUniqueId" ], |
|||
"WriteTo": [ |
|||
{ |
|||
"Name": "Console", |
|||
"Args": { |
|||
"initialMinimumLevel": "Information", |
|||
"standardErrorFromLevel": "Information", |
|||
"restrictedToMinimumLevel": "Information", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
}, |
|||
{ |
|||
"Name": "File", |
|||
"Args": { |
|||
"path": "Logs/Debug-.log", |
|||
"restrictedToMinimumLevel": "Debug", |
|||
"rollingInterval": "Day", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
}, |
|||
{ |
|||
"Name": "File", |
|||
"Args": { |
|||
"path": "Logs/Info-.log", |
|||
"restrictedToMinimumLevel": "Information", |
|||
"rollingInterval": "Day", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
}, |
|||
{ |
|||
"Name": "File", |
|||
"Args": { |
|||
"path": "Logs/Warn-.log", |
|||
"restrictedToMinimumLevel": "Warning", |
|||
"rollingInterval": "Day", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
}, |
|||
{ |
|||
"Name": "File", |
|||
"Args": { |
|||
"path": "Logs/Error-.log", |
|||
"restrictedToMinimumLevel": "Error", |
|||
"rollingInterval": "Day", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
}, |
|||
{ |
|||
"Name": "File", |
|||
"Args": { |
|||
"path": "Logs/Fatal-.log", |
|||
"restrictedToMinimumLevel": "Fatal", |
|||
"rollingInterval": "Day", |
|||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
} |
|||
@ -1 +0,0 @@ |
|||
{} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue