feat 网关继承微服务Swagger

This commit is contained in:
2025-11-01 23:30:58 +08:00
parent ccb12389ee
commit 3cc7d2b85d
30 changed files with 1385 additions and 68 deletions

View File

@ -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
};
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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>;

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -0,0 +1,6 @@
namespace KonSoft.InternalGateway.Aggregations.Base;
public interface IRequestInput
{
Dictionary<string, string> Endpoints { get; }
}

View File

@ -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);
}

View File

@ -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>;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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)
{
}
}

View File

@ -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;
}
}