Browse Source

增加网关聚合api文档

pull/363/head
cKey 4 years ago
parent
commit
7ced8c7b82
  1. 133
      aspnet-core/database/ApiGateway-Init.sql
  2. 11
      aspnet-core/modules/platform/LINGYUN.Abp.UI.Navigation.VueVbenAdmin/LINGYUN/Abp/UI/Navigation/VueVbenAdmin/AbpUINavigationVueVbenAdminNavigationDefinitionProvider.cs
  3. 1
      aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/BackendAdminHostModule.Configure.cs
  4. 33
      aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/TenantHeaderParamter.cs
  5. 3
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/.gitignore
  6. 24
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.Builder.cs
  7. 224
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.Configure.cs
  8. 153
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.cs
  9. 21
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayOptions.cs
  10. 1
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/LINGYUN.ApiGateway.Host.csproj
  11. 8
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Models/DownstreamOpenApi.cs
  12. 40
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Properties/launchSettings.json
  13. 33
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/TenantHeaderParamter.cs
  14. 28
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/appsettings.Development.json
  15. 7
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/appsettings.json
  16. BIN
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/event-bus-cap.db
  17. 1
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.HttpApi.Host/ApiGatewayHttpApiHostModule.Configure.cs
  18. 33
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.HttpApi.Host/TenantHeaderParamter.cs
  19. 1
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/AbpIdentityServerAdminHttpApiHostModule.Configure.cs
  20. 33
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/TenantHeaderParamter.cs
  21. 1
      aspnet-core/services/localization/LINGYUN.Abp.LocalizationManagement.HttpApi.Host/AbpLocalizationManagementHttpApiHostModule.Configure.cs
  22. 47
      aspnet-core/services/localization/LINGYUN.Abp.LocalizationManagement.HttpApi.Host/LocalizationCacheInitialize.cs
  23. 33
      aspnet-core/services/localization/LINGYUN.Abp.LocalizationManagement.HttpApi.Host/TenantHeaderParamter.cs
  24. 1
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/AbpMessageServiceHttpApiHostModule.Configure.cs
  25. 33
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/TenantHeaderParamter.cs
  26. 1
      aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/AppPlatformHttpApiHostModule.Configure.cs
  27. 4
      aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj.user
  28. 33
      aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantHeaderParamter.cs

133
aspnet-core/database/ApiGateway-Init.sql

