diff --git a/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/en.json b/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/en.json index 747d05d5a..5ef07596c 100644 --- a/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/en.json +++ b/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/en.json @@ -38,9 +38,6 @@ "UseTracing": "Http track", "LoadBalancer": "Load balancer", "LoadWay": "Balancer type", - "LeastConnection": "Least connection", - "RoundRobin": "Round robin", - "NoLoadBalance": "No load balance", "ServiceDiscovery": "Service discovery", "DiscoveryType": "Service type", "Host": "Host", diff --git a/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/zh-Hans.json b/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/zh-Hans.json index b1995e18f..f33808bb4 100644 --- a/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/zh-Hans.json +++ b/aspnet-core/modules/apigateway/LINGYUN.ApiGateway.Domain.Shared/LINGYUN/ApiGateway/Localization/DomainShared/zh-Hans.json @@ -38,9 +38,6 @@ "UseTracing": "HTTP追踪", "LoadBalancer": "负载均衡", "LoadWay": "负载方式", - "LeastConnection": "总是空闲服务器", - "RoundRobin": "服务器轮询", - "NoLoadBalance": "发往首个服务器", "ServiceDiscovery": "服务发现", "DiscoveryType": "实例类型", "Host": "主机地址", diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.cs b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.cs index cead07a03..0abc2078a 100644 --- a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.cs +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.cs @@ -1,7 +1,9 @@ using DotNetCore.CAP; using LINGYUN.Abp.EventBus.CAP; +using LINGYUN.ApiGateway.Localization; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,9 +24,11 @@ using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Http.Client.IdentityModel; using Volo.Abp.IdentityModel; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Security.Claims; using Volo.Abp.Security.Encryption; +using Volo.Abp.VirtualFileSystem; namespace LINGYUN.ApiGateway { @@ -125,12 +129,39 @@ namespace LINGYUN.ApiGateway } }); + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + + options.Resources + .Get() + .AddVirtualJson("/Localization/Host"); + }); + Configure(options => { // See https://github.com/abpframework/abp/pull/4564 options.ConfigureHttpRequestMessage = (requestMessage) => { }; }); + var mvcBuilder = context.Services.AddMvc(); + mvcBuilder.AddApplicationPart(typeof(ApiGatewayHostModule).Assembly); + + Configure(options => + { + options.EndpointConfigureActions.Add(endpointContext => + { + endpointContext.Endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}"); + endpointContext.Endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); + }); + }); + context.Services .AddOcelot() .AddPolly() @@ -142,6 +173,8 @@ namespace LINGYUN.ApiGateway var app = context.GetApplicationBuilder(); app.UseAuditing(); + app.UseRouting(); + app.UseConfiguredEndpoints(); // 启用ws协议 app.UseWebSockets(); app.UseOcelot().Wait(); diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Controllers/ApiGatewayController.cs b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Controllers/ApiGatewayController.cs new file mode 100644 index 000000000..8dba8e018 --- /dev/null +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Controllers/ApiGatewayController.cs @@ -0,0 +1,51 @@ +using LINGYUN.ApiGateway.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Multiplexer; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.ApiGateway.Controllers +{ + [RemoteService(Name = ApiGatewayConsts.RemoteServiceName)] + [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 GetAggregatorsAsync() + { + var aggregators = ServiceProvider.GetServices(); + + var aggregatorDtos = new ListResultDto(aggregators.Select(agg => agg.GetType().Name).ToList()); + + return Task.FromResult(Json(aggregatorDtos)); + } + + [HttpGet] + [Route("LoadBalancers")] + public async Task GetLoadBalancersAsync() + { + var loadBalancers = await LoadBalancerFinder.GetLoadBalancersAsync(); + + var loadBalancerDtos = new ListResultDto(loadBalancers); + + return Json(loadBalancerDtos); + } + } +} diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/LINGYUN.ApiGateway.Host.csproj b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/LINGYUN.ApiGateway.Host.csproj index 52b2f50b4..a7372211f 100644 --- a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/LINGYUN.ApiGateway.Host.csproj +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/LINGYUN.ApiGateway.Host.csproj @@ -5,6 +5,11 @@ + + + + + @@ -20,6 +25,7 @@ + diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Localization/Host/en.json b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Localization/Host/en.json new file mode 100644 index 000000000..bc32138d7 --- /dev/null +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Localization/Host/en.json @@ -0,0 +1,10 @@ +{ + "culture": "en", + "texts": { + "NoLoadBalancer": "No Load balancer", + "RoundRobin": "Round robin", + "LeastConnection": "Least connection", + "CookieStickySessions": "Cookie sticky sessions", + "CustomLoadBalancer": "Custom load balancer - {0}" + } +} \ No newline at end of file diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Localization/Host/zh-Hans.json b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Localization/Host/zh-Hans.json new file mode 100644 index 000000000..b99d0e511 --- /dev/null +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Localization/Host/zh-Hans.json @@ -0,0 +1,10 @@ +{ + "culture": "zh-Hans", + "texts": { + "NoLoadBalancer": "无负载均衡", + "RoundRobin": "服务器轮询", + "LeastConnection": "总是空闲服务器", + "CookieStickySessions": "粘性会话类型", + "CustomLoadBalancer": "自定义负载均衡器 - {0}" + } +} \ No newline at end of file diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Utils/ILoadBalancerFinder.cs b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Utils/ILoadBalancerFinder.cs new file mode 100644 index 000000000..5ce8de116 --- /dev/null +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Utils/ILoadBalancerFinder.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Localization; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.ApiGateway.Utils +{ + public interface ILoadBalancerFinder + { + Task> GetLoadBalancersAsync(); + } + + public class LoadBalancerDescriptor + { + public string Type { get; } + public string DisplayName { get; } + public LoadBalancerDescriptor(string type, string displayName) + { + Type = type; + DisplayName = displayName; + } + } +} diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Utils/LoadBalancerFinder.cs b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Utils/LoadBalancerFinder.cs new file mode 100644 index 000000000..d80f24666 --- /dev/null +++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Utils/LoadBalancerFinder.cs @@ -0,0 +1,58 @@ +using LINGYUN.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.ApiGateway.Utils +{ + public class LoadBalancerFinder : ILoadBalancerFinder, ISingletonDependency + { + private Lazy> lazyLoadBalancers; + protected List LoadBalancers => lazyLoadBalancers.Value; + protected IServiceProvider ServiceProvider { get; } + protected IStringLocalizer Localizer { get; } + public LoadBalancerFinder( + IServiceProvider serviceProvider, + IStringLocalizer localizer) + { + Localizer = localizer; + ServiceProvider = serviceProvider; + + lazyLoadBalancers = new Lazy>(() => FindLocalLoadBalancers()); + } + + public Task> GetLoadBalancersAsync() + { + return Task.FromResult(LoadBalancers); + } + + protected List FindLocalLoadBalancers() + { + List loadBalancers = new List + { + 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(); + 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; + } + } +} diff --git a/vueJs/src/api/apigateway.ts b/vueJs/src/api/apigateway.ts index 672b0c04a..aace63477 100644 --- a/vueJs/src/api/apigateway.ts +++ b/vueJs/src/api/apigateway.ts @@ -164,6 +164,17 @@ export default class ApiGateWay { _url += '&ReRouteKey=' + routeKey return ApiService.Delete(_url, serviceUrl) } + + public static getDefinedAggregatorProviders() { + const _url = '/api/ApiGateway/Basic/Aggregators' + return ApiService.Get>(_url, serviceUrl) + } + + public static getLoadBalancerProviders() { + const _url = '/api/ApiGateway/Basic/LoadBalancers' + return ApiService.Get>(_url, serviceUrl) + } + } export class ServiceDiscoveryProvider { @@ -559,3 +570,8 @@ export class AggregateReRouteGetByPaged extends PagedAndSortedResultRequestDto { return new AggregateReRouteGetByPaged() } } + +export class LoadBalancerDescriptor { + type!: string + displayName!: string +} diff --git a/vueJs/src/lang/en.ts b/vueJs/src/lang/en.ts index e4349e598..6e46c7903 100644 --- a/vueJs/src/lang/en.ts +++ b/vueJs/src/lang/en.ts @@ -420,7 +420,8 @@ export default { createAggregateRouteConfig: '新建路由配置', aggregateRouteKey: '路由标识', aggregateParameter: '聚合参数', - aggregateJsonPath: 'Json路径' + aggregateJsonPath: 'Json路径', + definedAggregatorProviders: '聚合提供者' }, identityServer: { otherOpera: '更多操作', diff --git a/vueJs/src/lang/zh.ts b/vueJs/src/lang/zh.ts index ae59d3473..204b0b151 100644 --- a/vueJs/src/lang/zh.ts +++ b/vueJs/src/lang/zh.ts @@ -424,7 +424,8 @@ export default { createAggregateRouteConfig: '新建路由配置', aggregateRouteKey: '路由标识', aggregateParameter: '聚合参数', - aggregateJsonPath: 'Json路径' + aggregateJsonPath: 'Json路径', + definedAggregatorProviders: '聚合提供者' }, identityServer: { otherOpera: '更多操作', diff --git a/vueJs/src/views/admin/apigateway/components/AggregateRouteCreateOrEditForm.vue b/vueJs/src/views/admin/apigateway/components/AggregateRouteCreateOrEditForm.vue index aa8772084..65df958f8 100644 --- a/vueJs/src/views/admin/apigateway/components/AggregateRouteCreateOrEditForm.vue +++ b/vueJs/src/views/admin/apigateway/components/AggregateRouteCreateOrEditForm.vue @@ -27,16 +27,34 @@ + + + - + + + @@ -139,6 +157,7 @@ export default class extends Vue { private appIdOptions!: RouteGroupAppIdDto[] private aggregateRoute: AggregateReRoute + private definedAggregatorProviders = new Array() get isEditRoute() { if (this.aggregateRouteId) { @@ -175,6 +194,12 @@ export default class extends Vue { this.aggregateRoute = AggregateReRoute.empty() } + mounted() { + ApiGatewayService.getDefinedAggregatorProviders().then(res => { + this.definedAggregatorProviders = res.items + }) + } + @Watch('aggregateRouteId', { immediate: true }) private onAggregateRouteIdChanged() { if (this.aggregateRouteId) { diff --git a/vueJs/src/views/admin/apigateway/components/GlobalCreateOrEditForm.vue b/vueJs/src/views/admin/apigateway/components/GlobalCreateOrEditForm.vue index 81e1eedb1..6abbd9e77 100644 --- a/vueJs/src/views/admin/apigateway/components/GlobalCreateOrEditForm.vue +++ b/vueJs/src/views/admin/apigateway/components/GlobalCreateOrEditForm.vue @@ -211,20 +211,15 @@ > - - @@ -360,6 +355,7 @@ import { checkPermission } from '@/utils/permission' import { Component, Vue, Prop, Watch } from 'vue-property-decorator' import ApiGatewayService, { RouteGroupAppIdDto, + LoadBalancerDescriptor, GlobalConfigurationDto, GlobalConfigurationCreateDto, GlobalConfigurationUpdateDto @@ -378,11 +374,13 @@ export default class extends Vue { private globalConfiguration: GlobalConfigurationDto private routeGroupAppIdOptions: RouteGroupAppIdDto[] + private loadBalancerProviders: LoadBalancerDescriptor[] constructor() { super() this.globalConfiguration = new GlobalConfigurationDto() this.routeGroupAppIdOptions = new Array() + this.loadBalancerProviders = new Array() } get hasEdit() { @@ -405,6 +403,9 @@ export default class extends Vue { ApiGatewayService.getRouteGroupAppIds().then(appKeys => { this.routeGroupAppIdOptions = appKeys.items }) + ApiGatewayService.getLoadBalancerProviders().then(res => { + this.loadBalancerProviders = res.items + }) } @Watch('appId', { immediate: true }) diff --git a/vueJs/src/views/admin/apigateway/components/RouteCreateOrEditForm.vue b/vueJs/src/views/admin/apigateway/components/RouteCreateOrEditForm.vue index 97bf93482..55263d0e2 100644 --- a/vueJs/src/views/admin/apigateway/components/RouteCreateOrEditForm.vue +++ b/vueJs/src/views/admin/apigateway/components/RouteCreateOrEditForm.vue @@ -392,20 +392,15 @@ > - - @@ -545,7 +540,7 @@ import ElInputTag from '@/components/InputTag/index.vue' import HostAndPortInputTag from './HostAndPortInputTag.vue' import DictionaryInputTag from './DictionaryInputTag.vue' import { Component, Vue, Prop, Watch } from 'vue-property-decorator' -import ApiGateWayService, { RouteGroupAppIdDto, ReRouteDto, ReRouteCreateDto, ReRouteUpdateDto } from '@/api/apigateway' +import ApiGateWayService, { LoadBalancerDescriptor, RouteGroupAppIdDto, ReRouteDto, ReRouteCreateDto, ReRouteUpdateDto } from '@/api/apigateway' @Component({ name: 'RouteCreateOrEditForm', @@ -564,6 +559,7 @@ export default class extends Vue { private activeTablePane: string private apiGateWayRoute: ReRouteDto + private loadBalancerProviders: LoadBalancerDescriptor[] private httpMethodsFilter: { [key: string]: string } = { GET: '', POST: 'success', @@ -622,6 +618,13 @@ export default class extends Vue { super() this.activeTablePane = 'basicOptions' this.apiGateWayRoute = new ReRouteDto() + this.loadBalancerProviders = new Array() + } + + mounted() { + ApiGateWayService.getLoadBalancerProviders().then(res => { + this.loadBalancerProviders = res.items + }) } @Watch('routeId', { immediate: true })