feat 网关继承微服务Swagger
This commit is contained in:
@ -0,0 +1,91 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
|
||||
public class AppConfigurationAggregation : AggregateServiceBase<ApplicationConfigurationDto>,
|
||||
IAppConfigurationAggregation, ITransientDependency
|
||||
{
|
||||
public string AppConfigRouteName => "EshopOnAbpApplicationConfiguration";
|
||||
public string AppConfigEndpoint => "api/abp/application-configuration";
|
||||
|
||||
protected IAppConfigurationRemoteService AppConfigurationRemoteService { get; }
|
||||
|
||||
public AppConfigurationAggregation(
|
||||
IAppConfigurationRemoteService appConfigurationRemoteService) : base(
|
||||
appConfigurationRemoteService)
|
||||
{
|
||||
AppConfigurationRemoteService = appConfigurationRemoteService;
|
||||
}
|
||||
|
||||
public async Task<ApplicationConfigurationDto> GetAppConfigurationAsync(AppConfigurationRequest input)
|
||||
{
|
||||
var remoteAppConfigurationResults =
|
||||
await AppConfigurationRemoteService.GetMultipleAsync(input.Endpoints);
|
||||
|
||||
//merge only application configuration settings data
|
||||
var mergedResult = MergeAppConfigurationSettingsData(remoteAppConfigurationResults);
|
||||
|
||||
//return result
|
||||
return mergedResult;
|
||||
}
|
||||
|
||||
private static ApplicationConfigurationDto MergeAppConfigurationSettingsData(
|
||||
IDictionary<string, ApplicationConfigurationDto> appConfigurations)
|
||||
{
|
||||
var appConfigurationDto = CreateInitialAppConfigDto(appConfigurations);
|
||||
|
||||
foreach (var (_, appConfig) in appConfigurations)
|
||||
{
|
||||
foreach (var resource in appConfig.Setting.Values)
|
||||
{
|
||||
appConfigurationDto.Setting.Values.TryAdd(resource.Key, resource.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return appConfigurationDto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks "Administration" clusterId to set the initial data from the AdministrationService.
|
||||
/// Otherwise uses the first available service for the initial application configuration data
|
||||
/// </summary>
|
||||
/// <param name="appConfigurations"></param>
|
||||
/// <returns></returns>
|
||||
private static ApplicationConfigurationDto CreateInitialAppConfigDto(
|
||||
IDictionary<string, ApplicationConfigurationDto> appConfigurations
|
||||
)
|
||||
{
|
||||
if (appConfigurations.Count == 0)
|
||||
{
|
||||
return new ApplicationConfigurationDto();
|
||||
}
|
||||
|
||||
if (appConfigurations.TryGetValue("Administration_AppConfig", out var administrationServiceData))
|
||||
{
|
||||
return MapServiceData(administrationServiceData);
|
||||
}
|
||||
|
||||
return MapServiceData(appConfigurations.First().Value);
|
||||
}
|
||||
|
||||
private static ApplicationConfigurationDto MapServiceData(ApplicationConfigurationDto appConfiguration)
|
||||
{
|
||||
return new ApplicationConfigurationDto
|
||||
{
|
||||
Localization = appConfiguration.Localization,
|
||||
Auth = appConfiguration.Auth,
|
||||
Clock = appConfiguration.Clock,
|
||||
Setting = appConfiguration.Setting,
|
||||
Features = appConfiguration.Features,
|
||||
Timing = appConfiguration.Timing,
|
||||
CurrentTenant = appConfiguration.CurrentTenant,
|
||||
CurrentUser = appConfiguration.CurrentUser,
|
||||
ExtraProperties = appConfiguration.ExtraProperties,
|
||||
GlobalFeatures = appConfiguration.GlobalFeatures,
|
||||
MultiTenancy = appConfiguration.MultiTenancy,
|
||||
ObjectExtensions = appConfiguration.ObjectExtensions
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
|
||||
public class AppConfigurationCachedService(IMemoryCache applicationConfigurationCache)
|
||||
: CachedServiceBase<ApplicationConfigurationDto>(applicationConfigurationCache), ISingletonDependency;
|
||||
@ -0,0 +1,13 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Json;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
|
||||
public class AppConfigurationRemoteService(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILogger<AggregateRemoteServiceBase<ApplicationConfigurationDto>> logger)
|
||||
: AggregateRemoteServiceBase<ApplicationConfigurationDto>(httpContextAccessor, jsonSerializer, logger),
|
||||
IAppConfigurationRemoteService, ITransientDependency;
|
||||
@ -0,0 +1,8 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
|
||||
public class AppConfigurationRequest : IRequestInput
|
||||
{
|
||||
public Dictionary<string, string> Endpoints { get; } = new();
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
|
||||
public interface IAppConfigurationAggregation
|
||||
{
|
||||
string AppConfigRouteName { get; }
|
||||
string AppConfigEndpoint { get; }
|
||||
Task<ApplicationConfigurationDto> GetAppConfigurationAsync(AppConfigurationRequest input);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
|
||||
public interface IAppConfigurationRemoteService : IAggregateRemoteService<ApplicationConfigurationDto>;
|
||||
@ -0,0 +1,103 @@
|
||||
using Volo.Abp.Json;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
public abstract class AggregateRemoteServiceBase<TDto> : IAggregateRemoteService<TDto>
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<AggregateRemoteServiceBase<TDto>> _logger;
|
||||
protected IJsonSerializer JsonSerializer { get; }
|
||||
|
||||
protected AggregateRemoteServiceBase(IHttpContextAccessor httpContextAccessor, IJsonSerializer jsonSerializer,
|
||||
ILogger<AggregateRemoteServiceBase<TDto>> logger)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
JsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, TDto>> GetMultipleAsync(
|
||||
Dictionary<string, string> serviceNameWithUrlDictionary)
|
||||
{
|
||||
Dictionary<string, Task<TDto>> completedTasks = new Dictionary<string, Task<TDto>>();
|
||||
Dictionary<string, Task<TDto>> runningTasks = new Dictionary<string, Task<TDto>>();
|
||||
Dictionary<string, TDto> completedResult = new Dictionary<string, TDto>();
|
||||
|
||||
using (HttpClient httpClient = CreateHttpClient())
|
||||
{
|
||||
foreach (var service in serviceNameWithUrlDictionary)
|
||||
{
|
||||
Task<TDto> requestTask =
|
||||
MakeRequestAsync<TDto>(httpClient, service.Value);
|
||||
runningTasks.Add(service.Key, requestTask);
|
||||
}
|
||||
|
||||
while (runningTasks.Count > 0)
|
||||
{
|
||||
KeyValuePair<string, Task<TDto>> completedTask = await WaitForAnyTaskAsync(runningTasks);
|
||||
|
||||
runningTasks.Remove(completedTask.Key);
|
||||
|
||||
try
|
||||
{
|
||||
TDto result = await completedTask.Value;
|
||||
|
||||
completedTasks.Add(completedTask.Key, completedTask.Value);
|
||||
completedResult.Add(completedTask.Key, result);
|
||||
|
||||
_logger.LogInformation("Localization Key: {0}, Value: {1}", completedTask.Key, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogInformation("Error for the {0}: {1}", completedTask.Key, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return completedResult;
|
||||
}
|
||||
|
||||
private HttpClient CreateHttpClient()
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
|
||||
var headers = _httpContextAccessor.HttpContext?.Request.Headers;
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public async Task<T> MakeRequestAsync<T>(HttpClient httpClient, string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(content);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogInformation("Error making request to {0}: {1}", url, e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<KeyValuePair<TKey, Task<TValue>>> WaitForAnyTaskAsync<TKey, TValue>(
|
||||
Dictionary<TKey, Task<TValue>> tasks)
|
||||
{
|
||||
var completedTask = Task.WhenAny(tasks.Values);
|
||||
var result = await completedTask;
|
||||
|
||||
var completedTaskPair = tasks.First(kv => kv.Value == result);
|
||||
|
||||
return completedTaskPair;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
namespace KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
public abstract class AggregateServiceBase<TDto>
|
||||
{
|
||||
private readonly IAggregateRemoteService<TDto> _remoteService;
|
||||
|
||||
public AggregateServiceBase(IAggregateRemoteService<TDto> remoteService)
|
||||
{
|
||||
_remoteService = remoteService;
|
||||
}
|
||||
|
||||
public virtual async Task<Dictionary<string, TDto>> GetMultipleFromRemoteAsync(List<string> missingKeys,
|
||||
Dictionary<string, string> endpoints)
|
||||
{
|
||||
return await _remoteService
|
||||
.GetMultipleAsync(endpoints
|
||||
.Where(kv => missingKeys.Contains(kv.Key))
|
||||
.ToDictionary(k => k.Key, v => v.Value));
|
||||
}
|
||||
|
||||
public List<string> GetMissingServiceKeys(
|
||||
IDictionary<string, TDto> serviceNamesWithData,
|
||||
Dictionary<string, string> serviceNamesWithUrls)
|
||||
{
|
||||
List<string> missingKeysInCache = serviceNamesWithUrls.Keys.Except(serviceNamesWithData.Keys).ToList();
|
||||
List<string> missingKeysInUrls = serviceNamesWithData.Keys.Except(serviceNamesWithUrls.Keys).ToList();
|
||||
|
||||
return missingKeysInCache.Concat(missingKeysInUrls).ToList();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
public abstract class CachedServiceBase<TCacheValue> : ICachedServiceBase<TCacheValue>
|
||||
{
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
protected MemoryCacheEntryOptions CacheEntryOptions { get; } = new()
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24),
|
||||
SlidingExpiration = TimeSpan.FromHours(4)
|
||||
};
|
||||
|
||||
protected CachedServiceBase(IMemoryCache cache)
|
||||
{
|
||||
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
||||
}
|
||||
|
||||
public void Add(string serviceName, TCacheValue data)
|
||||
{
|
||||
_cache.Set(serviceName, data, CacheEntryOptions);
|
||||
}
|
||||
|
||||
public IDictionary<string, TCacheValue> GetManyAsync(IEnumerable<string> serviceNames)
|
||||
{
|
||||
var result = new Dictionary<string, TCacheValue>();
|
||||
|
||||
foreach (var serviceName in serviceNames)
|
||||
{
|
||||
if (_cache.TryGetValue(serviceName, out TCacheValue data))
|
||||
{
|
||||
result.Add(serviceName, data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
public interface IAggregateRemoteService<TDto>
|
||||
{
|
||||
Task<Dictionary<string, TDto>> GetMultipleAsync(Dictionary<string, string> serviceNameWithUrlDictionary);
|
||||
Task<T> MakeRequestAsync<T>(HttpClient httpClient, string url);
|
||||
Task<KeyValuePair<TKey, Task<TValue>>> WaitForAnyTaskAsync<TKey, TValue>(Dictionary<TKey, Task<TValue>> tasks);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
public interface ICachedServiceBase<TValue>
|
||||
{
|
||||
void Add(string serviceName, TValue data);
|
||||
IDictionary<string, TValue> GetManyAsync(IEnumerable<string> serviceNames);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
public interface IRequestInput
|
||||
{
|
||||
Dictionary<string, string> Endpoints { get; }
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Localization;
|
||||
|
||||
public interface ILocalizationAggregation
|
||||
{
|
||||
string LocalizationRouteName { get; }
|
||||
string LocalizationEndpoint { get; }
|
||||
Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Localization;
|
||||
|
||||
public interface ILocalizationRemoteService : IAggregateRemoteService<ApplicationLocalizationDto>;
|
||||
@ -0,0 +1,69 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Localization;
|
||||
|
||||
public class LocalizationAggregation : AggregateServiceBase<ApplicationLocalizationDto>, ILocalizationAggregation,
|
||||
ITransientDependency
|
||||
{
|
||||
public string LocalizationRouteName => "EshopOnAbpLocalization";
|
||||
public string LocalizationEndpoint => "api/abp/application-localization";
|
||||
protected LocalizationCachedService LocalizationCachedService { get; }
|
||||
|
||||
public LocalizationAggregation(
|
||||
LocalizationCachedService localizationCachedService,
|
||||
ILocalizationRemoteService localizationRemoteService)
|
||||
: base(localizationRemoteService)
|
||||
{
|
||||
LocalizationCachedService = localizationCachedService;
|
||||
}
|
||||
|
||||
public async Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input)
|
||||
{
|
||||
// Check the cache service
|
||||
var cachedLocalization = LocalizationCachedService
|
||||
.GetManyAsync(input.Endpoints.Keys.ToArray());
|
||||
|
||||
// Compare cache with input service list
|
||||
var missingLocalizationKeys = GetMissingServiceKeys(cachedLocalization, input.Endpoints);
|
||||
|
||||
if (missingLocalizationKeys.Count != 0)
|
||||
{
|
||||
// Make request to remote localization service to get missing localizations
|
||||
var remoteLocalizationResults =
|
||||
await GetMultipleFromRemoteAsync(missingLocalizationKeys, input.Endpoints);
|
||||
|
||||
// Update localization cache
|
||||
foreach (var result in remoteLocalizationResults)
|
||||
{
|
||||
LocalizationCachedService.Add(result.Key, result.Value);
|
||||
}
|
||||
|
||||
cachedLocalization = LocalizationCachedService
|
||||
.GetManyAsync(input.Endpoints.Keys.ToArray());
|
||||
}
|
||||
|
||||
//merge result
|
||||
var mergedResult = MergeLocalizationData(cachedLocalization);
|
||||
|
||||
//return result
|
||||
return mergedResult;
|
||||
}
|
||||
|
||||
private static ApplicationLocalizationDto MergeLocalizationData(
|
||||
IDictionary<string, ApplicationLocalizationDto> resourceDictionary)
|
||||
{
|
||||
var localizationDto = new ApplicationLocalizationDto();
|
||||
|
||||
foreach (var localization in resourceDictionary)
|
||||
{
|
||||
foreach (var resource in localization.Value.Resources)
|
||||
{
|
||||
localizationDto.Resources.TryAdd(resource.Key, resource.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return localizationDto;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Localization;
|
||||
|
||||
public class LocalizationCachedService(IMemoryCache localizationCache)
|
||||
: CachedServiceBase<ApplicationLocalizationDto>(localizationCache), ISingletonDependency;
|
||||
@ -0,0 +1,18 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Json;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Localization;
|
||||
|
||||
public class LocalizationRemoteService : AggregateRemoteServiceBase<ApplicationLocalizationDto>,
|
||||
ILocalizationRemoteService, ITransientDependency
|
||||
{
|
||||
public LocalizationRemoteService(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILogger<AggregateRemoteServiceBase<ApplicationLocalizationDto>> logger)
|
||||
: base(httpContextAccessor, jsonSerializer, logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using KonSoft.InternalGateway.Aggregations.Base;
|
||||
|
||||
namespace KonSoft.InternalGateway.Aggregations.Localization;
|
||||
|
||||
public class LocalizationRequest : IRequestInput
|
||||
{
|
||||
public Dictionary<string, string> Endpoints { get; } = new();
|
||||
public string CultureName { get; set; }
|
||||
|
||||
public LocalizationRequest(string cultureName)
|
||||
{
|
||||
CultureName = cultureName;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
using KonSoft.Admin;
|
||||
using KonSoft.Shared.Hosting.AspNetCore;
|
||||
using KonSoft.Shared.Hosting.Gateways;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace KonSoft.InternalGateway
|
||||
@ -10,5 +14,71 @@ namespace KonSoft.InternalGateway
|
||||
)]
|
||||
public class InternalGatewayModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
var hostingEnvironment = context.Services.GetHostingEnvironment();
|
||||
|
||||
SwaggerConfigurationHelper.ConfigureWithOidc(
|
||||
context: context,
|
||||
authority: configuration["AuthServer:Authority"]!,
|
||||
scopes:
|
||||
[
|
||||
"Admin", "Dispatch", "Payment", "Report", "TenantManagement"
|
||||
],
|
||||
apiTitle: "Internal Gateway API",
|
||||
discoveryEndpoint: configuration["AuthServer:MetadataAddress"]
|
||||
);
|
||||
|
||||
context.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins(
|
||||
configuration["App:CorsOrigins"]!
|
||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(o => o.Trim().RemovePostFix("/"))
|
||||
.ToArray()
|
||||
)
|
||||
.WithAbpExposedHeaders()
|
||||
.SetIsOriginAllowedToAllowWildcardSubdomains()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
context.Services.AddMemoryCache();
|
||||
}
|
||||
|
||||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
var app = context.GetApplicationBuilder();
|
||||
var env = context.GetEnvironment();
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseCorrelationId();
|
||||
app.UseCors();
|
||||
app.UseAbpRequestLocalization();
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
app.UseSwaggerUIWithYarp(context);
|
||||
app.UseAbpSerilogEnrichers();
|
||||
|
||||
app.UseRewriter(new RewriteOptions()
|
||||
// Regex for "", "/" and "" (whitespace)
|
||||
.AddRedirect("^(|\\|\\s+)$", "/swagger"));
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapReverseProxyWithLocalization();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
@KonSoft.InternalGateway_HostAddress = http://localhost:5090
|
||||
|
||||
GET {{KonSoft.InternalGateway_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@ -1,9 +1,6 @@
|
||||
using KonSoft.InternalGateway;
|
||||
using KonSoft.InternalGateway.Extensions;
|
||||
using KonSoft.Shared.Hosting.AspNetCore;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using KonSoft.Shared.Hosting.Gateways;
|
||||
using Serilog;
|
||||
|
||||
var assemblyName = typeof(Program).Assembly.GetName().Name!;
|
||||
@ -11,6 +8,8 @@ SerilogConfigurationHelper.Configure(assemblyName);
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information($"Starting {assemblyName}.");
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration
|
||||
.AddAgileConfig(option =>
|
||||
@ -19,30 +18,13 @@ try
|
||||
});
|
||||
builder.Host
|
||||
.AddAppSettingsSecretsJson()
|
||||
.AddYarpJson()
|
||||
.UseAutofac()
|
||||
.UseSerilog();
|
||||
|
||||
builder.Services.AddAbpSwaggerGenWithOidc(builder.Configuration["AuthServer:Authority"]!, setupAction: options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Gateway",
|
||||
Version = "v1"
|
||||
});
|
||||
options.DocInclusionPredicate((docName, description) => true);
|
||||
options.CustomSchemaIds(type => type.FullName);
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddReverseProxy()
|
||||
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
||||
builder.Services.AddControllers();
|
||||
await builder.AddApplicationAsync<InternalGatewayModule>();
|
||||
var app = builder.Build();
|
||||
await app.InitializeApplicationAsync();
|
||||
app.UseSwaggerUIWithYarp();
|
||||
app.MapReverseProxy();
|
||||
app.MapGet("/heath", () => "Online");
|
||||
await app.RunAsync();
|
||||
|
||||
return 0;
|
||||
|
||||
@ -1,15 +1,5 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5090"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
@ -18,35 +8,8 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7264;http://localhost:5090"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_HTTPS_PORTS": "8081",
|
||||
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||
},
|
||||
"publishAllPorts": true,
|
||||
"useSSL": true
|
||||
"applicationUrl": "https://localhost:7264"
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:15968",
|
||||
"sslPort": 44395
|
||||
}
|
||||
}
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json"
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
using KonSoft.InternalGateway.Aggregations.ApplicationConfiguration;
|
||||
using KonSoft.InternalGateway.Aggregations.Localization;
|
||||
using Yarp.ReverseProxy.Configuration;
|
||||
|
||||
namespace KonSoft.InternalGateway
|
||||
{
|
||||
public static class ReverseProxyBuilderExtensions
|
||||
{
|
||||
public static ReverseProxyConventionBuilder MapReverseProxyWithLocalization(
|
||||
this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
return endpoints.MapReverseProxy(proxyBuilder =>
|
||||
{
|
||||
proxyBuilder.Use(async (context, next) =>
|
||||
{
|
||||
var endpoint = context.GetEndpoint();
|
||||
|
||||
var localizationAggregation = context.RequestServices
|
||||
.GetRequiredService<ILocalizationAggregation>();
|
||||
|
||||
var appConfigurationAggregation = context.RequestServices
|
||||
.GetRequiredService<IAppConfigurationAggregation>();
|
||||
|
||||
// The "/api/abp/application-localization" endpoint
|
||||
if (localizationAggregation.LocalizationRouteName == endpoint?.DisplayName)
|
||||
{
|
||||
var localizationRequestInput =
|
||||
CreateLocalizationRequestInput(context, localizationAggregation.LocalizationEndpoint);
|
||||
|
||||
var result = await localizationAggregation.GetLocalizationAsync(localizationRequestInput);
|
||||
await context.Response.WriteAsJsonAsync(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// The "/api/abp/application-configuration" endpoint
|
||||
if (appConfigurationAggregation.AppConfigRouteName == endpoint?.DisplayName)
|
||||
{
|
||||
var appConfigurationRequestInput =
|
||||
CreateAppConfigurationRequestInput(context, appConfigurationAggregation.AppConfigEndpoint);
|
||||
|
||||
var result =
|
||||
await appConfigurationAggregation.GetAppConfigurationAsync(appConfigurationRequestInput);
|
||||
await context.Response.WriteAsJsonAsync(result);
|
||||
return;
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
proxyBuilder.UseLoadBalancing();
|
||||
});
|
||||
}
|
||||
|
||||
private static AppConfigurationRequest CreateAppConfigurationRequestInput(HttpContext context,
|
||||
string appConfigurationPath)
|
||||
{
|
||||
var proxyConfig = context.RequestServices.GetRequiredService<IProxyConfigProvider>();
|
||||
|
||||
var input = new AppConfigurationRequest();
|
||||
string path = $"{appConfigurationPath}?includeLocalizationResources=false";
|
||||
|
||||
var clusterList = GetClusters(proxyConfig);
|
||||
foreach (var cluster in clusterList)
|
||||
{
|
||||
var hostUrl = new Uri(cluster.Value.Address) + $"{path}";
|
||||
// CacheKey/Endpoint dictionary key -> ex: ("Administration_AppConfig")
|
||||
input.Endpoints.Add($"{cluster.Key}_AppConfig", hostUrl);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static LocalizationRequest CreateLocalizationRequestInput(HttpContext context,
|
||||
string localizationPath)
|
||||
{
|
||||
var proxyConfig = context.RequestServices.GetRequiredService<IProxyConfigProvider>();
|
||||
|
||||
context.Request.Query.TryGetValue("CultureName", out var cultureName);
|
||||
|
||||
var input = new LocalizationRequest(cultureName);
|
||||
string path = $"{localizationPath}?cultureName={cultureName}&onlyDynamics=false";
|
||||
|
||||
var clusterList = GetClusters(proxyConfig);
|
||||
foreach (var cluster in clusterList)
|
||||
{
|
||||
var hostUrl = new Uri(cluster.Value.Address) + $"{path}";
|
||||
// Endpoint dictionary key -> ex: ("Administration_en")
|
||||
input.Endpoints.Add($"{cluster.Key}_{cultureName}", hostUrl);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static Dictionary<string, DestinationConfig> GetClusters(IProxyConfigProvider proxyConfig)
|
||||
{
|
||||
var yarpConfig = proxyConfig.GetConfig();
|
||||
|
||||
var routedClusters = yarpConfig.Clusters
|
||||
.SelectMany(t => t.Destinations,
|
||||
(clusterId, destination) => new { clusterId.ClusterId, destination.Value });
|
||||
|
||||
return routedClusters
|
||||
.GroupBy(q => q.Value.Address)
|
||||
.Select(t => t.First())
|
||||
.Distinct()
|
||||
.ToDictionary(k => k.ClusterId, v => v.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@
|
||||
"AuthServer": {
|
||||
"Authority": "https://devauth.konsoft.top",
|
||||
"RequireHttpsMetadata": true,
|
||||
"SwaggerClientId": "Gateway_Swagger"
|
||||
"SwaggerClientId": "Gateway_Swagger",
|
||||
"MetadataAddress": "https://devauth.konsoft.top"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user