From d789b5d48ab5812bd44471da9a83c604046dadf0 Mon Sep 17 00:00:00 2001
From: cKey <35512826+colinin@users.noreply.github.com>
Date: Fri, 21 May 2021 10:10:46 +0800
Subject: [PATCH] Add an aggregation api-gateway based on the
/api/abp/api-definition endpoint
---
aspnet-core/LINGYUN.MicroService.All.sln | 7 +
.../LINGYUN.MicroService.ApiGateway.sln | 7 +
.../DotNetCore/CAP/ConsumerServiceSelector.cs | 15 +-
.../AbpApiGatewayHostModule.cs | 168 ++++++++++++++++
.../AbpApiGatewayOptions.cs | 11 ++
.../LINGYUN.Abp.ApiGateway.Host/Dockerfile | 13 ++
.../LINGYUN.Abp.ApiGateway.Host.csproj | 28 +++
.../LINGYUN.Abp.ApiGateway.Host.csproj.user | 6 +
...iDescriptionFileConfigurationRepository.cs | 185 ++++++++++++++++++
.../Extenssions/OcelotMiddlewareExtensions.cs | 123 ++++++++++++
.../Multiplexer/AbpResponseMergeAggregator.cs | 51 +++++
.../LINGYUN.Abp.ApiGateway.Host/Program.cs | 47 +++++
.../Properties/launchSettings.json | 20 ++
.../LINGYUN.Abp.ApiGateway.Host/Startup.cs | 18 ++
.../LINGYUN.ApiGateway.Host/Program.cs | 22 +--
vueJs/package-lock.json | 137 +++++++++++--
16 files changed, 825 insertions(+), 33 deletions(-)
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayHostModule.cs
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayOptions.cs
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Dockerfile
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj.user
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Configuration/Repository/AbpApiDescriptionFileConfigurationRepository.cs
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Extenssions/OcelotMiddlewareExtensions.cs
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Middleware/Multiplexer/AbpResponseMergeAggregator.cs
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Program.cs
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Properties/launchSettings.json
create mode 100644 aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Startup.cs
diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln
index 663fd24cf..5db746f39 100644
--- a/aspnet-core/LINGYUN.MicroService.All.sln
+++ b/aspnet-core/LINGYUN.MicroService.All.sln
@@ -331,6 +331,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Rules.RulesEngi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.HttpOverrides", "modules\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj", "{13219C1C-23E1-4EBA-93FB-86830C93A800}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.ApiGateway.Host", "services\apigateway\LINGYUN.Abp.ApiGateway.Host\LINGYUN.Abp.ApiGateway.Host.csproj", "{A320E23E-792D-4736-B963-381F9D7AF605}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -865,6 +867,10 @@ Global
{13219C1C-23E1-4EBA-93FB-86830C93A800}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13219C1C-23E1-4EBA-93FB-86830C93A800}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13219C1C-23E1-4EBA-93FB-86830C93A800}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A320E23E-792D-4736-B963-381F9D7AF605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A320E23E-792D-4736-B963-381F9D7AF605}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A320E23E-792D-4736-B963-381F9D7AF605}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A320E23E-792D-4736-B963-381F9D7AF605}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1028,6 +1034,7 @@ Global
{4D83BDA7-2059-41C7-85AE-FEFAD5CD9498} = {6084D52D-775B-4A39-8CD5-AA2F362B5A61}
{8EF31071-3521-409D-9740-BBFBFC04C50E} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
{13219C1C-23E1-4EBA-93FB-86830C93A800} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E}
+ {A320E23E-792D-4736-B963-381F9D7AF605} = {19E6BD61-062B-4FAD-A51A-B55F5CE88B7A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}
diff --git a/aspnet-core/LINGYUN.MicroService.ApiGateway.sln b/aspnet-core/LINGYUN.MicroService.ApiGateway.sln
index 58554c708..101471af6 100644
--- a/aspnet-core/LINGYUN.MicroService.ApiGateway.sln
+++ b/aspnet-core/LINGYUN.MicroService.ApiGateway.sln
@@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EventBus.CAP",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.HttpOverrides", "modules\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj", "{7588F35B-7C0E-4D80-B43A-8A5C9AC6FE03}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.ApiGateway.Host", "services\apigateway\LINGYUN.Abp.ApiGateway.Host\LINGYUN.Abp.ApiGateway.Host.csproj", "{A38139F5-A856-4CA2-89F8-51798ED557CC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -83,6 +85,10 @@ Global
{7588F35B-7C0E-4D80-B43A-8A5C9AC6FE03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7588F35B-7C0E-4D80-B43A-8A5C9AC6FE03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7588F35B-7C0E-4D80-B43A-8A5C9AC6FE03}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A38139F5-A856-4CA2-89F8-51798ED557CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A38139F5-A856-4CA2-89F8-51798ED557CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A38139F5-A856-4CA2-89F8-51798ED557CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A38139F5-A856-4CA2-89F8-51798ED557CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -101,6 +107,7 @@ Global
{D6629DD3-BA0F-44B5-A97F-3B992ECAB85D} = {630FB448-8C5C-438F-930D-B0209407DE6A}
{1DA1835B-9EA4-4095-A8CF-10E2778206D3} = {D6629DD3-BA0F-44B5-A97F-3B992ECAB85D}
{7588F35B-7C0E-4D80-B43A-8A5C9AC6FE03} = {D6629DD3-BA0F-44B5-A97F-3B992ECAB85D}
+ {A38139F5-A856-4CA2-89F8-51798ED557CC} = {F3B1B755-37B6-420B-9E82-A5BDFF2BF647}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B327C65A-BD15-480D-825E-9D5A870C521A}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.EventBus.CAP/DotNetCore/CAP/ConsumerServiceSelector.cs b/aspnet-core/modules/common/LINGYUN.Abp.EventBus.CAP/DotNetCore/CAP/ConsumerServiceSelector.cs
index 36117ae68..f3df9280c 100644
--- a/aspnet-core/modules/common/LINGYUN.Abp.EventBus.CAP/DotNetCore/CAP/ConsumerServiceSelector.cs
+++ b/aspnet-core/modules/common/LINGYUN.Abp.EventBus.CAP/DotNetCore/CAP/ConsumerServiceSelector.cs
@@ -20,6 +20,10 @@ namespace DotNetCore.CAP
public class ConsumerServiceSelector : Internal.ConsumerServiceSelector
{
+ ///
+ /// CAP配置
+ ///
+ protected CapOptions CapOptions { get; }
///
/// Abp分布式事件配置
///
@@ -34,8 +38,10 @@ namespace DotNetCore.CAP
///
public ConsumerServiceSelector(
IServiceProvider serviceProvider,
+ IOptions capOptions,
IOptions distributedEventBusOptions) : base(serviceProvider)
{
+ CapOptions = capOptions.Value;
ServiceProvider = serviceProvider;
AbpDistributedEventBusOptions = distributedEventBusOptions.Value;
}
@@ -70,9 +76,12 @@ namespace DotNetCore.CAP
if (executorDescriptorList.Any(x => new ConsumerExecutorDescriptorComparer().Equals(x, consumerExecutorDescriptor)))
{
// 如果存在多个消费者,后续的消费者需要重新定义分组才能不被 CAP 框架过滤掉
- consumerExecutorDescriptor.Attribute.Group = handler.IsGenericType
- ? handler.GetGenericTypeDefinition().FullName
- : handler.FullName;
+ var groupAliaName = handler.IsGenericType
+ ? handler.GetGenericTypeDefinition().Name
+ : handler.Name;
+
+ // TODO: 2021-05-21 直接使用类型全名作为GroupName会引起用户困惑,加上组别名称在前
+ consumerExecutorDescriptor.Attribute.Group = $"{CapOptions.DefaultGroupName}.{groupAliaName}";
SetSubscribeAttribute(consumerExecutorDescriptor.Attribute);
}
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayHostModule.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayHostModule.cs
new file mode 100644
index 000000000..495fdfed7
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayHostModule.cs
@@ -0,0 +1,168 @@
+using LINGYUN.Abp.AspNetCore.HttpOverrides;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Builder;
+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.Extensions.Hosting;
+using Ocelot.Configuration.Repository;
+using Ocelot.DependencyInjection;
+using Ocelot.Extenssions;
+using Ocelot.Middleware.Multiplexer;
+using Ocelot.Provider.Polly;
+using StackExchange.Redis;
+using System;
+using System.Text;
+using Volo.Abp;
+using Volo.Abp.AspNetCore;
+using Volo.Abp.Autofac;
+using Volo.Abp.Caching;
+using Volo.Abp.Caching.StackExchangeRedis;
+using Volo.Abp.Http.Client;
+using Volo.Abp.Json;
+using Volo.Abp.Modularity;
+using Volo.Abp.Security.Encryption;
+using Volo.Abp.VirtualFileSystem;
+
+namespace LINGYUN.Abp.ApiGateway
+{
+ [DependsOn(
+ typeof(AbpAutofacModule),
+ typeof(AbpHttpClientModule),
+ typeof(AbpCachingStackExchangeRedisModule),
+ typeof(AbpAspNetCoreModule),
+ typeof(AbpAspNetCoreHttpOverridesModule)
+ )]
+ public class AbpApiGatewayHostModule : AbpModule
+ {
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddHttpClient("_AbpApiDefinitionClient");
+ context.Services.AddSingleton();
+ }
+
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var hostingEnvironment = context.Services.GetHostingEnvironment();
+ var configuration = context.Services.GetConfiguration();
+
+ // 解决某些不支持类型的序列化
+ Configure(options =>
+ {
+ // See: https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0#always-use-the-newtonsoft-json
+ options.UseHybridSerializer = false;
+ });
+ // 中文序列化的编码问题
+ //Configure(options =>
+ //{
+ // options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
+ //});
+
+ Configure(configuration.GetSection("ApiGateway"));
+
+ context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.Authority = configuration["AuthServer:Authority"];
+ options.RequireHttpsMetadata = false;
+ options.Audience = configuration["AuthServer:ApiName"];
+ });
+
+ Configure(options =>
+ {
+ // 最好统一命名,不然某个缓存变动其他应用服务有例外发生
+ options.KeyPrefix = "LINGYUN.Abp.Application";
+ // 滑动过期30天
+ options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30);
+ // 绝对过期60天
+ options.GlobalCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
+ });
+
+ Configure(options =>
+ {
+ var redisConfig = ConfigurationOptions.Parse(options.Configuration);
+ options.ConfigurationOptions = redisConfig;
+ options.InstanceName = configuration["Redis:InstanceName"];
+ });
+
+ // 加解密
+ Configure(options =>
+ {
+ var encryptionConfiguration = configuration.GetSection("Encryption");
+ if (encryptionConfiguration.Exists())
+ {
+ options.DefaultPassPhrase = encryptionConfiguration["PassPhrase"] ?? options.DefaultPassPhrase;
+ options.DefaultSalt = encryptionConfiguration.GetSection("Salt").Exists()
+ ? Encoding.ASCII.GetBytes(encryptionConfiguration["Salt"])
+ : options.DefaultSalt;
+ options.InitVectorBytes = encryptionConfiguration.GetSection("InitVector").Exists()
+ ? Encoding.ASCII.GetBytes(encryptionConfiguration["InitVector"])
+ : options.InitVectorBytes;
+ }
+ });
+
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+
+ var mvcBuilder = context.Services.AddMvc();
+ mvcBuilder.AddApplicationPart(typeof(AbpApiGatewayHostModule).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?}");
+ });
+ });
+
+ if (!hostingEnvironment.IsDevelopment())
+ {
+ // Ssl证书
+ var sslOptions = configuration.GetSection("App:SslOptions");
+ if (sslOptions.Exists())
+ {
+ var fileName = sslOptions["FileName"];
+ var password = sslOptions["Password"];
+ Configure(options =>
+ {
+ options.ConfigureEndpointDefaults(cfg =>
+ {
+ cfg.UseHttps(fileName, password);
+ });
+ });
+ }
+
+ var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
+ context.Services
+ .AddDataProtection()
+ .PersistKeysToStackExchangeRedis(redis, "ApiGatewayHost-Protection-Keys");
+ }
+
+ context.Services
+ .AddOcelot()
+ .AddPolly()
+ .AddSingletonDefinedAggregator();
+ }
+
+ public override void OnApplicationInitialization(ApplicationInitializationContext context)
+ {
+ var app = context.GetApplicationBuilder();
+
+ app.UseForwardedHeaders();
+ app.UseAuditing();
+ app.UseStaticFiles();
+ app.UseRouting();
+ app.UseAuthentication();
+ // 启用ws协议
+ app.UseWebSockets();
+ app.UseOcelot().Wait();
+ }
+ }
+}
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayOptions.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayOptions.cs
new file mode 100644
index 000000000..13eb7d228
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/AbpApiGatewayOptions.cs
@@ -0,0 +1,11 @@
+using Ocelot.Configuration.File;
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.ApiGateway
+{
+ public class AbpApiGatewayOptions
+ {
+ public FileGlobalConfiguration GlobalConfiguration { get; set; } = new FileGlobalConfiguration();
+ public List AggrageRouteUrls { get; set; } = new List();
+ }
+}
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Dockerfile b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Dockerfile
new file mode 100644
index 000000000..c94c5a31c
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Dockerfile
@@ -0,0 +1,13 @@
+FROM mcr.microsoft.com/dotnet/aspnet:5.0
+LABEL maintainer="colin.in@foxmail.com"
+WORKDIR /app
+
+COPY . /app
+
+ENV TZ=Asia/Shanghai
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
+
+EXPOSE 80/tcp
+VOLUME [ "./app/Logs" ]
+
+ENTRYPOINT ["dotnet", "LINGYUN.ApiGateway.Host.dll"]
\ No newline at end of file
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj
new file mode 100644
index 000000000..036fe7ce6
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net5.0
+ LINGYUN.Abp.ApiGateway
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj.user b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj.user
new file mode 100644
index 000000000..cff74a90e
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/LINGYUN.Abp.ApiGateway.Host.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ IIS Express
+
+
\ No newline at end of file
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Configuration/Repository/AbpApiDescriptionFileConfigurationRepository.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Configuration/Repository/AbpApiDescriptionFileConfigurationRepository.cs
new file mode 100644
index 000000000..f524033b1
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Configuration/Repository/AbpApiDescriptionFileConfigurationRepository.cs
@@ -0,0 +1,185 @@
+using LINGYUN.Abp.ApiGateway;
+using Microsoft.Extensions.Options;
+using Ocelot.Configuration.File;
+using Ocelot.Middleware.Multiplexer;
+using Ocelot.Responses;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Volo.Abp.Http.Client;
+using Volo.Abp.Http.Client.DynamicProxying;
+using Volo.Abp.Http.Modeling;
+
+namespace Ocelot.Configuration.Repository
+{
+ ///
+ /// 通过定义的RemoteServices来寻找abp端点定义的服务列表
+ /// 在AbpApiGatewayOptions.AggrageRouteUrls中定义聚合的url
+ /// 可以在发现相同端点后加入到聚合路由,聚合路由下游端点会自动加入聚合标识,以发现的聚合数量作为前缀
+ /// 例如/api/abp/api-definition、/api/abp/application-configuration
+ /// 下游端点将变成/aggregate1/api/abp/api-definition、/aggregate1/api/abp/application-configuration
+ /// 上游服务直接调用/api/abp/api-definition、/api/abp/application-configuration即可唤起路由聚合
+ ///
+ public class AbpApiDescriptionFileConfigurationRepository : IFileConfigurationRepository
+ {
+ private readonly AbpRemoteServiceOptions _remoteServiceOptions;
+ private readonly AbpApiGatewayOptions _apiGatewayOptions;
+
+ private readonly IApiDescriptionFinder _apiDescriptionFinder;
+ private readonly IHttpClientFactory _httpClientFactory;
+ public AbpApiDescriptionFileConfigurationRepository(
+ IHttpClientFactory httpClientFactory,
+ IApiDescriptionFinder apiDescriptionFinder,
+ IOptions apiGatewayOptions,
+ IOptions remoteServiceOptions
+ )
+ {
+ _httpClientFactory = httpClientFactory;
+ _apiDescriptionFinder = apiDescriptionFinder;
+
+ _apiGatewayOptions = apiGatewayOptions.Value;
+ _remoteServiceOptions = remoteServiceOptions.Value;
+ }
+ public async Task> Get()
+ {
+ var fileConfiguration = new FileConfiguration
+ {
+ GlobalConfiguration = _apiGatewayOptions.GlobalConfiguration
+ };
+ var apiDescriptionModels = await GetApiDescriptionsAsync();
+ int foundAggrageRouteCount = 0;
+ foreach (var apiDescriptionModel in apiDescriptionModels)
+ {
+ foreach (var moduleModel in apiDescriptionModel.Value.Modules)
+ {
+ foreach (var controllerModel in moduleModel.Value.Controllers)
+ {
+ foreach (var actionModel in controllerModel.Value.Actions)
+ {
+ var action = actionModel.Value;
+ var downstreamUrl = action.Url.EnsureStartsWith('/');
+
+ // TODO: 多个相同的下游路由地址应组合为聚合路由
+
+ // TODO: 下游路由地址已添加
+ var route = fileConfiguration.Routes
+ .Where(route => HasBeenAddedRoute(route, downstreamUrl))
+ .LastOrDefault();
+ string aggregateKey = "";
+
+ if (route != null)
+ {
+ // TODO: 下游方法已添加
+ if (route.UpstreamHttpMethod.Any(method => method.Equals(action.HttpMethod, StringComparison.CurrentCultureIgnoreCase)))
+ {
+ if (_apiGatewayOptions.AggrageRouteUrls.Any(url => url.Equals(downstreamUrl)))
+ {
+ foundAggrageRouteCount++;
+ var aggregateRoute = fileConfiguration.Aggregates
+ .Where(route => HasBeenAddedAggregateRoute(route, downstreamUrl))
+ .FirstOrDefault();
+ if (aggregateRoute == null)
+ {
+ aggregateRoute = new FileAggregateRoute
+ {
+ RouteIsCaseSensitive = false,
+ // TODO: 可实现自定义的聚合提供装置
+ Aggregator = nameof(AbpResponseMergeAggregator),
+ UpstreamPathTemplate = downstreamUrl,
+ RouteKeys = new List()
+ };
+ fileConfiguration.Aggregates.Add(aggregateRoute);
+ }
+ if (route.Key.IsNullOrWhiteSpace())
+ {
+ route.Key = $"aggregate{foundAggrageRouteCount}";
+ route.UpstreamPathTemplate = $"/{route.Key}{route.UpstreamPathTemplate}";
+ aggregateRoute.RouteKeys.Add(route.Key);
+ foundAggrageRouteCount++;
+ }
+ aggregateKey = $"aggregate{foundAggrageRouteCount}";
+ aggregateRoute.RouteKeys.Add(aggregateKey);
+ }
+ else
+ {
+ continue;
+ }
+ }
+ else
+ {
+ route.UpstreamHttpMethod.Add(action.HttpMethod);
+ continue;
+ }
+ }
+
+ var newRoute = new FileRoute
+ {
+ Key = aggregateKey,
+ UpstreamPathTemplate = (aggregateKey + downstreamUrl).EnsureStartsWith('/'),
+ DownstreamPathTemplate = downstreamUrl,
+ DangerousAcceptAnyServerCertificateValidator = false
+ };
+
+ var baseUrl = apiDescriptionModel.Key;
+ baseUrl = baseUrl.StartsWith("http://") ? baseUrl[7..] : baseUrl;
+ baseUrl = baseUrl.StartsWith("https://") ? baseUrl[8..] : baseUrl;
+ baseUrl = baseUrl.EndsWith("/") ? baseUrl[0..^1] : baseUrl;
+
+ var addresses = baseUrl.Split(":");
+ var hostAndPort = new FileHostAndPort
+ {
+ Host = addresses[0]
+ };
+ if (addresses.Length == 2)
+ {
+ hostAndPort.Port = int.Parse(addresses[1]);
+ }
+ newRoute.DownstreamHostAndPorts.Add(hostAndPort);
+ newRoute.UpstreamHttpMethod.Add(action.HttpMethod);
+
+ // abp api版本号支持通过query发送
+ //newRoute.DownstreamHttpVersion = action.SupportedVersions.Last();
+
+ fileConfiguration.Routes.Add(newRoute);
+ }
+ }
+ }
+ }
+
+ return new OkResponse(fileConfiguration);
+ }
+
+ private static bool HasBeenAddedRoute(FileRoute route, string downstreamUrl)
+ {
+ return route.DownstreamPathTemplate.Equals(downstreamUrl, StringComparison.CurrentCultureIgnoreCase);
+ }
+
+ private static bool HasBeenAddedAggregateRoute(FileAggregateRoute route, string upstreamUrl)
+ {
+ return route.UpstreamPathTemplate.Equals(upstreamUrl, StringComparison.CurrentCultureIgnoreCase);
+ }
+
+ public async Task Set(FileConfiguration fileConfiguration)
+ {
+ return await Task.FromResult(new OkResponse(fileConfiguration));
+ }
+
+ protected virtual async Task> GetApiDescriptionsAsync()
+ {
+ var client = _httpClientFactory.CreateClient("_AbpApiDefinitionClient");
+
+ var apiDescriptionModels = new Dictionary();
+
+ foreach (var remoteService in _remoteServiceOptions.RemoteServices)
+ {
+ var model = await _apiDescriptionFinder.GetApiDescriptionAsync(client, remoteService.Value.BaseUrl);
+
+ apiDescriptionModels.Add(remoteService.Value.BaseUrl, model);
+ }
+
+ return apiDescriptionModels;
+ }
+ }
+}
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Extenssions/OcelotMiddlewareExtensions.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Extenssions/OcelotMiddlewareExtensions.cs
new file mode 100644
index 000000000..88765bd26
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Extenssions/OcelotMiddlewareExtensions.cs
@@ -0,0 +1,123 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Ocelot.Configuration;
+using Ocelot.Configuration.Creator;
+using Ocelot.Configuration.Repository;
+using Ocelot.DependencyInjection;
+using Ocelot.Logging;
+using Ocelot.Middleware;
+using Ocelot.Responses;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Ocelot.Extenssions
+{
+ public static class OcelotMiddlewareExtensions
+ {
+ public static async Task UseOcelot(this IApplicationBuilder builder)
+ {
+ await builder.UseOcelot(new OcelotPipelineConfiguration());
+ return builder;
+ }
+
+ public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration)
+ {
+ var config = new OcelotPipelineConfiguration();
+ pipelineConfiguration?.Invoke(config);
+ return await builder.UseOcelot(config);
+ }
+
+ public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
+ {
+ var configuration = await CreateConfiguration(builder);
+
+ ConfigureDiagnosticListener(builder);
+
+ return CreateOcelotPipeline(builder, pipelineConfiguration);
+ }
+
+ public static Task UseOcelot(this IApplicationBuilder app, Action builderAction)
+ => UseOcelot(app, builderAction, new OcelotPipelineConfiguration());
+
+ public static async Task UseOcelot(this IApplicationBuilder app, Action builderAction, OcelotPipelineConfiguration configuration)
+ {
+ await CreateConfiguration(app);
+
+ ConfigureDiagnosticListener(app);
+
+ builderAction?.Invoke(app, configuration ?? new OcelotPipelineConfiguration());
+
+ app.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
+
+ return app;
+ }
+
+ private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
+ {
+ builder.BuildOcelotPipeline(pipelineConfiguration);
+
+ /*
+ inject first delegate into first piece of asp.net middleware..maybe not like this
+ then because we are updating the http context in ocelot it comes out correct for
+ rest of asp.net..
+ */
+
+ builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
+
+ return builder;
+ }
+ private static async Task CreateConfiguration(IApplicationBuilder builder)
+ {
+ var fileConfigRepo = builder.ApplicationServices.GetRequiredService();
+ var fileConfig = await fileConfigRepo.Get();
+ // IOptionsMonitor fileConfig = builder.ApplicationServices.GetService>();
+ IInternalConfigurationCreator internalConfigCreator = builder.ApplicationServices.GetService();
+ Response response = await internalConfigCreator.Create(fileConfig.Data);
+ if (response.IsError)
+ {
+ ThrowToStopOcelotStarting(response);
+ }
+ IInternalConfigurationRepository internalConfigRepo = builder.ApplicationServices.GetService();
+ internalConfigRepo.AddOrReplace(response.Data);
+ return GetOcelotConfigAndReturn(internalConfigRepo);
+ }
+
+ private static bool AdministrationApiInUse(IAdministrationPath adminPath)
+ {
+ return adminPath != null;
+ }
+
+ private static bool IsError(Response response)
+ {
+ return response == null || response.IsError;
+ }
+
+ private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
+ {
+ var ocelotConfiguration = provider.Get();
+
+ if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
+ {
+ ThrowToStopOcelotStarting(ocelotConfiguration);
+ }
+
+ return ocelotConfiguration.Data;
+ }
+
+ private static void ThrowToStopOcelotStarting(Response config)
+ {
+ throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
+ }
+
+ private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
+ {
+ var env = builder.ApplicationServices.GetService();
+ var listener = builder.ApplicationServices.GetService();
+ var diagnosticListener = builder.ApplicationServices.GetService();
+ diagnosticListener.SubscribeWithAdapter(listener);
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Middleware/Multiplexer/AbpResponseMergeAggregator.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Middleware/Multiplexer/AbpResponseMergeAggregator.cs
new file mode 100644
index 000000000..34c351415
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Ocelot/Middleware/Multiplexer/AbpResponseMergeAggregator.cs
@@ -0,0 +1,51 @@
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Ocelot.Multiplexer;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+
+namespace Ocelot.Middleware.Multiplexer
+{
+ public class AbpResponseMergeAggregator : IDefinedAggregator
+ {
+ public async Task Aggregate(List responses)
+ {
+ return await MapAbpApiDefinitionAggregateContentAsync(responses);
+ }
+
+ protected virtual async Task MapAbpApiDefinitionAggregateContentAsync(List responses)
+ {
+ JObject responseObject = null;
+ JsonMergeSettings mergeSetting = new JsonMergeSettings
+ {
+ MergeArrayHandling = MergeArrayHandling.Union,
+ PropertyNameComparison = System.StringComparison.CurrentCultureIgnoreCase
+ };
+ foreach (var httpResponse in responses)
+ {
+ var content = await httpResponse.Items.DownstreamResponse().Content.ReadAsStringAsync();
+ var contentObject = JsonConvert.DeserializeObject(content);
+ if (responseObject == null)
+ {
+ responseObject = JObject.FromObject(contentObject);
+ }
+ else
+ {
+ responseObject.Merge(contentObject, mergeSetting);
+ }
+ }
+ var stringContent = new StringContent(responseObject.ToString())
+ {
+ Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
+ };
+ stringContent.Headers.Add("_AbpErrorFormat", "true");
+ return new DownstreamResponse(stringContent, HttpStatusCode.OK,
+ new List>>(), "OK");
+ }
+
+ }
+}
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Program.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Program.cs
new file mode 100644
index 000000000..47f651df7
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Program.cs
@@ -0,0 +1,47 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using System;
+using System.IO;
+
+namespace LINGYUN.Abp.ApiGateway
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ try
+ {
+ var hostBuilder = CreateHostBuilder(args).Build();
+ Log.Information("Starting web host.");
+ hostBuilder.Run();
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Host terminated unexpectedly!");
+ return 1;
+ }
+ finally
+ {
+ Log.CloseAndFlush();
+ }
+ }
+
+ internal static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder
+ .UseKestrel()
+ .UseStartup();
+ })
+ .UseSerilog((context, provider, config) =>
+ {
+ config.ReadFrom.Configuration(context.Configuration);
+ })
+ .UseAutofac();
+ }
+}
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Properties/launchSettings.json b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Properties/launchSettings.json
new file mode 100644
index 000000000..ca554589e
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Properties/launchSettings.json
@@ -0,0 +1,20 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:54450/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "LINGYUN.Abp.ApiGateway.Host": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:30000"
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Startup.cs b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Startup.cs
new file mode 100644
index 000000000..8dd037ee2
--- /dev/null
+++ b/aspnet-core/services/apigateway/LINGYUN.Abp.ApiGateway.Host/Startup.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace LINGYUN.Abp.ApiGateway
+{
+ public class Startup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddApplication();
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.InitializeApplication();
+ }
+ }
+}
diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Program.cs b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Program.cs
index 05f076f50..52812b9e4 100644
--- a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Program.cs
+++ b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Program.cs
@@ -11,19 +11,12 @@ namespace LINGYUN.ApiGateway
{
public static int Main(string[] args)
{
- var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: true)
- .AddEnvironmentVariables()
- .Build();
- Log.Logger = new LoggerConfiguration()
- .ReadFrom.Configuration(configuration)
- .CreateLogger();
try
{
+ var hostBuilder = CreateHostBuilder(args).Build();
Log.Information("Starting web host.");
- CreateHostBuilder(args).Build().Run();
+ hostBuilder.Run();
+
return 0;
}
catch (Exception ex)
@@ -41,9 +34,14 @@ namespace LINGYUN.ApiGateway
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
- webBuilder.UseStartup();
+ webBuilder
+ .UseKestrel()
+ .UseStartup();
+ })
+ .UseSerilog((context, provider, config) =>
+ {
+ config.ReadFrom.Configuration(context.Configuration);
})
- .UseSerilog()
.UseAutofac();
}
}
diff --git a/vueJs/package-lock.json b/vueJs/package-lock.json
index 8617a18b2..f1fe61585 100644
--- a/vueJs/package-lock.json
+++ b/vueJs/package-lock.json
@@ -5949,9 +5949,9 @@
}
},
"concurrently": {
- "version": "5.2.0",
- "resolved": "https://registry.npm.taobao.org/concurrently/download/concurrently-5.2.0.tgz",
- "integrity": "sha1-6tVRIdCKD8gXCFWEwSPO3sLgiXU=",
+ "version": "5.3.0",
+ "resolved": "https://registry.nlark.com/concurrently/download/concurrently-5.3.0.tgz?cache=0&sync_timestamp=1620443100193&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fconcurrently%2Fdownload%2Fconcurrently-5.3.0.tgz",
+ "integrity": "sha1-dQDeZBDQQ8kSston3jICy0ibHns=",
"dev": true,
"requires": {
"chalk": "^2.4.2",
@@ -8338,8 +8338,8 @@
"dev": true
},
"eve": {
- "version": "git://github.com/adobe-webplatform/eve.git#eef80ed8d188423c2272746fb8ae5cc8dad84cb1",
- "from": "git://github.com/adobe-webplatform/eve.git#eef80ed"
+ "version": "git+ssh://git@github.com/adobe-webplatform/eve.git#eef80ed8d188423c2272746fb8ae5cc8dad84cb1",
+ "from": "eve@git://github.com/adobe-webplatform/eve.git#eef80ed"
},
"event-pubsub": {
"version": "4.3.0",
@@ -18346,10 +18346,10 @@
"dev": true
},
"raphael": {
- "version": "git+https://github.com/nhn/raphael.git#78a6ed3ec269f33b6457b0ec66f8c3d1f2ed70e0",
- "from": "git+https://github.com/nhn/raphael.git#2.2.0-c",
+ "version": "git+ssh://git@github.com/nhn/raphael.git#78a6ed3ec269f33b6457b0ec66f8c3d1f2ed70e0",
+ "from": "raphael@git+https://github.com/nhn/raphael.git#2.2.0-c",
"requires": {
- "eve": "git://github.com/adobe-webplatform/eve.git#eef80ed"
+ "eve": "eve@git://github.com/adobe-webplatform/eve.git#eef80ed"
}
},
"raw-body": {
@@ -18878,17 +18878,118 @@
}
},
"sass": {
- "version": "1.26.5",
- "resolved": "https://registry.npm.taobao.org/sass/download/sass-1.26.5.tgz",
- "integrity": "sha1-LXrs+7q/ophWfI8GYVtuJNLWgJk=",
+ "version": "1.32.13",
+ "resolved": "https://registry.nlark.com/sass/download/sass-1.32.13.tgz?cache=0&sync_timestamp=1620837185067&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsass%2Fdownload%2Fsass-1.32.13.tgz",
+ "integrity": "sha1-jSnISeYlpBW85xYJx8+V4V907QA=",
"dev": true,
"requires": {
- "chokidar": ">=2.0.0 <4.0.0"
+ "chokidar": ">=3.0.0 <4.0.0"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.nlark.com/anymatch/download/anymatch-3.1.2.tgz",
+ "integrity": "sha1-wFV8CWrzLxBhmPT04qODU343hxY=",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.2.0.tgz?cache=0&sync_timestamp=1610299285874&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-2.2.0.tgz",
+ "integrity": "sha1-dfUC7q+f/eQvyYgpZFvk6na9ni0=",
+ "dev": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz",
+ "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-3.5.1.tgz?cache=0&sync_timestamp=1610719499558&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-3.5.1.tgz",
+ "integrity": "sha1-7pznu+vSt59J8wR5nVRo4x4U5oo=",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.1",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.5.0"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz",
+ "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz?cache=0&sync_timestamp=1612536512306&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.3.2.tgz",
+ "integrity": "sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=",
+ "dev": true,
+ "optional": true
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.nlark.com/glob-parent/download/glob-parent-5.1.2.tgz?cache=0&sync_timestamp=1620073321855&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fglob-parent%2Fdownload%2Fglob-parent-5.1.2.tgz",
+ "integrity": "sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz",
+ "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz",
+ "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=",
+ "dev": true
+ },
+ "readdirp": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-3.5.0.tgz?cache=0&sync_timestamp=1615717425931&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freaddirp%2Fdownload%2Freaddirp-3.5.0.tgz",
+ "integrity": "sha1-m6dMAZsV02UnjS6Ru4xI17TULJ4=",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz",
+ "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
}
},
"sass-loader": {
"version": "8.0.2",
- "resolved": "https://registry.npm.taobao.org/sass-loader/download/sass-loader-8.0.2.tgz",
+ "resolved": "https://registry.nlark.com/sass-loader/download/sass-loader-8.0.2.tgz",
"integrity": "sha1-3r7NjDziQ8dkVPLoKQSCFQOACQ0=",
"dev": true,
"requires": {
@@ -18901,7 +19002,7 @@
"dependencies": {
"semver": {
"version": "6.3.0",
- "resolved": "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz?cache=0&sync_timestamp=1586886301819&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz",
+ "resolved": "https://registry.nlark.com/semver/download/semver-6.3.0.tgz?cache=0&sync_timestamp=1618846828850&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz",
"integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=",
"dev": true
}
@@ -19628,8 +19729,8 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"squire-rte": {
- "version": "github:seonim-ryu/Squire#fd40b4e3020845825701e9689f190bab3f4775d4",
- "from": "github:seonim-ryu/Squire#fd40b4e3020845825701e9689f190bab3f4775d4"
+ "version": "git+ssh://git@github.com/seonim-ryu/Squire.git#fd40b4e3020845825701e9689f190bab3f4775d4",
+ "from": "squire-rte@github:seonim-ryu/Squire#fd40b4e3020845825701e9689f190bab3f4775d4"
},
"ssf": {
"version": "0.10.3",
@@ -20751,7 +20852,7 @@
"integrity": "sha1-LRjs3+JlSL+ELoG2woscfiIvs8w=",
"requires": {
"core-js": "^3.6.4",
- "raphael": "git+https://github.com/nhn/raphael.git#2.2.0-c",
+ "raphael": "raphael@git+https://github.com/nhn/raphael.git#2.2.0-c",
"tui-code-snippet": "^2.3.1"
},
"dependencies": {
@@ -20796,7 +20897,7 @@
"markdown-it": "^9.0.0",
"plantuml-encoder": "^1.2.5",
"resize-observer-polyfill": "^1.5.0",
- "squire-rte": "github:seonim-ryu/Squire#fd40b4e3020845825701e9689f190bab3f4775d4",
+ "squire-rte": "squire-rte@github:seonim-ryu/Squire#fd40b4e3020845825701e9689f190bab3f4775d4",
"to-mark": "^1.1.9",
"tui-chart": "^3.7.0",
"tui-code-snippet": "^1.5.0",