@ -11,7 +11,7 @@
Target Server Version : 50736
File Encoding : 65001
Date: 30/10/2021 13:52:29
Date: 05/11/2021 09:45:37
*/
SET NAMES utf8mb4;
@ -36,10 +36,10 @@ INSERT INTO `__efmigrationshistory` VALUES ('20200618090102_Modify-ReRoute-Index
INSERT INTO `__efmigrationshistory` VALUES ('20200908020925_Upgrade-abp-3.1.0', '3.1.7');
-- ----------------------------
-- Table structure for api.gateway.published
-- Table structure for apa.published
-- ----------------------------
DROP TABLE IF EXISTS `api.gateway.published`;
CREATE TABLE `api.gateway.published` (
DROP TABLE IF EXISTS `apa.published`;
CREATE TABLE `apa.published` (
`Id` bigint(20) NOT NULL,
`Version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`Name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
@ -53,14 +53,14 @@ CREATE TABLE `api.gateway.published` (
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of api.gateway.published
-- Records of apa.published
-- ----------------------------
-- ----------------------------
-- Table structure for api.gateway.received
-- Table structure for apa.received
-- ----------------------------
DROP TABLE IF EXISTS `api.gateway.received`;
CREATE TABLE `api.gateway.received` (
DROP TABLE IF EXISTS `apa.received`;
CREATE TABLE `apa.received` (
`Id` bigint(20) NOT NULL,
`Version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`Name` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
@ -123,6 +123,10 @@ CREATE TABLE `appapigatewayaggregateconfig` (
-- ----------------------------
-- Records of appapigatewayaggregateconfig
-- ----------------------------
INSERT INTO `appapigatewayaggregateconfig` VALUES (1, 1418025237863149568, 'fff', NULL, NULL, 11);
INSERT INTO `appapigatewayaggregateconfig` VALUES (2, 1418025237863149568, 'sss', NULL, NULL, 11);
INSERT INTO `appapigatewayaggregateconfig` VALUES (3, 1418025237863149568, '242424', '', '', 11);
INSERT INTO `appapigatewayaggregateconfig` VALUES (4, 1420228420337348608, 'sss', 'ds', 'f', 12);
-- ----------------------------
-- Table structure for appapigatewayauthoptions
@ -136,7 +140,7 @@ CREATE TABLE `appapigatewayauthoptions` (
PRIMARY KEY (`Id`) USING BTREE,
UNIQUE INDEX `IX_AppApiGatewayAuthOptions_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewayAuthOptions_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 240 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewayauthoptions
@ -337,6 +341,12 @@ INSERT INTO `appapigatewayauthoptions` VALUES (236, 1442413171470688256, NULL, '
INSERT INTO `appapigatewayauthoptions` VALUES (237, 1449257280751026176, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (238, 1454289352609521664, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (239, 1454289896489115648, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (240, 1456263181821501440, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (241, 1456263413661655040, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (242, 1456263574232195072, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (243, 1456263679999959040, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (244, 1456263785251823616, NULL, '');
INSERT INTO `appapigatewayauthoptions` VALUES (245, 1456263957046321152, NULL, '');
-- ----------------------------
-- Table structure for appapigatewaybalanceroptions
@ -354,7 +364,7 @@ CREATE TABLE `appapigatewaybalanceroptions` (
UNIQUE INDEX `IX_AppApiGatewayBalancerOptions_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewayBalancerOptions_AppApiGatewayGlobalConfiguratio~` FOREIGN KEY (`ItemId`) REFERENCES `appapigatewayglobalconfiguration` (`ItemId`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `FK_AppApiGatewayBalancerOptions_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 250 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 258 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewaybalanceroptions
@ -562,6 +572,12 @@ INSERT INTO `appapigatewaybalanceroptions` VALUES (246, NULL, 144241317147068825
INSERT INTO `appapigatewaybalanceroptions` VALUES (247, NULL, 1449257280751026176, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (248, NULL, 1454289352609521664, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (249, NULL, 1454289896489115648, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (250, NULL, 1456263181821501440, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (251, NULL, 1456263413661655040, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (252, NULL, 1456263574232195072, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (253, NULL, 1456263679999959040, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (254, NULL, 1456263785251823616, NULL, NULL, NULL);
INSERT INTO `appapigatewaybalanceroptions` VALUES (255, NULL, 1456263957046321152, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for appapigatewaycacheoptions
@ -575,7 +591,7 @@ CREATE TABLE `appapigatewaycacheoptions` (
PRIMARY KEY (`Id`) USING BTREE,
UNIQUE INDEX `IX_AppApiGatewayCacheOptions_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewayCacheOptions_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 240 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewaycacheoptions
@ -776,6 +792,12 @@ INSERT INTO `appapigatewaycacheoptions` VALUES (236, 1442413171470688256, NULL,
INSERT INTO `appapigatewaycacheoptions` VALUES (237, 1449257280751026176, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (238, 1454289352609521664, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (239, 1454289896489115648, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (240, 1456263181821501440, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (241, 1456263413661655040, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (242, 1456263574232195072, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (243, 1456263679999959040, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (244, 1456263785251823616, NULL, NULL);
INSERT INTO `appapigatewaycacheoptions` VALUES (245, 1456263957046321152, NULL, NULL);
-- ----------------------------
-- Table structure for appapigatewaydiscovery
@ -848,7 +870,7 @@ CREATE TABLE `appapigatewayglobalconfiguration` (
-- Records of appapigatewayglobalconfiguration
-- ----------------------------
INSERT INTO `appapigatewayglobalconfiguration` VALUES (1, '{}', 'f7973118f2c2425c8cc96b59883b99aa', 1260841964962947072, NULL, 'http://localhost:30000', 'HTTP', NULL, 0, 1, 'TEST-APP');
I
-- ----------------------------
-- Table structure for appapigatewayheaders
-- ----------------------------
@ -891,7 +913,7 @@ CREATE TABLE `appapigatewayhttpoptions` (
UNIQUE INDEX `IX_AppApiGatewayHttpOptions_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewayHttpOptions_AppApiGatewayGlobalConfiguration_It~` FOREIGN KEY (`ItemId`) REFERENCES `appapigatewayglobalconfiguration` (`ItemId`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `FK_AppApiGatewayHttpOptions_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 250 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 258 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewayhttpoptions
@ -1099,6 +1121,12 @@ INSERT INTO `appapigatewayhttpoptions` VALUES (246, NULL, 1442413171470688256, N
INSERT INTO `appapigatewayhttpoptions` VALUES (247, NULL, 1449257280751026176, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (248, NULL, 1454289352609521664, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (249, NULL, 1454289896489115648, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (250, NULL, 1456263181821501440, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (251, NULL, 1456263413661655040, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (252, NULL, 1456263574232195072, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (253, NULL, 1456263679999959040, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (254, NULL, 1456263785251823616, NULL, 0, 0, 0, 0);
INSERT INTO `appapigatewayhttpoptions` VALUES (255, NULL, 1456263957046321152, NULL, 0, 0, 0, 0);
-- ----------------------------
-- Table structure for appapigatewayqosoptions
@ -1116,7 +1144,7 @@ CREATE TABLE `appapigatewayqosoptions` (
UNIQUE INDEX `IX_AppApiGatewayQoSOptions_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewayQoSOptions_AppApiGatewayGlobalConfiguration_Ite~` FOREIGN KEY (`ItemId`) REFERENCES `appapigatewayglobalconfiguration` (`ItemId`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `FK_AppApiGatewayQoSOptions_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 250 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 258 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewayqosoptions
@ -1324,6 +1352,12 @@ INSERT INTO `appapigatewayqosoptions` VALUES (246, NULL, 1442413171470688256, 50
INSERT INTO `appapigatewayqosoptions` VALUES (247, NULL, 1449257280751026176, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (248, NULL, 1454289352609521664, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (249, NULL, 1454289896489115648, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (250, NULL, 1456263181821501440, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (251, NULL, 1456263413661655040, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (252, NULL, 1456263574232195072, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (253, NULL, 1456263679999959040, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (254, NULL, 1456263785251823616, 50, 60000, 10000);
INSERT INTO `appapigatewayqosoptions` VALUES (255, NULL, 1456263957046321152, 50, 60000, 10000);
-- ----------------------------
-- Table structure for appapigatewayratelimitoptions
@ -1347,6 +1381,7 @@ CREATE TABLE `appapigatewayratelimitoptions` (
-- ----------------------------
INSERT INTO `appapigatewayratelimitoptions` VALUES (1, 1260841964962947072, 'ClientId', '{\n \"error\": {\n \"code\": \"429\",\n \"message\": \"您的操作过快,请稍后再试!\",\n \"details\": \"您的操作过快,请稍后再试!\",\n \"data\": {},\n \"validationErrors\": []\n }\n}', 'ocelot', 1, 429);
-- ----------------------------
-- Table structure for appapigatewayratelimitrule
-- ----------------------------
@ -1365,7 +1400,7 @@ CREATE TABLE `appapigatewayratelimitrule` (
UNIQUE INDEX `IX_AppApiGatewayRateLimitRule_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewayRateLimitRule_AppApiGatewayDynamicReRoute_Dynam~` FOREIGN KEY (`DynamicReRouteId`) REFERENCES `appapigatewaydynamicreroute` (`DynamicReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `FK_AppApiGatewayRateLimitRule_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 240 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewayratelimitrule
@ -1566,6 +1601,12 @@ INSERT INTO `appapigatewayratelimitrule` VALUES (236, 1442413171470688256, NULL,
INSERT INTO `appapigatewayratelimitrule` VALUES (237, 1449257280751026176, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (238, 1454289352609521664, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (239, 1454289896489115648, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (240, 1456263181821501440, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (241, 1456263413661655040, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (242, 1456263574232195072, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (243, 1456263679999959040, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (244, 1456263785251823616, NULL, '', 0, NULL, NULL, NULL);
INSERT INTO `appapigatewayratelimitrule` VALUES (245, 1456263957046321152, NULL, '', 0, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for appapigatewayreroute
@ -1605,7 +1646,7 @@ CREATE TABLE `appapigatewayreroute` (
PRIMARY KEY (`Id`) USING BTREE,
UNIQUE INDEX `AK_AppApiGatewayReRoute_ReRouteId`(`ReRouteId`) USING BTREE,
UNIQUE INDEX `IX_AppApiGatewayReRoute_AppId_DownstreamPathTemplate_UpstreamPa~`(`AppId`, `DownstreamPathTemplate`, `UpstreamPathTemplate`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 503 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 511 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewayreroute
@ -1660,8 +1701,8 @@ INSERT INTO `appapigatewayreroute` VALUES (60, '{}', '8409117162504f71aa66982f05
INSERT INTO `appapigatewayreroute` VALUES (61, '{}', '9f520820071b4e14bc94ab57989cea1f', 1263304204797648896, '【平台服务】- 框架配置', '/api/abp/application-configuration', '', '', '/api/abp/platform/application-configuration', 'GET,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30025,', '', '', 'platform-configuration', 0, 120000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (62, '{}', '530ab314560f41678b40f48da9383d51', 1263304872891555840, '【后台管理】- 租户管理', '/api/tenant-management/tenants', '', '', '/api/tenant-management/tenants', 'GET,POST,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (63, '{}', '21334c6da4c349cc883c38c13de0e754', 1263305106250047488, '【后台管理】- 特定租户管理', '/api/tenant-management/tenants/{id}', '', '', '/api/tenant-management/tenants/{id}', 'GET,PUT,DELETE,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (64, '{}', 'cc8fdf1b2d0b414ebf2dc51a6dc78305', 1263305244594970624, '【后台管理】- 租户连接字符串', '/api/tenant-management/tenants/{id}/connection-string', '', '', '/api/tenant-management/tenants/{id}/connection-string', 'GET,PUT,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 2, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (65, '{}', 'aaf285ed10da4024ba561d5cf8c6322b', 1263305430536855552, '【后台管理】- 特定租户连接字符串', '/api/tenant-management/tenants/{id}/connection-string/{name}', '', '', '/api/tenant-management/tenants/{id}/connection-string/{name}', 'GET,DELETE,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 1, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (64, '{}', 'cc8fdf1b2d0b414ebf2dc51a6dc78305', 1263305244594970624, '【后台管理】- 租户连接字符串', '/api/tenant-management/tenants/{id}/connection-string', '', '', '/api/tenant-management/tenants/{id}/concatenation', 'GET,PUT,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 2, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (65, '{}', 'aaf285ed10da4024ba561d5cf8c6322b', 1263305430536855552, '【后台管理】- 特定租户连接字符串', '/api/tenant-management/tenants/{id}/connection-string/{name}', '', '', '/api/tenant-management/tenants/{id}/concatenation/{name}', 'GET,DELETE,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 1, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (66, '{}', '6a7da198f4c84d94969a437adc47642b', 1263639172959174656, '【后台管理】- 全局设置', '/api/setting-management/settings/by-global', '', '', '/api/setting-management/settings/by-global/app', 'GET,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', 'setting-global', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (67, '{}', '755b4dce5c34444785fa3b647fef4131', 1264799968944640000, '【身份认证服务】- 验证手机号', '/api/account/phone/verify', '', '', '/api/account/phone/verify', 'POST,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30015,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (68, '{}', '535191c570ae453ab320012304d7a62c', 1264800070161584128, '【身份认证服务】- 手机号注册', '/api/account/phone/register', '', '', '/api/account/phone/register', 'POST,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30015,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
@ -1782,13 +1823,18 @@ INSERT INTO `appapigatewayreroute` VALUES (219, '{}', '701ddf9f15844e968f0a98001
INSERT INTO `appapigatewayreroute` VALUES (225, '{}', '13249916a52a4568b55b6c3fa813b374', 1393020696332705792, '【后台管理】- 路由代理测试', '/api/connect', '', '', '/api/connect', 'GET,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (482, '{}', '295034c498744783ba0ecf3b80546ca5', 1395924337284407296, '【后台管理】- 通过名称查询租户', '/api/tenant-management/tenants/name/{name}', '', '', '/api/tenant-management/tenants/name/{name}', 'GET,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30010,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (483, '{}', 'be25633a37d14ab4b94803698c528e4c', 1406817452004757504, '【平台服务】- 参照名称查询字典', '/api/platform/datas/by-name/{name}', '', '', '/api/platform/datas/by-name/{name}', 'GET,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30025,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (484, '{}', 'a95d0fe8a6eb4b5484ebd47878f7092f', 1421397683162664960, '【身份认证服务】- 管理角色声明', '/api​/identity​/roles​/{id}​/claims', '', NULL, '/api​/identity​/roles​/{id}​/claims', 'GET,POST,PUT,DELETE,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30015,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (493, '{}', '7f1af94bf83343fd91bf9e8eaa0e2fc5', 1431803251955654656, '【身份认证服务】- 换取token', '/connect/token', '', '', '/connect/token', 'POST,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:44385,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (494, '{}', 'dd09d208eca64c91b804377f45c85974', 1431806909455851520, '【身份认证服务】- 用户信息', '/connect/userinfo', '', '', '/connect/userinfo', 'GET,POST,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:44385,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (495, '{}', 'f0660047374f43b3bcdf3555799488c6', 1432189824874373120, '【身份认证服务】- 发送变更手机号短信', '/api/identity/my-profile/send-phone-number-change-code', '', '', '/api/identity/my-profile/send-phone-number-change-code', 'PUT,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30015,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (496, '{}', 'da9dbe339b3e453abf79df15fe774bda', 1432190028071624704, '【身份认证服务】- 变更手机号', '/api/identity/my-profile/change-phone-number', '', '', '/api/identity/my-profile/change-phone-number', 'PUT,', '', '', '', '', '', '', '', 1, '', '', 'HTTP', '127.0.0.1:30015,', '', '', '', 0, 30000, 1, '', 'TEST-APP');
IINSERT INTO `appapigatewayreroute` VALUES (501, '{}', '4019724446ad4a609916b6fb4eb2b489', 1454289352609521664, '【后台管理】- 系统日志列表', '/api/auditing/logging', '', NULL, '/api/auditing/logging', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30010,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (501, '{}', '4019724446ad4a609916b6fb4eb2b489', 1454289352609521664, '【后台管理】- 系统日志列表', '/api/auditing/logging', '', NULL, '/api/auditing/logging', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30010,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (502, '{}', '03fd15647a4b41f3bd650fdbaa069cc0', 1454289896489115648, '【后台管理】- 系统日志', '/api/auditing/logging/{everything}', '', NULL, '/api/auditing/logging/{everything}', 'GET,DELETE,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30010,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (503, '{}', '31dfee95c57b4558b9d96c92ed07334d', 1456263181821501440, '【后台管理服务】- Api文档', '/swagger/v1/swagger.json', '', NULL, '/admin/v1/swagger.json', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30010,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (504, '{}', '6dd0dce5366d40e0930c09940253653d', 1456263413661655040, '【身份认证服务】- Api文档', '/swagger/v1/swagger.json', '', NULL, '/ids-admin/v1/swagger.json', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30015,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (505, '{}', '611409b212844d9c9d90445e09c59cd0', 1456263574232195072, '【消息服务】- Api文档', '/swagger/v1/swagger.json', '', NULL, '/messages/v1/swagger.json', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30020,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (506, '{}', 'c2fd89c2cc9a4823be4d691eaad75c45', 1456263679999959040, '【平台服务】- Api文档', '/swagger/v1/swagger.json', '', NULL, '/platform/v1/swagger.json', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30025,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (507, '{}', '814d4b9e3c7b4179950047fb43c1ab82', 1456263785251823616, '【本地化管理】- Api文档', '/swagger/v1/swagger.json', '', NULL, '/localization/v1/swagger.json', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30030,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
INSERT INTO `appapigatewayreroute` VALUES (508, '{}', '34fd7f3411fe487bbf6d1cb487349b86', 1456263957046321152, '【服务网关管理】- Api文档', '/swagger/v1/swagger.json', '', NULL, '/apigateway-admin/v1/swagger.json', 'GET,', '', '', '', '', '', '', NULL, 1, NULL, NULL, 'HTTP', '127.0.0.1:30001,', '', NULL, NULL, 0, 10000, 1, NULL, 'TEST-APP');
-- ----------------------------
-- Table structure for appapigatewayroutegroup
@ -1832,7 +1878,7 @@ CREATE TABLE `appapigatewaysecurityoptions` (
PRIMARY KEY (`Id`) USING BTREE,
UNIQUE INDEX `IX_AppApiGatewaySecurityOptions_ReRouteId`(`ReRouteId`) USING BTREE,
CONSTRAINT `FK_AppApiGatewaySecurityOptions_AppApiGatewayReRoute_ReRouteId` FOREIGN KEY (`ReRouteId`) REFERENCES `appapigatewayreroute` (`ReRouteId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 240 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of appapigatewaysecurityoptions
@ -2033,44 +2079,11 @@ INSERT INTO `appapigatewaysecurityoptions` VALUES (236, 1442413171470688256, '',
INSERT INTO `appapigatewaysecurityoptions` VALUES (237, 1449257280751026176, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (238, 1454289352609521664, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (239, 1454289896489115648, '', '');
-- ----------------------------
-- Table structure for cap.published
-- ----------------------------
DROP TABLE IF EXISTS `cap.published`;
CREATE TABLE `cap.published` (
`Id` bigint(20) NOT NULL,
`Version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`Name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`Content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`Retries` int(11) NULL DEFAULT NULL,
`Added` datetime(0) NOT NULL,
`ExpiresAt` datetime(0) NULL DEFAULT NULL,
`StatusName` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
PRIMARY KEY (`Id`) USING BTREE,
INDEX `IX_ExpiresAt`(`ExpiresAt`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of cap.published
-- ----------------------------
-- ----------------------------
-- Table structure for cap.received
-- ----------------------------
DROP TABLE IF EXISTS `cap.received`;
CREATE TABLE `cap.received` (
`Id` bigint(20) NOT NULL,
`Version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`Name` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`Group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`Content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`Retries` int(11) NULL DEFAULT NULL,
`Added` datetime(0) NOT NULL,
`ExpiresAt` datetime(0) NULL DEFAULT NULL,
`StatusName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
PRIMARY KEY (`Id`) USING BTREE,
INDEX `IX_ExpiresAt`(`ExpiresAt`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `appapigatewaysecurityoptions` VALUES (240, 1456263181821501440, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (241, 1456263413661655040, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (242, 1456263574232195072, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (243, 1456263679999959040, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (244, 1456263785251823616, '', '');
INSERT INTO `appapigatewaysecurityoptions` VALUES (245, 1456263957046321152, '', '');
SET FOREIGN_KEY_CHECKS = 1;

11
aspnet-core/modules/platform/LINGYUN.Abp.UI.Navigation.VueVbenAdmin/LINGYUN/Abp/UI/Navigation/VueVbenAdmin/AbpUINavigationVueVbenAdminNavigationDefinitionProvider.cs

@ -178,6 +178,17 @@ namespace LINGYUN.Abp.UI.Navigation.VueVbenAdmin
component: "/sys/logging/index",
description: "系统日志"));
manage.AddItem(
new ApplicationMenu(
name: "ApiDocument",
displayName: "Api 文档",
url: "/openapi",
component: "IFrame",
description: "Api 文档",
multiTenancySides: MultiTenancySides.Host)
// TODO: 注意在部署完毕之后手动修改此菜单iframe地址
.SetProperty("frameSrc", "http://127.0.0.1:30000/swagger/index.html"));
return new NavigationDefinition(manage);
}

1
aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/BackendAdminHostModule.Configure.cs

@ -206,6 +206,7 @@ namespace LINGYUN.Abp.BackendAdmin
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}

33
aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.BackendAdmin
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}

3
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/.gitignore

@ -1,4 +1,5 @@
bin
obj
Logs
appsettings.*.json
appsettings.*.json
event-bus-cap.db

24
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.Builder.cs

@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace LINGYUN.ApiGateway
{
public partial class ApiGatewayHostModule
{
private void UseSwagger(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Open API Document");
var options = app.ApplicationServices.GetRequiredService<IOptions<ApiGatewayOptions>>().Value;
foreach (var api in options.DownstreamOpenApis)
{
c.SwaggerEndpoint(api.EndPoint, api.Name);
}
});
}
}
}

224
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.Configure.cs

@ -0,0 +1,224 @@
using DotNetCore.CAP;
using LINGYUN.Abp.Serilog.Enrichers.Application;
using LINGYUN.ApiGateway.Localization;
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.OpenApi.Models;
using Ocelot.Configuration.Repository;
using Ocelot.DependencyInjection;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Provider.Polly;
using StackExchange.Redis;
using System;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Localization;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.ApiGateway
{
public partial class ApiGatewayHostModule
{
private void PreConfigureApp()
{
AbpSerilogEnrichersConsts.ApplicationName = "ApiGateWay";
}
private void PreConfigureCAP(IConfiguration configuration)
{
PreConfigure<CapOptions>(options =>
{
options
.UseSqlite("Data Source=./event-bus-cap.db")
.UseDashboard()
.UseRabbitMQ(rabbitMQOptions =>
{
configuration.GetSection("CAP:RabbitMQ").Bind(rabbitMQOptions);
});
});
}
private void PreConfigureApiGateway(IServiceCollection services, IConfiguration configuration)
{
// 不启用则使用本地配置文件的方式启动Ocelot
if (configuration.GetValue<bool>("EnabledDynamicOcelot"))
{
services.AddSingleton<IFileConfigurationRepository, ApiHttpClientFileConfigurationRepository>();
}
}
private void ConfigureKestrelServer(IConfiguration configuration, bool isDevelopment = true)
{
// fix: 不限制请求体大小,解决上传文件问题
Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = null;
options.Limits.MaxRequestBufferSize = null;
});
if (!isDevelopment)
{
// Ssl证书
var sslOptions = configuration.GetSection("App:SslOptions");
if (sslOptions.Exists())
{
var fileName = sslOptions["FileName"];
var password = sslOptions["Password"];
Configure<KestrelServerOptions>(options =>
{
options.ConfigureEndpointDefaults(cfg =>
{
cfg.UseHttps(fileName, password);
});
});
}
}
}
private void ConfigureAbpAutoMapper()
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<ApiGatewayMapperProfile>(validate: true);
});
}
private void ConfigureJsonSerializer()
{
// 中文序列化的编码问题
Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
}
private void ConfigureVirtualFileSystem()
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<ApiGatewayHostModule>();
});
}
private void ConfigureLocalization()
{
Configure<AbpLocalizationOptions>(options =>
{
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
options.Resources
.Get<ApiGatewayResource>()
.AddVirtualJson("/Localization/Host");
});
}
private void ConfigureMvc(IServiceCollection services)
{
var mvcBuilder = services.AddMvc();
mvcBuilder.AddApplicationPart(typeof(ApiGatewayHostModule).Assembly);
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add(endpointContext =>
{
endpointContext.Endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
endpointContext.Endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
});
}
private void ConfigureOcelot(IServiceCollection services)
{
services
.AddOcelot()
.AddPolly()
.AddSingletonDefinedAggregator<AbpApiDefinitionAggregator>();
}
private void ConfigureCaching(IConfiguration configuration)
{
Configure<AbpDistributedCacheOptions>(options =>
{
// 最好统一命名,不然某个缓存变动其他应用服务有例外发生
options.KeyPrefix = "LINGYUN.Abp.Application";
// 滑动过期30天
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30);
// 绝对过期60天
options.GlobalCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
});
Configure<RedisCacheOptions>(options =>
{
var redisConfig = ConfigurationOptions.Parse(options.Configuration);
options.ConfigurationOptions = redisConfig;
options.InstanceName = configuration["Redis:InstanceName"];
});
}
private void ConfigureApiGateway(IConfiguration configuration)
{
Configure<ApiGatewayOptions>(configuration.GetSection("ApiGateway"));
}
private void ConfigureSwagger(IServiceCollection services)
{
services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Open API Document", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "bearer",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
},
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}
private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.Audience = configuration["AuthServer:ApiName"];
});
if (!isDevelopment)
{
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
services
.AddDataProtection()
.PersistKeysToStackExchangeRedis(redis, "ApiGatewayHost-Protection-Keys");
}
}
}
}

153
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayHostModule.cs

@ -1,38 +1,18 @@
using DotNetCore.CAP;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.Serilog.Enrichers.Application;
using LINGYUN.ApiGateway.Localization;
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.Encodings.Web;
using System.Text.Unicode;
using Volo.Abp;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.Http.Client.IdentityModel;
using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.ApiGateway
{
@ -47,29 +27,15 @@ namespace LINGYUN.ApiGateway
typeof(AbpAspNetCoreSerilogModule),
typeof(AbpAspNetCoreHttpOverridesModule)
)]
public class ApiGatewayHostModule : AbpModule
public partial class ApiGatewayHostModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AbpSerilogEnrichersConsts.ApplicationName = "ApiGateWay";
var configuration = context.Services.GetConfiguration();
// 不启用则使用本地配置文件的方式启动Ocelot
if (configuration.GetValue<bool>("EnabledDynamicOcelot"))
{
context.Services.AddSingleton<IFileConfigurationRepository, ApiHttpClientFileConfigurationRepository>();
}
PreConfigure<CapOptions>(options =>
{
options
.UseSqlite("Data Source=./event-bus-cap.db")
.UseDashboard()
.UseRabbitMQ(rabbitMQOptions =>
{
configuration.GetSection("CAP:RabbitMQ").Bind(rabbitMQOptions);
});
});
PreConfigureApp();
PreConfigureCAP(configuration);
PreConfigureApiGateway(context.Services, configuration);
}
public override void ConfigureServices(ServiceConfigurationContext context)
@ -77,105 +43,20 @@ namespace LINGYUN.ApiGateway
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
// fix: 不限制请求体大小,解决上传文件问题
Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = null;
options.Limits.MaxRequestBufferSize = null;
});
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<ApiGatewayMapperProfile>(validate: true);
});
Configure<ApiGatewayOptions>(configuration.GetSection("ApiGateway"));
// 中文序列化的编码问题
Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.Audience = configuration["AuthServer:ApiName"];
});
Configure<AbpDistributedCacheOptions>(options =>
{
// 最好统一命名,不然某个缓存变动其他应用服务有例外发生
options.KeyPrefix = "LINGYUN.Abp.Application";
// 滑动过期30天
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30);
// 绝对过期60天
options.GlobalCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
});
Configure<RedisCacheOptions>(options =>
{
var redisConfig = ConfigurationOptions.Parse(options.Configuration);
options.ConfigurationOptions = redisConfig;
options.InstanceName = configuration["Redis:InstanceName"];
});
ConfigureLocalization();
ConfigureAbpAutoMapper();
ConfigureJsonSerializer();
ConfigureVirtualFileSystem();
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<ApiGatewayHostModule>();
});
ConfigureCaching(configuration);
ConfigureApiGateway(configuration);
Configure<AbpLocalizationOptions>(options =>
{
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
ConfigureKestrelServer(configuration, hostingEnvironment.IsDevelopment());
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
options.Resources
.Get<ApiGatewayResource>()
.AddVirtualJson("/Localization/Host");
});
var mvcBuilder = context.Services.AddMvc();
mvcBuilder.AddApplicationPart(typeof(ApiGatewayHostModule).Assembly);
Configure<AbpEndpointRouterOptions>(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<KestrelServerOptions>(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<AbpApiDefinitionAggregator>();
ConfigureMvc(context.Services);
ConfigureSwagger(context.Services);
ConfigureOcelot(context.Services);
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -196,7 +77,7 @@ namespace LINGYUN.ApiGateway
appNext.UseRouting();
appNext.UseConfiguredEndpoints();
});
UseSwagger(app);
// 启用ws协议
app.UseWebSockets();
app.UseAbpSerilogEnrichers();

21
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/ApiGatewayOptions.cs

@ -1,7 +1,14 @@
namespace LINGYUN.ApiGateway
{
public class ApiGatewayOptions
{
public string AppId { get; set; }
}
}
using LINGYUN.ApiGateway.Models;
namespace LINGYUN.ApiGateway
{
public class ApiGatewayOptions
{
public string AppId { get; set; }
public DownstreamOpenApi[] DownstreamOpenApis { get; set; }
public ApiGatewayOptions()
{
DownstreamOpenApis = new DownstreamOpenApi[0];
}
}
}

1
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/LINGYUN.ApiGateway.Host.csproj

@ -25,6 +25,7 @@
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="8.4.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.3" />
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="4.4.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="4.4.0" />
<PackageReference Include="Volo.Abp.Autofac" Version="4.4.0" />

8
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Models/DownstreamOpenApi.cs

@ -0,0 +1,8 @@
namespace LINGYUN.ApiGateway.Models
{
public class DownstreamOpenApi
{
public string Name { get; set; }
public string EndPoint { get; set; }
}
}

40
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/Properties/launchSettings.json

@ -1,20 +1,20 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:30000",
"sslPort": 0
}
},
"profiles": {
"LINGYUN.ApiGateway.Host": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:30000;https://localhost:30443",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:30000",
"sslPort": 0
}
},
"profiles": {
"LINGYUN.ApiGateway.Host": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:30000;https://localhost:30443",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

33
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.ApiGateway
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}

28
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/appsettings.Development.json

@ -9,7 +9,33 @@
},
"EnabledDynamicOcelot": true,
"ApiGateway": {
"AppId": "TEST-APP" //OcelotAPI,
"AppId": "TEST-APP",
"DownstreamOpenApis": [
{
"Name": "ApiGateway Admin API",
"EndPoint": "/apigateway-admin/v1/swagger.json"
},
{
"Name": "Backend Admin API",
"EndPoint": "/admin/v1/swagger.json"
},
{
"Name": "Platform API",
"EndPoint": "/platform/v1/swagger.json"
},
{
"Name": "Localization API",
"EndPoint": "/localization/v1/swagger.json"
},
{
"Name": "Messages API",
"EndPoint": "/messages/v1/swagger.json"
},
{
"Name": "IdentityServer Admin API",
"EndPoint": "/ids-admin/v1/swagger.json"
}
]
},
"Redis": {
"Configuration": "localhost,defaultDatabase=10",

7
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/appsettings.json

@ -1,4 +1,11 @@
{
"AuthServer": {
"Authority": "http://10.21.15.28:44385/",
"ApiName": "lingyun-abp-application",
"SwaggerClientId": "ApigatewayHostClient",
"SwaggerClientSecret": "1q2w3e*",
"ApiSocpes": "lingyun-abp-application"
},
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",
"InitVectorBytes": "s83ng0abvd02js84",

BIN
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/event-bus-cap.db

Binary file not shown.

1
aspnet-core/services/apigateway/LINGYUN.ApiGateway.HttpApi.Host/ApiGatewayHttpApiHostModule.Configure.cs

@ -152,6 +152,7 @@ namespace LINGYUN.ApiGateway
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}

33
aspnet-core/services/apigateway/LINGYUN.ApiGateway.HttpApi.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.ApiGateway
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}

1
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/AbpIdentityServerAdminHttpApiHostModule.Configure.cs

@ -215,6 +215,7 @@ namespace LINGYUN.Abp.IdentityServer4
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}

33
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.IdentityServer4
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}

1
aspnet-core/services/localization/LINGYUN.Abp.LocalizationManagement.HttpApi.Host/AbpLocalizationManagementHttpApiHostModule.Configure.cs

@ -170,6 +170,7 @@ namespace LINGYUN.Abp.LocalizationManagement
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}

47
aspnet-core/services/localization/LINGYUN.Abp.LocalizationManagement.HttpApi.Host/LocalizationCacheInitialize.cs

@ -1,47 +0,0 @@
using LINGYUN.Abp.Localization.Dynamic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.LocalizationManagement
{
public interface ILocalizationCacheInitialize : IAsyncInitialize
{
}
public class LocalizationCacheInitialize : ILocalizationCacheInitialize, ITransientDependency
{
protected IDistributedCache<LocalizationCacheItem> Cache { get; }
protected ITextRepository TextRepository { get; }
public LocalizationCacheInitialize(
IDistributedCache<LocalizationCacheItem> cache,
ITextRepository textRepository)
{
Cache = cache;
TextRepository = textRepository;
}
public virtual async Task InitializeAsync()
{
var texts = await TextRepository.GetListAsync();
foreach (var textGroup in texts.GroupBy(x => x.ResourceName))
{
foreach (var textCulture in textGroup.GroupBy(x => x.CultureName))
{
var cacheKey = LocalizationCacheItem.NormalizeKey(textGroup.Key, textCulture.Key);
var cacheItem = new LocalizationCacheItem(
textGroup.Key,
textCulture.Key,
textCulture
.Select(x => new LocalizationText(x.Key, x.Value))
.ToList());
await Cache.SetAsync(cacheKey, cacheItem);
}
}
}
}
}

33
aspnet-core/services/localization/LINGYUN.Abp.LocalizationManagement.HttpApi.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.LocalizationManagement
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}

1
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/AbpMessageServiceHttpApiHostModule.Configure.cs

@ -185,6 +185,7 @@ namespace LINGYUN.Abp.MessageService
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}

33
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}

1
aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/AppPlatformHttpApiHostModule.Configure.cs

@ -215,6 +215,7 @@ namespace LINGYUN.Platform
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}

4
aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj.user

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
<PropertyGroup>
<ShowAllFiles>true</ShowAllFiles>
</PropertyGroup>
</Project>

33
aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantHeaderParamter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Platform
{
public class TenantHeaderParamter : IOperationFilter
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id in http header",
Required = false
});
}
}
}
}
Loading…
Cancel
Save