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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user