From b8a76dfd939fb2efbf5c2963d69abda6893a44f1 Mon Sep 17 00:00:00 2001 From: Sam <315859133@qq.com> Date: Thu, 5 Feb 2026 14:06:23 +0800 Subject: [PATCH] feat(console): add ASP.NET Core Identity dependency --- .config/dotnet-tools.json | 13 - .gitignore | 130 - .vscode/settings.json | 3 - CONSOLE_DEVELOPMENT.md | 316 -- Fengling.Console.csproj | 25 + Fengling.Refactory.Buiding.sln | 54 - LAYOUT_FIX.md | 72 - OpenCode.md | 288 -- README.md | 370 --- docker/docker-compose.yml | 20 - docs/plans/2025-02-01-auth-service.md | 1023 ------- .../2025-02-01-microservices-architecture.md | 216 -- docs/plans/fengling-console-plan.md | 203 -- docs/task-01-create-project-structure.md | 100 - docs/task-02-create-database-models.md | 128 - docs/task-03-configure-openiddict.md | 202 -- docs/task-04-create-auth-controller.md | 171 -- docs/task-05-create-openiddict-endpoints.md | 186 -- docs/task-06-create-seed-data.md | 154 - docs/task-07-create-health-check.md | 77 - docs/task-08-create-dockerfile.md | 74 - docs/task-10-add-oauth-client-management.md | 176 -- docs/task-11-pre-register-console-client.md | 69 - docs/task-12-create-console-project.md | 101 - docs/task-12-create-console-web-project.md | 152 - docs/testing-guide.md | 398 --- package-lock.json | 1421 --------- package.json | 9 - sql/init.sql | 89 - src/Fengling.AuthService/.dockerignore | 5 - .../Configuration/OpenIddictSetup.cs | 53 - .../Controllers/AccessLogsController.cs | 158 - .../Controllers/AccountController.cs | 119 - .../Controllers/AuditLogsController.cs | 159 - .../Controllers/AuthorizationController.cs | 217 -- .../Controllers/DashboardController.cs | 61 - .../Controllers/LogoutController.cs | 71 - .../Controllers/OAuthClientsController.cs | 263 -- .../Controllers/RolesController.cs | 290 -- .../Controllers/StatsController.cs | 62 - .../Controllers/TenantsController.cs | 344 --- .../Controllers/TokenController.cs | 255 -- .../Controllers/UsersController.cs | 291 -- .../Data/ApplicationDbContext.cs | 99 - .../Data/ApplicationDbContextFactory.cs | 16 - .../20260201153600_InitialCreate.Designer.cs | 312 -- .../20260201153600_InitialCreate.cs | 244 -- ...202015716_AddOAuthApplications.Designer.cs | 379 --- .../20260202015716_AddOAuthApplications.cs | 53 - ...0260202031310_AddTenantAndLogs.Designer.cs | 621 ---- .../20260202031310_AddTenantAndLogs.cs | 214 -- ...0202064650_AddOAuthDescription.Designer.cs | 625 ---- .../20260202064650_AddOAuthDescription.cs | 29 - .../ApplicationDbContextModelSnapshot.cs | 622 ---- src/Fengling.AuthService/Data/SeedData.cs | 143 - src/Fengling.AuthService/Dockerfile | 19 - .../Fengling.AuthService.csproj | 35 - .../Fengling.AuthService.http | 6 - src/Fengling.AuthService/Models/AccessLog.cs | 43 - .../Models/ApplicationRole.cs | 14 - .../Models/ApplicationUser.cs | 13 - src/Fengling.AuthService/Models/AuditLog.cs | 47 - .../Models/OAuthApplication.cs | 21 - src/Fengling.AuthService/Models/Tenant.cs | 47 - src/Fengling.AuthService/Program.cs | 120 - .../Properties/launchSettings.json | 14 - src/Fengling.AuthService/README.md | 61 - .../ViewModels/AuthorizeViewModel.cs | 6 - .../ViewModels/DashboardViewModel.cs | 7 - .../ViewModels/LoginViewModel.cs | 14 - .../ViewModels/RegisterViewModel.cs | 26 - .../Views/Account/Login.cshtml | 81 - .../Views/Account/Register.cshtml | 95 - .../Views/Authorization/Authorize.cshtml | 146 - .../Views/Dashboard/Index.cshtml | 155 - .../Views/Dashboard/Profile.cshtml | 50 - .../Views/Dashboard/Settings.cshtml | 90 - .../Views/Shared/_Layout.cshtml | 162 -- .../Views/_ViewImports.cshtml | 3 - .../Views/_ViewStart.cshtml | 3 - .../appsettings.Development.json | 8 - .../appsettings.Testing.json | 22 - src/Fengling.AuthService/appsettings.json | 21 - .../wwwroot/css/styles.css | 210 -- src/Fengling.AuthService/wwwroot/login.html | 193 -- src/Fengling.Console.Web/.env.development | 7 - src/Fengling.Console.Web/.env.production | 7 - src/Fengling.Console.Web/.gitignore | 24 - src/Fengling.Console.Web/README.md | 139 - src/Fengling.Console.Web/index.html | 13 - src/Fengling.Console.Web/package-lock.json | 1500 ---------- src/Fengling.Console.Web/package.json | 23 - .../public/silent-renew.html | 25 - src/Fengling.Console.Web/public/vite.svg | 1 - src/Fengling.Console.Web/src/App.vue | 25 - src/Fengling.Console.Web/src/api/gateway.ts | 88 - src/Fengling.Console.Web/src/api/index.ts | 49 - src/Fengling.Console.Web/src/assets/vue.svg | 1 - .../src/components/HelloWorld.vue | 41 - src/Fengling.Console.Web/src/main.ts | 15 - src/Fengling.Console.Web/src/router/index.ts | 83 - src/Fengling.Console.Web/src/services/oidc.ts | 87 - src/Fengling.Console.Web/src/stores/auth.ts | 104 - src/Fengling.Console.Web/src/style.css | 79 - .../src/views/Audit/AccessLog.vue | 346 --- .../src/views/Audit/AuditLog.vue | 377 --- .../src/views/Auth/Callback.vue | 88 - .../src/views/Auth/Login.vue | 59 - .../src/views/Dashboard/Dashboard.vue | 257 -- .../src/views/Gateway/Dashboard.vue | 192 -- .../src/views/OAuth/ClientList.vue | 496 ---- .../src/views/Users/RoleList.vue | 404 --- .../src/views/Users/TenantList.vue | 575 ---- .../src/views/Users/UserList.vue | 490 ---- src/Fengling.Console.Web/tsconfig.app.json | 20 - src/Fengling.Console.Web/tsconfig.json | 7 - src/Fengling.Console.Web/tsconfig.node.json | 26 - src/Fengling.Console.Web/vite.config.ts | 29 - src/YarpGateway.Admin/.env.development | 1 - src/YarpGateway.Admin/.gitignore | 24 - src/YarpGateway.Admin/.vscode/extensions.json | 3 - src/YarpGateway.Admin/Dockerfile | 18 - src/YarpGateway.Admin/README.md | 167 -- src/YarpGateway.Admin/index.html | 13 - src/YarpGateway.Admin/nginx.conf | 21 - src/YarpGateway.Admin/package-lock.json | 2560 ----------------- src/YarpGateway.Admin/package.json | 27 - src/YarpGateway.Admin/public/vite.svg | 1 - src/YarpGateway.Admin/src/App.vue | 27 - src/YarpGateway.Admin/src/api/index.ts | 92 - src/YarpGateway.Admin/src/assets/vue.svg | 1 - .../src/components/HelloWorld.vue | 41 - .../src/components/Layout.vue | 132 - src/YarpGateway.Admin/src/main.ts | 20 - src/YarpGateway.Admin/src/router/index.ts | 45 - src/YarpGateway.Admin/src/stores/tenant.ts | 43 - src/YarpGateway.Admin/src/style.css | 23 - .../src/views/ClusterInstances.vue | 145 - src/YarpGateway.Admin/src/views/Dashboard.vue | 226 -- .../src/views/GlobalRoutes.vue | 128 - .../src/views/TenantList.vue | 120 - .../src/views/TenantRoutes.vue | 139 - src/YarpGateway.Admin/tsconfig.app.json | 16 - src/YarpGateway.Admin/tsconfig.json | 7 - src/YarpGateway.Admin/tsconfig.node.json | 26 - src/YarpGateway.Admin/vite.config.ts | 21 - .../Config/DatabaseClusterConfigProvider.cs | 99 - .../Config/DatabaseRouteConfigProvider.cs | 83 - src/YarpGateway/Config/JwtConfig.cs | 9 - src/YarpGateway/Config/RedisConfig.cs | 8 - .../Controllers/GatewayConfigController.cs | 272 -- src/YarpGateway/Data/GatewayDbContext.cs | 52 - .../Data/GatewayDbContextFactory.cs | 22 - src/YarpGateway/Dockerfile | 14 - .../DynamicProxyConfigProvider.cs | 69 - .../DistributedWeightedRoundRobinPolicy.cs | 243 -- src/YarpGateway/Metrics/GatewayMetrics.cs | 30 - .../Middleware/JwtTransformMiddleware.cs | 83 - .../Middleware/TenantRoutingMiddleware.cs | 63 - .../20260201120312_InitialCreate.Designer.cs | 209 -- .../20260201120312_InitialCreate.cs | 133 - ...33826_AddIsGlobalToTenantRoute.Designer.cs | 205 -- ...20260201133826_AddIsGlobalToTenantRoute.cs | 87 - .../GatewayDbContextModelSnapshot.cs | 202 -- src/YarpGateway/Migrations/script.sql | 89 - src/YarpGateway/Models/GwServiceInstance.cs | 18 - src/YarpGateway/Models/GwTenant.cs | 15 - src/YarpGateway/Models/GwTenantRoute.cs | 19 - src/YarpGateway/Program.cs | 113 - .../Properties/launchSettings.json | 14 - .../Services/RedisConnectionManager.cs | 138 - src/YarpGateway/Services/RouteCache.cs | 138 - src/YarpGateway/YarpGateway.csproj | 23 - src/YarpGateway/appsettings.Development.json | 8 - src/YarpGateway/appsettings.json | 64 - src/YarpGateway/logs/gateway-20260201.log | 1867 ------------ src/YarpGateway/sql/init.sql | 72 - src/YarpGateway/sql/update-isglobal.sql | 174 -- .../AccountIntegrationTests.cs | 108 - .../Fengling.AuthService.Tests.csproj | 28 - 180 files changed, 25 insertions(+), 28187 deletions(-) delete mode 100644 .config/dotnet-tools.json delete mode 100644 .gitignore delete mode 100644 .vscode/settings.json delete mode 100644 CONSOLE_DEVELOPMENT.md create mode 100644 Fengling.Console.csproj delete mode 100644 Fengling.Refactory.Buiding.sln delete mode 100644 LAYOUT_FIX.md delete mode 100644 OpenCode.md delete mode 100644 README.md delete mode 100644 docker/docker-compose.yml delete mode 100644 docs/plans/2025-02-01-auth-service.md delete mode 100644 docs/plans/2025-02-01-microservices-architecture.md delete mode 100644 docs/plans/fengling-console-plan.md delete mode 100644 docs/task-01-create-project-structure.md delete mode 100644 docs/task-02-create-database-models.md delete mode 100644 docs/task-03-configure-openiddict.md delete mode 100644 docs/task-04-create-auth-controller.md delete mode 100644 docs/task-05-create-openiddict-endpoints.md delete mode 100644 docs/task-06-create-seed-data.md delete mode 100644 docs/task-07-create-health-check.md delete mode 100644 docs/task-08-create-dockerfile.md delete mode 100644 docs/task-10-add-oauth-client-management.md delete mode 100644 docs/task-11-pre-register-console-client.md delete mode 100644 docs/task-12-create-console-project.md delete mode 100644 docs/task-12-create-console-web-project.md delete mode 100644 docs/testing-guide.md delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 sql/init.sql delete mode 100644 src/Fengling.AuthService/.dockerignore delete mode 100644 src/Fengling.AuthService/Configuration/OpenIddictSetup.cs delete mode 100644 src/Fengling.AuthService/Controllers/AccessLogsController.cs delete mode 100644 src/Fengling.AuthService/Controllers/AccountController.cs delete mode 100644 src/Fengling.AuthService/Controllers/AuditLogsController.cs delete mode 100644 src/Fengling.AuthService/Controllers/AuthorizationController.cs delete mode 100644 src/Fengling.AuthService/Controllers/DashboardController.cs delete mode 100644 src/Fengling.AuthService/Controllers/LogoutController.cs delete mode 100644 src/Fengling.AuthService/Controllers/OAuthClientsController.cs delete mode 100644 src/Fengling.AuthService/Controllers/RolesController.cs delete mode 100644 src/Fengling.AuthService/Controllers/StatsController.cs delete mode 100644 src/Fengling.AuthService/Controllers/TenantsController.cs delete mode 100644 src/Fengling.AuthService/Controllers/TokenController.cs delete mode 100644 src/Fengling.AuthService/Controllers/UsersController.cs delete mode 100644 src/Fengling.AuthService/Data/ApplicationDbContext.cs delete mode 100644 src/Fengling.AuthService/Data/ApplicationDbContextFactory.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.Designer.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.Designer.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.Designer.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.Designer.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.cs delete mode 100644 src/Fengling.AuthService/Data/Migrations/ApplicationDbContextModelSnapshot.cs delete mode 100644 src/Fengling.AuthService/Data/SeedData.cs delete mode 100644 src/Fengling.AuthService/Dockerfile delete mode 100644 src/Fengling.AuthService/Fengling.AuthService.csproj delete mode 100644 src/Fengling.AuthService/Fengling.AuthService.http delete mode 100644 src/Fengling.AuthService/Models/AccessLog.cs delete mode 100644 src/Fengling.AuthService/Models/ApplicationRole.cs delete mode 100644 src/Fengling.AuthService/Models/ApplicationUser.cs delete mode 100644 src/Fengling.AuthService/Models/AuditLog.cs delete mode 100644 src/Fengling.AuthService/Models/OAuthApplication.cs delete mode 100644 src/Fengling.AuthService/Models/Tenant.cs delete mode 100644 src/Fengling.AuthService/Program.cs delete mode 100644 src/Fengling.AuthService/Properties/launchSettings.json delete mode 100644 src/Fengling.AuthService/README.md delete mode 100644 src/Fengling.AuthService/ViewModels/AuthorizeViewModel.cs delete mode 100644 src/Fengling.AuthService/ViewModels/DashboardViewModel.cs delete mode 100644 src/Fengling.AuthService/ViewModels/LoginViewModel.cs delete mode 100644 src/Fengling.AuthService/ViewModels/RegisterViewModel.cs delete mode 100644 src/Fengling.AuthService/Views/Account/Login.cshtml delete mode 100644 src/Fengling.AuthService/Views/Account/Register.cshtml delete mode 100644 src/Fengling.AuthService/Views/Authorization/Authorize.cshtml delete mode 100644 src/Fengling.AuthService/Views/Dashboard/Index.cshtml delete mode 100644 src/Fengling.AuthService/Views/Dashboard/Profile.cshtml delete mode 100644 src/Fengling.AuthService/Views/Dashboard/Settings.cshtml delete mode 100644 src/Fengling.AuthService/Views/Shared/_Layout.cshtml delete mode 100644 src/Fengling.AuthService/Views/_ViewImports.cshtml delete mode 100644 src/Fengling.AuthService/Views/_ViewStart.cshtml delete mode 100644 src/Fengling.AuthService/appsettings.Development.json delete mode 100644 src/Fengling.AuthService/appsettings.Testing.json delete mode 100644 src/Fengling.AuthService/appsettings.json delete mode 100644 src/Fengling.AuthService/wwwroot/css/styles.css delete mode 100644 src/Fengling.AuthService/wwwroot/login.html delete mode 100644 src/Fengling.Console.Web/.env.development delete mode 100644 src/Fengling.Console.Web/.env.production delete mode 100644 src/Fengling.Console.Web/.gitignore delete mode 100644 src/Fengling.Console.Web/README.md delete mode 100644 src/Fengling.Console.Web/index.html delete mode 100644 src/Fengling.Console.Web/package-lock.json delete mode 100644 src/Fengling.Console.Web/package.json delete mode 100644 src/Fengling.Console.Web/public/silent-renew.html delete mode 100644 src/Fengling.Console.Web/public/vite.svg delete mode 100644 src/Fengling.Console.Web/src/App.vue delete mode 100644 src/Fengling.Console.Web/src/api/gateway.ts delete mode 100644 src/Fengling.Console.Web/src/api/index.ts delete mode 100644 src/Fengling.Console.Web/src/assets/vue.svg delete mode 100644 src/Fengling.Console.Web/src/components/HelloWorld.vue delete mode 100644 src/Fengling.Console.Web/src/main.ts delete mode 100644 src/Fengling.Console.Web/src/router/index.ts delete mode 100644 src/Fengling.Console.Web/src/services/oidc.ts delete mode 100644 src/Fengling.Console.Web/src/stores/auth.ts delete mode 100644 src/Fengling.Console.Web/src/style.css delete mode 100644 src/Fengling.Console.Web/src/views/Audit/AccessLog.vue delete mode 100644 src/Fengling.Console.Web/src/views/Audit/AuditLog.vue delete mode 100644 src/Fengling.Console.Web/src/views/Auth/Callback.vue delete mode 100644 src/Fengling.Console.Web/src/views/Auth/Login.vue delete mode 100644 src/Fengling.Console.Web/src/views/Dashboard/Dashboard.vue delete mode 100644 src/Fengling.Console.Web/src/views/Gateway/Dashboard.vue delete mode 100644 src/Fengling.Console.Web/src/views/OAuth/ClientList.vue delete mode 100644 src/Fengling.Console.Web/src/views/Users/RoleList.vue delete mode 100644 src/Fengling.Console.Web/src/views/Users/TenantList.vue delete mode 100644 src/Fengling.Console.Web/src/views/Users/UserList.vue delete mode 100644 src/Fengling.Console.Web/tsconfig.app.json delete mode 100644 src/Fengling.Console.Web/tsconfig.json delete mode 100644 src/Fengling.Console.Web/tsconfig.node.json delete mode 100644 src/Fengling.Console.Web/vite.config.ts delete mode 100644 src/YarpGateway.Admin/.env.development delete mode 100644 src/YarpGateway.Admin/.gitignore delete mode 100644 src/YarpGateway.Admin/.vscode/extensions.json delete mode 100644 src/YarpGateway.Admin/Dockerfile delete mode 100644 src/YarpGateway.Admin/README.md delete mode 100644 src/YarpGateway.Admin/index.html delete mode 100644 src/YarpGateway.Admin/nginx.conf delete mode 100644 src/YarpGateway.Admin/package-lock.json delete mode 100644 src/YarpGateway.Admin/package.json delete mode 100644 src/YarpGateway.Admin/public/vite.svg delete mode 100644 src/YarpGateway.Admin/src/App.vue delete mode 100644 src/YarpGateway.Admin/src/api/index.ts delete mode 100644 src/YarpGateway.Admin/src/assets/vue.svg delete mode 100644 src/YarpGateway.Admin/src/components/HelloWorld.vue delete mode 100644 src/YarpGateway.Admin/src/components/Layout.vue delete mode 100644 src/YarpGateway.Admin/src/main.ts delete mode 100644 src/YarpGateway.Admin/src/router/index.ts delete mode 100644 src/YarpGateway.Admin/src/stores/tenant.ts delete mode 100644 src/YarpGateway.Admin/src/style.css delete mode 100644 src/YarpGateway.Admin/src/views/ClusterInstances.vue delete mode 100644 src/YarpGateway.Admin/src/views/Dashboard.vue delete mode 100644 src/YarpGateway.Admin/src/views/GlobalRoutes.vue delete mode 100644 src/YarpGateway.Admin/src/views/TenantList.vue delete mode 100644 src/YarpGateway.Admin/src/views/TenantRoutes.vue delete mode 100644 src/YarpGateway.Admin/tsconfig.app.json delete mode 100644 src/YarpGateway.Admin/tsconfig.json delete mode 100644 src/YarpGateway.Admin/tsconfig.node.json delete mode 100644 src/YarpGateway.Admin/vite.config.ts delete mode 100644 src/YarpGateway/Config/DatabaseClusterConfigProvider.cs delete mode 100644 src/YarpGateway/Config/DatabaseRouteConfigProvider.cs delete mode 100644 src/YarpGateway/Config/JwtConfig.cs delete mode 100644 src/YarpGateway/Config/RedisConfig.cs delete mode 100644 src/YarpGateway/Controllers/GatewayConfigController.cs delete mode 100644 src/YarpGateway/Data/GatewayDbContext.cs delete mode 100644 src/YarpGateway/Data/GatewayDbContextFactory.cs delete mode 100644 src/YarpGateway/Dockerfile delete mode 100644 src/YarpGateway/DynamicProxy/DynamicProxyConfigProvider.cs delete mode 100644 src/YarpGateway/LoadBalancing/DistributedWeightedRoundRobinPolicy.cs delete mode 100644 src/YarpGateway/Metrics/GatewayMetrics.cs delete mode 100644 src/YarpGateway/Middleware/JwtTransformMiddleware.cs delete mode 100644 src/YarpGateway/Middleware/TenantRoutingMiddleware.cs delete mode 100644 src/YarpGateway/Migrations/20260201120312_InitialCreate.Designer.cs delete mode 100644 src/YarpGateway/Migrations/20260201120312_InitialCreate.cs delete mode 100644 src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.Designer.cs delete mode 100644 src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.cs delete mode 100644 src/YarpGateway/Migrations/GatewayDbContextModelSnapshot.cs delete mode 100644 src/YarpGateway/Migrations/script.sql delete mode 100644 src/YarpGateway/Models/GwServiceInstance.cs delete mode 100644 src/YarpGateway/Models/GwTenant.cs delete mode 100644 src/YarpGateway/Models/GwTenantRoute.cs delete mode 100644 src/YarpGateway/Program.cs delete mode 100644 src/YarpGateway/Properties/launchSettings.json delete mode 100644 src/YarpGateway/Services/RedisConnectionManager.cs delete mode 100644 src/YarpGateway/Services/RouteCache.cs delete mode 100644 src/YarpGateway/YarpGateway.csproj delete mode 100644 src/YarpGateway/appsettings.Development.json delete mode 100644 src/YarpGateway/appsettings.json delete mode 100644 src/YarpGateway/logs/gateway-20260201.log delete mode 100644 src/YarpGateway/sql/init.sql delete mode 100644 src/YarpGateway/sql/update-isglobal.sql delete mode 100644 test/Fengling.AuthService.Tests/AccountIntegrationTests.cs delete mode 100644 test/Fengling.AuthService.Tests/Fengling.AuthService.Tests.csproj diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json deleted file mode 100644 index cdb3c8d..0000000 --- a/.config/dotnet-tools.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "csharpier": { - "version": "1.2.5", - "commands": [ - "csharpier" - ], - "rollForward": false - } - } -} \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8a5c34a..0000000 --- a/.gitignore +++ /dev/null @@ -1,130 +0,0 @@ -## Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleasePublic/ -x64/ -x86/ -[Aa][Rr][Ii][Ll][Mm][Oo][Uu][Tt][Ee][Rr]/[Aa][Pp][Pp][Bb][Uu][Ii][Ll][Dd]/ -x64/ -x86/ -[Aa]rm/ -[Aa]rm64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -## Visual Studio cache/options directory -.vs/ -.vscode/ - -## Rider -.idea/ - -## Git Worktrees -.worktrees/ - -## User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -## Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleasePublic/ -x64/ -x86/ -[Aa][Rr][Ii][Ll][Mm][Oo][Uu][Tt][Ee][Rr]/ -[Aa][Rr][Ii][Ll][Mm][Ee][Dd]/ -[Aa][Nn][Uu][Tt][Tt][Ee][Rr][Dd][Ee][Rr]/ - -## NuGet Packages -*.nupkg -*.snupkg -**/packages/* -!**/packages/build/ - -## .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -## ASP.NET Scaffolding -ScaffoldingReadMe.txt - -## Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -## Test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -## Node.js -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -## Vue / Vite -dist/ -dist-ssr/ -*.local -.nuxt/ -.vuepress/dist/ - -## Environment -.env -.env.local -.env.*.local - -## OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -## Database -*.db -*.sqlite -*.sqlite3 - -## Docker -*.log -docker-compose.override.yml diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 66d2c07..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "deepscan.enable": true -} \ No newline at end of file diff --git a/CONSOLE_DEVELOPMENT.md b/CONSOLE_DEVELOPMENT.md deleted file mode 100644 index 4f0b8f4..0000000 --- a/CONSOLE_DEVELOPMENT.md +++ /dev/null @@ -1,316 +0,0 @@ -# 风铃认证中心 - 管理端开发完成 - -## 项目概述 - -本项目为风铃认证中心提供完整的 Web 管理界面和后端 API。 - -## 技术栈 - -### 前端 -- Vue 3 + TypeScript -- Vite -- Element Plus UI 框架 -- Pinia 状态管理 -- Vue Router 路由 -- Axios HTTP 客户端 - -### 后端 -- ASP.NET Core 10.0 -- Entity Framework Core 10.0 -- PostgreSQL 数据库 -- OpenIddict OAuth2/OIDC 服务 -- Serilog 日志 -- OpenTelemetry 可观测性 - -## 完成的功能 - -### 前端页面 - -#### 1. 仪表盘(Dashboard) -- 统计卡片:用户数、租户数、OAuth应用数、今日访问 -- 最近活动时间线 -- 系统信息展示 -- 位置:`src/views/Dashboard/Dashboard.vue` - -#### 2. 用户管理 -- 用户列表(分页、搜索) -- 添加/编辑用户 -- 角色分配 -- 重置密码 -- 删除用户 -- 位置:`src/views/Users/UserList.vue` - -#### 3. 角色管理 -- 角色列表(分页、搜索) -- 添加/编辑角色 -- 权限配置 -- 查看角色用户 -- 移除用户角色 -- 位置:`src/views/Users/RoleList.vue` - -#### 4. 租户管理 -- 租户列表(分页、搜索) -- 添加/编辑租户 -- 租户设置(注册限制、密码策略、会话超时) -- 查看租户用户和角色 -- 位置:`src/views/Users/TenantList.vue` - -#### 5. OAuth 应用管理 -- 应用列表(分页、搜索) -- 添加/编辑应用 -- 完整配置(重定向URI、授权类型、权限范围等) -- 查看密钥 -- 位置:`src/views/OAuth/ClientList.vue` - -#### 6. 访问日志 -- 日志列表(多条件筛选) -- 日志详情查看 -- 导出 CSV -- 位置:`src/views/Audit/AccessLog.vue` - -#### 7. 审计日志 -- 日志列表(多条件筛选) -- 日志详情查看(包含变更前/后数据) -- 导出 CSV -- 位置:`src/views/Audit/AuditLog.vue` - -### 后端 API - -#### 控制器 - -1. **AuthController** - 认证端点 - - POST /connect/token - 登录(密码模式) - - POST /connect/token/refresh - 刷新令牌 - - POST /connect/revoke - 撤销令牌 - -2. **UsersController** - 用户管理 - - GET /api/users - 获取用户列表(分页、搜索) - - GET /api/users/{id} - 获取单个用户 - - POST /api/users - 创建用户 - - PUT /api/users/{id} - 更新用户 - - PUT /api/users/{id}/password - 重置密码 - - DELETE /api/users/{id} - 删除用户 - -3. **RolesController** - 角色管理 - - GET /api/roles - 获取角色列表(分页、搜索) - - GET /api/roles/{id} - 获取单个角色 - - GET /api/roles/{id}/users - 获取角色用户 - - POST /api/roles - 创建角色 - - PUT /api/roles/{id} - 更新角色 - - DELETE /api/roles/{id} - 删除角色 - - DELETE /api/roles/{id}/users/{userId} - 移除用户角色 - -4. **TenantsController** - 租户管理 - - GET /api/tenants - 获取租户列表(分页、搜索) - - GET /api/tenants/{id} - 获取单个租户 - - GET /api/tenants/{tenantId}/users - 获取租户用户 - - GET /api/tenants/{tenantId}/roles - 获取租户角色 - - GET /api/tenants/{tenantId}/settings - 获取租户设置 - - PUT /api/tenants/{tenantId}/settings - 更新租户设置 - - POST /api/tenants - 创建租户 - - PUT /api/tenants/{id} - 更新租户 - - DELETE /api/tenants/{id} - 删除租户 - -5. **OAuthClientsController** - OAuth 应用管理 - - GET /api/oauthclients - 获取应用列表(分页、搜索) - - GET /api/oauthclients/{id} - 获取单个应用 - - GET /api/oauthclients/{id}/secret - 获取应用密钥 - - POST /api/oauthclients - 创建应用 - - PUT /api/oauthclients/{id} - 更新应用 - - DELETE /api/oauthclients/{id} - 删除应用 - -6. **AccessLogsController** - 访问日志 - - GET /api/access-logs - 获取日志列表(分页、筛选) - - GET /api/access-logs/export - 导出 CSV - -7. **AuditLogsController** - 审计日志 - - GET /api/audit-logs - 获取日志列表(分页、筛选) - - GET /api/audit-logs/export - 导出 CSV - -8. **StatsController** - 统计数据 - - GET /api/stats/dashboard - 仪表盘统计数据 - - GET /api/stats/system - 系统统计信息 - -9. **HealthCheckController** - 健康检查 - - GET /health - 健康检查端点 - -### 数据模型 - -#### 核心模型 - -1. **ApplicationUser** - 用户 - - 继承自 IdentityUser - - RealName, Phone, TenantId, CreatedTime, UpdatedTime, IsDeleted - -2. **ApplicationRole** - 角色 - - 继承自 IdentityRole - - Description, DisplayName, TenantId, IsSystem, Permissions, CreatedTime - -3. **Tenant** - 租户 - - Id, TenantId, Name, ContactName, ContactEmail, ContactPhone - - MaxUsers, Description, Status, ExpiresAt, CreatedAt, UpdatedAt, IsDeleted - -4. **OAuthApplication** - OAuth 应用 - - Id, ClientId, ClientSecret, DisplayName - - RedirectUris, PostLogoutRedirectUris, Scopes, GrantTypes - - ClientType, ConsentType, Status, Description, CreatedAt, UpdatedAt - -5. **AccessLog** - 访问日志 - - UserName, TenantId, Action, Resource, Method, IpAddress - - UserAgent, Status, Duration, RequestData, ResponseData, ErrorMessage - - CreatedAt - -6. **AuditLog** - 审计日志 - - Operator, TenantId, Operation, Action - - TargetType, TargetId, TargetName, IpAddress, Description - - OldValue, NewValue, ErrorMessage, Status, CreatedAt - -### 数据库配置 - -- **数据库类型**: PostgreSQL -- **连接字符串**: `Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542` -- **迁移**: - - InitialCreate - 初始化表结构 - - AddOAuthApplications - 添加 OAuth 应用表 - - AddTenantAndLogs - 添加租户和日志表 - - AddOAuthDescription - 添加 OAuth 描述字段 - -### 初始数据 - -SeedData 初始化以下数据: - -#### 默认租户 -- TenantId: default -- Name: 默认租户 -- MaxUsers: 1000 - -#### 系统角色 -1. **Admin** - 管理员 - - 所有权限 - - IsSystem: true - -2. **User** - 普通用户 - - user.view 权限 - - IsSystem: true - -#### 默认用户 -1. **admin** - - 密码: Admin@123 - - 角色: Admin - -2. **testuser** - - 密码: Test@123 - - 角色: User - -#### OAuth 应用 -- **fengling-console** - 风铃运管中心 - - ClientSecret: console-secret-change-in-production - - 授权类型: authorization_code, refresh_token - -## 运行说明 - -### 后端(AuthService) - -```bash -cd src/Fengling.AuthService -dotnet build -dotnet run -``` - -服务地址: http://localhost:5000 -API 文档: http://localhost:5000/swagger -健康检查: http://localhost:5000/health - -### 前端(Console.Web) - -```bash -cd src/Fengling.Console.Web -npm install -npm run dev -``` - -开发地址: http://localhost:5173 - -生产构建: -```bash -npm run build -``` - -## API 代理配置 - -Vite 开发服务器配置了 API 代理: - -- `/api/auth/*` → `http://localhost:5000` (AuthService) -- `/api/gateway/*` → `http://localhost:5001` (YarpGateway) - -## 默认登录 - -- 用户名: admin -- 密码: Admin@123 - -## 文件结构 - -``` -Fengling.Refactory.Buiding/ -├── src/ -│ ├── Fengling.AuthService/ # 后端认证服务 -│ │ ├── Controllers/ # API 控制器 -│ │ ├── Data/ # 数据库上下文和种子数据 -│ │ ├── Models/ # 数据模型 -│ │ ├── Configuration/ # 配置类 -│ │ └── Migrations/ # 数据库迁移 -│ │ -│ └── Fengling.Console.Web/ # 前端管理界面 -│ ├── src/ -│ │ ├── api/ # API 调用封装 -│ │ ├── stores/ # Pinia 状态管理 -│ │ ├── router/ # Vue Router 路由 -│ │ └── views/ # 页面组件 -│ │ ├── Auth/ # 认证页面 -│ │ ├── Dashboard/ # 仪表盘 -│ │ ├── Users/ # 用户/角色/租户管理 -│ │ ├── OAuth/ # OAuth 应用管理 -│ │ └── Audit/ # 日志管理 -│ └── dist/ # 生产构建输出 -│ -└── docs/ # 项目文档 -``` - -## 安全建议 - -1. **生产环境修改**: - - 更改默认管理员密码 - - 修改 OAuth 应用密钥 - - 配置 HTTPS - - 限制数据库访问 - -2. **密码策略**: - - 至少 8 位 - - 包含字母和数字 - - 可根据租户设置自定义策略 - -## 后续优化建议 - -1. **前端**: - - 添加更多图表可视化 - - 实现前端国际化 - - 添加单元测试 - -2. **后端**: - - 添加 API 限流 - - 实现缓存策略 - - 添加更多日志级别 - - 完善错误处理 - -3. **功能**: - - OAuth 授权码流程完善 - - 双因素认证(2FA) - - 用户自助服务(找回密码、注册) - - API 权限细粒度控制 - -## 版本信息 - -- 前端版本: v1.0.0 -- 后端版本: v1.0.0 -- .NET 版本: 10.0.2 -- Node.js 版本: 推荐 18+ diff --git a/Fengling.Console.csproj b/Fengling.Console.csproj new file mode 100644 index 0000000..8f57e67 --- /dev/null +++ b/Fengling.Console.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/Fengling.Refactory.Buiding.sln b/Fengling.Refactory.Buiding.sln deleted file mode 100644 index c05165a..0000000 --- a/Fengling.Refactory.Buiding.sln +++ /dev/null @@ -1,54 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YarpGateway", "src\YarpGateway\YarpGateway.csproj", "{8DDFE39A-06AE-4C02-BA80-27F0C809E959}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fengling.AuthService", "src\Fengling.AuthService\Fengling.AuthService.csproj", "{469FA168-1656-483D-A40D-072FFE8C5E33}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Debug|x64.ActiveCfg = Debug|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Debug|x64.Build.0 = Debug|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Debug|x86.ActiveCfg = Debug|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Debug|x86.Build.0 = Debug|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Release|Any CPU.Build.0 = Release|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Release|x64.ActiveCfg = Release|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Release|x64.Build.0 = Release|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Release|x86.ActiveCfg = Release|Any CPU - {8DDFE39A-06AE-4C02-BA80-27F0C809E959}.Release|x86.Build.0 = Release|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Debug|Any CPU.Build.0 = Debug|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Debug|x64.ActiveCfg = Debug|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Debug|x64.Build.0 = Debug|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Debug|x86.ActiveCfg = Debug|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Debug|x86.Build.0 = Debug|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Release|Any CPU.ActiveCfg = Release|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Release|Any CPU.Build.0 = Release|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Release|x64.ActiveCfg = Release|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Release|x64.Build.0 = Release|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Release|x86.ActiveCfg = Release|Any CPU - {469FA168-1656-483D-A40D-072FFE8C5E33}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {8DDFE39A-06AE-4C02-BA80-27F0C809E959} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {469FA168-1656-483D-A40D-072FFE8C5E33} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - EndGlobalSection -EndGlobal diff --git a/LAYOUT_FIX.md b/LAYOUT_FIX.md deleted file mode 100644 index be7a4ae..0000000 --- a/LAYOUT_FIX.md +++ /dev/null @@ -1,72 +0,0 @@ -# 风铃认证中心 - 布局和路由修复 - -## 修复的问题 - -1. **菜单点击不跳转** - 添加了 `router.push({ name: index })` 到 `handleMenuSelect` -2. **App.vue 缺少 router-view** - 更新为包含 `` 和基础样式 -3. **面包屑显示** - 优化为只在非 Dashboard 页面显示 -4. **内容区域样式** - 添加 `content-wrapper` 包裹,优化布局 - -## 现在的布局结构 - -``` -App.vue -└── - ├── Login.vue (路径: /login) - ├── Callback.vue (路径: /auth/callback) - └── Dashboard.vue (路径: / 及其子路由) - ├── 侧边栏 (el-aside) - └── 主内容区 (el-main) - ├── 面包屑 (可选) - └── (嵌套子路由) - ├── Dashboard/Dashboard.vue - ├── Users/UserList.vue - ├── Users/RoleList.vue - ├── Users/TenantList.vue - ├── OAuth/ClientList.vue - ├── Audit/AccessLog.vue - └── Audit/AuditLog.vue -``` - -## 运行项目 - -### 启动后端 -```bash -cd src/Fengling.AuthService -dotnet run -``` - -### 启动前端 -```bash -cd src/Fengling.Console.Web -npm run dev -``` - -访问: http://localhost:5173 - -默认登录: -- 用户名: admin -- 密码: Admin@123 - -## 功能测试清单 - -- [ ] 登录页面正常显示 -- [ ] 登录成功后跳转到仪表盘 -- [ ] 侧边栏菜单正常显示 -- [ ] 点击菜单项正确跳转 -- [ ] 当前菜单项高亮显示 -- [ ] 面包屑导航正确显示 -- [ ] 各个管理页面正常加载 -- [ ] 退出登录功能正常 - -## 页面路由 - -| 路径 | 名称 | 页面 | -|------|------|------| -| / | Dashboard | 仪表盘 | -| /users | UserList | 用户列表 | -| /roles | RoleList | 角色管理 | -| /tenants | TenantList | 租户管理 | -| /oauth/clients | OAuthClients | OAuth 应用 | -| /logs/access | AccessLog | 访问日志 | -| /logs/audit | AuditLog | 审计日志 | diff --git a/OpenCode.md b/OpenCode.md deleted file mode 100644 index 7097956..0000000 --- a/OpenCode.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -# Fengling Project Rewrite - Conversation Summary -## Project Information -**Project Name**: Fengling (风灵) - QR Code Marketing Management Platform Rewrite -**Project Locations**: -- **Old Project**: `/Users/movingsam/Fengling.Refactory/` - - `Fengling.Backend.Web/` - Old monolithic backend - - `Yarp.Gateway/` - Old gateway (no longer relevant for reference) - -- **New Project**: `/Users/movingsam/Fengling.Refactory.Buiding/` - - `src/YarpGateway/` - New independent gateway service - - `src/YarpGateway.Admin/` - Vue3 admin UI -## What We Did -### Phase 1: Initial Gateway Setup -1. Created YARP Gateway backend with: - - YARP 2.2.0 (reverse proxy framework) - - EF Core 9.0.0 + PostgreSQL (192.168.100.10:5432) - - StackExchange.Redis (192.168.100.10:6379) for distributed locks - - Serilog for logging -2. Created Vue3 Admin frontend with: - - Vue 3 + TypeScript + Vite + Element Plus + Pinia - - Running on http://localhost:5173 -### Phase 2: Core Architecture Implementation -#### 1. Route Priority Design (99% Global + 1% Tenant-Specific) -**User's requirement**: -> "普通情况(租户走特定的实例(pod)只是特殊场景)所以是要对 前缀匹配到不同的服务这块的ui呢" -**Implementation**: -- Added `IsGlobal` boolean field to `GwTenantRoute` table -- Route priority: Tenant-specific routes > Global routes -- Database migration: `20260201133826_AddIsGlobalToTenantRoute` -**Benefit**: -- Before: 100 tenants × 10 services = 1000 route configurations -- After: 10 global routes + few tenant-specific routes -#### 2. In-Memory Route Caching -**File**: `src/YarpGateway/Services/RouteCache.cs` -- Loads routes from database at startup -- Priority-based lookup: tenant route → global route → 404 -- Hot reload support via `ReloadAsync()` -- Avoids database queries per request -#### 3. Redis Distributed Load Balancing -**File**: `src/YarpGateway/LoadBalancing/DistributedWeightedRoundRobinPolicy.cs` -- Implements weighted round-robin algorithm -- Uses Redis for distributed locks: `lock:{instanceName}:{clusterId}` -- Stores load balancing state in Redis: `lb:{instanceName}:{clusterId}:state` -- Supports multiple gateway instances -#### 4. Dynamic Proxy Configuration -**File**: `src/YarpGateway/DynamicProxy/DynamicProxyConfigProvider.cs` -- Implements `IProxyConfigProvider` -- Loads routes and clusters from database -- Provides configuration to YARP -#### 5. Tenant Routing Middleware -**File**: `src/YarpGateway/Middleware/TenantRoutingMiddleware.cs` -- Extracts tenant ID from JWT headers (`X-Tenant-Id`) -- Uses `RouteCache` to get route cluster -- Sets `context.Items["DynamicClusterId"]` for YARP -### Phase 3: Frontend Development -Created Vue3 admin pages: -1. **Dashboard.vue** - Statistics dashboard -2. **TenantList.vue** - Tenant management -3. **TenantRoutes.vue** - Tenant-specific routes -4. **GlobalRoutes.vue** - Global routes management (NEW) -5. **ClusterInstances.vue** - Service instance management -### Phase 4: Bug Fixes -1. Fixed 4K screen width constraints (removed max-width, added 100% width/height) -2. Fixed route `/tenants` not displaying until page refresh -3. Fixed CORS configuration (allowed origins: localhost:5173, 127.0.0.1:5173) -4. Fixed multiple compilation errors in `DistributedWeightedRoundRobinPolicy.cs`: - - Missing using statements - - JsonSerializer ambiguity (used full namespace) - - HashCode.Combine signature errors - - Typo: `M.achineName` → `MachineName` - - Changed `await using var` to `using var` -### Phase 5: Database Configuration -Applied database migration: -```bash -dotnet ef database update -``` -## Current Status -### ✅ Completed -1. Backend compiles successfully -2. Frontend runs on http://localhost:5173 -3. Database migrations applied -4. Global routes management UI created -5. Redis distributed load balancing implemented -6. In-memory route caching implemented -### ⚠️ Known Issue: YARP Dynamic Routing -**Problem**: -- Accessing `/api/product/test` returns 404 -- Logs show: "Request reached end of middleware pipeline" -- `DynamicProxyConfigProvider` is registered but not being used by YARP -**User's comment**: -> "你等下 下游的应用还没建立 肯定404吧" -> "你先把网关的ui更新一下功能呀。这个你先别急着测试吧" -**Conclusion**: The 404 is expected because downstream microservices don't exist yet. User wants to focus on UI updates and move to microservices analysis. -## Database Schema -### Tables -#### Tenants -```sql -Id: bigint (PK) -TenantCode: varchar(50) (unique) -TenantName: varchar(100) -Status: int (1=enabled, 0=disabled) -IsDeleted: boolean -CreatedTime, UpdatedTime, Version -``` -#### TenantRoutes -```sql -Id: bigint (PK) -TenantCode: varchar(50) (empty string = global route) -ServiceName: varchar(100) -ClusterId: varchar(100) -PathPattern: varchar(200) -Priority: int (0=global, 10=tenant) -Status: int -IsGlobal: boolean (NEW) -IsDeleted: boolean -CreatedTime, UpdatedTime, Version -``` -#### ServiceInstances -```sql -Id: bigint (PK) -ClusterId: varchar(100) -DestinationId: varchar(100) -Address: varchar(200) -Health: int (1=healthy) -Weight: int -Status: int -IsDeleted: boolean -CreatedTime, UpdatedTime, Version -``` -## Configuration -### Backend (appsettings.json) -```json -{ - "ConnectionStrings": { - "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_gateway;Username=movingsam;Password=sl52788542" - }, - "Redis": { - "ConnectionString": "192.168.100.10:6379", - "Database": 0, - "InstanceName": "YarpGateway" - }, - "Cors": { - "AllowedOrigins": ["http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5174"], - "AllowAnyOrigin": false - }, - "ReverseProxy": { - "Routes": {}, - "Clusters": {} - } -} -``` -### Ports -- Frontend: http://localhost:5173 -- Backend: http://0.0.0.0:8080 -## API Endpoints -### Tenant Management -- `GET /api/gateway/tenants` - List tenants -- `POST /api/gateway/tenants` - Create tenant -- `DELETE /api/gateway/tenants/{id}` - Delete tenant -### Tenant Routes -- `GET /api/gateway/tenants/{tenantCode}/routes` - List tenant routes -- `POST /api/gateway/tenants/{tenantCode}/routes` - Create tenant route -### Global Routes -- `GET /api/gateway/routes/global` - List global routes -- `POST /api/gateway/routes/global` - Create global route -- `DELETE /api/gateway/routes/{id}` - Delete route -### Cluster Instances -- `GET /api/gateway/clusters/{clusterId}/instances` - List instances -- `POST /api/gateway/clusters/{clusterId}/instances` - Add instance -- `DELETE /api/gateway/instances/{id}` - Delete instance -### Configuration -- `POST /api/gateway/reload` - Reload configuration -## File Structure -``` -/Users/movingsam/Fengling.Refactory.Buiding/src/ -├── YarpGateway/ -│ ├── Config/ -│ │ ├── DatabaseRouteConfigProvider.cs -│ │ ├── DatabaseClusterConfigProvider.cs -│ │ ├── JwtConfig.cs -│ │ └── RedisConfig.cs -│ ├── Controllers/ -│ │ └── GatewayConfigController.cs -│ ├── Data/ -│ │ ├── GatewayDbContext.cs -│ │ └── GatewayDbContextFactory.cs -│ ├── DynamicProxy/ -│ │ └── DynamicProxyConfigProvider.cs -│ ├── LoadBalancing/ -│ │ └── DistributedWeightedRoundRobinPolicy.cs -│ ├── Middleware/ -│ │ ├── JwtTransformMiddleware.cs -│ │ └── TenantRoutingMiddleware.cs -│ ├── Models/ -│ │ ├── GwTenant.cs -│ │ ├── GwTenantRoute.cs -│ │ └── GwServiceInstance.cs -│ ├── Services/ -│ │ ├── RouteCache.cs -│ │ └── RedisConnectionManager.cs -│ ├── Migrations/ -│ │ ├── 20260201120312_InitialCreate.cs -│ │ └── 20260201133826_AddIsGlobalToTenantRoute.cs -│ ├── sql/ -│ │ └── init.sql -│ ├── Program.cs -│ └── appsettings.json -│ -└── YarpGateway.Admin/ - ├── src/ - │ ├── api/ - │ │ └── index.ts - │ ├── components/ - │ │ └── Layout.vue - │ ├── stores/ - │ │ └── tenant.ts - │ ├── views/ - │ │ ├── Dashboard.vue - │ │ ├── TenantList.vue - │ │ ├── TenantRoutes.vue - │ │ ├── GlobalRoutes.vue - │ │ └── ClusterInstances.vue - │ ├── router/ - │ │ └── index.ts - │ └── main.ts - └── package.json -``` -## What We're Doing Now -The user wants to stop working on the gateway routing issue and instead focus on: -1. Updating the gateway UI functionality (COMPLETED - GlobalRoutes.vue added) -2. Analyzing the old backend for microservices split -User's exact words: -> "你先把网关的ui更新一下功能呀。这个你先别急着测试吧" -> "我准备开始分析业务的微服务拆分了" -## Next Steps (For New Conversation) -### Immediate Priority: Microservices Analysis -**Task**: Analyze the old backend (`/Users/movingsam/Fengling.Refactory/Fengling.Backend.Web/`) to determine how to split it into microservices -**Known Old Backend Structure**: -``` -/Users/movingsam/Fengling.Refactory/Fengling.Backend.Web/src/src/ -├── account/ # Account module -├── activityplan/ # Activity planning -├── basis/ # Basic configuration -├── channel/ # Channel management -├── company/ # Company management -├── coupon/ # Coupon management -├── fieldConfig/ # Field configuration -├── flow/ # Workflow -├── gift/ # Gift management -├── integralConfig/ # Points configuration -├── member/ # Member management -├── promoter/ # Promoter management -├── qipei/ # Service matching -├── reports/ # Reports -├── riskManage/ # Risk management -└── [many more modules...] -``` -### Analysis Goals -1. Identify business domain boundaries -2. Determine which modules should become independent microservices -3. Design inter-service communication patterns -4. Plan database splitting strategy -5. Consider shared services (auth, configuration, etc.) -### Deferred Tasks (Lower Priority) -1. Fix YARP `DynamicProxyConfigProvider` to properly integrate with YARP -2. Test dynamic routing with actual downstream services -3. Complete deployment architecture (Docker, Kubernetes) -## Key Technical Decisions -### 1. Why Global Routes + Tenant-Specific Routes? -**Reason**: 99% of tenants share the same services, only 1% need dedicated instances -**Benefit**: Drastically reduces configuration complexity -### 2. Why YARP? -- Microsoft official support -- High performance (based on Kestrel) -- Extensible (custom load balancing policies) -- Dynamic configuration support -### 3. Why Redis? -- Distributed locks for multi-instance scenarios -- Persistent load balancing state -- High performance (millisecond response) -## Important Notes for Continuation -1. **Database Access**: PostgreSQL at 192.168.100.10:5432, Database: fengling_gateway -2. **Redis Access**: 192.168.100.10:6379 -3. **Project Location**: `/Users/movingsam/Fengling.Refactory.Buiding/` -4. **User Preference**: Manually handles database migrations (user applies SQL manually) -5. **Old Gateway**: User confirmed the old gateway at `/Users/movingsam/Fengling.Refactory/Yarp.Gateway/` is no longer relevant for reference ---- \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index cf31a1c..0000000 --- a/README.md +++ /dev/null @@ -1,370 +0,0 @@ -# YARP Gateway - 租户路由网关 - -基于YARP的租户感知API网关,支持JWT解析、动态租户路由、加权负载均衡。 - -## 功能特性 - -- ✅ JWT解析与Header传递 -- ✅ 基于租户的动态路由 -- ✅ 加权轮询负载均衡 -- ✅ PostgreSQL配置持久化 -- ✅ RESTful API管理接口 -- ✅ Docker Compose部署 -- ✅ Serilog结构化日志 -- ✅ Prometheus指标导出 - -## 架构设计 - -``` -客户端请求 → JWT解析中间件 → 租户路由中间件 → YARP网关 → 后端微服务 -``` - -### 路由流程 - -1. 客户端携带JWT访问 `/api/product/list` -2. JWT解析中间件提取租户ID(如 `customerA`) -3. 添加Header: `X-Tenant-Id: customerA` -4. 租户路由中间件根据路径提取服务名(`product`) -5. 动态构造Cluster ID: `customerA-product` -6. YARP将请求转发到该Cluster下的服务实例 -7. 加权轮询策略选择实例 - -## 快速开始 - -### 前置要求 - -- .NET 10 SDK -- PostgreSQL 16+ -- Docker & Docker Compose(可选) - -### 本地开发 - -1. **创建数据库** - -```bash -psql -h 192.168.100.10 -U postgres -c "CREATE DATABASE fengling_gateway;" -``` - -2. **执行迁移** - -```bash -psql -h 192.168.100.10 -U postgres -d fengling_gateway -f sql/init.sql -``` - -3. **运行网关** - -```bash -cd src/YarpGateway -dotnet run -``` - -4. **测试请求** - -```bash -# 生成测试JWT(需要配置Auth服务) -# 然后测试请求 -curl -H "Authorization: Bearer " \ - http://localhost:8080/api/product/list -``` - -### Docker部署 - -1. **启动所有服务** - -```bash -cd docker -docker-compose up -d -``` - -2. **查看日志** - -```bash -docker-compose logs -f gateway -``` - -3. **停止服务** - -```bash -docker-compose down -``` - -## API接口 - -### 租户管理 - -**获取所有租户** -```http -GET /api/gateway/tenants -``` - -**创建租户** -```http -POST /api/gateway/tenants -Content-Type: application/json - -{ - "tenantCode": "customerC", - "tenantName": "客户C" -} -``` - -**删除租户** -```http -DELETE /api/gateway/tenants/{id} -``` - -### 路由管理 - -**获取租户路由** -```http -GET /api/gateway/tenants/{tenantCode}/routes -``` - -**创建路由** -```http -POST /api/gateway/tenants/{tenantCode}/routes -Content-Type: application/json - -{ - "serviceName": "product", - "pathPattern": "/api/product/{**catch-all}" -} -``` - -**删除路由** -```http -DELETE /api/gateway/routes/{id} -``` - -### 服务实例管理 - -**获取实例列表** -```http -GET /api/gateway/clusters/{clusterId}/instances -``` - -**添加实例** -```http -POST /api/gateway/clusters/{clusterId}/instances -Content-Type: application/json - -{ - "destinationId": "product-3", - "address": "http://customerA-product-3:8001", - "weight": 2 -} -``` - -**删除实例** -```http -DELETE /api/gateway/instances/{id} -``` - -### 配置热更新 - -```http -POST /api/gateway/reload -``` - -## JWT格式要求 - -### 必需Claims - -```json -{ - "tenant": "customerA", - "sub": "123456", - "unique_name": "张三", - "role": ["admin", "user"] -} -``` - -### Header转换 - -JWT解析后,以下Header会自动添加到请求中: - -- `X-Tenant-Id`: 租户ID -- `X-User-Id`: 用户ID -- `X-User-Name`: 用户名 -- `X-Roles`: 角色列表(逗号分隔) - -## 负载均衡策略 - -### 加权轮询 (WeightedRoundRobin) - -权重高的实例获得更多流量分配。 - -**配置权重**: -```bash -POST /api/gateway/clusters/customerA-product/instances -{ - "destinationId": "product-1", - "address": "http://customerA-product-1:8001", - "weight": 10 # 权重10 -} -``` - -**默认权重**: 1 - -## 数据库表结构 - -### gw_tenant -租户基础信息表 - -| 字段 | 类型 | 说明 | -|------|------|------| -| Id | BIGINT | 主键 | -| TenantCode | VARCHAR(50) | 租户编码(唯一) | -| TenantName | VARCHAR(100) | 租户名称 | -| Status | INTEGER | 状态:1=启用 0=禁用 | - -### gw_tenant_route -租户服务路由配置表 - -| 字段 | 类型 | 说明 | -|------|------|------| -| Id | BIGINT | 主键 | -| TenantCode | VARCHAR(50) | 租户编码 | -| ServiceName | VARCHAR(100) | 服务名称 | -| ClusterId | VARCHAR(100) | YARP Cluster ID | -| PathPattern | VARCHAR(200) | 路径匹配模式 | - -### gw_service_instance -服务实例配置表 - -| 字段 | 类型 | 说明 | -|------|------|------| -| Id | BIGINT | 主键 | -| ClusterId | VARCHAR(100) | Cluster ID | -| DestinationId | VARCHAR(100) | Destination ID | -| Address | VARCHAR(200) | 服务地址 | -| Weight | INTEGER | 权重 | -| Health | INTEGER | 健康状态:1=健康 0=不健康 | - -## 监控和日志 - -### 日志位置 - -- **控制台**: 实时输出 -- **文件**: `logs/gateway-{Date}.log` - -### Prometheus指标 - -默认导出到 `/metrics` 端点(需添加 Prometheus 包) - -**可用指标**: -- `gateway_requests_total`: 请求总数(按租户、服务、状态码分组) -- `gateway_request_duration_seconds`: 请求耗时(按租户、服务分组) - -## 配置说明 - -### appsettings.json - -```json -{ - "ConnectionStrings": { - "DefaultConnection": "Host=postgres;Port=5432;Database=fengling_gateway;..." - }, - "Jwt": { - "Authority": "https://your-auth-server.com", - "Audience": "fengling-gateway" - }, - "ReverseProxy": { - "Routes": { "catch-all-route": { ... } }, - "Clusters": { "dynamic-cluster": { ... } } - }, - "Serilog": { ... } -} -``` - -### 环境变量 - -- `ConnectionStrings__DefaultConnection`: 数据库连接字符串 -- `Jwt__Authority`: JWT认证服务器地址 -- `Jwt__Audience`: JWT受众 - -## 性能调优 - -### 连接池配置 - -```csharp -services.AddDbContext(options => - options.UseNpgsql(connectionString, o => - { - o.CommandTimeout(30); - o.MaxBatchSize(100); - })); -``` - -### 日志级别优化 - -生产环境建议: -```json -{ - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft.AspNetCore": "Warning", - "Yarp.ReverseProxy": "Information" - } - } -} -``` - -## 故障排查 - -### 常见问题 - -**1. 路由404** -- 检查 `gw_tenant_route` 表是否有对应路由配置 -- 检查JWT中的tenant claim是否正确 - -**2. 数据库连接失败** -- 验证连接字符串是否正确 -- 检查PostgreSQL是否启动 -- 检查防火墙设置 - -**3. 负载均衡不均** -- 检查实例权重配置 -- 检查实例健康状态 - -## 项目结构 - -``` -src/YarpGateway/ -├── Config/ # 配置提供者 -│ ├── JwtConfig.cs -│ ├── DatabaseRouteConfigProvider.cs -│ └── DatabaseClusterConfigProvider.cs -├── Controllers/ # API控制器 -│ └── GatewayConfigController.cs -├── Data/ # 数据库上下文 -│ └── GatewayDbContext.cs -├── LoadBalancing/ # 负载均衡策略 -│ └── WeightedRoundRobinPolicy.cs -├── Middleware/ # 中间件 -│ ├── JwtTransformMiddleware.cs -│ └── TenantRoutingMiddleware.cs -├── Metrics/ # 监控指标 -│ └── GatewayMetrics.cs -├── Models/ # 数据模型 -│ ├── GwTenant.cs -│ ├── GwTenantRoute.cs -│ └── GwServiceInstance.cs -├── appsettings.json -├── Dockerfile -└── Program.cs -``` - -## 开发计划 - -- [ ] 添加Prometheus指标导出 -- [ ] 实现Vue3管理界面 -- [ ] 添加限流策略 -- [ ] 添加熔断机制 -- [ ] 实现配置中心集成 -- [ ] 添加服务发现集成 - -## 许可证 - -MIT License diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index cf4b02d..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.8' - -services: - gateway: - build: ./src/YarpGateway - container_name: fengling-gateway - ports: - - "8080:8080" - environment: - - ConnectionStrings__DefaultConnection=Host=192.168.100.10;Port=5432;Database=fengling_gateway;Username=postgres;Password=postgres - - Jwt__Authority=https://your-auth-server.com - volumes: - - ./logs:/app/logs - networks: - - fengling-network - restart: unless-stopped - -networks: - fengling-network: - driver: bridge diff --git a/docs/plans/2025-02-01-auth-service.md b/docs/plans/2025-02-01-auth-service.md deleted file mode 100644 index fbdcf81..0000000 --- a/docs/plans/2025-02-01-auth-service.md +++ /dev/null @@ -1,1023 +0,0 @@ -# Authentication Service Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Build a standalone authentication service using OpenIddict to handle user authentication, JWT token issuance, and multi-tenant support. - -**Architecture:** ASP.NET Core Web API with OpenIddict for OAuth2/OIDC support, PostgreSQL for user/role data, JWT tokens with embedded TenantId for multi-tenant isolation. - -**Tech Stack:** -- .NET 9.0 / ASP.NET Core -- OpenIddict 5.x -- Entity Framework Core 9.0 -- PostgreSQL -- Serilog -- OpenTelemetry - ---- - -## Task 1: Create Project Structure - -**Files:** -- Create: `src/Fengling.AuthService/Fengling.AuthService.csproj` -- Create: `src/Fengling.AuthService/Program.cs` -- Create: `src/Fengling.AuthService/appsettings.json` - -**Step 1: Create project file** - -Run: -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src -dotnet new webapi -n Fengling.AuthService -o Fengling.AuthService -``` - -**Step 2: Update project file with dependencies** - -Edit: `src/Fengling.AuthService/Fengling.AuthService.csproj` - -```xml - - - net9.0 - enable - enable - - - - - - - - - - - - - - - -``` - -**Step 3: Create appsettings.json** - -Create: `src/Fengling.AuthService/appsettings.json` - -```json -{ - "ConnectionStrings": { - "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542" - }, - "OpenIddict": { - "Issuer": "https://auth.fengling.local", - "Audience": "fengling-api" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} -``` - -**Step 4: Commit** - -```bash -git add src/Fengling.AuthService/ -git commit -m "feat(auth): create authentication service project structure" -``` - ---- - -## Task 2: Create Database Models - -**Files:** -- Create: `src/Fengling.AuthService/Data/ApplicationDbContext.cs` -- Create: `src/Fengling.AuthService/Models/ApplicationUser.cs` -- Create: `src/Fengling.AuthService/Models/ApplicationRole.cs` -- Create: `src/Fengling.AuthService/Data/Migrations/20250201_InitialCreate.cs` - -**Step 1: Create ApplicationUser model** - -Create: `src/Fengling.AuthService/Models/ApplicationUser.cs` - -```csharp -using Microsoft.AspNetCore.Identity; - -namespace Fengling.AuthService.Models; - -public class ApplicationUser : IdentityUser -{ - public string? RealName { get; set; } - public string? Phone { get; set; } - public long TenantId { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedTime { get; set; } - public bool IsDeleted { get; set; } -} -``` - -**Step 2: Create ApplicationRole model** - -Create: `src/Fengling.AuthService/Models/ApplicationRole.cs` - -```csharp -using Microsoft.AspNetCore.Identity; - -namespace Fengling.AuthService.Models; - -public class ApplicationRole : IdentityRole -{ - public string? Description { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; -} -``` - -**Step 3: Create ApplicationDbContext** - -Create: `src/Fengling.AuthService/Data/ApplicationDbContext.cs` - -```csharp -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Data; - -public class ApplicationDbContext : IdentityDbContext -{ - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - builder.Entity(entity => - { - entity.Property(e => e.RealName).HasMaxLength(100); - entity.Property(e => e.Phone).HasMaxLength(20); - entity.HasIndex(e => e.TenantId); - entity.HasIndex(e => e.Phone).IsUnique(); - }); - - builder.Entity(entity => - { - entity.Property(e => e.Description).HasMaxLength(200); - }); - } -} -``` - -**Step 4: Add migration** - -Run: -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src/Fengling.AuthService -dotnet ef migrations add InitialCreate -o Data/Migrations -``` - -**Step 5: Update database** - -Run: -```bash -dotnet ef database update -``` - -**Step 6: Commit** - -```bash -git add src/Fengling.AuthService/Models/ src/Fengling.AuthService/Data/ -git commit -m "feat(auth): add user and role models with EF Core migrations" -``` - ---- - -## Task 3: Configure OpenIddict - -**Files:** -- Create: `src/Fengling.AuthService/Configuration/OpenIddictSetup.cs` -- Modify: `src/Fengling.AuthService/Program.cs` - -**Step 1: Create OpenIddict configuration** - -Create: `src/Fengling.AuthService/Configuration/OpenIddictSetup.cs` - -```csharp -using Microsoft.Extensions.DependencyInjection; -using OpenIddict.Validation.AspNetCore; - -namespace Fengling.AuthService.Configuration; - -public static class OpenIddictSetup -{ - public static IServiceCollection AddOpenIddictConfiguration(this IServiceCollection services, IConfiguration configuration) - { - services.AddOpenIddict() - .AddCore(options => - { - options.UseEntityFrameworkCore() - .UseDbContext(); - }) - .AddServer(options => - { - options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "https://auth.fengling.local"); - options.AddSigningKey(new SymmetricSecurityKey( - System.Text.Encoding.UTF8.GetBytes("fengling-super-secret-key-for-dev-only-change-in-prod-please!!!"))); - - options.AllowAuthorizationCodeFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow() - .RequireProofKeyForCodeExchange(); - - options.RegisterScopes("api", "offline_access"); - - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - options.UseAspNetCore() - .EnableAuthorizationEndpointPassThrough() - .EnableTokenEndpointPassThrough() - .EnableLogoutEndpointPassThrough(); - }) - .AddValidation(options => - { - options.UseLocalServer(); - options.UseAspNetCore(); - }); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; - }); - - return services; - } -} -``` - -**Step 2: Update Program.cs with OpenIddict and EF Core** - -Edit: `src/Fengling.AuthService/Program.cs` - -```csharp -using Fengling.AuthService.Configuration; -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.OpenApi.Models; -using OpenTelemetry; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; -using Serilog; - -var builder = WebApplication.CreateBuilder(args); - -// Serilog -Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(builder.Configuration) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") - .CreateLogger(); - -builder.Host.UseSerilog(); - -// Database -builder.Services.AddDbContext(options => - options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); - -// Identity -builder.Services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - -// OpenIddict -builder.Services.AddOpenIddictConfiguration(builder.Configuration); - -// OpenTelemetry -builder.Services.AddOpenTelemetry() - .ConfigureResource(resource => - resource.AddService("Fengling.AuthService")) - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddSource("OpenIddict.Server.AspNetCore") - .AddOtlpExporter(); - -// Controllers -builder.Services.AddControllers(); - -// Swagger -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(options => -{ - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Fengling Auth Service", - Version = "v1", - Description = "Authentication and authorization service using OpenIddict" - }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows - { - Password = new OpenApiOAuthFlow - { - TokenUrl = "/connect/token" - } - } - }); -}); - -var app = builder.Build(); - -// Configure pipeline -app.UseSwagger(); -app.UseSwaggerUI(options => -{ - options.SwaggerEndpoint("/swagger/v1/swagger.json", "Fengling Auth Service v1"); - options.OAuthClientId("swagger-ui"); - options.OAuthUsePkce(); -}); - -app.UseRouting(); -app.UseAuthentication(); -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); -``` - -**Step 3: Run to verify startup** - -Run: -```bash -dotnet run -``` -Expected: Service starts without errors, Swagger UI available at http://localhost:5000/swagger - -**Step 4: Commit** - -```bash -git add src/Fengling.AuthService/Configuration/ src/Fengling.AuthService/Program.cs -git commit -m "feat(auth): configure OpenIddict with JWT and OAuth2 support" -``` - ---- - -## Task 4: Create Auth Controller - -**Files:** -- Create: `src/Fengling.AuthService/Controllers/AuthController.cs` -- Create: `src/Fengling.AuthService/DTOs/LoginRequest.cs` -- Create: `src/Fengling.AuthService/DTOs/LoginResponse.cs` -- Create: `src/Fengling.AuthService/DTOs/TokenResponse.cs` - -**Step 1: Create DTOs** - -Create: `src/Fengling.AuthService/DTOs/LoginRequest.cs` - -```csharp -namespace Fengling.AuthService.DTOs; - -public class LoginRequest -{ - public string UserName { get; set; } = string.Empty; - public string Password { get; set; } = string.Empty; - public long TenantId { get; set; } -} -``` - -Create: `src/Fengling.AuthService/DTOs/LoginResponse.cs` - -```csharp -namespace Fengling.AuthService.DTOs; - -public class LoginResponse -{ - public string AccessToken { get; set; } = string.Empty; - public string RefreshToken { get; set; } = string.Empty; - public int ExpiresIn { get; set; } - public string TokenType { get; set; } = "Bearer"; -} -``` - -**Step 2: Create AuthController** - -Create: `src/Fengling.AuthService/Controllers/AuthController.cs` - -```csharp -using Fengling.AuthService.DTOs; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -public class AuthController : ControllerBase -{ - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly IOpenIddictScopeManager _scopeManager; - private readonly ILogger _logger; - - public AuthController( - SignInManager signInManager, - UserManager userManager, - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - ILogger logger) - { - _signInManager = signInManager; - _userManager = userManager; - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _scopeManager = scopeManager; - _logger = logger; - } - - [HttpPost("login")] - public async Task Login([FromBody] LoginRequest request) - { - var user = await _userManager.FindByNameAsync(request.UserName); - if (user == null || user.IsDeleted) - { - return Unauthorized(new { error = "用户不存在" }); - } - - if (user.TenantId != request.TenantId) - { - return Unauthorized(new { error = "租户不匹配" }); - } - - var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); - if (!result.Succeeded) - { - return Unauthorized(new { error = "用户名或密码错误" }); - } - - var token = await GenerateTokenAsync(user); - return Ok(token); - } - - private async Task GenerateTokenAsync(ApplicationUser user) - { - var claims = new List - { - new(Claims.Subject, user.Id.ToString()), - new(Claims.Name, user.UserName ?? string.Empty), - new(Claims.Email, user.Email ?? string.Empty), - new("tenant_id", user.TenantId.ToString()) - }; - - var roles = await _userManager.GetRolesAsync(user); - foreach (var role in roles) - { - claims.Add(new Claim(Claims.Role, role)); - } - - var identity = new System.Security.Claims.ClaimsIdentity(claims, "Server"); - var principal = new System.Security.Claims.ClaimsPrincipal(identity); - - return new LoginResponse - { - AccessToken = "token-placeholder", // Will be replaced by OpenIddict - RefreshToken = "refresh-placeholder", - ExpiresIn = 3600, - TokenType = "Bearer" - }; - } -} -``` - -**Step 3: Run to verify controller compilation** - -Run: -```bash -dotnet build -``` -Expected: Build succeeds - -**Step 4: Commit** - -```bash -git add src/Fengling.AuthService/Controllers/ src/Fengling.AuthService/DTOs/ -git commit -m "feat(auth): add authentication controller with login endpoint" -``` - ---- - -## Task 5: Create OpenIddict Endpoints - -**Files:** -- Create: `src/Fengling.AuthService/Controllers/AuthorizationController.cs` - -**Step 1: Create authorization endpoints** - -Create: `src/Fengling.AuthService/Controllers/AuthorizationController.cs` - -```csharp -using Fengling.AuthService.Models; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -public class AuthorizationController : Controller -{ - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly IOpenIddictScopeManager _scopeManager; - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly ILogger _logger; - - public AuthorizationController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - SignInManager signInManager, - UserManager userManager, - ILogger logger) - { - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _scopeManager = scopeManager; - _signInManager = signInManager; - _userManager = userManager; - _logger = logger; - } - - [HttpPost("~/connect/token")] - [Produces("application/json")] - public async Task Exchange() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - if (request.IsPasswordGrantType()) - { - var user = await _userManager.FindByNameAsync(request.Username); - if (user == null || user.IsDeleted) - { - return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户不存在" - })); - } - - var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); - if (!result.Succeeded) - { - return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户名或密码错误" - })); - } - - var principal = await _signInManager.CreateUserPrincipalAsync(user); - - var claims = new List - { - new(Claims.Subject, user.Id.ToString()), - new(Claims.Name, user.UserName ?? string.Empty), - new(Claims.Email, user.Email ?? string.Empty), - new("tenant_id", user.TenantId.ToString()) - }; - - var roles = await _userManager.GetRolesAsync(user); - foreach (var role in roles) - { - claims.Add(new Claim(Claims.Role, role)); - } - - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } - - return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.UnsupportedGrantType, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "不支持的授权类型" - })); - } - - [HttpGet("~/connect/authorize")] - [HttpPost("~/connect/authorize")] - [IgnoreAntiforgeryToken] - public async Task Authorize() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - var result = await HttpContext.AuthenticateAsync(); - if (result == null || !result.Succeeded) - { - return Challenge( - new AuthenticationProperties - { - RedirectUri = Request.Path + Request.QueryString - }, - OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } - - return Ok(new { message = "Authorization endpoint" }); - } - - [HttpPost("~/connect/logout")] - [ValidateAntiForgeryToken] - public async Task Logout() - { - await HttpContext.SignOutAsync(); - return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } -} -``` - -**Step 2: Run to verify** - -Run: -```bash -dotnet run -``` - -Test with curl: -```bash -curl -X POST http://localhost:5000/connect/token \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=password" \ - -d "username=admin" \ - -d "password=Admin@123" \ - -d "scope=api offline_access" -``` -Expected: 400 error (user doesn't exist yet, but endpoint is working) - -**Step 3: Commit** - -```bash -git add src/Fengling.AuthService/Controllers/AuthorizationController.cs -git commit -m "feat(auth): add OpenIddict authorization endpoints" -``` - ---- - -## Task 6: Create Seed Data - -**Files:** -- Create: `src/Fengling.AuthService/Data/SeedData.cs` -- Modify: `src/Fengling.AuthService/Program.cs` - -**Step 1: Create seed data class** - -Create: `src/Fengling.AuthService/Data/SeedData.cs` - -```csharp -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Data; - -public static class SeedData -{ - public static async Task Initialize(IServiceProvider serviceProvider) - { - using var scope = serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - var roleManager = scope.ServiceProvider.GetRequiredService>(); - - // Ensure database is created - context.Database.EnsureCreated(); - - // Create admin role - var adminRole = await roleManager.FindByNameAsync("Admin"); - if (adminRole == null) - { - adminRole = new ApplicationRole - { - Name = "Admin", - Description = "System administrator", - CreatedTime = DateTime.UtcNow - }; - await roleManager.CreateAsync(adminRole); - } - - // Create default admin user - var adminUser = await userManager.FindByNameAsync("admin"); - if (adminUser == null) - { - adminUser = new ApplicationUser - { - UserName = "admin", - Email = "admin@fengling.local", - RealName = "系统管理员", - Phone = "13800138000", - TenantId = 1, - EmailConfirmed = true, - IsDeleted = false, - CreatedTime = DateTime.UtcNow - }; - - var result = await userManager.CreateAsync(adminUser, "Admin@123"); - if (result.Succeeded) - { - await userManager.AddToRoleAsync(adminUser, "Admin"); - } - } - - // Create test user - var testUser = await userManager.FindByNameAsync("testuser"); - if (testUser == null) - { - testUser = new ApplicationUser - { - UserName = "testuser", - Email = "test@fengling.local", - RealName = "测试用户", - Phone = "13900139000", - TenantId = 1, - EmailConfirmed = true, - IsDeleted = false, - CreatedTime = DateTime.UtcNow - }; - - var result = await userManager.CreateAsync(testUser, "Test@123"); - if (result.Succeeded) - { - var userRole = new ApplicationRole - { - Name = "User", - Description = "普通用户", - CreatedTime = DateTime.UtcNow - }; - await roleManager.CreateAsync(userRole); - await userManager.AddToRoleAsync(testUser, "User"); - } - } - } -} -``` - -**Step 2: Update Program.cs to call seed data** - -Edit: `src/Fengling.AuthService/Program.cs` (add after `var app = builder.Build();`) - -```csharp -// Seed data -using (var scope = app.Services.CreateScope()) -{ - await Data.SeedData.Initialize(scope.ServiceProvider); -} -``` - -**Step 3: Run to create seed data** - -Run: -```bash -dotnet run -``` -Expected: Logs show admin user created successfully - -**Step 4: Test login** - -Run: -```bash -curl -X POST http://localhost:5000/connect/token \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=password" \ - -d "username=admin" \ - -d "password=Admin@123" \ - -d "scope=api offline_access" -``` -Expected: Returns access_token and refresh_token - -**Step 5: Commit** - -```bash -git add src/Fengling.AuthService/Data/SeedData.cs src/Fengling.AuthService/Program.cs -git commit -m "feat(auth): add seed data for admin and test users" -``` - ---- - -## Task 7: Create Health Check Endpoint - -**Files:** -- Modify: `src/Fengling.AuthService/Program.cs` - -**Step 1: Add health check configuration** - -Edit: `src/Fengling.AuthService/Program.cs` (add after builder services) - -```csharp -builder.Services.AddHealthChecks() - .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection")!); -``` - -**Step 2: Add health check endpoint** - -Edit: `src/Fengling.AuthService/Program.cs` (before app.Run()) - -```csharp -app.MapHealthChecks("/health"); -``` - -**Step 3: Test health check** - -Run: -```bash -dotnet run -``` - -Test: -```bash -curl http://localhost:5000/health -``` -Expected: "Healthy" - -**Step 4: Commit** - -```bash -git add src/Fengling.AuthService/Program.cs -git commit -m "feat(auth): add health check endpoint" -``` - ---- - -## Task 8: Create Dockerfile - -**Files:** -- Create: `src/Fengling.AuthService/Dockerfile` - -**Step 1: Create Dockerfile** - -Create: `src/Fengling.AuthService/Dockerfile` - -```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base -WORKDIR /app -EXPOSE 80 - -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build -WORKDIR /src -COPY ["Fengling.AuthService.csproj", "./"] -RUN dotnet restore "Fengling.AuthService.csproj" -COPY . . -WORKDIR "/src" -RUN dotnet build "Fengling.AuthService.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "Fengling.AuthService.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Fengling.AuthService.dll"] -``` - -**Step 2: Create .dockerignore** - -Create: `src/Fengling.AuthService/.dockerignore` - -``` -bin/ -obj/ -Dockerfile -.dockerignore -*.md -``` - -**Step 3: Commit** - -```bash -git add src/Fengling.AuthService/Dockerfile src/Fengling.AuthService/.dockerignore -git commit -m "feat(auth): add Dockerfile for containerization" -``` - ---- - -## Task 9: Create Documentation - -**Files:** -- Create: `src/Fengling.AuthService/README.md` - -**Step 1: Create README** - -Create: `src/Fengling.AuthService/README.md` - -```markdown -# Fengling Auth Service - -Authentication and authorization service using OpenIddict. - -## Features - -- JWT token issuance -- OAuth2/OIDC support -- Multi-tenant support (TenantId in JWT claims) -- Role-based access control (RBAC) -- Health check endpoint - -## API Endpoints - -### Get Token -``` -POST /connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password -username={username} -password={password} -scope=api offline_access -``` - -### Health Check -``` -GET /health -``` - -## Default Users - -- **Admin**: username=admin, password=Admin@123, role=Admin -- **Test User**: username=testuser, password=Test@123, role=User - -## Running Locally - -```bash -dotnet run -``` - -Service runs on port 5000. - -## Docker - -```bash -docker build -t fengling-auth:latest . -docker run -p 5000:80 fengling-auth:latest -``` - -## Environment Variables - -- `ConnectionStrings__DefaultConnection`: PostgreSQL connection string -- `OpenIddict__Issuer`: Token issuer URL -- `OpenIddict__Audience`: Token audience - -## Database - -- PostgreSQL -- Uses ASP.NET Core Identity for user/role management -- Tenant isolation via `TenantId` column -``` - -**Step 2: Commit** - -```bash -git add src/Fengling.AuthService/README.md -git commit -m "docs(auth): add API documentation" -``` - ---- - -## Summary - -This implementation plan creates a fully functional authentication service with: - -1. **Project structure** with OpenIddict and EF Core dependencies -2. **Database models** for users and roles with multi-tenant support -3. **OpenIddict configuration** for JWT token issuance -4. **Auth controller** with login endpoint -5. **OAuth2 endpoints** for token exchange -6. **Seed data** for admin and test users -7. **Health check** endpoint -8. **Docker support** for containerization -9. **Documentation** for API usage - -The service issues JWT tokens with embedded `tenant_id` claims, enabling multi-tenant routing via the YARP Gateway. diff --git a/docs/plans/2025-02-01-microservices-architecture.md b/docs/plans/2025-02-01-microservices-architecture.md deleted file mode 100644 index 1a880b3..0000000 --- a/docs/plans/2025-02-01-microservices-architecture.md +++ /dev/null @@ -1,216 +0,0 @@ -# Fengling Microservices Architecture Design - -**Date**: 2025-02-01 -**Status**: Approved - -## Overview -风灵(Fengling)系统从单体架构重构为微服务架构,采用核心业务微服务拆分策略,每服务独立数据库,通过RabbitMQ异步通信。 - -## Core Business Services - -### 1. 会员服务 -- **Database**: `fengling_member` -- **Entities**: Member, MemberLevel, MemberTag, MemberGroup -- **Responsibilities**: 会员信息管理、会员等级、会员分组、标签管理 - -### 2. 推广员服务 -- **Database**: `fengling_promoter` -- **Entities**: Promoter, PromotersActivity, PromoterStore -- **Responsibilities**: 推广员管理、推广员活动、推广员积分 - -### 3. 营销活动服务 -- **Database**: `fengling_activity` -- **Entities**: Activity, ActivityAward, ActivitySign, AfeActivity -- **Responsibilities**: 活动创建、活动报名、签到、抽奖、礼品发放 - -### 4. 优惠券服务 -- **Database**: `fengling_coupon` -- **Entities**: Coupon, CouponExpense, CouponTask -- **Responsibilities**: 优惠券发放、核销、任务配置 - -### 5. 礼品服务 -- **Database**: `fengling_gift` -- **Entities**: Gift, GiftCategory, GiftExpense -- **Responsibilities**: 礼品管理、礼品发放记录 - -### 6. 订单服务 -- **Database**: `fengling_order` -- **Entities**: Order, VirtualOrder, StoreshopOrders -- **Responsibilities**: 订单创建、订单状态管理 - -### 7. 渠道服务 -- **Database**: `fengling_channel` -- **Entities**: Channel, ChannelQrCode, ChannelApply, ChannelTag -- **Responsibilities**: 渠道管理、二维码生成、渠道申请审核 - -### 8. 门店服务 -- **Database**: `fengling_store` -- **Entities**: Store, StoreCategory, StoreLevel, StoreApply -- **Responsibilities**: 门店管理、门店等级、门店申请审核 - -### 9. 账户服务 -- **Database**: `fengling_account` -- **Entities**: Account, CustomerBalance, WalletBalance -- **Responsibilities**: 资金账户、积分账户、钱包余额管理 - -### 10. 积分服务 -- **Database**: `fengling_points` -- **Entities**: Points, IntegralRule, IntegralDetail, PointClearConfig -- **Responsibilities**: 积分规则配置、积分发放/扣减、积分明细 - -## Infrastructure Services - -### 1. 认证授权服务 -- **Technology**: OpenIddict (开源免费) -- **Responsibilities**: - - 用户认证(JWT Token签发) - - OAuth2/OIDC标准支持 - - 多租户认证(TenantId嵌入Token) - - 权限验证 -- **Gateway Integration**: 网关验证Token并传递TenantId到下游服务 - -### 2. 配置管理 -- **Approach**: K8s ConfigMap + appsettings环境变量 -- **Shared Library**: `Fengling.Configuration` - - 统一配置读取 - - 环境变量覆盖支持 - - 无需额外部署 -- **Benefits**: 简单可靠,零额外组件 - -### 3. 日志服务 -- **Technology**: Serilog + 云厂商日志服务 -- **Collection**: - - 应用输出JSON到stdout - - 云厂商Agent抓取日志 -- **Format**: JSON结构化(TraceId, SpanId, TenantId) - -### 4. 链路追踪 -- **Technology**: OpenTelemetry + Jaeger -- **Scope**: HTTP/RabbitMQ/DB/Redis -- **Retention**: 30天 - -### 5. 消息队列 -- **Technology**: RabbitMQ (集群部署) -- **Exchanges**: - - `activity.exchange`: 营销活动相关消息 - - `order.exchange`: 订单相关消息 - - `member.exchange`: 会员相关消息 - - `points.exchange`: 积分相关消息 -- **Persistence**: 开启持久化 -- **Dead Letter Queue**: 每个队列配置DLQ - -## Communication Pattern - -**All services use RabbitMQ for asynchronous communication** - -### Message Flows - -1. **Order Created**: - - Order Service → `order.exchange` → Points Service (add points) - - Order Service → `order.exchange` → Coupon Service (consume coupon) - -2. **Activity Signed**: - - Activity Service → `activity.exchange` → Points Service (add sign points) - - Activity Service → `activity.exchange` → Gift Service (issue gift) - -3. **Member Registered**: - - Member Service → `member.exchange` → Channel Service (bind channel) - - Member Service → `member.exchange` → Points Service (init account) - -## Database Strategy - -**Each microservice has its own PostgreSQL database** - -- Naming convention: `fengling_` -- No cross-service joins allowed -- Data consistency via eventual consistency (message queue) -- Tenant isolation via `TenantId` column in all tables - -## Security - -1. **Authentication**: JWT Token via OpenIddict -2. **Authorization**: Role-based access control (RBAC) -3. **Tenant Isolation**: TenantId in JWT + TenantId column in all tables -4. **API Security**: Gateway validates all incoming requests - -## Deployment - -- **Infrastructure**: Kubernetes -- **Gateway**: YARP Gateway (already implemented) -- **Load Balancing**: Kubernetes Service + Ingress -- **Configuration**: K8s ConfigMap -- **Logging**: Cloud provider log aggregation -- **Monitoring**: Prometheus + Grafana (optional) - -## Implementation Priority - -### Phase 1: Infrastructure (Current) -1. ✅ YARP Gateway -2. 🔄 Authentication Service (in progress) -3. RabbitMQ Setup -4. OpenTelemetry + Jaeger Setup - -### Phase 2: Core Services -5. Member Service -6. Promoter Service -7. Activity Service -8. Order Service - -### Phase 3: Supporting Services -9. Coupon Service -10. Gift Service -11. Channel Service -12. Store Service -13. Account Service -14. Points Service - -## Technology Stack - -- **.NET Version**: .NET 9.0 -- **Language**: C# 13 -- **Database**: PostgreSQL -- **ORM**: Entity Framework Core 9.0 -- **Cache**: Redis (StackExchange.Redis) -- **Message Queue**: RabbitMQ (MassTransit) -- **Authentication**: OpenIddict -- **Logging**: Serilog -- **Tracing**: OpenTelemetry -- **API Gateway**: YARP -- **Container**: Docker -- **Orchestration**: Kubernetes - -## Reference Architecture - -``` -[Client App] - | - v -[YARP Gateway] - | - |---[Tenant Routing]---> - | -[Authentication Service] (OpenIddict) - | - v -[Service Mesh (RabbitMQ)] - | - +---[Member Service]--->[fengling_member DB] - +---[Promoter Service]-->[fengling_promoter DB] - +---[Activity Service]-->[fengling_activity DB] - +---[Order Service]----->[fengling_order DB] - +---[Coupon Service]--->[fengling_coupon DB] - +---[Gift Service]----->[fengling_gift DB] - +---[Channel Service]-->[fengling_channel DB] - +---[Store Service]---->[fengling_store DB] - +---[Account Service]-->[fengling_account DB] - +---[Points Service]--->[fengling_points DB] -``` - -## Migration Strategy - -1. **Phase 1**: Extract shared libraries (Configuration, Logging, Tracing) -2. **Phase 2**: Implement Authentication Service -3. **Phase 3**: Extract services one by one (least dependent first) -4. **Phase 4**: Migrate data from monolithic database -5. **Phase 5**: Update Gateway routing to new services -6. **Phase 6**: Decommission old monolithic application diff --git a/docs/plans/fengling-console-plan.md b/docs/plans/fengling-console-plan.md deleted file mode 100644 index cb344ef..0000000 --- a/docs/plans/fengling-console-plan.md +++ /dev/null @@ -1,203 +0,0 @@ -# Fengling.Console 运管中心规划 - -## 项目概述 - -**项目名称**: Fengling.Console -**中文名称**: 风铃运管中心 -**定位**: 统一运维管理平台(前端Vue项目) - -## 架构设计 - -### 前端(新建) -- **项目**: `src/Fengling.Console.Web/` (Vue 3 + Vite) -- **功能**: - - 网关路由管理 - - 租户管理 - - OAuth Client管理 - - 用户管理 - - 集群实例管理 - -### 后端API(已有) -1. **Fengling.AuthService** - 认证服务 - - OAuth Client管理 API - - 用户管理 API - - Token 端点 - -2. **YarpGateway** - 网关服务 - - 租户管理 API - - 路由管理 API - - 集群实例管理 API - -## 功能模块 - -### 模块1: 认证登录 -- OAuth2 授权码流登录 -- Token刷新机制 -- 登出功能 -- Client: fengling-console - -### 模块2: 网关管理 -- 租户列表 -- 租户路由配置 -- 集群实例管理 -- 全局路由配置 -- 负载均衡策略 - -### 模块3: OAuth Client管理 -- Client ID/Secret管理 -- 重定向URI配置 -- 授权类型配置 -- Scope配置 -- Client状态管理 - -### 模块4: 用户管理 -- 用户列表 -- 用户角色分配 -- 租户分配 -- 用户状态管理 - -## 认证集成 - -### Fengling.Console作为OAuth Client -已在AuthService中预注册: - -```yaml -ClientId: "fengling-console" -ClientSecret: "console-secret-change-in-production" -RedirectUris: ["http://console.fengling.local/auth/callback"] -PostLogoutRedirectUris: ["http://console.fengling.local/"] -Scopes: ["api", "offline_access"] -GrantTypes: ["authorization_code", "refresh_token"] -``` - -## 目录结构 - -``` -src/ -├── Fengling.AuthService/ # 认证服务(已完成) -│ ├── Controllers/ -│ │ ├── OAuthClientsController.cs # OAuth Client管理API -│ │ └── AuthController.cs # 认证API -│ ├── Models/ -│ │ ├── OAuthApplication.cs -│ │ ├── ApplicationUser.cs -│ │ └── ApplicationRole.cs -│ └── Data/ -│ ├── SeedData.cs # 预注册fengling-console -│ └── ApplicationDbContext.cs -├── YarpGateway/ # 网关服务(已有) -│ ├── Controllers/ -│ │ ├── GatewayConfigController.cs -│ │ ├── TenantController.cs -│ │ └── ClusterInstanceController.cs -│ ├── Models/ -│ │ ├── GwTenant.cs -│ │ ├── GwTenantRoute.cs -│ │ └── GwServiceInstance.cs -│ └── Data/ -│ └── GatewayDbContext.cs -└── Fengling.Console.Web/ # 运管中心前端(新建) - ├── src/ - │ ├── views/ - │ │ ├── Auth/ # 认证相关 - │ │ │ ├── Login.vue - │ │ │ └── Callback.vue - │ │ ├── Gateway/ # 网关管理 - │ │ │ ├── TenantList.vue - │ │ │ ├── TenantRoutes.vue - │ │ │ ├── ClusterInstances.vue - │ │ │ └── GlobalRoutes.vue - │ │ ├── OAuth/ # OAuth管理 - │ │ │ └── ClientList.vue - │ │ └── Users/ # 用户管理 - │ │ └── UserList.vue - │ ├── components/ - │ ├── api/ - │ │ ├── auth.ts # AuthService API - │ │ ├── gateway.ts # Gateway API - │ │ └── oauth.ts # OAuth API - │ ├── stores/ - │ │ ├── auth.ts - │ │ └── user.ts - │ └── router/ - │ └── index.ts - ├── package.json - └── vite.config.ts -``` - -## API调用关系 - -``` -Fengling.Console.Web (前端) - ├── OAuth2登录 → Fengling.AuthService (/connect/authorize) - ├── OAuth Client管理 → Fengling.AuthService (/api/oauthclients) - ├── 用户管理 → Fengling.AuthService (/api/users) - ├── 租户管理 → YarpGateway (/api/tenants) - ├── 路由管理 → YarpGateway (/api/routes) - └── 集群管理 → YarpGateway (/api/instances) -``` - -## 技术栈 - -### 前端 (Fengling.Console.Web) -- Vue 3 + TypeScript -- Vite -- Element Plus (UI组件库) -- Pinia (状态管理) -- Vue Router -- Axios (HTTP客户端) - -### 后端API -- Fengling.AuthService: .NET 10.0, OpenIddict 7.2.0 -- YarpGateway: .NET 10.0, YARP 2.2.0 - -## 实施计划 - -### Phase 1: 认证服务扩展(已完成) -- [x] Task 1-9: 完成AuthService基础功能 -- [x] Task 10: 添加OAuth Client模型和管理API -- [x] Task 11: 预注册Fengling.Console作为Client - -### Phase 2: 运管中心前端(待开始) -- [ ] Task 12: 创建Fengling.Console.Web Vue项目 -- [ ] Task 13: 配置项目结构和依赖 -- [ ] Task 14: 实现OAuth2登录流程 -- [ ] Task 15: 实现网关管理界面(迁移YarpGateway.Admin) -- [ ] Task 16: 实现OAuth Client管理界面 -- [ ] Task 17: 实现用户管理界面 -- [ ] Task 18: 配置API封装(调用AuthService和YarpGateway) -- [ ] Task 19: 添加Dockerfile和部署配置 - -## 依赖关系 - -``` -Fengling.Console.Web (Vue前端) - ↓ (OAuth2) -Fengling.AuthService (认证API) - ↓ (JWT Token验证) -YarpGateway (网关API) -``` - -## 环境配置 - -### 开发环境 -```env -VITE_AUTH_SERVICE_URL=http://auth.fengling.local:5000 -VITE_GATEWAY_SERVICE_URL=http://gateway.fengling.local:5000 -VITE_CLIENT_ID=fengling-console -VITE_REDIRECT_URI=http://console.fengling.local:5173/auth/callback -``` - -### 生产环境 -```env -VITE_AUTH_SERVICE_URL=https://auth.fengling.local -VITE_GATEWAY_SERVICE_URL=https://gateway.fengling.local -``` - -## 注意事项 - -1. **无后端项目**: Fengling.Console是纯前端项目 -2. **直接调用现有API**: 复用AuthService和YarpGateway的API -3. **OAuth2认证**: 使用授权码流登录 -4. **Token管理**: 自动刷新access token -5. **跨域处理**: 配置CORS或使用同域代理 diff --git a/docs/task-01-create-project-structure.md b/docs/task-01-create-project-structure.md deleted file mode 100644 index 7fba32f..0000000 --- a/docs/task-01-create-project-structure.md +++ /dev/null @@ -1,100 +0,0 @@ -# Task 1: Create Project Structure - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Fengling.AuthService.csproj` -- Create: `src/Fengling.AuthService/Program.cs` -- Create: `src/Fengling.AuthService/appsettings.json` - -## Implementation Steps - -### Step 1: Create project file - -Run: -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src -dotnet new webapi -n Fengling.AuthService -o Fengling.AuthService -``` - -### Step 2: Update project file with dependencies - -Edit: `src/Fengling.AuthService/Fengling.AuthService.csproj` - -```xml - - - net10.0 - enable - enable - - - - - - - - - - - - - - - -``` - -### Step 3: Create appsettings.json - -Create: `src/Fengling.AuthService/appsettings.json` - -```json -{ - "ConnectionStrings": { - "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542" - }, - "OpenIddict": { - "Issuer": "https://auth.fengling.local", - "Audience": "fengling-api" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} -``` - -### Step 4: Commit - -```bash -git add src/Fengling.AuthService/ -git commit -m "feat(auth): create authentication service project structure" -``` - -## Context - -This is the first task of implementing the Fengling Authentication Service. We're building a standalone authentication service using OpenIddict for JWT token issuance and multi-tenant support. This task establishes the basic ASP.NET Core Web API project structure with all necessary dependencies including OpenIddict, EF Core, PostgreSQL, Serilog, and OpenTelemetry. - -**Architecture**: ASP.NET Core Web API with OpenIddict for OAuth2/OIDC, PostgreSQL for user data. - -**Tech Stack**: .NET 10.0, OpenIddict 7.2.0, EF Core 10.0.2, PostgreSQL, Serilog 9.0.0, OpenTelemetry 1.11.0. - -**Working Directory**: `/Users/movingsam/Fengling.Refactory.Buiding/src` - -## Verification - -- [ ] Project file created successfully -- [ ] Target framework set to net10.0 -- [ ] All NuGet packages added (latest versions) -- [ ] appsettings.json configured with connection string and OpenIddict settings -- [ ] Project builds successfully -- [ ] Committed to git - -## Notes - -- OpenIddict packages updated to 7.2.0 (latest) -- All dependencies updated to latest versions -- Used .NET 10.0 as target framework diff --git a/docs/task-02-create-database-models.md b/docs/task-02-create-database-models.md deleted file mode 100644 index 6279770..0000000 --- a/docs/task-02-create-database-models.md +++ /dev/null @@ -1,128 +0,0 @@ -# Task 2: Create Database Models - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Data/ApplicationDbContext.cs` -- Create: `src/Fengling.AuthService/Models/ApplicationUser.cs` -- Create: `src/Fengling.AuthService/Models/ApplicationRole.cs` -- Create: `src/Fengling.AuthService/Data/Migrations/20250201_InitialCreate.cs` - -## Implementation Steps - -### Step 1: Create ApplicationUser model - -Create: `src/Fengling.AuthService/Models/ApplicationUser.cs` - -```csharp -using Microsoft.AspNetCore.Identity; - -namespace Fengling.AuthService.Models; - -public class ApplicationUser : IdentityUser -{ - public string? RealName { get; set; } - public string? Phone { get; set; } - public long TenantId { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedTime { get; set; } - public bool IsDeleted { get; set; } -} -``` - -### Step 2: Create ApplicationRole model - -Create: `src/Fengling.AuthService/Models/ApplicationRole.cs` - -```csharp -using Microsoft.AspNetCore.Identity; - -namespace Fengling.AuthService.Models; - -public class ApplicationRole : IdentityRole -{ - public string? Description { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; -} -``` - -### Step 3: Create ApplicationDbContext - -Create: `src/Fengling.AuthService/Data/ApplicationDbContext.cs` - -```csharp -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Data; - -public class ApplicationDbContext : IdentityDbContext -{ - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - builder.Entity(entity => - { - entity.Property(e => e.RealName).HasMaxLength(100); - entity.Property(e => e.Phone).HasMaxLength(20); - entity.HasIndex(e => e.TenantId); - entity.HasIndex(e => e.Phone).IsUnique(); - }); - - builder.Entity(entity => - { - entity.Property(e => e.Description).HasMaxLength(200); - }); - } -} -``` - -### Step 4: Add migration - -Run: -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src/Fengling.AuthService -dotnet ef migrations add InitialCreate -o Data/Migrations -``` - -### Step 5: Update database - -Run: -```bash -dotnet ef database update -``` - -### Step 6: Commit - -```bash -git add src/Fengling.AuthService/Models/ src/Fengling.AuthService/Data/ -git commit -m "feat(auth): add user and role models with EF Core migrations" -``` - -## Context - -This task creates the database models for users and roles with multi-tenant support. We're using ASP.NET Core Identity with long primary keys (IdentityUser) and adding custom properties like RealName, Phone, and TenantId for multi-tenant isolation. - -**Tech Stack**: EF Core 9.0, PostgreSQL, ASP.NET Core Identity - -## Verification - -- [ ] ApplicationUser model created with long key type and custom properties -- [ ] ApplicationRole model created with description property -- [ ] ApplicationDbContext configured with proper entity configurations -- [ ] EF Core migration generated successfully -- [ ] Database updated with schema -- [ ] Committed to git - -## Notes - -- Using long (Int64) as key type for better scalability -- TenantId added to ApplicationUser for multi-tenant support -- Phone number has unique index constraint diff --git a/docs/task-03-configure-openiddict.md b/docs/task-03-configure-openiddict.md deleted file mode 100644 index 03041dc..0000000 --- a/docs/task-03-configure-openiddict.md +++ /dev/null @@ -1,202 +0,0 @@ -# Task 3: Configure OpenIddict - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Configuration/OpenIddictSetup.cs` -- Modify: `src/Fengling.AuthService/Program.cs` - -## Implementation Steps - -### Step 1: Create OpenIddict configuration - -Create: `src/Fengling.AuthService/Configuration/OpenIddictSetup.cs` - -```csharp -using Microsoft.Extensions.DependencyInjection; -using OpenIddict.Validation.AspNetCore; - -namespace Fengling.AuthService.Configuration; - -public static class OpenIddictSetup -{ - public static IServiceCollection AddOpenIddictConfiguration(this IServiceCollection services, IConfiguration configuration) - { - services.AddOpenIddict() - .AddCore(options => - { - options.UseEntityFrameworkCore() - .UseDbContext(); - }) - .AddServer(options => - { - options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "https://auth.fengling.local"); - options.AddSigningKey(new SymmetricSecurityKey( - System.Text.Encoding.UTF8.GetBytes("fengling-super-secret-key-for-dev-only-change-in-prod-please!!!"))); - - options.AllowAuthorizationCodeFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow() - .RequireProofKeyForCodeExchange(); - - options.RegisterScopes("api", "offline_access"); - - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - options.UseAspNetCore() - .EnableAuthorizationEndpointPassThrough() - .EnableTokenEndpointPassThrough() - .EnableLogoutEndpointPassThrough(); - }) - .AddValidation(options => - { - options.UseLocalServer(); - options.UseAspNetCore(); - }); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; - }); - - return services; - } -} -``` - -### Step 2: Update Program.cs with OpenIddict and EF Core - -Edit: `src/Fengling.AuthService/Program.cs` - -```csharp -using Fengling.AuthService.Configuration; -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.OpenApi.Models; -using OpenTelemetry; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; -using Serilog; - -var builder = WebApplication.CreateBuilder(args); - -// Serilog -Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(builder.Configuration) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") - .CreateLogger(); - -builder.Host.UseSerilog(); - -// Database -builder.Services.AddDbContext(options => - options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); - -// Identity -builder.Services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - -// OpenIddict -builder.Services.AddOpenIddictConfiguration(builder.Configuration); - -// OpenTelemetry -builder.Services.AddOpenTelemetry() - .ConfigureResource(resource => - resource.AddService("Fengling.AuthService")) - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddSource("OpenIddict.Server.AspNetCore") - .AddOtlpExporter(); - -// Controllers -builder.Services.AddControllers(); - -// Swagger -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(options => -{ - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Fengling Auth Service", - Version = "v1", - Description = "Authentication and authorization service using OpenIddict" - }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows - { - Password = new OpenApiOAuthFlow - { - TokenUrl = "/connect/token" - } - } - }); -}); - -var app = builder.Build(); - -// Configure pipeline -app.UseSwagger(); -app.UseSwaggerUI(options => -{ - options.SwaggerEndpoint("/swagger/v1/swagger.json", "Fengling Auth Service v1"); - options.OAuthClientId("swagger-ui"); - options.OAuthUsePkce(); -}); - -app.UseRouting(); -app.UseAuthentication(); -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); -``` - -### Step 3: Run to verify startup - -Run: -```bash -dotnet run -``` -Expected: Service starts without errors, Swagger UI available at http://localhost:5000/swagger - -### Step 4: Commit - -```bash -git add src/Fengling.AuthService/Configuration/ src/Fengling.AuthService/Program.cs -git commit -m "feat(auth): configure OpenIddict with JWT and OAuth2 support" -``` - -## Context - -This task configures OpenIddict for OAuth2/OIDC authentication, including: -- Server configuration with token endpoints -- Password flow for user authentication -- Development certificates for signing -- Integration with ASP.NET Core Identity -- Swagger UI with OAuth2 support - -**Tech Stack**: OpenIddict 7.2.0, ASP.NET Core Identity, Swagger - -## Verification - -- [ ] OpenIddictSetup class created -- [ ] Program.cs updated with OpenIddict configuration -- [ ] Service starts without errors -- [ ] Swagger UI accessible -- [ ] Committed to git - -## Notes - -- Using symmetric key for token signing (dev only) -- Password flow enabled for direct authentication -- Development certificates used (replace in production) diff --git a/docs/task-04-create-auth-controller.md b/docs/task-04-create-auth-controller.md deleted file mode 100644 index 577f894..0000000 --- a/docs/task-04-create-auth-controller.md +++ /dev/null @@ -1,171 +0,0 @@ -# Task 4: Create Auth Controller - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Controllers/AuthController.cs` -- Create: `src/Fengling.AuthService/DTOs/LoginRequest.cs` -- Create: `src/Fengling.AuthService/DTOs/LoginResponse.cs` -- Create: `src/Fengling.AuthService/DTOs/TokenResponse.cs` - -## Implementation Steps - -### Step 1: Create DTOs - -Create: `src/Fengling.AuthService/DTOs/LoginRequest.cs` - -```csharp -namespace Fengling.AuthService.DTOs; - -public class LoginRequest -{ - public string UserName { get; set; } = string.Empty; - public string Password { get; set; } = string.Empty; - public long TenantId { get; set; } -} -``` - -Create: `src/Fengling.AuthService/DTOs/LoginResponse.cs` - -```csharp -namespace Fengling.AuthService.DTOs; - -public class LoginResponse -{ - public string AccessToken { get; set; } = string.Empty; - public string RefreshToken { get; set; } = string.Empty; - public int ExpiresIn { get; set; } - public string TokenType { get; set; } = "Bearer"; -} -``` - -### Step 2: Create AuthController - -Create: `src/Fengling.AuthService/Controllers/AuthController.cs` - -```csharp -using Fengling.AuthService.DTOs; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -public class AuthController : ControllerBase -{ - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly IOpenIddictScopeManager _scopeManager; - private readonly ILogger _logger; - - public AuthController( - SignInManager signInManager, - UserManager userManager, - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - ILogger logger) - { - _signInManager = signInManager; - _userManager = userManager; - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _scopeManager = scopeManager; - _logger = logger; - } - - [HttpPost("login")] - public async Task Login([FromBody] LoginRequest request) - { - var user = await _userManager.FindByNameAsync(request.UserName); - if (user == null || user.IsDeleted) - { - return Unauthorized(new { error = "用户不存在" }); - } - - if (user.TenantId != request.TenantId) - { - return Unauthorized(new { error = "租户不匹配" }); - } - - var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); - if (!result.Succeeded) - { - return Unauthorized(new { error = "用户名或密码错误" }); - } - - var token = await GenerateTokenAsync(user); - return Ok(token); - } - - private async Task GenerateTokenAsync(ApplicationUser user) - { - var claims = new List - { - new(Claims.Subject, user.Id.ToString()), - new(Claims.Name, user.UserName ?? string.Empty), - new(Claims.Email, user.Email ?? string.Empty), - new("tenant_id", user.TenantId.ToString()) - }; - - var roles = await _userManager.GetRolesAsync(user); - foreach (var role in roles) - { - claims.Add(new Claim(Claims.Role, role)); - } - - var identity = new System.Security.Claims.ClaimsIdentity(claims, "Server"); - var principal = new System.Security.Claims.ClaimsPrincipal(identity); - - return new LoginResponse - { - AccessToken = "token-placeholder", - RefreshToken = "refresh-placeholder", - ExpiresIn = 3600, - TokenType = "Bearer" - }; - } -} -``` - -### Step 3: Run to verify controller compilation - -Run: -```bash -dotnet build -``` -Expected: Build succeeds - -### Step 4: Commit - -```bash -git add src/Fengling.AuthService/Controllers/ src/Fengling.AuthService/DTOs/ -git commit -m "feat(auth): add authentication controller with login endpoint" -``` - -## Context - -This task creates an authentication controller with a login endpoint. The login endpoint validates user credentials, checks tenant isolation, and generates JWT tokens with embedded tenant_id claims for multi-tenant routing. - -**Tech Stack**: ASP.NET Core Controllers, OpenIddict - -## Verification - -- [ ] LoginRequest DTO created -- [ ] LoginResponse DTO created -- [ ] AuthController created with login endpoint -- [ ] Tenant validation implemented -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Token generation is placeholder, will be replaced by OpenIddict in next task -- Tenant validation ensures multi-tenant isolation diff --git a/docs/task-05-create-openiddict-endpoints.md b/docs/task-05-create-openiddict-endpoints.md deleted file mode 100644 index cf713f7..0000000 --- a/docs/task-05-create-openiddict-endpoints.md +++ /dev/null @@ -1,186 +0,0 @@ -# Task 5: Create OpenIddict Endpoints - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Controllers/AuthorizationController.cs` - -## Implementation Steps - -### Step 1: Create authorization endpoints - -Create: `src/Fengling.AuthService/Controllers/AuthorizationController.cs` - -```csharp -using Fengling.AuthService.Models; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -public class AuthorizationController : Controller -{ - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly IOpenIddictScopeManager _scopeManager; - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly ILogger _logger; - - public AuthorizationController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - SignInManager signInManager, - UserManager userManager, - ILogger logger) - { - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _scopeManager = scopeManager; - _signInManager = signInManager; - _userManager = userManager; - _logger = logger; - } - - [HttpPost("~/connect/token")] - [Produces("application/json")] - public async Task Exchange() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - if (request.IsPasswordGrantType()) - { - var user = await _userManager.FindByNameAsync(request.Username); - if (user == null || user.IsDeleted) - { - return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户不存在" - })); - } - - var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); - if (!result.Succeeded) - { - return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户名或密码错误" - })); - } - - var principal = await _signInManager.CreateUserPrincipalAsync(user); - - var claims = new List - { - new(Claims.Subject, user.Id.ToString()), - new(Claims.Name, user.UserName ?? string.Empty), - new(Claims.Email, user.Email ?? string.Empty), - new("tenant_id", user.TenantId.ToString()) - }; - - var roles = await _userManager.GetRolesAsync(user); - foreach (var role in roles) - { - claims.Add(new Claim(Claims.Role, role)); - } - - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } - - return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.UnsupportedGrantType, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "不支持的授权类型" - })); - } - - [HttpGet("~/connect/authorize")] - [HttpPost("~/connect/authorize")] - [IgnoreAntiforgeryToken] - public async Task Authorize() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - var result = await HttpContext.AuthenticateAsync(); - if (result == null || !result.Succeeded) - { - return Challenge( - new AuthenticationProperties - { - RedirectUri = Request.Path + Request.QueryString - }, - OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } - - return Ok(new { message = "Authorization endpoint" }); - } - - [HttpPost("~/connect/logout")] - [ValidateAntiForgeryToken] - public async Task Logout() - { - await HttpContext.SignOutAsync(); - return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } -} -``` - -### Step 2: Run to verify - -Run: -```bash -dotnet run -``` - -Test with curl: -```bash -curl -X POST http://localhost:5000/connect/token \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=password" \ - -d "username=admin" \ - -d "password=Admin@123" \ - -d "scope=api offline_access" -``` -Expected: 400 error (user doesn't exist yet, but endpoint is working) - -### Step 3: Commit - -```bash -git add src/Fengling.AuthService/Controllers/AuthorizationController.cs -git commit -m "feat(auth): add OpenIddict authorization endpoints" -``` - -## Context - -This task creates OpenIddict OAuth2/OIDC endpoints including token exchange, authorize, and logout. The token endpoint supports password flow for direct authentication. - -**Tech Stack**: OpenIddict 7.2.0, ASP.NET Core - -## Verification - -- [ ] AuthorizationController created -- [ ] Token endpoint supports password flow -- [ ] Tenant ID added to token claims -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Token endpoint returns JWT with tenant_id claim -- Logout endpoint clears session diff --git a/docs/task-06-create-seed-data.md b/docs/task-06-create-seed-data.md deleted file mode 100644 index 4a0e832..0000000 --- a/docs/task-06-create-seed-data.md +++ /dev/null @@ -1,154 +0,0 @@ -# Task 6: Create Seed Data - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Data/SeedData.cs` -- Modify: `src/Fengling.AuthService/Program.cs` - -## Implementation Steps - -### Step 1: Create seed data class - -Create: `src/Fengling.AuthService/Data/SeedData.cs` - -```csharp -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Data; - -public static class SeedData -{ - public static async Task Initialize(IServiceProvider serviceProvider) - { - using var scope = serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - var roleManager = scope.ServiceProvider.GetRequiredService>(); - - context.Database.EnsureCreated(); - - var adminRole = await roleManager.FindByNameAsync("Admin"); - if (adminRole == null) - { - adminRole = new ApplicationRole - { - Name = "Admin", - Description = "System administrator", - CreatedTime = DateTime.UtcNow - }; - await roleManager.CreateAsync(adminRole); - } - - var adminUser = await userManager.FindByNameAsync("admin"); - if (adminUser == null) - { - adminUser = new ApplicationUser - { - UserName = "admin", - Email = "admin@fengling.local", - RealName = "系统管理员", - Phone = "13800138000", - TenantId = 1, - EmailConfirmed = true, - IsDeleted = false, - CreatedTime = DateTime.UtcNow - }; - - var result = await userManager.CreateAsync(adminUser, "Admin@123"); - if (result.Succeeded) - { - await userManager.AddToRoleAsync(adminUser, "Admin"); - } - } - - var testUser = await userManager.FindByNameAsync("testuser"); - if (testUser == null) - { - testUser = new ApplicationUser - { - UserName = "testuser", - Email = "test@fengling.local", - RealName = "测试用户", - Phone = "13900139000", - TenantId = 1, - EmailConfirmed = true, - IsDeleted = false, - CreatedTime = DateTime.UtcNow - }; - - var result = await userManager.CreateAsync(testUser, "Test@123"); - if (result.Succeeded) - { - var userRole = new ApplicationRole - { - Name = "User", - Description = "普通用户", - CreatedTime = DateTime.UtcNow - }; - await roleManager.CreateAsync(userRole); - await userManager.AddToRoleAsync(testUser, "User"); - } - } - } -} -``` - -### Step 2: Update Program.cs to call seed data - -Edit: `src/Fengling.AuthService/Program.cs` (add after `var app = builder.Build();`) - -```csharp -using (var scope = app.Services.CreateScope()) -{ - await Data.SeedData.Initialize(scope.ServiceProvider); -} -``` - -### Step 3: Run to create seed data - -Run: -```bash -dotnet run -``` -Expected: Logs show admin user created successfully - -### Step 4: Test login - -Run: -```bash -curl -X POST http://localhost:5000/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"userName":"admin","password":"Admin@123","tenantId":1}' -``` -Expected: Returns token (placeholder for now) - -### Step 5: Commit - -```bash -git add src/Fengling.AuthService/Data/SeedData.cs src/Fengling.AuthService/Program.cs -git commit -m "feat(auth): add seed data for admin and test users" -``` - -## Context - -This task creates initial seed data including admin and test users with default passwords. This allows immediate testing of the authentication service. - -**Tech Stack**: ASP.NET Core Identity - -## Verification - -- [ ] SeedData class created -- [ ] Program.cs calls seed data on startup -- [ ] Admin user created: admin/Admin@123 -- [ ] Test user created: testuser/Test@123 -- [ ] Both users assigned to tenant 1 -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Passwords should be changed in production -- All users assigned to tenant 1 for initial setup diff --git a/docs/task-07-create-health-check.md b/docs/task-07-create-health-check.md deleted file mode 100644 index 14c937f..0000000 --- a/docs/task-07-create-health-check.md +++ /dev/null @@ -1,77 +0,0 @@ -# Task 7: Create Health Check Endpoint - -## Task Description - -**Files:** -- Modify: `src/Fengling.AuthService/Program.cs` -- Modify: `src/Fengling.AuthService/Fengling.AuthService.csproj` - -## Implementation Steps - -### Step 1: Add health check package - -Edit: `src/Fengling.AuthService/Fengling.AuthService.csproj` - -Add package reference: -```xml - - -``` - -### Step 2: Add health check configuration - -Edit: `src/Fengling.AuthService/Program.cs` (add after builder services) - -```csharp -builder.Services.AddHealthChecks() - .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection")!); -``` - -### Step 3: Add health check endpoint - -Edit: `src/Fengling.AuthService/Program.cs` (before app.Run()) - -```csharp -app.MapHealthChecks("/health"); -``` - -### Step 4: Test health check - -Run: -```bash -dotnet run -``` - -Test: -```bash -curl http://localhost:5000/health -``` -Expected: "Healthy" - -### Step 5: Commit - -```bash -git add src/Fengling.AuthService/Program.cs src/Fengling.AuthService/Fengling.AuthService.csproj -git commit -m "feat(auth): add health check endpoint" -``` - -## Context - -This task adds a health check endpoint to monitor service and database connectivity. Health checks are essential for container orchestration and monitoring. - -**Tech Stack**: ASP.NET Core Health Checks, PostgreSQL - -## Verification - -- [ ] Health check packages added -- [ ] Health check configuration added -- [ ] Health check endpoint mapped -- [ ] Endpoint returns "Healthy" -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Health check endpoint: /health -- Monitors PostgreSQL connection -- Ready for Kubernetes liveness/readiness probes diff --git a/docs/task-08-create-dockerfile.md b/docs/task-08-create-dockerfile.md deleted file mode 100644 index ef62f94..0000000 --- a/docs/task-08-create-dockerfile.md +++ /dev/null @@ -1,74 +0,0 @@ -# Task 8: Create Dockerfile - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Dockerfile` -- Create: `src/Fengling.AuthService/.dockerignore` - -## Implementation Steps - -### Step 1: Create Dockerfile - -Create: `src/Fengling.AuthService/Dockerfile` - -```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base -WORKDIR /app -EXPOSE 80 - -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src -COPY ["Fengling.AuthService.csproj", "./"] -RUN dotnet restore "Fengling.AuthService.csproj" -COPY . . -WORKDIR "/src" -RUN dotnet build "Fengling.AuthService.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "Fengling.AuthService.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Fengling.AuthService.dll"] -``` - -### Step 2: Create .dockerignore - -Create: `src/Fengling.AuthService/.dockerignore` - -``` -bin/ -obj/ -Dockerfile -.dockerignore -*.md -``` - -### Step 3: Commit - -```bash -git add src/Fengling.AuthService/Dockerfile src/Fengling.AuthService/.dockerignore -git commit -m "feat(auth): add Dockerfile for containerization" -``` - -## Context - -This task adds Docker support for containerization. The multi-stage Dockerfile builds the project in a SDK container and copies only the runtime to a lightweight runtime container. - -**Tech Stack**: Docker, .NET 10.0 - -## Verification - -- [ ] Dockerfile created with multi-stage build -- [ ] .dockerignore created -- [ ] Dockerfile uses .NET 10.0 -- [ ] Exposes port 80 -- [ ] Committed to git - -## Notes - -- Multi-stage build reduces image size -- Only runtime container in final image -- Ready for Kubernetes deployment diff --git a/docs/task-10-add-oauth-client-management.md b/docs/task-10-add-oauth-client-management.md deleted file mode 100644 index a28827e..0000000 --- a/docs/task-10-add-oauth-client-management.md +++ /dev/null @@ -1,176 +0,0 @@ -# Task 10: Add OAuth Client Management - -## Task Description - -**Files:** -- Create: `src/Fengling.AuthService/Models/OAuthApplication.cs` -- Create: `src/Fengling.AuthService/Controllers/OAuthClientsController.cs` -- Modify: `src/Fengling.AuthService/Data/ApplicationDbContext.cs` - -## Implementation Steps - -### Step 1: Create OAuthApplication model - -Create: `src/Fengling.AuthService/Models/OAuthApplication.cs` - -```csharp -namespace Fengling.AuthService.Models; - -public class OAuthApplication -{ - public long Id { get; set; } - public string ClientId { get; set; } = string.Empty; - public string? ClientSecret { get; set; } - public string DisplayName { get; set; } = string.Empty; - public string[] RedirectUris { get; set; } = Array.Empty(); - public string[] PostLogoutRedirectUris { get; set; } = Array.Empty(); - public string[] Scopes { get; set; } = Array.Empty(); - public string[] GrantTypes { get; set; } = Array.Empty(); - public string ClientType { get; set; } = "public"; - public string ConsentType { get; set; } = "implicit"; - public string Status { get; set; } = "active"; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedAt { get; set; } -} -``` - -### Step 2: Update ApplicationDbContext - -Edit: `src/Fengling.AuthService/Data/ApplicationDbContext.cs` - -Add to context: -```csharp -public DbSet OAuthApplications { get; set; } -``` - -Add to OnModelCreating: -```csharp -builder.Entity(entity => -{ - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.ClientId).IsUnique(); - entity.Property(e => e.ClientId).HasMaxLength(100); - entity.Property(e => e.ClientSecret).HasMaxLength(200); - entity.Property(e => e.DisplayName).HasMaxLength(100); - entity.Property(e => e.ClientType).HasMaxLength(20); - entity.Property(e => e.ConsentType).HasMaxLength(20); - entity.Property(e => e.Status).HasMaxLength(20); -}); -``` - -### Step 3: Add migration - -Run: -```bash -dotnet ef migrations add AddOAuthApplications -dotnet ef database update -``` - -### Step 4: Create OAuthClientsController - -Create: `src/Fengling.AuthService/Controllers/OAuthClientsController.cs` - -```csharp -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -public class OAuthClientsController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly ILogger _logger; - - public OAuthClientsController( - ApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - [HttpGet] - public async Task>> GetClients() - { - return await _context.OAuthApplications.ToListAsync(); - } - - [HttpGet("{id}")] - public async Task> GetClient(long id) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - return client; - } - - [HttpPost] - public async Task> CreateClient(OAuthApplication application) - { - _context.OAuthApplications.Add(application); - await _context.SaveChangesAsync(); - return CreatedAtAction(nameof(GetClient), new { id = application.Id }, application); - } - - [HttpPut("{id}")] - public async Task UpdateClient(long id, OAuthApplication application) - { - if (id != application.Id) - { - return BadRequest(); - } - - _context.Entry(application).State = EntityState.Modified; - await _context.SaveChangesAsync(); - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteClient(long id) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - _context.OAuthApplications.Remove(client); - await _context.SaveChangesAsync(); - return NoContent(); - } -} -``` - -### Step 5: Commit - -```bash -git add src/Fengling.AuthService/Models/ src/Fengling.AuthService/Controllers/ src/Fengling.AuthService/Data/ -git commit -m "feat(auth): add OAuth client management API" -``` - -## Context - -This task adds OAuth client management functionality for managing OAuth applications. This will be used by Fengling.Console to register and manage clients. - -**Tech Stack**: EF Core, ASP.NET Core Controllers - -## Verification - -- [ ] OAuthApplication model created -- [ ] DbSet added to ApplicationDbContext -- [ ] Migration generated and applied -- [ ] CRUD API endpoints created -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Clients can be registered through this API -- Fengling.Console will be pre-registered in Task 11 -- Status field enables client enable/disable diff --git a/docs/task-11-pre-register-console-client.md b/docs/task-11-pre-register-console-client.md deleted file mode 100644 index d332b2f..0000000 --- a/docs/task-11-pre-register-console-client.md +++ /dev/null @@ -1,69 +0,0 @@ -# Task 11: Pre-register Fengling.Console as OAuth Client - -## Task Description - -**Files:** -- Modify: `src/Fengling.AuthService/Data/SeedData.cs` - -## Implementation Steps - -### Step 1: Add Fengling.Console registration to SeedData - -Edit: `src/Fengling.AuthService/Data/SeedData.cs` - -Add after existing seed data: -```csharp -// Register Fengling.Console as OAuth client -var consoleClient = await context.OAuthApplications - .FirstOrDefaultAsync(c => c.ClientId == "fengling-console"); -if (consoleClient == null) -{ - consoleClient = new OAuthApplication - { - ClientId = "fengling-console", - ClientSecret = "console-secret-change-in-production", - DisplayName = "Fengling 运管中心", - RedirectUris = new[] { "http://console.fengling.local/auth/callback" }, - PostLogoutRedirectUris = new[] { "http://console.fengling.local/" }, - Scopes = new[] { "api", "offline_access" }, - GrantTypes = new[] { "authorization_code", "refresh_token" }, - ClientType = "confidential", - ConsentType = "implicit", - Status = "active", - CreatedAt = DateTime.UtcNow - }; - context.OAuthApplications.Add(consoleClient); - await context.SaveChangesAsync(); -} -``` - -### Step 2: Commit - -```bash -git add src/Fengling.AuthService/Data/SeedData.cs -git commit -m "feat(auth): pre-register Fengling.Console as OAuth client" -``` - -## Context - -This task pre-registers Fengling.Console as an OAuth client in the seed data. This allows the console to use OAuth2 authorization code flow for authentication. - -**OAuth Client Configuration:** -- ClientId: `fengling-console` -- Redirect URI: `http://console.fengling.local/auth/callback` -- Scopes: `api`, `offline_access` -- Grant Types: `authorization_code`, `refresh_token` - -## Verification - -- [ ] Fengling.Console client added to seed data -- [ ] Client configured with correct redirect URIs -- [ ] Client has required scopes and grant types -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Client secret should be changed in production -- Redirect URI matches Fengling.Console domain -- Client will be created on first application startup diff --git a/docs/task-12-create-console-project.md b/docs/task-12-create-console-project.md deleted file mode 100644 index f7faa55..0000000 --- a/docs/task-12-create-console-project.md +++ /dev/null @@ -1,101 +0,0 @@ -# Task 12: Create Fengling.Console Project Structure - -## Task Description - -**Files:** -- Create: `src/Fengling.Console/Fengling.Console.csproj` -- Create: `src/Fengling.Console/Program.cs` -- Create: `src/Fengling.Console/appsettings.json` - -## Implementation Steps - -### Step 1: Create project - -Run: -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src -dotnet new webapi -n Fengling.Console -o Fengling.Console -``` - -### Step 2: Update project file with dependencies - -Edit: `src/Fengling.Console/Fengling.Console.csproj` - -```xml - - - net10.0 - enable - enable - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - -``` - -### Step 3: Create appsettings.json - -Edit: `src/Fengling.Console/appsettings.json` - -```json -{ - "ConnectionStrings": { - "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_gateway;Username=movingsam;Password=sl52788542" - }, - "AuthService": { - "BaseUrl": "http://auth.fengling.local", - "ClientId": "fengling-console", - "ClientSecret": "console-secret-change-in-production" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} -``` - -### Step 4: Commit - -```bash -git add src/Fengling.Console/ -git commit -m "feat(console): create console backend project structure" -``` - -## Context - -This task creates the Fengling.Console backend project. This will be a unified management API that includes gateway management, OAuth client management, and user management proxy. - -**Tech Stack**: .NET 10.0, EF Core 10.0, PostgreSQL, Serilog, OpenTelemetry - -## Verification - -- [ ] Project created with webapi template -- [ ] Target framework set to net10.0 -- [ ] All packages added -- [ ] appsettings.json configured -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- Shares database with YarpGateway -- Communicates with AuthService via OAuth2 -- Will contain migrated gateway management APIs diff --git a/docs/task-12-create-console-web-project.md b/docs/task-12-create-console-web-project.md deleted file mode 100644 index 9585a9e..0000000 --- a/docs/task-12-create-console-web-project.md +++ /dev/null @@ -1,152 +0,0 @@ -# Task 12: Create Fengling.Console.Web Frontend Project - -## Task Description - -**Files:** -- Create: `src/Fengling.Console.Web/` (Vue 3 + Vite project) -- Create: `src/Fengling.Console.Web/.env.development` -- Create: `src/Fengling.Console.Web/.env.production` -- Create: `src/Fengling.Console.Web/vite.config.ts` -- Create: `src/Fengling.Console.Web/src/api/auth.ts` -- Create: `src/Fengling.Console.Web/src/api/gateway.ts` -- Create: `src/Fengling.Console.Web/src/stores/auth.ts` -- Create: `src/Fengling.Console.Web/src/router/index.ts` -- Create: `src/Fengling.Console.Web/src/views/Auth/Login.vue` -- Create: `src/Fengling.Console.Web/src/views/Auth/Callback.vue` -- Create: `src/Fengling.Console.Web/src/views/Gateway/Dashboard.vue` -- Create: `src/Fengling.Console.Web/src/views/OAuth/ClientList.vue` -- Create: `src/Fengling.Console.Web/src/views/Users/UserList.vue` - -## Implementation Steps - -### Step 1: Create Vue project - -Run: -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src -npm create vite@latest Fengling.Console.Web -- --template vue-ts -``` - -### Step 2: Install dependencies - -Run: -```bash -cd Fengling.Console.Web -npm install -npm install element-plus @element-plus/icons-vue pinia vue-router axios -``` - -### Step 3: Configure environment variables - -Create: `src/Fengling.Console.Web/.env.development` -```env -VITE_AUTH_SERVICE_URL=http://localhost:5000 -VITE_GATEWAY_SERVICE_URL=http://localhost:5001 -VITE_CLIENT_ID=fengling-console -VITE_REDIRECT_URI=http://localhost:5173/auth/callback -``` - -Create: `src/Fengling.Console.Web/.env.production` -```env -VITE_AUTH_SERVICE_URL=https://auth.fengling.local -VITE_GATEWAY_SERVICE_URL=https://gateway.fengling.local -VITE_CLIENT_ID=fengling-console -VITE_REDIRECT_URI=https://console.fengling.local/auth/callback -``` - -### Step 4: Configure Vite - -Create: `src/Fengling.Console.Web/vite.config.ts` -```typescript -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import path from 'path' - -export default defineConfig(({ mode }) => ({ - plugins: [vue()], - resolve: { - alias: { - '@': path.resolve(__dirname, 'src') - } - }, - server: { - port: 5173, - proxy: { - '/api/auth': { - target: 'http://localhost:5000', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api\/auth/, '') - }, - '/api/gateway': { - target: 'http://localhost:5001', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api\/gateway/, '/api') - } - } - } -})) -``` - -### Step 5: Create API modules - -Create: `src/Fengling.Console.Web/src/api/auth.ts` - AuthService API封装 -Create: `src/Fengling.Console.Web/src/api/gateway.ts` - Gateway API封装 - -### Step 6: Create store - -Create: `src/Fengling.Console.Web/src/stores/auth.ts` - Pinia store for auth - -### Step 7: Create router - -Create: `src/Fengling.Console.Web/src/router/index.ts` - Vue Router配置 - -### Step 8: Create views - -Create authentication views: -- `src/Fengling.Console.Web/src/views/Auth/Login.vue` -- `src/Fengling.Console.Web/src/views/Auth/Callback.vue` - -Create gateway management views: -- `src/Fengling.Console.Web/src/views/Gateway/Dashboard.vue` - -Create OAuth management views: -- `src/Fengling.Console.Web/src/views/OAuth/ClientList.vue` - -Create user management views: -- `src/Fengling.Console.Web/src/views/Users/UserList.vue` - -### Step 9: Commit - -```bash -git add src/Fengling.Console.Web/ -git commit -m "feat(console): create Vue 3 frontend project with OAuth2 and basic modules" -``` - -## Context - -This task creates the Fengling.Console.Web frontend project using Vue 3 + Vite. The frontend directly calls AuthService and YarpGateway APIs through Vite proxy configuration. - -**Tech Stack**: Vue 3.4, TypeScript, Vite 5.4, Element Plus, Pinia, Vue Router 4, Axios - -## Verification - -- [ ] Vue 3 project created with Vite -- [ ] Dependencies installed (Element Plus, Pinia, Vue Router, Axios) -- [ ] Environment variables configured -- [ ] Vite proxy configured for auth and gateway APIs -- [ ] API modules created -- [ ] Auth store created -- [ ] Router configured -- [ ] Authentication views created (Login, Callback) -- [ ] Dashboard view created -- [ ] OAuth Client management view created -- [ ] User management view created -- [ ] Build succeeds -- [ ] Committed to git - -## Notes - -- OAuth2 authorization code flow ready but AuthService endpoint not yet implemented -- Password flow login implemented -- Gateway management views can be migrated from YarpGateway.Admin -- Token refresh interceptor to be added diff --git a/docs/testing-guide.md b/docs/testing-guide.md deleted file mode 100644 index f4a1a60..0000000 --- a/docs/testing-guide.md +++ /dev/null @@ -1,398 +0,0 @@ -# YARP Gateway 测试指南 - -## 1. 数据库准备 - -### 使用EF Core Migrations初始化数据库 - -项目使用 EF Core 管理数据库,已生成迁移脚本位于 `sql/init.sql`。 - -```bash -# 连接到PostgreSQL并执行迁移脚本 -psql -h 192.168.100.10 -U postgres -d fengling_gateway -f sql/init.sql -``` - -如果数据库不存在,先创建数据库: - -```bash -psql -h 192.168.100.10 -U postgres -c "CREATE DATABASE fengling_gateway;" -``` - -### 验证数据 - -```bash -psql -h 192.168.100.10 -U postgres -d fengling_gateway -c "\dt" -``` - -应该看到3个表: -- `Tenants` -- `TenantRoutes` -- `ServiceInstances` - -## 2. 启动网关 - -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -dotnet run -``` - -网关将在 `http://localhost:8080` 启动。 - -## 3. 测试API接口 - -### 3.1 创建租户 - -```bash -curl -X POST http://localhost:8080/api/gateway/tenants \ - -H "Content-Type: application/json" \ - -d '{ - "tenantCode": "customerA", - "tenantName": "客户A" - }' -``` - -响应示例: -```json -{ - "id": 1738377721234, - "tenantCode": "customerA", - "tenantName": "客户A", - "status": 1, - "createdBy": null, - "createdTime": "2026-02-01T20:08:41.234Z", - "updatedBy": null, - "updatedTime": null, - "isDeleted": false, - "version": 0 -} -``` - -### 3.2 查看所有租户 - -```bash -curl http://localhost:8080/api/gateway/tenants -``` - -### 3.3 为租户创建路由 - -```bash -curl -X POST http://localhost:8080/api/gateway/tenants/customerA/routes \ - -H "Content-Type: application/json" \ - -d '{ - "serviceName": "product", - "pathPattern": "/api/product/{**catch-all}" - }' -``` - -响应示例: -```json -{ - "id": 1738377722345, - "tenantCode": "customerA", - "serviceName": "product", - "clusterId": "customerA-product", - "pathPattern": "/api/product/{**catch-all}", - "priority": 0, - "status": 1 -} -``` - -### 3.4 查看租户路由 - -```bash -curl http://localhost:8080/api/gateway/tenants/customerA/routes -``` - -### 3.5 添加服务实例 - -```bash -curl -X POST http://localhost:8080/api/gateway/clusters/customerA-product/instances \ - -H "Content-Type: application/json" \ - -d '{ - "destinationId": "product-1", - "address": "http://localhost:8001", - "weight": 1 - }' -``` - -### 3.6 查看Cluster实例 - -```bash -curl http://localhost:8080/api/gateway/clusters/customerA-product/instances -``` - -### 3.7 重新加载配置 - -```bash -curl -X POST http://localhost:8080/api/gateway/reload -``` - -响应: -```json -{ - "message": "Config reloaded successfully" -} -``` - -## 4. JWT测试 - -### 4.1 生成测试JWT - -使用 https://jwt.io/ 生成测试JWT: - -**Header**: -```json -{ - "alg": "HS256", - "typ": "JWT" -} -``` - -**Payload**: -```json -{ - "tenant": "customerA", - "sub": "123456", - "unique_name": "张三", - "role": ["admin", "user"], - "exp": 1738369200 -} -``` - -**Secret** (示例密钥): -``` -your-secret-key-at-least-32-bytes-long -``` - -### 4.2 测试JWT解析 - -```bash -JWT_TOKEN="your-generated-jwt-token" - -curl -X GET http://localhost:8080/api/product/list \ - -H "Authorization: Bearer $JWT_TOKEN" -``` - -### 4.3 验证Header传递 - -创建一个测试服务来接收请求: - -**创建测试服务** (test-service.js): -```javascript -const http = require('http'); - -const server = http.createServer((req, res) => { - console.log('收到请求:'); - console.log('URL:', req.url); - console.log('Headers:', req.headers); - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - message: '请求成功', - tenantId: req.headers['x-tenant-id'], - userId: req.headers['x-user-id'], - userName: req.headers['x-user-name'], - roles: req.headers['x-roles'] - })); -}); - -server.listen(8001, () => { - console.log('测试服务运行在 http://localhost:8001'); -}); -``` - -**启动测试服务**: -```bash -node test-service.js -``` - -## 5. 负载均衡测试 - -### 5.1 添加多个服务实例 - -```bash -# 实例1 -curl -X POST http://localhost:8080/api/gateway/clusters/customerA-product/instances \ - -H "Content-Type: application/json" \ - -d '{ - "destinationId": "product-1", - "address": "http://localhost:8001", - "weight": 10 - }' - -# 实例2 -curl -X POST http://localhost:8080/api/gateway/clusters/customerA-product/instances \ - -H "Content-Type: application/json" \ - -d '{ - "destinationId": "product-2", - "address": "http://localhost:8002", - "weight": 5 - }' -``` - -### 5.2 并发测试 - -```bash -for i in {1..20}; do - curl -X GET http://localhost:8080/api/product/list \ - -H "Authorization: Bearer $JWT_TOKEN" & -done -wait -``` - -观察日志输出,验证请求是否按权重分配到不同实例。 - -## 6. 日志查看 - -### 查看网关日志 - -控制台会输出实时日志,包括: -- JWT解析日志 -- 路由选择日志 -- 负载均衡选择日志 - -### 日志文件位置 - -`/Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway/logs/gateway-YYYYMMDD.log` - -## 7. 故障排查 - -### 问题1: 无法连接数据库 - -**测试连接**: -```bash -psql -h 192.168.100.10 -U postgres -d fengling_gateway -``` - -**检查配置**: -- 确认 `appsettings.json` 中的连接字符串正确 -- 确认 PostgreSQL 已启动并可访问 - -### 问题2: 路由404 - -**检查租户配置**: -```bash -curl http://localhost:8080/api/gateway/tenants -``` - -**检查路由配置**: -```bash -curl http://localhost:8080/api/gateway/tenants/customerA/routes -``` - -**检查实例配置**: -```bash -curl http://localhost:8080/api/gateway/clusters/customerA-product/instances -``` - -### 问题3: JWT解析失败 - -**验证JWT格式**: -```bash -echo "your.jwt.token" | cut -d'.' -f2 | base64 -d -``` - -**检查日志中的错误信息** - -### 问题4: 数据库迁移失败 - -**手动执行SQL脚本**: -```bash -psql -h 192.168.100.10 -U postgres -d fengling_gateway -f sql/init.sql -``` - -**检查迁移历史**: -```sql -SELECT * FROM "__EFMigrationsHistory"; -``` - -## 8. 使用Docker Compose - -如果使用Docker Compose部署: - -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/docker -docker-compose up -d -``` - -查看日志: -```bash -docker-compose logs -f gateway -``` - -## 9. 清理 - -### 停止网关 - -按 `Ctrl+C` 停止 `dotnet run` - -### 删除数据库(如果需要) - -```bash -psql -h 192.168.100.10 -U postgres -c "DROP DATABASE fengling_gateway;" -``` - -## 10. 创建示例数据 - -### 创建完整示例数据 - -```bash -# 1. 创建租户 -curl -X POST http://localhost:8080/api/gateway/tenants \ - -H "Content-Type: application/json" \ - -d '{"tenantCode": "customerB", "tenantName": "客户B"}' - -# 2. 创建订单服务路由 -curl -X POST http://localhost:8080/api/gateway/tenants/customerA/routes \ - -H "Content-Type: application/json" \ - -d '{"serviceName": "order", "pathPattern": "/api/order/{**catch-all}"}' - -# 3. 为客户B创建产品服务 -curl -X POST http://localhost:8080/api/gateway/tenants/customerB/routes \ - -H "Content-Type: application/json" \ - -d '{"serviceName": "product", "pathPattern": "/api/product/{**catch-all}"}' - -# 4. 为客户B产品服务添加实例 -curl -X POST http://localhost:8080/api/gateway/clusters/customerB-product/instances \ - -H "Content-Type: application/json" \ - -d '{"destinationId": "product-1", "address": "http://localhost:8003", "weight": 1}' -``` - -### 验证配置 - -```bash -# 查看所有租户 -curl http://localhost:8080/api/gateway/tenants | jq - -# 查看客户A的所有路由 -curl http://localhost:8080/api/gateway/tenants/customerA/routes | jq - -# 查看客户A产品服务的所有实例 -curl http://localhost:8080/api/gateway/clusters/customerA-product/instances | jq -``` - -## 11. EF Core Migrations管理 - -### 添加新迁移 - -```bash -cd /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -dotnet ef migrations add AddNewFeature -``` - -### 生成SQL脚本 - -```bash -dotnet ef migrations script --context GatewayDbContext --output ../sql/migration_$(date +%Y%m%d).sql -``` - -### 应用迁移到数据库 - -```bash -dotnet ef database update --context GatewayDbContext -``` - -### 回滚迁移 - -```bash -dotnet ef database update 20260201120312_InitialCreate --context GatewayDbContext -``` diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index aefd0c3..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1421 +0,0 @@ -{ - "name": "Fengling.Refactory.Buiding", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "axios": "^1.13.4", - "element-plus": "^2.13.2", - "pinia": "^3.0.4", - "vue-router": "^5.0.1" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", - "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", - "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@element-plus/icons-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", - "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@popperjs/core": { - "name": "@sxzz/popperjs-es", - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", - "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@types/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@vue-macros/common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", - "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", - "license": "MIT", - "dependencies": { - "@vue/compiler-sfc": "^3.5.22", - "ast-kit": "^2.1.2", - "local-pkg": "^1.1.2", - "magic-string-ast": "^1.0.2", - "unplugin-utils": "^0.3.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/vue-macros" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.2.25" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", - "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.27", - "entities": "^7.0.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", - "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", - "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.27", - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", - "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.9" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", - "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", - "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", - "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/runtime-core": "3.5.27", - "@vue/shared": "3.5.27", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", - "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27" - }, - "peerDependencies": { - "vue": "3.5.27" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", - "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ast-kit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", - "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "pathe": "^2.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/ast-walker-scope": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", - "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.4", - "ast-kit": "^2.1.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/element-plus": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.2.tgz", - "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", - "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^3.4.1", - "@element-plus/icons-vue": "^2.3.2", - "@floating-ui/dom": "^1.0.1", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", - "@types/lodash": "^4.17.20", - "@types/lodash-es": "^4.17.12", - "@vueuse/core": "^10.11.0", - "async-validator": "^4.2.5", - "dayjs": "^1.11.19", - "lodash": "^4.17.23", - "lodash-es": "^4.17.23", - "lodash-unified": "^1.0.3", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.2.0" - }, - "peerDependencies": { - "vue": "^3.3.0" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash-unified": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", - "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", - "license": "MIT", - "peerDependencies": { - "@types/lodash-es": "*", - "lodash": "*", - "lodash-es": "*" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magic-string-ast": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", - "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.19" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-wheel-es": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", - "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", - "license": "BSD-3-Clause" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pinia": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", - "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^7.7.7" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.5.0", - "vue": "^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/vue": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", - "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-sfc": "3.5.27", - "@vue/runtime-dom": "3.5.27", - "@vue/server-renderer": "3.5.27", - "@vue/shared": "3.5.27" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-router": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.1.tgz", - "integrity": "sha512-t+lFugGXMdaq8lbn+vXG4j2H9UlsP205Tszz1wcDk9FyxqItBzcdJQ06IhpkQ2mHOfiTOHZeBshkskzPzHJkCw==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^7.28.6", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.0.0", - "ast-walker-scope": "^0.8.3", - "chokidar": "^5.0.0", - "json5": "^2.2.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.0", - "muggle-string": "^0.4.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "scule": "^1.3.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1", - "yaml": "^2.8.2" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "@pinia/colada": "^0.18.1", - "@vue/compiler-sfc": "^3.5.17", - "pinia": "^3.0.4", - "vue": "^3.5.0" - }, - "peerDependenciesMeta": { - "@pinia/colada": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "pinia": { - "optional": true - } - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-api": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.5.tgz", - "integrity": "sha512-DgVcW8H/Nral7LgZEecYFFYXnAvGuN9C3L3DtWekAncFBedBczpNW8iHKExfaM559Zm8wQWrwtYZ9lXthEHtDw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.0.5" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-kit": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", - "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.0.5", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^2.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-shared": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", - "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/vue-router/node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "license": "MIT" - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 7b9bfee..0000000 --- a/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "axios": "^1.13.4", - "element-plus": "^2.13.2", - "pinia": "^3.0.4", - "vue-router": "^5.0.1" - } -} diff --git a/sql/init.sql b/sql/init.sql deleted file mode 100644 index 9a8b36d..0000000 --- a/sql/init.sql +++ /dev/null @@ -1,89 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -START TRANSACTION; -CREATE TABLE "ServiceInstances" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "ClusterId" character varying(100) NOT NULL, - "DestinationId" character varying(100) NOT NULL, - "Address" character varying(200) NOT NULL, - "Health" integer NOT NULL, - "Weight" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_ServiceInstances" PRIMARY KEY ("Id") -); - -CREATE TABLE "Tenants" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "TenantName" character varying(100) NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_Tenants" PRIMARY KEY ("Id"), - CONSTRAINT "AK_Tenants_TenantCode" UNIQUE ("TenantCode") -); - -CREATE TABLE "TenantRoutes" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "ServiceName" character varying(100) NOT NULL, - "ClusterId" character varying(100) NOT NULL, - "PathPattern" character varying(200) NOT NULL, - "Priority" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_TenantRoutes" PRIMARY KEY ("Id"), - CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode" FOREIGN KEY ("TenantCode") REFERENCES "Tenants" ("TenantCode") ON DELETE RESTRICT -); - -CREATE UNIQUE INDEX "IX_ServiceInstances_ClusterId_DestinationId" ON "ServiceInstances" ("ClusterId", "DestinationId"); - -CREATE INDEX "IX_ServiceInstances_Health" ON "ServiceInstances" ("Health"); - -CREATE INDEX "IX_TenantRoutes_ClusterId" ON "TenantRoutes" ("ClusterId"); - -CREATE UNIQUE INDEX "IX_TenantRoutes_TenantCode_ServiceName" ON "TenantRoutes" ("TenantCode", "ServiceName"); - -CREATE UNIQUE INDEX "IX_Tenants_TenantCode" ON "Tenants" ("TenantCode"); - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260201120312_InitialCreate', '9.0.0'); - -ALTER TABLE "TenantRoutes" DROP CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode"; - -ALTER TABLE "Tenants" DROP CONSTRAINT "AK_Tenants_TenantCode"; - -DROP INDEX "IX_TenantRoutes_TenantCode_ServiceName"; - -ALTER TABLE "TenantRoutes" ADD "IsGlobal" boolean NOT NULL DEFAULT FALSE; - -CREATE INDEX "IX_TenantRoutes_ServiceName" ON "TenantRoutes" ("ServiceName"); - -CREATE INDEX "IX_TenantRoutes_ServiceName_IsGlobal_Status" ON "TenantRoutes" ("ServiceName", "IsGlobal", "Status"); - -CREATE INDEX "IX_TenantRoutes_TenantCode" ON "TenantRoutes" ("TenantCode"); - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260201133826_AddIsGlobalToTenantRoute', '9.0.0'); - -COMMIT; - diff --git a/src/Fengling.AuthService/.dockerignore b/src/Fengling.AuthService/.dockerignore deleted file mode 100644 index 72acb0b..0000000 --- a/src/Fengling.AuthService/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -bin/ -obj/ -Dockerfile -.dockerignore -*.md diff --git a/src/Fengling.AuthService/Configuration/OpenIddictSetup.cs b/src/Fengling.AuthService/Configuration/OpenIddictSetup.cs deleted file mode 100644 index 8d5abcd..0000000 --- a/src/Fengling.AuthService/Configuration/OpenIddictSetup.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.Extensions.DependencyInjection; - -namespace Fengling.AuthService.Configuration; - -public static class OpenIddictSetup -{ - public static IServiceCollection AddOpenIddictConfiguration( - this IServiceCollection services, - IConfiguration configuration - ) - { - var isTesting = configuration.GetValue("Testing", false); - - var builder = services.AddOpenIddict(); - - builder.AddCore(options => - { - options.UseEntityFrameworkCore().UseDbContext(); - }); - - if (!isTesting) - { - builder.AddServer(options => - { - options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "https://auth.fengling.local"); - - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - options.AllowAuthorizationCodeFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow() - .RequireProofKeyForCodeExchange(); - - options.RegisterScopes("api", "offline_access"); - }); - } - - builder.AddValidation(options => - { - options.UseLocalServer(); - }); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }); - - return services; - } -} diff --git a/src/Fengling.AuthService/Controllers/AccessLogsController.cs b/src/Fengling.AuthService/Controllers/AccessLogsController.cs deleted file mode 100644 index 3abe130..0000000 --- a/src/Fengling.AuthService/Controllers/AccessLogsController.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class AccessLogsController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly ILogger _logger; - - public AccessLogsController( - ApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - [HttpGet] - public async Task> GetAccessLogs( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? userName = null, - [FromQuery] string? tenantId = null, - [FromQuery] string? action = null, - [FromQuery] string? status = null, - [FromQuery] DateTime? startTime = null, - [FromQuery] DateTime? endTime = null) - { - var query = _context.AccessLogs.AsQueryable(); - - if (!string.IsNullOrEmpty(userName)) - { - query = query.Where(l => l.UserName != null && l.UserName.Contains(userName)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(l => l.TenantId == tenantId); - } - - if (!string.IsNullOrEmpty(action)) - { - query = query.Where(l => l.Action == action); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(l => l.Status == status); - } - - if (startTime.HasValue) - { - query = query.Where(l => l.CreatedAt >= startTime.Value); - } - - if (endTime.HasValue) - { - query = query.Where(l => l.CreatedAt <= endTime.Value); - } - - var totalCount = await query.CountAsync(); - var logs = await query - .OrderByDescending(l => l.CreatedAt) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var result = logs.Select(l => new - { - id = l.Id, - userName = l.UserName, - tenantId = l.TenantId, - action = l.Action, - resource = l.Resource, - method = l.Method, - ipAddress = l.IpAddress, - userAgent = l.UserAgent, - status = l.Status, - duration = l.Duration, - requestData = l.RequestData, - responseData = l.ResponseData, - errorMessage = l.ErrorMessage, - createdAt = l.CreatedAt, - }); - - return Ok(new - { - items = result, - totalCount, - page, - pageSize - }); - } - - [HttpGet("export")] - public async Task ExportAccessLogs( - [FromQuery] string? userName = null, - [FromQuery] string? tenantId = null, - [FromQuery] string? action = null, - [FromQuery] string? status = null, - [FromQuery] DateTime? startTime = null, - [FromQuery] DateTime? endTime = null) - { - var query = _context.AccessLogs.AsQueryable(); - - if (!string.IsNullOrEmpty(userName)) - { - query = query.Where(l => l.UserName != null && l.UserName.Contains(userName)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(l => l.TenantId == tenantId); - } - - if (!string.IsNullOrEmpty(action)) - { - query = query.Where(l => l.Action == action); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(l => l.Status == status); - } - - if (startTime.HasValue) - { - query = query.Where(l => l.CreatedAt >= startTime.Value); - } - - if (endTime.HasValue) - { - query = query.Where(l => l.CreatedAt <= endTime.Value); - } - - var logs = await query - .OrderByDescending(l => l.CreatedAt) - .Take(10000) - .ToListAsync(); - - var csv = new System.Text.StringBuilder(); - csv.AppendLine("ID,UserName,TenantId,Action,Resource,Method,IpAddress,UserAgent,Status,Duration,CreatedAt"); - - foreach (var log in logs) - { - csv.AppendLine($"{log.Id},{log.UserName},{log.TenantId},{log.Action},{log.Resource},{log.Method},{log.IpAddress},\"{log.UserAgent}\",{log.Status},{log.Duration},{log.CreatedAt:yyyy-MM-dd HH:mm:ss}"); - } - - return File(System.Text.Encoding.UTF8.GetBytes(csv.ToString()), "text/csv", $"access-logs-{DateTime.UtcNow:yyyyMMdd}.csv"); - } -} diff --git a/src/Fengling.AuthService/Controllers/AccountController.cs b/src/Fengling.AuthService/Controllers/AccountController.cs deleted file mode 100644 index d8ee7cb..0000000 --- a/src/Fengling.AuthService/Controllers/AccountController.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Fengling.AuthService.ViewModels; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; - -namespace Fengling.AuthService.Controllers; - -[Route("account")] -public class AccountController : Controller -{ - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public AccountController( - UserManager userManager, - SignInManager signInManager, - ILogger logger) - { - _userManager = userManager; - _signInManager = signInManager; - _logger = logger; - } - - [HttpGet("login")] - public IActionResult Login(string returnUrl = "/") - { - return View(new LoginInputModel { ReturnUrl = returnUrl }); - } - - [HttpPost("login")] - [ValidateAntiForgeryToken] - public async Task Login(LoginInputModel model) - { - if (!ModelState.IsValid) - { - return View(model); - } - - var user = await _userManager.FindByNameAsync(model.Username); - if (user == null || user.IsDeleted) - { - ModelState.AddModelError(string.Empty, "用户名或密码错误"); - return View(model); - } - - var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, false); - if (!result.Succeeded) - { - if (result.IsLockedOut) - { - ModelState.AddModelError(string.Empty, "账号已被锁定"); - } - else - { - ModelState.AddModelError(string.Empty, "用户名或密码错误"); - } - return View(model); - } - - return LocalRedirect(model.ReturnUrl); - } - - [HttpGet("register")] - public IActionResult Register(string returnUrl = "/") - { - return View(new RegisterViewModel { ReturnUrl = returnUrl }); - } - - [HttpPost("register")] - [ValidateAntiForgeryToken] - public async Task Register(RegisterViewModel model) - { - if (!ModelState.IsValid) - { - return View(model); - } - - var user = new ApplicationUser - { - UserName = model.Username, - Email = model.Email, - NormalizedUserName = model.Username.ToUpper(), - NormalizedEmail = model.Email.ToUpper() - }; - - var result = await _userManager.CreateAsync(user, model.Password); - if (!result.Succeeded) - { - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } - return View(model); - } - - await _signInManager.SignInAsync(user, isPersistent: false); - return LocalRedirect(model.ReturnUrl); - } - - [HttpGet("profile")] - [HttpGet("settings")] - [HttpGet("logout")] - public IActionResult NotImplemented() - { - return RedirectToAction("Index", "Dashboard"); - } - - [HttpPost("logout")] - [ValidateAntiForgeryToken] - public async Task LogoutPost() - { - await _signInManager.SignOutAsync(); - return Redirect("/"); - } -} diff --git a/src/Fengling.AuthService/Controllers/AuditLogsController.cs b/src/Fengling.AuthService/Controllers/AuditLogsController.cs deleted file mode 100644 index f009aca..0000000 --- a/src/Fengling.AuthService/Controllers/AuditLogsController.cs +++ /dev/null @@ -1,159 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class AuditLogsController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly ILogger _logger; - - public AuditLogsController( - ApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - [HttpGet] - public async Task> GetAuditLogs( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? operatorName = null, - [FromQuery] string? tenantId = null, - [FromQuery] string? operation = null, - [FromQuery] string? action = null, - [FromQuery] DateTime? startTime = null, - [FromQuery] DateTime? endTime = null) - { - var query = _context.AuditLogs.AsQueryable(); - - if (!string.IsNullOrEmpty(operatorName)) - { - query = query.Where(l => l.Operator.Contains(operatorName)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(l => l.TenantId == tenantId); - } - - if (!string.IsNullOrEmpty(operation)) - { - query = query.Where(l => l.Operation == operation); - } - - if (!string.IsNullOrEmpty(action)) - { - query = query.Where(l => l.Action == action); - } - - if (startTime.HasValue) - { - query = query.Where(l => l.CreatedAt >= startTime.Value); - } - - if (endTime.HasValue) - { - query = query.Where(l => l.CreatedAt <= endTime.Value); - } - - var totalCount = await query.CountAsync(); - var logs = await query - .OrderByDescending(l => l.CreatedAt) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var result = logs.Select(l => new - { - id = l.Id, - @operator = l.Operator, - tenantId = l.TenantId, - operation = l.Operation, - action = l.Action, - targetType = l.TargetType, - targetId = l.TargetId, - targetName = l.TargetName, - ipAddress = l.IpAddress, - description = l.Description, - oldValue = l.OldValue, - newValue = l.NewValue, - errorMessage = l.ErrorMessage, - status = l.Status, - createdAt = l.CreatedAt, - }); - - return Ok(new - { - items = result, - totalCount, - page, - pageSize - }); - } - - [HttpGet("export")] - public async Task ExportAuditLogs( - [FromQuery] string? operatorName = null, - [FromQuery] string? tenantId = null, - [FromQuery] string? operation = null, - [FromQuery] string? action = null, - [FromQuery] DateTime? startTime = null, - [FromQuery] DateTime? endTime = null) - { - var query = _context.AuditLogs.AsQueryable(); - - if (!string.IsNullOrEmpty(operatorName)) - { - query = query.Where(l => l.Operator.Contains(operatorName)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(l => l.TenantId == tenantId); - } - - if (!string.IsNullOrEmpty(operation)) - { - query = query.Where(l => l.Operation == operation); - } - - if (!string.IsNullOrEmpty(action)) - { - query = query.Where(l => l.Action == action); - } - - if (startTime.HasValue) - { - query = query.Where(l => l.CreatedAt >= startTime.Value); - } - - if (endTime.HasValue) - { - query = query.Where(l => l.CreatedAt <= endTime.Value); - } - - var logs = await query - .OrderByDescending(l => l.CreatedAt) - .Take(10000) - .ToListAsync(); - - var csv = new System.Text.StringBuilder(); - csv.AppendLine("ID,Operator,TenantId,Operation,Action,TargetType,TargetId,TargetName,IpAddress,Description,Status,CreatedAt"); - - foreach (var log in logs) - { - csv.AppendLine($"{log.Id},{log.Operator},{log.TenantId},{log.Operation},{log.Action},{log.TargetType},{log.TargetId},{log.TargetName},{log.IpAddress},\"{log.Description}\",{log.Status},{log.CreatedAt:yyyy-MM-dd HH:mm:ss}"); - } - - return File(System.Text.Encoding.UTF8.GetBytes(csv.ToString()), "text/csv", $"audit-logs-{DateTime.UtcNow:yyyyMMdd}.csv"); - } -} diff --git a/src/Fengling.AuthService/Controllers/AuthorizationController.cs b/src/Fengling.AuthService/Controllers/AuthorizationController.cs deleted file mode 100644 index a1baff4..0000000 --- a/src/Fengling.AuthService/Controllers/AuthorizationController.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using System.Security.Claims; -using Fengling.AuthService.ViewModels; -using Microsoft.AspNetCore; -using Microsoft.Extensions.Primitives; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("connect")] -public class AuthorizationController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - SignInManager signInManager, - UserManager userManager, - ILogger logger) - : Controller -{ - - [HttpGet("authorize")] - [HttpPost("authorize")] - public async Task Authorize() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - // If prompt=login was specified by the client application, - // immediately return the user agent to the login page. - if (request.HasPromptValue(OpenIddictConstants.PromptValues.Login)) - { - // To avoid endless login -> authorization redirects, the prompt=login flag - // is removed from the authorization request payload before redirecting the user. - var prompt = string.Join(" ", request.GetPromptValues().Remove(OpenIddictConstants.PromptValues.Login)); - - var parameters = Request.HasFormContentType - ? Request.Form.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList() - : Request.Query.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList(); - - parameters.Add(KeyValuePair.Create(OpenIddictConstants.Parameters.Prompt, new StringValues(prompt))); - - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties - { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) - }); - } - - // Retrieve the user principal stored in the authentication cookie. - // If a max_age parameter was provided, ensure that the cookie is not too old. - // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); - if (result is not { Succeeded: true } || (request.MaxAge != null && result.Properties?.IssuedUtc != null && - DateTimeOffset.UtcNow - result.Properties.IssuedUtc > - TimeSpan.FromSeconds(request.MaxAge.Value))) - { - // If the client application requested promptless authentication, - // return an error indicating that the user is not logged in. - if (request.HasPromptValue(OpenIddictConstants.PromptValues.None)) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = - OpenIddictConstants.Errors.LoginRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." - }!)); - } - - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties - { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create( - Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) - }); - } - - // Retrieve the profile of the logged in user. - var user = await userManager.GetUserAsync(result.Principal) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); - - // Retrieve the application details from the database. - var application = await applicationManager.FindByClientIdAsync(request.ClientId!) ?? - throw new InvalidOperationException( - "Details concerning the calling client application cannot be found."); - - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await authorizationManager.FindAsync( - subject: await userManager.GetUserIdAsync(user), - client: (await applicationManager.GetIdAsync(application))!, - status: OpenIddictConstants.Statuses.Valid, - type: OpenIddictConstants.AuthorizationTypes.Permanent, - scopes: request.GetScopes()).ToListAsync(); - - switch (await applicationManager.GetConsentTypeAsync(application)) - { - // If the consent is external (e.g when authorizations are granted by a sysadmin), - // immediately return an error if no authorization can be found in the database. - case OpenIddictConstants.ConsentTypes.External when !authorizations.Any(): - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = - OpenIddictConstants.Errors.ConsentRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." - }!)); - - // If the consent is implicit or if an authorization was found, - // return an authorization response without displaying the consent form. - case OpenIddictConstants.ConsentTypes.Implicit: - case OpenIddictConstants.ConsentTypes.External when authorizations.Any(): - case OpenIddictConstants.ConsentTypes.Explicit - when authorizations.Any() && !request.HasPromptValue(OpenIddictConstants.PromptValues.Consent): - var principal = await signInManager.CreateUserPrincipalAsync(user); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(request.GetScopes()); - principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - if (authorization == null) - { - authorization = await authorizationManager.CreateAsync( - principal: principal, - subject: await userManager.GetUserIdAsync(user), - client: (await applicationManager.GetIdAsync(application))!, - type: OpenIddictConstants.AuthorizationTypes.Permanent, - scopes: principal.GetScopes()); - } - - principal.SetAuthorizationId(await authorizationManager.GetIdAsync(authorization)); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - - // At this point, no authorization was found in the database and an error must be returned - // if the client application specified prompt=none in the authorization request. - case OpenIddictConstants.ConsentTypes.Explicit when request.HasPromptValue(OpenIddictConstants.PromptValues.None): - case OpenIddictConstants.ConsentTypes.Systematic when request.HasPromptValue(OpenIddictConstants.PromptValues.None): - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = - OpenIddictConstants.Errors.ConsentRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "Interactive user consent is required." - }!)); - - // In every other case, render the consent form. - default: - return View(new AuthorizeViewModel(await applicationManager.GetDisplayNameAsync(application),request.Scope)); - } - } - - private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) - { - // Note: by default, claims are NOT automatically included in the access and identity tokens. - // To allow OpenIddict to serialize them, you must attach them a destination, that specifies - // whether they should be included in access tokens, in identity tokens or in both. - - switch (claim.Type) - { - case OpenIddictConstants.Claims.Name: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (principal.HasScope(OpenIddictConstants.Permissions.Scopes.Profile)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - - case OpenIddictConstants.Claims.Email: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (principal.HasScope(OpenIddictConstants.Permissions.Scopes.Email)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - - case OpenIddictConstants.Claims.Role: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (principal.HasScope(OpenIddictConstants.Permissions.Scopes.Roles)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - - // Never include the security stamp in the access and identity tokens, as it's a secret value. - case "AspNet.Identity.SecurityStamp": yield break; - - default: - yield return OpenIddictConstants.Destinations.AccessToken; - yield break; - } - } -} diff --git a/src/Fengling.AuthService/Controllers/DashboardController.cs b/src/Fengling.AuthService/Controllers/DashboardController.cs deleted file mode 100644 index 1732d36..0000000 --- a/src/Fengling.AuthService/Controllers/DashboardController.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Fengling.AuthService.ViewModels; -using Microsoft.AspNetCore.Mvc; - -namespace Fengling.AuthService.Controllers; - -[Route("dashboard")] -public class DashboardController : Controller -{ - private readonly ILogger _logger; - - public DashboardController(ILogger logger) - { - _logger = logger; - } - - [HttpGet("")] - [HttpGet("index")] - public IActionResult Index() - { - if (User.Identity?.IsAuthenticated != true) - { - return RedirectToAction("Login", "Account", new { returnUrl = "/dashboard" }); - } - - return View("Index", new DashboardViewModel - { - Username = User.Identity?.Name, - Email = User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value - }); - } - - [HttpGet("profile")] - public IActionResult Profile() - { - if (User.Identity?.IsAuthenticated != true) - { - return RedirectToAction("Login", "Account", new { returnUrl = "/dashboard/profile" }); - } - - return View(new DashboardViewModel - { - Username = User.Identity?.Name, - Email = User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value - }); - } - - [HttpGet("settings")] - public IActionResult Settings() - { - if (User.Identity?.IsAuthenticated != true) - { - return RedirectToAction("Login", "Account", new { returnUrl = "/dashboard/settings" }); - } - - return View(new DashboardViewModel - { - Username = User.Identity?.Name, - Email = User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value - }); - } -} diff --git a/src/Fengling.AuthService/Controllers/LogoutController.cs b/src/Fengling.AuthService/Controllers/LogoutController.cs deleted file mode 100644 index 502a4b6..0000000 --- a/src/Fengling.AuthService/Controllers/LogoutController.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("connect")] -public class LogoutController : ControllerBase -{ - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public LogoutController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - UserManager userManager, - SignInManager signInManager, - ILogger logger) - { - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _userManager = userManager; - _signInManager = signInManager; - _logger = logger; - } - - [HttpGet("endsession")] - [HttpPost("endsession")] - [IgnoreAntiforgeryToken] - public async Task EndSession() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("OpenIddict request is null"); - - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); - if (result.Succeeded) - { - await _signInManager.SignOutAsync(); - } - - if (request.ClientId != null) - { - var application = await _applicationManager.FindByClientIdAsync(request.ClientId); - if (application != null) - { - var postLogoutRedirectUri = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); - if (!string.IsNullOrEmpty(request.PostLogoutRedirectUri)) - { - if (postLogoutRedirectUri.Contains(request.PostLogoutRedirectUri)) - { - return Redirect(request.PostLogoutRedirectUri); - } - } - } - } - - - return Redirect("/"); - } -} diff --git a/src/Fengling.AuthService/Controllers/OAuthClientsController.cs b/src/Fengling.AuthService/Controllers/OAuthClientsController.cs deleted file mode 100644 index a98e39d..0000000 --- a/src/Fengling.AuthService/Controllers/OAuthClientsController.cs +++ /dev/null @@ -1,263 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Security.Claims; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class OAuthClientsController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly ILogger _logger; - - public OAuthClientsController( - ApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - [HttpGet] - public async Task> GetClients( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 10, - [FromQuery] string? displayName = null, - [FromQuery] string? clientId = null, - [FromQuery] string? status = null) - { - var query = _context.OAuthApplications.AsQueryable(); - - if (!string.IsNullOrEmpty(displayName)) - { - query = query.Where(c => c.DisplayName.Contains(displayName)); - } - - if (!string.IsNullOrEmpty(clientId)) - { - query = query.Where(c => c.ClientId.Contains(clientId)); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(c => c.Status == status); - } - - var totalCount = await query.CountAsync(); - var clients = await query - .OrderByDescending(c => c.CreatedAt) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var result = clients.Select(c => new - { - id = c.Id, - clientId = c.ClientId, - displayName = c.DisplayName, - redirectUris = c.RedirectUris, - postLogoutRedirectUris = c.PostLogoutRedirectUris, - scopes = c.Scopes, - grantTypes = c.GrantTypes, - clientType = c.ClientType, - consentType = c.ConsentType, - status = c.Status, - description = c.Description, - createdAt = c.CreatedAt, - }); - - return Ok(new - { - items = result, - totalCount, - page, - pageSize - }); - } - - [HttpGet("{id}")] - public async Task> GetClient(long id) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - return Ok(new - { - id = client.Id, - clientId = client.ClientId, - displayName = client.DisplayName, - redirectUris = client.RedirectUris, - postLogoutRedirectUris = client.PostLogoutRedirectUris, - scopes = client.Scopes, - grantTypes = client.GrantTypes, - clientType = client.ClientType, - consentType = client.ConsentType, - status = client.Status, - description = client.Description, - createdAt = client.CreatedAt, - }); - } - - [HttpGet("{id}/secret")] - public async Task> GetClientSecret(long id) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - return Ok(new - { - clientId = client.ClientId, - clientSecret = client.ClientSecret, - }); - } - - [HttpPost] - public async Task> CreateClient(CreateOAuthClientDto dto) - { - if (await _context.OAuthApplications.AnyAsync(c => c.ClientId == dto.ClientId)) - { - return BadRequest(new { message = "Client ID 已存在" }); - } - - var client = new OAuthApplication - { - ClientId = dto.ClientId, - ClientSecret = dto.ClientSecret, - DisplayName = dto.DisplayName, - RedirectUris = dto.RedirectUris, - PostLogoutRedirectUris = dto.PostLogoutRedirectUris, - Scopes = dto.Scopes, - GrantTypes = dto.GrantTypes, - ClientType = dto.ClientType, - ConsentType = dto.ConsentType, - Status = dto.Status, - Description = dto.Description, - CreatedAt = DateTime.UtcNow, - }; - - _context.OAuthApplications.Add(client); - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "create", "OAuthClient", client.Id, client.DisplayName, null, SerializeToJson(dto)); - - return CreatedAtAction(nameof(GetClient), new { id = client.Id }, client); - } - - [HttpPut("{id}")] - public async Task UpdateClient(long id, UpdateOAuthClientDto dto) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - var oldValue = SerializeToJson(client); - - client.DisplayName = dto.DisplayName; - client.RedirectUris = dto.RedirectUris; - client.PostLogoutRedirectUris = dto.PostLogoutRedirectUris; - client.Scopes = dto.Scopes; - client.GrantTypes = dto.GrantTypes; - client.ClientType = dto.ClientType; - client.ConsentType = dto.ConsentType; - client.Status = dto.Status; - client.Description = dto.Description; - - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "update", "OAuthClient", client.Id, client.DisplayName, oldValue, SerializeToJson(client)); - - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteClient(long id) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - var oldValue = SerializeToJson(client); - - _context.OAuthApplications.Remove(client); - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "delete", "OAuthClient", client.Id, client.DisplayName, oldValue); - - return NoContent(); - } - - private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) - { - var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; - var tenantId = User.FindFirstValue("TenantId"); - - var log = new AuditLog - { - Operator = userName, - TenantId = tenantId, - Operation = operation, - Action = action, - TargetType = targetType, - TargetId = targetId, - TargetName = targetName, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", - Status = "success", - OldValue = oldValue, - NewValue = newValue, - }; - - _context.AuditLogs.Add(log); - await _context.SaveChangesAsync(); - } - - private string SerializeToJson(object obj) - { - return System.Text.Json.JsonSerializer.Serialize(obj, new System.Text.Json.JsonSerializerOptions - { - WriteIndented = false - }); - } -} - -public class CreateOAuthClientDto -{ - public string ClientId { get; set; } = string.Empty; - public string ClientSecret { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string[] RedirectUris { get; set; } = Array.Empty(); - public string[] PostLogoutRedirectUris { get; set; } = Array.Empty(); - public string[] Scopes { get; set; } = Array.Empty(); - public string[] GrantTypes { get; set; } = Array.Empty(); - public string ClientType { get; set; } = "confidential"; - public string ConsentType { get; set; } = "implicit"; - public string Status { get; set; } = "active"; - public string? Description { get; set; } -} - -public class UpdateOAuthClientDto -{ - public string DisplayName { get; set; } = string.Empty; - public string[] RedirectUris { get; set; } = Array.Empty(); - public string[] PostLogoutRedirectUris { get; set; } = Array.Empty(); - public string[] Scopes { get; set; } = Array.Empty(); - public string[] GrantTypes { get; set; } = Array.Empty(); - public string ClientType { get; set; } = "confidential"; - public string ConsentType { get; set; } = "implicit"; - public string Status { get; set; } = "active"; - public string? Description { get; set; } -} diff --git a/src/Fengling.AuthService/Controllers/RolesController.cs b/src/Fengling.AuthService/Controllers/RolesController.cs deleted file mode 100644 index 9beef92..0000000 --- a/src/Fengling.AuthService/Controllers/RolesController.cs +++ /dev/null @@ -1,290 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Security.Claims; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class RolesController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly RoleManager _roleManager; - private readonly UserManager _userManager; - private readonly ILogger _logger; - - public RolesController( - ApplicationDbContext context, - RoleManager roleManager, - UserManager userManager, - ILogger logger) - { - _context = context; - _roleManager = roleManager; - _userManager = userManager; - _logger = logger; - } - - [HttpGet] - public async Task> GetRoles( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 10, - [FromQuery] string? name = null, - [FromQuery] string? tenantId = null) - { - var query = _context.Roles.AsQueryable(); - - if (!string.IsNullOrEmpty(name)) - { - query = query.Where(r => r.Name != null && r.Name.Contains(name)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(r => r.TenantId.ToString() == tenantId); - } - - var totalCount = await query.CountAsync(); - var roles = await query - .OrderByDescending(r => r.CreatedTime) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var result = new List(); - - foreach (var role in roles) - { - var users = await _userManager.GetUsersInRoleAsync(role.Name!); - result.Add(new - { - id = role.Id, - name = role.Name, - displayName = role.DisplayName, - description = role.Description, - tenantId = role.TenantId, - isSystem = role.IsSystem, - permissions = role.Permissions, - userCount = users.Count, - createdAt = role.CreatedTime, - }); - } - - return Ok(new - { - items = result, - totalCount, - page, - pageSize - }); - } - - [HttpGet("{id}")] - public async Task> GetRole(long id) - { - var role = await _context.Roles.FindAsync(id); - if (role == null) - { - return NotFound(); - } - - return Ok(new - { - id = role.Id, - name = role.Name, - displayName = role.DisplayName, - description = role.Description, - tenantId = role.TenantId, - isSystem = role.IsSystem, - permissions = role.Permissions, - createdAt = role.CreatedTime, - }); - } - - [HttpGet("{id}/users")] - public async Task>> GetRoleUsers(long id) - { - var role = await _context.Roles.FindAsync(id); - if (role == null) - { - return NotFound(); - } - - var users = await _userManager.GetUsersInRoleAsync(role.Name!); - - var result = users.Select(async u => new - { - id = u.Id, - userName = u.UserName, - email = u.Email, - realName = u.RealName, - tenantId = u.TenantId, - roles = await _userManager.GetRolesAsync(u), - isActive = !u.LockoutEnabled || u.LockoutEnd == null || u.LockoutEnd < DateTimeOffset.UtcNow, - createdAt = u.CreatedTime, - }); - - return Ok(await Task.WhenAll(result)); - } - - [HttpPost] - public async Task> CreateRole(CreateRoleDto dto) - { - var role = new ApplicationRole - { - Name = dto.Name, - DisplayName = dto.DisplayName, - Description = dto.Description, - TenantId = dto.TenantId, - Permissions = dto.Permissions, - IsSystem = false, - CreatedTime = DateTime.UtcNow, - }; - - var result = await _roleManager.CreateAsync(role); - if (!result.Succeeded) - { - return BadRequest(result.Errors); - } - - await CreateAuditLog("role", "create", "Role", role.Id, role.DisplayName, null, SerializeToJson(dto)); - - return CreatedAtAction(nameof(GetRole), new { id = role.Id }, role); - } - - [HttpPut("{id}")] - public async Task UpdateRole(long id, UpdateRoleDto dto) - { - var role = await _context.Roles.FindAsync(id); - if (role == null) - { - return NotFound(); - } - - if (role.IsSystem) - { - return BadRequest("系统角色不能修改"); - } - - var oldValue = System.Text.Json.JsonSerializer.Serialize(role); - - role.DisplayName = dto.DisplayName; - role.Description = dto.Description; - role.Permissions = dto.Permissions; - - await _context.SaveChangesAsync(); - - await CreateAuditLog("role", "update", "Role", role.Id, role.DisplayName, oldValue, System.Text.Json.JsonSerializer.Serialize(role)); - - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteRole(long id) - { - var role = await _context.Roles.FindAsync(id); - if (role == null) - { - return NotFound(); - } - - if (role.IsSystem) - { - return BadRequest("系统角色不能删除"); - } - - var oldValue = System.Text.Json.JsonSerializer.Serialize(role); - var users = await _userManager.GetUsersInRoleAsync(role.Name!); - - foreach (var user in users) - { - await _userManager.RemoveFromRoleAsync(user, role.Name!); - } - - _context.Roles.Remove(role); - await _context.SaveChangesAsync(); - - await CreateAuditLog("role", "delete", "Role", role.Id, role.DisplayName, oldValue); - - return NoContent(); - } - - [HttpDelete("{id}/users/{userId}")] - public async Task RemoveUserFromRole(long id, long userId) - { - var role = await _context.Roles.FindAsync(id); - if (role == null) - { - return NotFound(); - } - - var user = await _userManager.FindByIdAsync(userId.ToString()); - if (user == null) - { - return NotFound(); - } - - var result = await _userManager.RemoveFromRoleAsync(user, role.Name!); - if (!result.Succeeded) - { - return BadRequest(result.Errors); - } - - await CreateAuditLog("role", "update", "UserRole", null, $"{role.Name} - {user.UserName}"); - - return NoContent(); - } - - private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) - { - var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; - var tenantId = User.FindFirstValue("TenantId"); - - var log = new AuditLog - { - Operator = userName, - TenantId = tenantId, - Operation = operation, - Action = action, - TargetType = targetType, - TargetId = targetId, - TargetName = targetName, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", - Status = "success", - OldValue = oldValue, - NewValue = newValue, - }; - - _context.AuditLogs.Add(log); - await _context.SaveChangesAsync(); - } - - private string SerializeToJson(object obj) - { - return System.Text.Json.JsonSerializer.Serialize(obj, new System.Text.Json.JsonSerializerOptions - { - WriteIndented = false - }); - } -} - -public class CreateRoleDto -{ - public string Name { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string? Description { get; set; } - public long? TenantId { get; set; } - public List Permissions { get; set; } = new(); -} - -public class UpdateRoleDto -{ - public string DisplayName { get; set; } = string.Empty; - public string? Description { get; set; } - public List Permissions { get; set; } = new(); -} diff --git a/src/Fengling.AuthService/Controllers/StatsController.cs b/src/Fengling.AuthService/Controllers/StatsController.cs deleted file mode 100644 index c665ee3..0000000 --- a/src/Fengling.AuthService/Controllers/StatsController.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class StatsController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly ILogger _logger; - - public StatsController( - ApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - [HttpGet("dashboard")] - public async Task> GetDashboardStats() - { - var today = DateTime.UtcNow.Date; - var tomorrow = today.AddDays(1); - - var userCount = await _context.Users.CountAsync(u => !u.IsDeleted); - var tenantCount = await _context.Tenants.CountAsync(t => !t.IsDeleted); - var oauthClientCount = await _context.OAuthApplications.CountAsync(); - var todayAccessCount = await _context.AccessLogs - .CountAsync(l => l.CreatedAt >= today && l.CreatedAt < tomorrow); - - return Ok(new - { - userCount, - tenantCount, - oauthClientCount, - todayAccessCount, - }); - } - - [HttpGet("system")] - public ActionResult GetSystemStats() - { - var uptime = TimeSpan.FromMilliseconds(Environment.TickCount64); - var process = System.Diagnostics.Process.GetCurrentProcess(); - - return Ok(new - { - uptime = $"{uptime.Days}天 {uptime.Hours}小时 {uptime.Minutes}分钟", - memoryUsed = process.WorkingSet64 / 1024 / 1024, - cpuTime = process.TotalProcessorTime, - machineName = Environment.MachineName, - osVersion = Environment.OSVersion.ToString(), - processorCount = Environment.ProcessorCount, - }); - } -} diff --git a/src/Fengling.AuthService/Controllers/TenantsController.cs b/src/Fengling.AuthService/Controllers/TenantsController.cs deleted file mode 100644 index bb2ffc8..0000000 --- a/src/Fengling.AuthService/Controllers/TenantsController.cs +++ /dev/null @@ -1,344 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Security.Claims; -using System.Text.Json; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class TenantsController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly UserManager _userManager; - private readonly ILogger _logger; - - public TenantsController( - ApplicationDbContext context, - UserManager userManager, - ILogger logger) - { - _context = context; - _userManager = userManager; - _logger = logger; - } - - [HttpGet] - public async Task> GetTenants( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 10, - [FromQuery] string? name = null, - [FromQuery] string? tenantId = null, - [FromQuery] string? status = null) - { - var query = _context.Tenants.AsQueryable(); - - if (!string.IsNullOrEmpty(name)) - { - query = query.Where(t => t.Name.Contains(name)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(t => t.TenantId.Contains(tenantId)); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(t => t.Status == status); - } - - var totalCount = await query.CountAsync(); - var tenants = await query - .OrderByDescending(t => t.CreatedAt) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var result = new List(); - - foreach (var tenant in tenants) - { - var userCount = await _context.Users.CountAsync(u => u.TenantId == tenant.Id && !u.IsDeleted); - result.Add(new - { - id = tenant.Id, - tenantId = tenant.TenantId, - name = tenant.Name, - contactName = tenant.ContactName, - contactEmail = tenant.ContactEmail, - contactPhone = tenant.ContactPhone, - maxUsers = tenant.MaxUsers, - userCount, - status = tenant.Status, - expiresAt = tenant.ExpiresAt, - description = tenant.Description, - createdAt = tenant.CreatedAt, - }); - } - - return Ok(new - { - items = result, - totalCount, - page, - pageSize - }); - } - - [HttpGet("{id}")] - public async Task> GetTenant(long id) - { - var tenant = await _context.Tenants.FindAsync(id); - if (tenant == null) - { - return NotFound(); - } - - return Ok(new - { - id = tenant.Id, - tenantId = tenant.TenantId, - name = tenant.Name, - contactName = tenant.ContactName, - contactEmail = tenant.ContactEmail, - contactPhone = tenant.ContactPhone, - maxUsers = tenant.MaxUsers, - status = tenant.Status, - expiresAt = tenant.ExpiresAt, - description = tenant.Description, - createdAt = tenant.CreatedAt, - updatedAt = tenant.UpdatedAt, - }); - } - - [HttpGet("{tenantId}/users")] - public async Task>> GetTenantUsers(string tenantId) - { - var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); - if (tenant == null) - { - return NotFound(); - } - - var users = await _context.Users - .Where(u => u.TenantId == tenant.Id && !u.IsDeleted) - .ToListAsync(); - - var result = users.Select(async u => new - { - id = u.Id, - userName = u.UserName, - email = u.Email, - realName = u.RealName, - tenantId = u.TenantId, - roles = await _userManager.GetRolesAsync(u), - isActive = !u.LockoutEnabled || u.LockoutEnd == null || u.LockoutEnd < DateTimeOffset.UtcNow, - createdAt = u.CreatedTime, - }); - - return Ok(await Task.WhenAll(result)); - } - - [HttpGet("{tenantId}/roles")] - public async Task>> GetTenantRoles(string tenantId) - { - var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); - if (tenant == null) - { - return NotFound(); - } - - var roles = await _context.Roles - .Where(r => r.TenantId == tenant.Id) - .ToListAsync(); - - var result = roles.Select(r => new - { - id = r.Id, - name = r.Name, - displayName = r.DisplayName, - }); - - return Ok(result); - } - - [HttpGet("{tenantId}/settings")] - public async Task> GetTenantSettings(string tenantId) - { - var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); - if (tenant == null) - { - return NotFound(); - } - - var settings = new TenantSettings - { - AllowRegistration = false, - AllowedEmailDomains = "", - DefaultRoleId = null, - PasswordPolicy = new List { "requireNumber", "requireLowercase" }, - MinPasswordLength = 8, - SessionTimeout = 120, - }; - - return Ok(settings); - } - - [HttpPut("{tenantId}/settings")] - public async Task UpdateTenantSettings(string tenantId, TenantSettings settings) - { - var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); - if (tenant == null) - { - return NotFound(); - } - - await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.TenantId, null, JsonSerializer.Serialize(settings)); - - return NoContent(); - } - - [HttpPost] - public async Task> CreateTenant(CreateTenantDto dto) - { - var tenant = new Tenant - { - TenantId = dto.TenantId, - Name = dto.Name, - ContactName = dto.ContactName, - ContactEmail = dto.ContactEmail, - ContactPhone = dto.ContactPhone, - MaxUsers = dto.MaxUsers, - Description = dto.Description, - Status = dto.Status, - ExpiresAt = dto.ExpiresAt, - CreatedAt = DateTime.UtcNow, - }; - - _context.Tenants.Add(tenant); - await _context.SaveChangesAsync(); - - await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantId, null, JsonSerializer.Serialize(dto)); - - return CreatedAtAction(nameof(GetTenant), new { id = tenant.Id }, tenant); - } - - [HttpPut("{id}")] - public async Task UpdateTenant(long id, UpdateTenantDto dto) - { - var tenant = await _context.Tenants.FindAsync(id); - if (tenant == null) - { - return NotFound(); - } - - var oldValue = JsonSerializer.Serialize(tenant); - - tenant.Name = dto.Name; - tenant.ContactName = dto.ContactName; - tenant.ContactEmail = dto.ContactEmail; - tenant.ContactPhone = dto.ContactPhone; - tenant.MaxUsers = dto.MaxUsers; - tenant.Description = dto.Description; - tenant.Status = dto.Status; - tenant.ExpiresAt = dto.ExpiresAt; - tenant.UpdatedAt = DateTime.UtcNow; - - await _context.SaveChangesAsync(); - - await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.TenantId, oldValue, JsonSerializer.Serialize(tenant)); - - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteTenant(long id) - { - var tenant = await _context.Tenants.FindAsync(id); - if (tenant == null) - { - return NotFound(); - } - - var oldValue = JsonSerializer.Serialize(tenant); - - var users = await _context.Users.Where(u => u.TenantId == tenant.Id).ToListAsync(); - foreach (var user in users) - { - user.IsDeleted = true; - user.UpdatedTime = DateTime.UtcNow; - } - - tenant.IsDeleted = true; - await _context.SaveChangesAsync(); - - await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.TenantId, oldValue); - - return NoContent(); - } - - private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) - { - var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; - var tenantId = User.FindFirstValue("TenantId"); - - var log = new AuditLog - { - Operator = userName, - TenantId = tenantId, - Operation = operation, - Action = action, - TargetType = targetType, - TargetId = targetId, - TargetName = targetName, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", - Status = "success", - OldValue = oldValue, - NewValue = newValue, - }; - - _context.AuditLogs.Add(log); - await _context.SaveChangesAsync(); - } -} - -public class CreateTenantDto -{ - public string TenantId { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public string ContactName { get; set; } = string.Empty; - public string ContactEmail { get; set; } = string.Empty; - public string? ContactPhone { get; set; } - public int? MaxUsers { get; set; } - public string? Description { get; set; } - public string Status { get; set; } = "active"; - public DateTime? ExpiresAt { get; set; } -} - -public class UpdateTenantDto -{ - public string Name { get; set; } = string.Empty; - public string ContactName { get; set; } = string.Empty; - public string ContactEmail { get; set; } = string.Empty; - public string? ContactPhone { get; set; } - public int? MaxUsers { get; set; } - public string? Description { get; set; } - public string Status { get; set; } = "active"; - public DateTime? ExpiresAt { get; set; } -} - -public class TenantSettings -{ - public bool AllowRegistration { get; set; } - public string AllowedEmailDomains { get; set; } = string.Empty; - public long? DefaultRoleId { get; set; } - public List PasswordPolicy { get; set; } = new(); - public int MinPasswordLength { get; set; } = 8; - public int SessionTimeout { get; set; } = 120; -} diff --git a/src/Fengling.AuthService/Controllers/TokenController.cs b/src/Fengling.AuthService/Controllers/TokenController.cs deleted file mode 100644 index 1949792..0000000 --- a/src/Fengling.AuthService/Controllers/TokenController.cs +++ /dev/null @@ -1,255 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Abstractions; -using OpenIddict.Server.AspNetCore; -using System.Security.Claims; -using System.Security.Cryptography; -using Microsoft.AspNetCore; -using static OpenIddict.Abstractions.OpenIddictConstants; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("connect")] -public class TokenController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - UserManager userManager, - SignInManager signInManager, - ILogger logger) - : ControllerBase -{ - private readonly ILogger _logger = logger; - - [HttpPost("token")] - public async Task Exchange() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("OpenIddict request is null"); - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); - - if (request.IsAuthorizationCodeGrantType()) - { - return await ExchangeAuthorizationCodeAsync(request, result); - } - - if (request.IsRefreshTokenGrantType()) - { - return await ExchangeRefreshTokenAsync(request); - } - - if (request.IsPasswordGrantType()) - { - return await ExchangePasswordAsync(request); - } - - return BadRequest(new OpenIddictResponse - { - Error = Errors.UnsupportedGrantType, - ErrorDescription = "The specified grant type is not supported." - }); - } - - private async Task ExchangeAuthorizationCodeAsync(OpenIddictRequest request, - AuthenticateResult result) - { - var application = await applicationManager.FindByClientIdAsync(request.ClientId); - if (application == null) - { - return BadRequest(new OpenIddictResponse - { - Error = Errors.InvalidClient, - ErrorDescription = "The specified client is invalid." - }); - } - - var authorization = await authorizationManager.FindAsync( - subject: result.Principal?.GetClaim(Claims.Subject), - client: await applicationManager.GetIdAsync(application), - status: Statuses.Valid, - type: AuthorizationTypes.Permanent, - scopes: request.GetScopes()).FirstOrDefaultAsync(); - - if (authorization == null) - { - return BadRequest(new OpenIddictResponse - { - Error = Errors.InvalidGrant, - ErrorDescription = "The authorization code is invalid." - }); - } - - var user = await userManager.FindByIdAsync(result.Principal?.GetClaim(Claims.Subject)); - if (user == null || user.IsDeleted) - { - return BadRequest(new OpenIddictResponse - { - Error = Errors.InvalidGrant, - ErrorDescription = "The user is no longer valid." - }); - } - - var claims = new List - { - new(Claims.Subject, await userManager.GetUserIdAsync(user)), - new(Claims.Name, await userManager.GetUserNameAsync(user)), - new(Claims.Email, await userManager.GetEmailAsync(user) ?? ""), - new("tenant_id", user.TenantId.ToString()) - }; - - var roles = await userManager.GetRolesAsync(user); - foreach (var role in roles) - { - claims.Add(new Claim(Claims.Role, role)); - } - - var identity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - var principal = new ClaimsPrincipal(identity); - - principal.SetScopes(request.GetScopes()); - principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - principal.SetAuthorizationId(await authorizationManager.GetIdAsync(authorization)); - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } - - private async Task ExchangeRefreshTokenAsync(OpenIddictRequest request) - { - var principalResult = - await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - if (principalResult is not { Succeeded: true }) - { - return BadRequest(new OpenIddictResponse - { - Error = Errors.InvalidGrant, - ErrorDescription = "The refresh token is invalid." - }); - } - - var user = await userManager.GetUserAsync(principalResult.Principal); - if (user == null || user.IsDeleted) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid." - }!)); - } - - // Ensure the user is still allowed to sign in. - if (!await signInManager.CanSignInAsync(user)) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "The user is no longer allowed to sign in." - }!)); - } - - var principal = principalResult.Principal; - foreach (var claim in principal!.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } - - - principal.SetScopes(request.GetScopes()); - principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } - private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) - { - // Note: by default, claims are NOT automatically included in the access and identity tokens. - // To allow OpenIddict to serialize them, you must attach them a destination, that specifies - // whether they should be included in access tokens, in identity tokens or in both. - - switch (claim.Type) - { - case OpenIddictConstants.Claims.Name: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (principal.HasScope(OpenIddictConstants.Permissions.Scopes.Profile)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - - case OpenIddictConstants.Claims.Email: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (principal.HasScope(OpenIddictConstants.Permissions.Scopes.Email)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - - case OpenIddictConstants.Claims.Role: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (principal.HasScope(OpenIddictConstants.Permissions.Scopes.Roles)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - - // Never include the security stamp in the access and identity tokens, as it's a secret value. - case "AspNet.Identity.SecurityStamp": yield break; - - default: - yield return OpenIddictConstants.Destinations.AccessToken; - yield break; - } - } - - private async Task ExchangePasswordAsync(OpenIddictRequest request) - { - var user = await userManager.FindByNameAsync(request.Username); - if (user == null || user.IsDeleted) - { - return BadRequest(new OpenIddictResponse - { - Error = Errors.InvalidGrant, - ErrorDescription = "用户名或密码错误" - }); - } - - var signInResult = await signInManager.CheckPasswordSignInAsync(user, request.Password, false); - if (!signInResult.Succeeded) - { - return BadRequest(new OpenIddictResponse - { - Error = Errors.InvalidGrant, - ErrorDescription = "用户名或密码错误" - }); - } - - var claims = new List - { - new(Claims.Subject, await userManager.GetUserIdAsync(user)), - new(Claims.Name, await userManager.GetUserNameAsync(user)), - new(Claims.Email, await userManager.GetEmailAsync(user) ?? ""), - new("tenant_id", user.TenantId.ToString()) - }; - - var roles = await userManager.GetRolesAsync(user); - foreach (var role in roles) - { - claims.Add(new Claim(Claims.Role, role)); - } - - var identity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - var principal = new ClaimsPrincipal(identity); - - principal.SetScopes(request.GetScopes()); - principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } -} \ No newline at end of file diff --git a/src/Fengling.AuthService/Controllers/UsersController.cs b/src/Fengling.AuthService/Controllers/UsersController.cs deleted file mode 100644 index 5c098c3..0000000 --- a/src/Fengling.AuthService/Controllers/UsersController.cs +++ /dev/null @@ -1,291 +0,0 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Security.Claims; - -namespace Fengling.AuthService.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class UsersController : ControllerBase -{ - private readonly ApplicationDbContext _context; - private readonly UserManager _userManager; - private readonly RoleManager _roleManager; - private readonly ILogger _logger; - - public UsersController( - ApplicationDbContext context, - UserManager userManager, - RoleManager roleManager, - ILogger logger) - { - _context = context; - _userManager = userManager; - _roleManager = roleManager; - _logger = logger; - } - - [HttpGet] - public async Task> GetUsers( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 10, - [FromQuery] string? userName = null, - [FromQuery] string? email = null, - [FromQuery] string? tenantId = null) - { - var query = _context.Users.AsQueryable(); - - if (!string.IsNullOrEmpty(userName)) - { - query = query.Where(u => u.UserName!.Contains(userName)); - } - - if (!string.IsNullOrEmpty(email)) - { - query = query.Where(u => u.Email != null && u.Email.Contains(email)); - } - - if (!string.IsNullOrEmpty(tenantId)) - { - query = query.Where(u => u.TenantId.ToString() == tenantId); - } - - var totalCount = await query.CountAsync(); - var users = await query - .OrderByDescending(u => u.CreatedTime) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var result = users.Select(async u => new - { - id = u.Id, - userName = u.UserName, - email = u.Email, - realName = u.RealName, - phone = u.Phone, - tenantId = u.TenantId, - roles = (await _userManager.GetRolesAsync(u)).ToList(), - emailConfirmed = u.EmailConfirmed, - isActive = !u.LockoutEnabled || u.LockoutEnd == null || u.LockoutEnd < DateTimeOffset.UtcNow, - createdAt = u.CreatedTime, - }); - - return Ok(new - { - items = await Task.WhenAll(result), - totalCount, - page, - pageSize - }); - } - - [HttpGet("{id}")] - public async Task> GetUser(long id) - { - var user = await _context.Users.FindAsync(id); - if (user == null) - { - return NotFound(); - } - - var roles = await _userManager.GetRolesAsync(user); - - return Ok(new - { - id = user.Id, - userName = user.UserName, - email = user.Email, - realName = user.RealName, - phone = user.Phone, - tenantId = user.TenantId, - roles, - emailConfirmed = user.EmailConfirmed, - isActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, - createdAt = user.CreatedTime, - }); - } - - [HttpPost] - public async Task> CreateUser(CreateUserDto dto) - { - var user = new ApplicationUser - { - UserName = dto.UserName, - Email = dto.Email, - RealName = dto.RealName, - Phone = dto.Phone, - TenantId = dto.TenantId ?? 0, - EmailConfirmed = dto.EmailConfirmed, - CreatedTime = DateTime.UtcNow, - }; - - var result = await _userManager.CreateAsync(user, dto.Password); - if (!result.Succeeded) - { - return BadRequest(result.Errors); - } - - if (dto.RoleIds != null && dto.RoleIds.Any()) - { - foreach (var roleId in dto.RoleIds) - { - var role = await _roleManager.FindByIdAsync(roleId.ToString()); - if (role != null) - { - await _userManager.AddToRoleAsync(user, role.Name!); - } - } - } - - if (!dto.IsActive) - { - await _userManager.SetLockoutEnabledAsync(user, true); - await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); - } - - await CreateAuditLog("user", "create", "User", user.Id, user.UserName, null, SerializeToJson(dto)); - - return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user); - } - - [HttpPut("{id}")] - public async Task UpdateUser(long id, UpdateUserDto dto) - { - var user = await _context.Users.FindAsync(id); - if (user == null) - { - return NotFound(); - } - - var oldValue = System.Text.Json.JsonSerializer.Serialize(user); - - user.Email = dto.Email; - user.RealName = dto.RealName; - user.Phone = dto.Phone; - user.EmailConfirmed = dto.EmailConfirmed; - user.UpdatedTime = DateTime.UtcNow; - - if (dto.IsActive) - { - await _userManager.SetLockoutEnabledAsync(user, false); - await _userManager.SetLockoutEndDateAsync(user, null); - } - else - { - await _userManager.SetLockoutEnabledAsync(user, true); - await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); - } - - await _context.SaveChangesAsync(); - - await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user)); - - return NoContent(); - } - - [HttpPut("{id}/password")] - public async Task ResetPassword(long id, ResetPasswordDto dto) - { - var user = await _userManager.FindByIdAsync(id.ToString()); - if (user == null) - { - return NotFound(); - } - - var token = await _userManager.GeneratePasswordResetTokenAsync(user); - var result = await _userManager.ResetPasswordAsync(user, token, dto.NewPassword); - - if (!result.Succeeded) - { - return BadRequest(result.Errors); - } - - await CreateAuditLog("user", "reset_password", "User", user.Id, user.UserName); - - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteUser(long id) - { - var user = await _context.Users.FindAsync(id); - if (user == null) - { - return NotFound(); - } - - var oldValue = System.Text.Json.JsonSerializer.Serialize(user); - user.IsDeleted = true; - user.UpdatedTime = DateTime.UtcNow; - await _context.SaveChangesAsync(); - - await CreateAuditLog("user", "delete", "User", user.Id, user.UserName, oldValue); - - return NoContent(); - } - - private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) - { - var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; - var tenantId = User.FindFirstValue("TenantId"); - - var log = new AuditLog - { - Operator = userName, - TenantId = tenantId, - Operation = operation, - Action = action, - TargetType = targetType, - TargetId = targetId, - TargetName = targetName, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", - Status = "success", - OldValue = oldValue, - NewValue = newValue, - }; - - _context.AuditLogs.Add(log); - await _context.SaveChangesAsync(); - } - - private string SerializeToJson(object obj) - { - return System.Text.Json.JsonSerializer.Serialize(obj, new System.Text.Json.JsonSerializerOptions - { - WriteIndented = false - }); - } -} - -public class CreateUserDto -{ - public string UserName { get; set; } = string.Empty; - public string Email { get; set; } = string.Empty; - public string RealName { get; set; } = string.Empty; - public string? Phone { get; set; } - public long? TenantId { get; set; } - public List RoleIds { get; set; } = new(); - public string Password { get; set; } = string.Empty; - public bool EmailConfirmed { get; set; } - public bool IsActive { get; set; } = true; -} - -public class UpdateUserDto -{ - public string Email { get; set; } = string.Empty; - public string RealName { get; set; } = string.Empty; - public string? Phone { get; set; } - public bool EmailConfirmed { get; set; } - public bool IsActive { get; set; } = true; -} - -public class ResetPasswordDto -{ - public string NewPassword { get; set; } = string.Empty; -} diff --git a/src/Fengling.AuthService/Data/ApplicationDbContext.cs b/src/Fengling.AuthService/Data/ApplicationDbContext.cs deleted file mode 100644 index dd4e1d5..0000000 --- a/src/Fengling.AuthService/Data/ApplicationDbContext.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Data; - -public class ApplicationDbContext : IdentityDbContext -{ - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } - - public DbSet OAuthApplications { get; set; } - public DbSet Tenants { get; set; } - public DbSet AccessLogs { get; set; } - public DbSet AuditLogs { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - builder.Entity(entity => - { - entity.Property(e => e.RealName).HasMaxLength(100); - entity.Property(e => e.Phone).HasMaxLength(20); - entity.HasIndex(e => e.TenantId); - entity.HasIndex(e => e.Phone).IsUnique(); - }); - - builder.Entity(entity => - { - entity.Property(e => e.Description).HasMaxLength(200); - }); - - builder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.ClientId).IsUnique(); - entity.Property(e => e.ClientId).HasMaxLength(100); - entity.Property(e => e.ClientSecret).HasMaxLength(200); - entity.Property(e => e.DisplayName).HasMaxLength(100); - entity.Property(e => e.ClientType).HasMaxLength(20); - entity.Property(e => e.ConsentType).HasMaxLength(20); - entity.Property(e => e.Status).HasMaxLength(20); - entity.Property(e => e.Description).HasMaxLength(500); - }); - - builder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.TenantId).IsUnique(); - entity.Property(e => e.TenantId).HasMaxLength(50); - entity.Property(e => e.Name).HasMaxLength(100); - entity.Property(e => e.ContactName).HasMaxLength(50); - entity.Property(e => e.ContactEmail).HasMaxLength(100); - entity.Property(e => e.ContactPhone).HasMaxLength(20); - entity.Property(e => e.Status).HasMaxLength(20); - entity.Property(e => e.Description).HasMaxLength(500); - }); - - builder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.CreatedAt); - entity.HasIndex(e => e.UserName); - entity.HasIndex(e => e.TenantId); - entity.HasIndex(e => e.Action); - entity.HasIndex(e => e.Status); - entity.Property(e => e.UserName).HasMaxLength(50); - entity.Property(e => e.TenantId).HasMaxLength(50); - entity.Property(e => e.Action).HasMaxLength(20); - entity.Property(e => e.Resource).HasMaxLength(200); - entity.Property(e => e.Method).HasMaxLength(10); - entity.Property(e => e.IpAddress).HasMaxLength(50); - entity.Property(e => e.UserAgent).HasMaxLength(500); - entity.Property(e => e.Status).HasMaxLength(20); - }); - - builder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.CreatedAt); - entity.HasIndex(e => e.Operator); - entity.HasIndex(e => e.TenantId); - entity.HasIndex(e => e.Operation); - entity.HasIndex(e => e.Action); - entity.Property(e => e.Operator).HasMaxLength(50); - entity.Property(e => e.TenantId).HasMaxLength(50); - entity.Property(e => e.Operation).HasMaxLength(20); - entity.Property(e => e.Action).HasMaxLength(20); - entity.Property(e => e.TargetType).HasMaxLength(50); - entity.Property(e => e.TargetName).HasMaxLength(100); - entity.Property(e => e.IpAddress).HasMaxLength(50); - entity.Property(e => e.Description).HasMaxLength(500); - entity.Property(e => e.Status).HasMaxLength(20); - }); - } -} diff --git a/src/Fengling.AuthService/Data/ApplicationDbContextFactory.cs b/src/Fengling.AuthService/Data/ApplicationDbContextFactory.cs deleted file mode 100644 index 62c3423..0000000 --- a/src/Fengling.AuthService/Data/ApplicationDbContextFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Fengling.AuthService.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; - -namespace Fengling.AuthService.Data; - -public class ApplicationDbContextFactory : IDesignTimeDbContextFactory -{ - public ApplicationDbContext CreateDbContext(string[] args) - { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseNpgsql("Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542"); - - return new ApplicationDbContext(optionsBuilder.Options); - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.Designer.cs b/src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.Designer.cs deleted file mode 100644 index 42966cb..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.Designer.cs +++ /dev/null @@ -1,312 +0,0 @@ -// -using System; -using Fengling.AuthService.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260201153600_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("Phone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("RealName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("Phone") - .IsUnique(); - - b.HasIndex("TenantId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.cs b/src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.cs deleted file mode 100644 index d9e4db8..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260201153600_InitialCreate.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), - Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RealName = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), - Phone = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), - TenantId = table.Column(type: "bigint", nullable: false), - CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedTime = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false), - UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "boolean", nullable: false), - PasswordHash = table.Column(type: "text", nullable: true), - SecurityStamp = table.Column(type: "text", nullable: true), - ConcurrencyStamp = table.Column(type: "text", nullable: true), - PhoneNumber = table.Column(type: "text", nullable: true), - PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), - TwoFactorEnabled = table.Column(type: "boolean", nullable: false), - LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), - LockoutEnabled = table.Column(type: "boolean", nullable: false), - AccessFailedCount = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RoleId = table.Column(type: "bigint", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "bigint", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "text", nullable: false), - ProviderKey = table.Column(type: "text", nullable: false), - ProviderDisplayName = table.Column(type: "text", nullable: true), - UserId = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "bigint", nullable: false), - RoleId = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "bigint", nullable: false), - LoginProvider = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false), - Value = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_Phone", - table: "AspNetUsers", - column: "Phone", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_TenantId", - table: "AspNetUsers", - column: "TenantId"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.Designer.cs b/src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.Designer.cs deleted file mode 100644 index 3312d48..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.Designer.cs +++ /dev/null @@ -1,379 +0,0 @@ -// -using System; -using Fengling.AuthService.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260202015716_AddOAuthApplications")] - partial class AddOAuthApplications - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("Phone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("RealName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("Phone") - .IsUnique(); - - b.HasIndex("TenantId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.OAuthApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ClientSecret") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ConsentType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.PrimitiveCollection("GrantTypes") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("PostLogoutRedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("RedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("Scopes") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("OAuthApplications"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.cs b/src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.cs deleted file mode 100644 index 7a69abe..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260202015716_AddOAuthApplications.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - /// - public partial class AddOAuthApplications : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "OAuthApplications", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ClientId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - ClientSecret = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - DisplayName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - RedirectUris = table.Column(type: "text[]", nullable: false), - PostLogoutRedirectUris = table.Column(type: "text[]", nullable: false), - Scopes = table.Column(type: "text[]", nullable: false), - GrantTypes = table.Column(type: "text[]", nullable: false), - ClientType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - ConsentType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OAuthApplications", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_OAuthApplications_ClientId", - table: "OAuthApplications", - column: "ClientId", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "OAuthApplications"); - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.Designer.cs b/src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.Designer.cs deleted file mode 100644 index b03ca1a..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.Designer.cs +++ /dev/null @@ -1,621 +0,0 @@ -// -using System; -using System.Collections.Generic; -using Fengling.AuthService.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260202031310_AddTenantAndLogs")] - partial class AddTenantAndLogs - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Fengling.AuthService.Models.AccessLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Duration") - .HasColumnType("integer"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Method") - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("RequestData") - .HasColumnType("text"); - - b.Property("Resource") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ResponseData") - .HasColumnType("text"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TenantId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UserAgent") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("UserName") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("Action"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("Status"); - - b.HasIndex("TenantId"); - - b.HasIndex("UserName"); - - b.ToTable("AccessLogs"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("IsSystem") - .HasColumnType("boolean"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.PrimitiveCollection>("Permissions") - .HasColumnType("text[]"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("Phone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("RealName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("Phone") - .IsUnique(); - - b.HasIndex("TenantId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IpAddress") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("NewValue") - .HasColumnType("text"); - - b.Property("OldValue") - .HasColumnType("text"); - - b.Property("Operation") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Operator") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TargetId") - .HasColumnType("bigint"); - - b.Property("TargetName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("Action"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("Operation"); - - b.HasIndex("Operator"); - - b.HasIndex("TenantId"); - - b.ToTable("AuditLogs"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.OAuthApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ClientSecret") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ConsentType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.PrimitiveCollection("GrantTypes") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("PostLogoutRedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("RedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("Scopes") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("OAuthApplications"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ContactName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ContactPhone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("MaxUsers") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TenantId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("TenantId") - .IsUnique(); - - b.ToTable("Tenants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.HasOne("Fengling.AuthService.Models.Tenant", null) - .WithMany("Users") - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b => - { - b.Navigation("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.cs b/src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.cs deleted file mode 100644 index 5a8aa57..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260202031310_AddTenantAndLogs.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - /// - public partial class AddTenantAndLogs : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "DisplayName", - table: "AspNetRoles", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "IsSystem", - table: "AspNetRoles", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn>( - name: "Permissions", - table: "AspNetRoles", - type: "text[]", - nullable: true); - - migrationBuilder.AddColumn( - name: "TenantId", - table: "AspNetRoles", - type: "bigint", - nullable: true); - - migrationBuilder.CreateTable( - name: "AccessLogs", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserName = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - TenantId = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - Action = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Resource = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - Method = table.Column(type: "character varying(10)", maxLength: 10, nullable: true), - IpAddress = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - UserAgent = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Duration = table.Column(type: "integer", nullable: false), - RequestData = table.Column(type: "text", nullable: true), - ResponseData = table.Column(type: "text", nullable: true), - ErrorMessage = table.Column(type: "text", nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AccessLogs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AuditLogs", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Operator = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - TenantId = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - Operation = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Action = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - TargetType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - TargetId = table.Column(type: "bigint", nullable: true), - TargetName = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), - IpAddress = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), - OldValue = table.Column(type: "text", nullable: true), - NewValue = table.Column(type: "text", nullable: true), - ErrorMessage = table.Column(type: "text", nullable: true), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AuditLogs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Tenants", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - TenantId = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - ContactName = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - ContactEmail = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - ContactPhone = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), - MaxUsers = table.Column(type: "integer", nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - ExpiresAt = table.Column(type: "timestamp with time zone", nullable: true), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tenants", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_AccessLogs_Action", - table: "AccessLogs", - column: "Action"); - - migrationBuilder.CreateIndex( - name: "IX_AccessLogs_CreatedAt", - table: "AccessLogs", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_AccessLogs_Status", - table: "AccessLogs", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_AccessLogs_TenantId", - table: "AccessLogs", - column: "TenantId"); - - migrationBuilder.CreateIndex( - name: "IX_AccessLogs_UserName", - table: "AccessLogs", - column: "UserName"); - - migrationBuilder.CreateIndex( - name: "IX_AuditLogs_Action", - table: "AuditLogs", - column: "Action"); - - migrationBuilder.CreateIndex( - name: "IX_AuditLogs_CreatedAt", - table: "AuditLogs", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_AuditLogs_Operation", - table: "AuditLogs", - column: "Operation"); - - migrationBuilder.CreateIndex( - name: "IX_AuditLogs_Operator", - table: "AuditLogs", - column: "Operator"); - - migrationBuilder.CreateIndex( - name: "IX_AuditLogs_TenantId", - table: "AuditLogs", - column: "TenantId"); - - migrationBuilder.CreateIndex( - name: "IX_Tenants_TenantId", - table: "Tenants", - column: "TenantId", - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_AspNetUsers_Tenants_TenantId", - table: "AspNetUsers", - column: "TenantId", - principalTable: "Tenants", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_AspNetUsers_Tenants_TenantId", - table: "AspNetUsers"); - - migrationBuilder.DropTable( - name: "AccessLogs"); - - migrationBuilder.DropTable( - name: "AuditLogs"); - - migrationBuilder.DropTable( - name: "Tenants"); - - migrationBuilder.DropColumn( - name: "DisplayName", - table: "AspNetRoles"); - - migrationBuilder.DropColumn( - name: "IsSystem", - table: "AspNetRoles"); - - migrationBuilder.DropColumn( - name: "Permissions", - table: "AspNetRoles"); - - migrationBuilder.DropColumn( - name: "TenantId", - table: "AspNetRoles"); - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.Designer.cs b/src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.Designer.cs deleted file mode 100644 index 47f017a..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.Designer.cs +++ /dev/null @@ -1,625 +0,0 @@ -// -using System; -using System.Collections.Generic; -using Fengling.AuthService.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260202064650_AddOAuthDescription")] - partial class AddOAuthDescription - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Fengling.AuthService.Models.AccessLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Duration") - .HasColumnType("integer"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Method") - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("RequestData") - .HasColumnType("text"); - - b.Property("Resource") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ResponseData") - .HasColumnType("text"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TenantId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UserAgent") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("UserName") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("Action"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("Status"); - - b.HasIndex("TenantId"); - - b.HasIndex("UserName"); - - b.ToTable("AccessLogs"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("IsSystem") - .HasColumnType("boolean"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.PrimitiveCollection>("Permissions") - .HasColumnType("text[]"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("Phone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("RealName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("Phone") - .IsUnique(); - - b.HasIndex("TenantId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IpAddress") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("NewValue") - .HasColumnType("text"); - - b.Property("OldValue") - .HasColumnType("text"); - - b.Property("Operation") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Operator") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TargetId") - .HasColumnType("bigint"); - - b.Property("TargetName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("Action"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("Operation"); - - b.HasIndex("Operator"); - - b.HasIndex("TenantId"); - - b.ToTable("AuditLogs"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.OAuthApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ClientSecret") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ConsentType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.PrimitiveCollection("GrantTypes") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("PostLogoutRedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("RedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("Scopes") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("OAuthApplications"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ContactName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ContactPhone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("MaxUsers") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TenantId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("TenantId") - .IsUnique(); - - b.ToTable("Tenants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.HasOne("Fengling.AuthService.Models.Tenant", null) - .WithMany("Users") - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b => - { - b.Navigation("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.cs b/src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.cs deleted file mode 100644 index 9f6d766..0000000 --- a/src/Fengling.AuthService/Data/Migrations/20260202064650_AddOAuthDescription.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - /// - public partial class AddOAuthDescription : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Description", - table: "OAuthApplications", - type: "character varying(500)", - maxLength: 500, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Description", - table: "OAuthApplications"); - } - } -} diff --git a/src/Fengling.AuthService/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Fengling.AuthService/Data/Migrations/ApplicationDbContextModelSnapshot.cs deleted file mode 100644 index 2dd38f3..0000000 --- a/src/Fengling.AuthService/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ /dev/null @@ -1,622 +0,0 @@ -// -using System; -using System.Collections.Generic; -using Fengling.AuthService.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Fengling.AuthService.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Fengling.AuthService.Models.AccessLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Duration") - .HasColumnType("integer"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Method") - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("RequestData") - .HasColumnType("text"); - - b.Property("Resource") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ResponseData") - .HasColumnType("text"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TenantId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UserAgent") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("UserName") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("Action"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("Status"); - - b.HasIndex("TenantId"); - - b.HasIndex("UserName"); - - b.ToTable("AccessLogs"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("IsSystem") - .HasColumnType("boolean"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.PrimitiveCollection>("Permissions") - .HasColumnType("text[]"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("Phone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("RealName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("bigint"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("Phone") - .IsUnique(); - - b.HasIndex("TenantId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IpAddress") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("NewValue") - .HasColumnType("text"); - - b.Property("OldValue") - .HasColumnType("text"); - - b.Property("Operation") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Operator") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TargetId") - .HasColumnType("bigint"); - - b.Property("TargetName") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("Action"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("Operation"); - - b.HasIndex("Operator"); - - b.HasIndex("TenantId"); - - b.ToTable("AuditLogs"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.OAuthApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ClientSecret") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ConsentType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.PrimitiveCollection("GrantTypes") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("PostLogoutRedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("RedirectUris") - .IsRequired() - .HasColumnType("text[]"); - - b.PrimitiveCollection("Scopes") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("OAuthApplications"); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ContactName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ContactPhone") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("MaxUsers") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TenantId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("TenantId") - .IsUnique(); - - b.ToTable("Tenants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("RoleId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => - { - b.HasOne("Fengling.AuthService.Models.Tenant", null) - .WithMany("Users") - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Fengling.AuthService.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b => - { - b.Navigation("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Fengling.AuthService/Data/SeedData.cs b/src/Fengling.AuthService/Data/SeedData.cs deleted file mode 100644 index 017c766..0000000 --- a/src/Fengling.AuthService/Data/SeedData.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.AuthService.Data; - -public static class SeedData -{ - public static async Task Initialize(IServiceProvider serviceProvider) - { - using var scope = serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - var roleManager = scope.ServiceProvider.GetRequiredService>(); - - context.Database.EnsureCreated(); - - var defaultTenant = await context.Tenants - .FirstOrDefaultAsync(t => t.TenantId == "default"); - if (defaultTenant == null) - { - defaultTenant = new Tenant - { - TenantId = "default", - Name = "默认租户", - ContactName = "系统管理员", - ContactEmail = "admin@fengling.local", - ContactPhone = "13800138000", - MaxUsers = 1000, - Description = "系统默认租户", - Status = "active", - CreatedAt = DateTime.UtcNow - }; - context.Tenants.Add(defaultTenant); - await context.SaveChangesAsync(); - } - - var adminRole = await roleManager.FindByNameAsync("Admin"); - if (adminRole == null) - { - adminRole = new ApplicationRole - { - Name = "Admin", - DisplayName = "管理员", - Description = "System administrator", - TenantId = defaultTenant.Id, - IsSystem = true, - Permissions = new List - { - "user.manage", "user.view", - "role.manage", "role.view", - "tenant.manage", "tenant.view", - "oauth.manage", "oauth.view", - "log.view", "system.config" - }, - CreatedTime = DateTime.UtcNow - }; - await roleManager.CreateAsync(adminRole); - } - - var userRole = await roleManager.FindByNameAsync("User"); - if (userRole == null) - { - userRole = new ApplicationRole - { - Name = "User", - DisplayName = "普通用户", - Description = "Regular user", - TenantId = defaultTenant.Id, - IsSystem = true, - Permissions = new List { "user.view" }, - CreatedTime = DateTime.UtcNow - }; - await roleManager.CreateAsync(userRole); - } - - var adminUser = await userManager.FindByNameAsync("admin"); - if (adminUser == null) - { - adminUser = new ApplicationUser - { - UserName = "admin", - Email = "admin@fengling.local", - RealName = "系统管理员", - Phone = "13800138000", - TenantId = defaultTenant.Id, - EmailConfirmed = true, - IsDeleted = false, - CreatedTime = DateTime.UtcNow - }; - - var result = await userManager.CreateAsync(adminUser, "Admin@123"); - if (result.Succeeded) - { - await userManager.AddToRoleAsync(adminUser, "Admin"); - } - } - - var testUser = await userManager.FindByNameAsync("testuser"); - if (testUser == null) - { - testUser = new ApplicationUser - { - UserName = "testuser", - Email = "test@fengling.local", - RealName = "测试用户", - Phone = "13900139000", - TenantId = defaultTenant.Id, - EmailConfirmed = true, - IsDeleted = false, - CreatedTime = DateTime.UtcNow - }; - - var result = await userManager.CreateAsync(testUser, "Test@123"); - if (result.Succeeded) - { - await userManager.AddToRoleAsync(testUser, "User"); - } - } - - var consoleClient = await context.OAuthApplications - .FirstOrDefaultAsync(c => c.ClientId == "fengling-console"); - if (consoleClient == null) - { - consoleClient = new OAuthApplication - { - ClientId = "fengling-console", - ClientSecret = "console-secret-change-in-production", - DisplayName = "Fengling 运管中心", - RedirectUris = new[] { "http://console.fengling.local/auth/callback" }, - PostLogoutRedirectUris = new[] { "http://console.fengling.local/" }, - Scopes = new[] { "api", "offline_access" }, - GrantTypes = new[] { "authorization_code", "refresh_token" }, - ClientType = "confidential", - ConsentType = "implicit", - Status = "active", - CreatedAt = DateTime.UtcNow - }; - context.OAuthApplications.Add(consoleClient); - await context.SaveChangesAsync(); - } - } -} diff --git a/src/Fengling.AuthService/Dockerfile b/src/Fengling.AuthService/Dockerfile deleted file mode 100644 index 736232d..0000000 --- a/src/Fengling.AuthService/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base -WORKDIR /app -EXPOSE 80 - -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src -COPY ["Fengling.AuthService.csproj", "./"] -RUN dotnet restore "Fengling.AuthService.csproj" -COPY . . -WORKDIR "/src" -RUN dotnet build "Fengling.AuthService.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "Fengling.AuthService.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Fengling.AuthService.dll"] diff --git a/src/Fengling.AuthService/Fengling.AuthService.csproj b/src/Fengling.AuthService/Fengling.AuthService.csproj deleted file mode 100644 index 30dfedf..0000000 --- a/src/Fengling.AuthService/Fengling.AuthService.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - net10.0 - enable - enable - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - diff --git a/src/Fengling.AuthService/Fengling.AuthService.http b/src/Fengling.AuthService/Fengling.AuthService.http deleted file mode 100644 index 2746cc8..0000000 --- a/src/Fengling.AuthService/Fengling.AuthService.http +++ /dev/null @@ -1,6 +0,0 @@ -@Fengling.AuthService_HostAddress = http://localhost:5132 - -GET {{Fengling.AuthService_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/src/Fengling.AuthService/Models/AccessLog.cs b/src/Fengling.AuthService/Models/AccessLog.cs deleted file mode 100644 index d0c4a2e..0000000 --- a/src/Fengling.AuthService/Models/AccessLog.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.AuthService.Models; - -public class AccessLog -{ - [Key] - public long Id { get; set; } - - [MaxLength(50)] - public string? UserName { get; set; } - - [MaxLength(50)] - public string? TenantId { get; set; } - - [MaxLength(20)] - public string Action { get; set; } = string.Empty; - - [MaxLength(200)] - public string? Resource { get; set; } - - [MaxLength(10)] - public string? Method { get; set; } - - [MaxLength(50)] - public string? IpAddress { get; set; } - - [MaxLength(500)] - public string? UserAgent { get; set; } - - [MaxLength(20)] - public string Status { get; set; } = "success"; - - public int Duration { get; set; } - - public string? RequestData { get; set; } - - public string? ResponseData { get; set; } - - public string? ErrorMessage { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -} diff --git a/src/Fengling.AuthService/Models/ApplicationRole.cs b/src/Fengling.AuthService/Models/ApplicationRole.cs deleted file mode 100644 index a015315..0000000 --- a/src/Fengling.AuthService/Models/ApplicationRole.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Fengling.AuthService.Models; - -public class ApplicationRole : IdentityRole -{ - public string? Description { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public long? TenantId { get; set; } - public bool IsSystem { get; set; } - public string? DisplayName { get; set; } - public List? Permissions { get; set; } -} diff --git a/src/Fengling.AuthService/Models/ApplicationUser.cs b/src/Fengling.AuthService/Models/ApplicationUser.cs deleted file mode 100644 index d1b4fa5..0000000 --- a/src/Fengling.AuthService/Models/ApplicationUser.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Fengling.AuthService.Models; - -public class ApplicationUser : IdentityUser -{ - public string? RealName { get; set; } - public string? Phone { get; set; } - public long TenantId { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedTime { get; set; } - public bool IsDeleted { get; set; } -} diff --git a/src/Fengling.AuthService/Models/AuditLog.cs b/src/Fengling.AuthService/Models/AuditLog.cs deleted file mode 100644 index 60708c6..0000000 --- a/src/Fengling.AuthService/Models/AuditLog.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.AuthService.Models; - -public class AuditLog -{ - [Key] - public long Id { get; set; } - - [MaxLength(50)] - [Required] - public string Operator { get; set; } = string.Empty; - - [MaxLength(50)] - public string? TenantId { get; set; } - - [MaxLength(20)] - public string Operation { get; set; } = string.Empty; - - [MaxLength(20)] - public string Action { get; set; } = string.Empty; - - [MaxLength(50)] - public string? TargetType { get; set; } - - public long? TargetId { get; set; } - - [MaxLength(100)] - public string? TargetName { get; set; } - - [MaxLength(50)] - public string IpAddress { get; set; } = string.Empty; - - [MaxLength(500)] - public string? Description { get; set; } - - public string? OldValue { get; set; } - - public string? NewValue { get; set; } - - public string? ErrorMessage { get; set; } - - [MaxLength(20)] - public string Status { get; set; } = "success"; - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -} diff --git a/src/Fengling.AuthService/Models/OAuthApplication.cs b/src/Fengling.AuthService/Models/OAuthApplication.cs deleted file mode 100644 index 59d3b11..0000000 --- a/src/Fengling.AuthService/Models/OAuthApplication.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.AuthService.Models; - -public class OAuthApplication -{ - public long Id { get; set; } - public string ClientId { get; set; } = string.Empty; - public string? ClientSecret { get; set; } - public string DisplayName { get; set; } = string.Empty; - public string[] RedirectUris { get; set; } = Array.Empty(); - public string[] PostLogoutRedirectUris { get; set; } = Array.Empty(); - public string[] Scopes { get; set; } = Array.Empty(); - public string[] GrantTypes { get; set; } = Array.Empty(); - public string ClientType { get; set; } = "public"; - public string ConsentType { get; set; } = "implicit"; - public string Status { get; set; } = "active"; - public string? Description { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedAt { get; set; } -} diff --git a/src/Fengling.AuthService/Models/Tenant.cs b/src/Fengling.AuthService/Models/Tenant.cs deleted file mode 100644 index d25c7c2..0000000 --- a/src/Fengling.AuthService/Models/Tenant.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.AuthService.Models; - -public class Tenant -{ - [Key] - public long Id { get; set; } - - [MaxLength(50)] - [Required] - public string TenantId { get; set; } = string.Empty; - - [MaxLength(100)] - [Required] - public string Name { get; set; } = string.Empty; - - [MaxLength(50)] - [Required] - public string ContactName { get; set; } = string.Empty; - - [MaxLength(100)] - [Required] - [EmailAddress] - public string ContactEmail { get; set; } = string.Empty; - - [MaxLength(20)] - public string? ContactPhone { get; set; } - - public int? MaxUsers { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - - [MaxLength(500)] - public string? Description { get; set; } - - [MaxLength(20)] - public string Status { get; set; } = "active"; - - public DateTime? ExpiresAt { get; set; } - - public DateTime? UpdatedAt { get; set; } - - public bool IsDeleted { get; set; } - - public ICollection Users { get; set; } = new List(); -} diff --git a/src/Fengling.AuthService/Program.cs b/src/Fengling.AuthService/Program.cs deleted file mode 100644 index 3640d60..0000000 --- a/src/Fengling.AuthService/Program.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Fengling.AuthService.Configuration; -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.OpenApi; -using OpenTelemetry; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; -using Serilog; - -var builder = WebApplication.CreateBuilder(args); - -Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(builder.Configuration) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") - .CreateLogger(); - -builder.Host.UseSerilog(); - -var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); -builder.Services.AddDbContext(options => -{ - if (connectionString.StartsWith("DataSource=")) - { - options.UseInMemoryDatabase(connectionString); - } - else - { - options.UseNpgsql(connectionString); - } -}); - -builder.Services.AddRazorPages(); -builder.Services.AddControllersWithViews(); - -builder.Services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - -builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; -}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => -{ - options.Cookie.Name = "Fengling.Auth"; - options.Cookie.SecurePolicy = CookieSecurePolicy.None; - options.Cookie.SameSite = SameSiteMode.Lax; - options.ExpireTimeSpan = TimeSpan.FromDays(7); -}); - -builder.Services.AddOpenIddictConfiguration(builder.Configuration); - -builder.Services.AddOpenTelemetry() - .ConfigureResource(resource => - resource.AddService("Fengling.AuthService")) - .WithTracing(tracing => - tracing.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddSource("OpenIddict.Server.AspNetCore") - .AddOtlpExporter()); - -builder.Services.AddControllersWithViews(); - -builder.Services.AddHealthChecks() - .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection")!); - -builder.Services.AddSwaggerGen(options => -{ - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Fengling Auth Service", - Version = "v1", - Description = "Authentication and authorization service using OpenIddict" - }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows - { - Password = new OpenApiOAuthFlow - { - TokenUrl = new Uri("/connect/token", UriKind.Relative) - } - } - }); -}); - -var app = builder.Build(); - -using (var scope = app.Services.CreateScope()) -{ - await SeedData.Initialize(scope.ServiceProvider); -} - -app.UseStaticFiles(); -app.UseRouting(); -app.UseAuthentication(); -app.UseAuthorization(); - -var isTesting = builder.Configuration.GetValue("Testing", false); -if (!isTesting) -{ - app.UseSwagger(); - app.UseSwaggerUI(options => - { - options.SwaggerEndpoint("/swagger/v1/swagger.json", "Fengling Auth Service v1"); - options.OAuthClientId("swagger-ui"); - options.OAuthUsePkce(); - }); -} - -app.MapRazorPages(); -app.MapControllers(); -app.MapHealthChecks("/health"); - -app.Run(); diff --git a/src/Fengling.AuthService/Properties/launchSettings.json b/src/Fengling.AuthService/Properties/launchSettings.json deleted file mode 100644 index 8fa6f08..0000000 --- a/src/Fengling.AuthService/Properties/launchSettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5132", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/Fengling.AuthService/README.md b/src/Fengling.AuthService/README.md deleted file mode 100644 index dadefbc..0000000 --- a/src/Fengling.AuthService/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Fengling Auth Service - -Authentication and authorization service using OpenIddict. - -## Features - -- JWT token issuance -- OAuth2/OIDC support -- Multi-tenant support (TenantId in JWT claims) -- Role-based access control (RBAC) -- Health check endpoint - -## API Endpoints - -### Get Token -``` -POST /connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password -username={username} -password={password} -scope=api offline_access -``` - -### Health Check -``` -GET /health -``` - -## Default Users - -- **Admin**: username=admin, password=Admin@123, role=Admin -- **Test User**: username=testuser, password=Test@123, role=User - -## Running Locally - -```bash -dotnet run -``` - -Service runs on port 5000. - -## Docker - -```bash -docker build -t fengling-auth:latest . -docker run -p 5000:80 fengling-auth:latest -``` - -## Environment Variables - -- `ConnectionStrings__DefaultConnection`: PostgreSQL connection string -- `OpenIddict__Issuer`: Token issuer URL -- `OpenIddict__Audience`: Token audience - -## Database - -- PostgreSQL -- Uses ASP.NET Core Identity for user/role management -- Tenant isolation via `TenantId` column diff --git a/src/Fengling.AuthService/ViewModels/AuthorizeViewModel.cs b/src/Fengling.AuthService/ViewModels/AuthorizeViewModel.cs deleted file mode 100644 index 088f5db..0000000 --- a/src/Fengling.AuthService/ViewModels/AuthorizeViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Fengling.AuthService.ViewModels; - -public record AuthorizeViewModel(string? ApplicationName, string? Scope) -{ - public string[]? Scopes => Scope?.Split(' ') ?? null; -} \ No newline at end of file diff --git a/src/Fengling.AuthService/ViewModels/DashboardViewModel.cs b/src/Fengling.AuthService/ViewModels/DashboardViewModel.cs deleted file mode 100644 index 5ff159b..0000000 --- a/src/Fengling.AuthService/ViewModels/DashboardViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Fengling.AuthService.ViewModels; - -public class DashboardViewModel -{ - public string? Username { get; set; } - public string? Email { get; set; } -} diff --git a/src/Fengling.AuthService/ViewModels/LoginViewModel.cs b/src/Fengling.AuthService/ViewModels/LoginViewModel.cs deleted file mode 100644 index bddc0fd..0000000 --- a/src/Fengling.AuthService/ViewModels/LoginViewModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Fengling.AuthService.ViewModels; - -public class LoginViewModel -{ - public string ReturnUrl { get; set; } -} - -public class LoginInputModel -{ - public string Username { get; set; } - public string Password { get; set; } - public bool RememberMe { get; set; } - public string ReturnUrl { get; set; } -} \ No newline at end of file diff --git a/src/Fengling.AuthService/ViewModels/RegisterViewModel.cs b/src/Fengling.AuthService/ViewModels/RegisterViewModel.cs deleted file mode 100644 index 555da86..0000000 --- a/src/Fengling.AuthService/ViewModels/RegisterViewModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.AuthService.ViewModels; - -public class RegisterViewModel -{ - [Required(ErrorMessage = "用户名不能为空")] - [StringLength(50, MinimumLength = 3, ErrorMessage = "用户名长度必须在3-50个字符之间")] - public string Username { get; set; } - - [Required(ErrorMessage = "邮箱不能为空")] - [EmailAddress(ErrorMessage = "请输入有效的邮箱地址")] - public string Email { get; set; } - - [Required(ErrorMessage = "密码不能为空")] - [StringLength(100, MinimumLength = 6, ErrorMessage = "密码长度必须在6-100个字符之间")] - [DataType(DataType.Password)] - public string Password { get; set; } - - [Required(ErrorMessage = "确认密码不能为空")] - [DataType(DataType.Password)] - [Compare("Password", ErrorMessage = "两次输入的密码不一致")] - public string ConfirmPassword { get; set; } - - public string ReturnUrl { get; set; } -} \ No newline at end of file diff --git a/src/Fengling.AuthService/Views/Account/Login.cshtml b/src/Fengling.AuthService/Views/Account/Login.cshtml deleted file mode 100644 index 156b39f..0000000 --- a/src/Fengling.AuthService/Views/Account/Login.cshtml +++ /dev/null @@ -1,81 +0,0 @@ - @model Fengling.AuthService.ViewModels.LoginInputModel - -@{ - Layout = "_Layout"; - ViewData["Title"] = "登录"; -} - -
-
-
-
- - - - - -
-

欢迎回来

-

登录到 Fengling Auth

-
- -
- @if (!ViewData.ModelState.IsValid) - { -
- @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) - { -

@error.ErrorMessage

- } -
- } - -
- - -
- - -
- -
- - -
- -
-
- - -
-
- - -
-
- -
- 还没有账号? - 立即注册 -
-
-
diff --git a/src/Fengling.AuthService/Views/Account/Register.cshtml b/src/Fengling.AuthService/Views/Account/Register.cshtml deleted file mode 100644 index cd58d86..0000000 --- a/src/Fengling.AuthService/Views/Account/Register.cshtml +++ /dev/null @@ -1,95 +0,0 @@ -@model Fengling.AuthService.ViewModels.RegisterViewModel - -@{ - Layout = "_Layout"; - ViewData["Title"] = "注册"; -} - -
-
-
-
- - - - - - -
-

创建账号

-

加入 Fengling Auth

-
- -
- @if (!ViewData.ModelState.IsValid) - { -
- @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) - { -

@error.ErrorMessage

- } -
- } - -
- - -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
-
- -
- 已有账号? - 立即登录 -
-
-
diff --git a/src/Fengling.AuthService/Views/Authorization/Authorize.cshtml b/src/Fengling.AuthService/Views/Authorization/Authorize.cshtml deleted file mode 100644 index dc4dda9..0000000 --- a/src/Fengling.AuthService/Views/Authorization/Authorize.cshtml +++ /dev/null @@ -1,146 +0,0 @@ -@model Fengling.AuthService.ViewModels.AuthorizeViewModel - -@{ - Layout = "_Layout"; - ViewData["Title"] = "授权确认"; -} - -
-
- -
-
- - - -
-

授权确认

-

- @Model.ApplicationName - 请求访问您的账户 -

-
- - -
- -
-
-
-
- @(Model.ApplicationName?.Substring(0, Math.Min(1, Model.ApplicationName.Length)).ToUpper() ?? "A") -
-
-
-

@Model.ApplicationName

-

- 该应用将获得以下权限: -

-
-
-
- - -
-

请求的权限

-
- @if (Model.Scopes != null && Model.Scopes.Length > 0) - { - @foreach (var scope in Model.Scopes) - { -
- - - - -
-

@GetScopeDisplayName(scope)

-

@GetScopeDescription(scope)

-
-
- } - } - else - { -

无特定权限请求

- } -
-
- - -
-
- - - - - -

- 授予权限后,该应用将能够访问您的账户信息。您可以随时在授权管理中撤销权限。 -

-
-
-
- - -
-
- - -
-
- - - -
-
- -@functions { - private string GetScopeDisplayName(string scope) - { - return scope switch - { - "openid" => "OpenID Connect", - "profile" => "个人资料", - "email" => "电子邮件地址", - "phone" => "电话号码", - "address" => "地址信息", - "roles" => "角色权限", - "offline_access" => "离线访问", - _ => scope - }; - } - - private string GetScopeDescription(string scope) - { - return scope switch - { - "openid" => "用于用户身份验证", - "profile" => "访问您的姓名、头像等基本信息", - "email" => "访问您的电子邮件地址", - "phone" => "访问您的电话号码", - "address" => "访问您的地址信息", - "roles" => "访问您的角色和权限信息", - "offline_access" => "在您离线时仍可访问数据", - _ => "自定义权限范围" - }; - } -} diff --git a/src/Fengling.AuthService/Views/Dashboard/Index.cshtml b/src/Fengling.AuthService/Views/Dashboard/Index.cshtml deleted file mode 100644 index 03198a8..0000000 --- a/src/Fengling.AuthService/Views/Dashboard/Index.cshtml +++ /dev/null @@ -1,155 +0,0 @@ -@model Fengling.AuthService.ViewModels.DashboardViewModel - -@{ - Layout = "_Layout"; - ViewData["Title"] = "控制台"; -} - -
-
-

欢迎,@Model.Username

-

这里是您的控制台首页

-
- -
-
-
-
-

已登录应用

-

3

-
-
- - - - - -
-
-
- -
-
-
-

授权次数

-

12

-
-
- - - - -
-
-
- -
-
-
-

活跃会话

-

5

-
-
- - - - -
-
-
- -
-
-
-

安全评分

-

92%

-
-
- - - -
-
-
-
- -
-
-

最近活动

-
-
-
- F -
-
-

登录成功

-

通过 Fengling.Console.Web 登录

-
- 2分钟前 -
- -
-
- ✓ -
-
-

授权成功

-

授予 Fengling.Console.Web 访问权限

-
- 5分钟前 -
- -
-
- 🔄 -
-
-

令牌刷新

-

刷新访问令牌

-
- 1小时前 -
-
-
- -
-

快捷操作

- -
-
-
diff --git a/src/Fengling.AuthService/Views/Dashboard/Profile.cshtml b/src/Fengling.AuthService/Views/Dashboard/Profile.cshtml deleted file mode 100644 index bbeec08..0000000 --- a/src/Fengling.AuthService/Views/Dashboard/Profile.cshtml +++ /dev/null @@ -1,50 +0,0 @@ -@model Fengling.AuthService.ViewModels.DashboardViewModel - -@{ - Layout = "_Layout"; - ViewData["Title"] = "个人资料"; -} - -
-
-

个人资料

-

管理您的个人信息

-
- -
-
-
-
- @(Model.Username?.Substring(0, 1).ToUpper() ?? "U") -
-
-

@Model.Username

-

@Model.Email

-
-
- -
-
- -
- @Model.Username -
-
- -
- -
- @Model.Email -
-
- -
- -
- 2026-01-15 -
-
-
-
-
-
diff --git a/src/Fengling.AuthService/Views/Dashboard/Settings.cshtml b/src/Fengling.AuthService/Views/Dashboard/Settings.cshtml deleted file mode 100644 index d4e311d..0000000 --- a/src/Fengling.AuthService/Views/Dashboard/Settings.cshtml +++ /dev/null @@ -1,90 +0,0 @@ -@model Fengling.AuthService.ViewModels.DashboardViewModel - -@{ - Layout = "_Layout"; - ViewData["Title"] = "设置"; -} - -
-
-

账户设置

-

管理您的账户设置和偏好

-
- -
-
-

修改密码

-
-
- - -
- -
- - -
- -
- - -
- - -
-
- -
-

安全选项

-
-
-
-

两步验证

-

为您的账户添加额外的安全保护

-
- -
- -
-
-

登录通知

-

当有新设备登录时发送通知

-
- -
-
-
- -
-

危险区域

-
-
-

删除账户

-

永久删除您的账户和所有数据

-
- -
-
-
-
diff --git a/src/Fengling.AuthService/Views/Shared/_Layout.cshtml b/src/Fengling.AuthService/Views/Shared/_Layout.cshtml deleted file mode 100644 index 533acb9..0000000 --- a/src/Fengling.AuthService/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - - - - @ViewData["Title"] - Fengling Auth - - - - - - - - - - -
-
- -
-
- - - - - -
- Fengling Auth -
- - - - - -
- @if (User.Identity?.IsAuthenticated == true) - { - -
- - - - -
- } - else - { - - 登录 - - 注册 - - } -
-
-
- - -
- @RenderBody() -
- - - - - - \ No newline at end of file diff --git a/src/Fengling.AuthService/Views/_ViewImports.cshtml b/src/Fengling.AuthService/Views/_ViewImports.cshtml deleted file mode 100644 index 1dbdf3d..0000000 --- a/src/Fengling.AuthService/Views/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using Fengling.AuthService -@using Fengling.AuthService.ViewModels -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/src/Fengling.AuthService/Views/_ViewStart.cshtml b/src/Fengling.AuthService/Views/_ViewStart.cshtml deleted file mode 100644 index 1af6e49..0000000 --- a/src/Fengling.AuthService/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} \ No newline at end of file diff --git a/src/Fengling.AuthService/appsettings.Development.json b/src/Fengling.AuthService/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/src/Fengling.AuthService/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/src/Fengling.AuthService/appsettings.Testing.json b/src/Fengling.AuthService/appsettings.Testing.json deleted file mode 100644 index bcf5701..0000000 --- a/src/Fengling.AuthService/appsettings.Testing.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "ConnectionStrings": { - "DefaultConnection": "DataSource=:memory:" - }, - "Jwt": { - "Issuer": "https://auth.fengling.local", - "Audience": "fengling-api", - "Secret": "FenglingAuthSecretKey2024!ChangeThisInProduction!" - }, - "OpenIddict": { - "Issuer": "https://auth.fengling.local", - "Audience": "fengling-api" - }, - "Testing": true, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/src/Fengling.AuthService/appsettings.json b/src/Fengling.AuthService/appsettings.json deleted file mode 100644 index 14eeb16..0000000 --- a/src/Fengling.AuthService/appsettings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ConnectionStrings": { - "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542" - }, - "Jwt": { - "Issuer": "https://auth.fengling.local", - "Audience": "fengling-api", - "Secret": "FenglingAuthSecretKey2024!ChangeThisInProduction!" - }, - "OpenIddict": { - "Issuer": "https://auth.fengling.local", - "Audience": "fengling-api" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/src/Fengling.AuthService/wwwroot/css/styles.css b/src/Fengling.AuthService/wwwroot/css/styles.css deleted file mode 100644 index e3191b4..0000000 --- a/src/Fengling.AuthService/wwwroot/css/styles.css +++ /dev/null @@ -1,210 +0,0 @@ -/* ============================================ - shadcn UI Theme Variables - Based on shadcn/ui default theme - ============================================ */ - -/* Light Mode Variables */ -:root { - /* Background & Foreground */ - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - /* Card */ - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - /* Popover */ - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - /* Primary */ - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - /* Secondary */ - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - /* Muted */ - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - /* Accent */ - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - /* Destructive */ - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - /* Borders & Inputs */ - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - - /* Ring */ - --ring: 222.2 84% 4.9%; - - /* Radius */ - --radius: 0.5rem; -} - -/* Dark Mode Variables */ -.dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; -} - -/* ============================================ - Base Styles - ============================================ */ - -* { - border-color: hsl(var(--border)); -} - -body { - background-color: hsl(var(--background)); - color: hsl(var(--foreground)); - font-feature-settings: "rlig" 1, "calt" 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* ============================================ - Utility Classes - ============================================ */ - -/* Background colors */ -.bg-primary { - background-color: hsl(var(--primary)); -} - -.bg-secondary { - background-color: hsl(var(--secondary)); -} - -.bg-muted { - background-color: hsl(var(--muted)); -} - -.bg-accent { - background-color: hsl(var(--accent)); -} - -.bg-destructive { - background-color: hsl(var(--destructive)); -} - -/* Text colors */ -.text-primary-foreground { - color: hsl(var(--primary-foreground)); -} - -.text-secondary-foreground { - color: hsl(var(--secondary-foreground)); -} - -.text-muted-foreground { - color: hsl(var(--muted-foreground)); -} - -.text-accent-foreground { - color: hsl(var(--accent-foreground)); -} - -.text-destructive-foreground { - color: hsl(var(--destructive-foreground)); -} - -.text-foreground { - color: hsl(var(--foreground)); -} - -/* Borders */ -.border-border { - border-color: hsl(var(--border)); -} - -/* Hover effects */ -.hover\:bg-muted:hover { - background-color: hsl(var(--muted)); -} - -.hover\:bg-primary:hover { - background-color: hsl(var(--primary)); -} - -.hover\:text-foreground:hover { - color: hsl(var(--foreground)); -} - -/* ============================================ - Transitions - ============================================ */ - -.transition-colors { - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.transition-all { - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -/* ============================================ - Component Styles - ============================================ */ - -/* Button Styles */ -.btn-primary { - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); -} - -.btn-primary:hover { - background-color: hsl(var(--primary) / 0.9); -} - -/* Card Styles */ -.card { - background-color: hsl(var(--card)); - border: 1px solid hsl(var(--border)); - border-radius: var(--radius); -} - -.card-foreground { - color: hsl(var(--card-foreground)); -} - -/* Input Styles */ -.input { - background-color: hsl(var(--background)); - border: 1px solid hsl(var(--input)); - color: hsl(var(--foreground)); -} - -.input:focus { - outline: none; - border-color: hsl(var(--ring)); - box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2); -} diff --git a/src/Fengling.AuthService/wwwroot/login.html b/src/Fengling.AuthService/wwwroot/login.html deleted file mode 100644 index 2bf3304..0000000 --- a/src/Fengling.AuthService/wwwroot/login.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - 登录 - 风铃认证服务 - - - -
-

风铃认证服务

- -
- -
- - -
-
- - -
-
- - -
- -
-
- - - - diff --git a/src/Fengling.Console.Web/.env.development b/src/Fengling.Console.Web/.env.development deleted file mode 100644 index 8f1a7f7..0000000 --- a/src/Fengling.Console.Web/.env.development +++ /dev/null @@ -1,7 +0,0 @@ -# Fengling.Console.Web 运管中心前端 - -## 开发环境配置 -VITE_AUTH_SERVER_URL=http://localhost:5132 -VITE_GATEWAY_SERVICE_URL=http://localhost:5001 -VITE_CLIENT_ID=fengling-console -VITE_REDIRECT_URI=http://localhost:5173/auth/callback diff --git a/src/Fengling.Console.Web/.env.production b/src/Fengling.Console.Web/.env.production deleted file mode 100644 index 841ad76..0000000 --- a/src/Fengling.Console.Web/.env.production +++ /dev/null @@ -1,7 +0,0 @@ -# Fengling.Console.Web 运管中心前端 - -## 生产环境配置 -VITE_AUTH_SERVER_URL=https://auth.fengling.local -VITE_GATEWAY_SERVICE_URL=https://gateway.fengling.local -VITE_CLIENT_ID=fengling-console -VITE_REDIRECT_URI=https://console.fengling.local/auth/callback diff --git a/src/Fengling.Console.Web/.gitignore b/src/Fengling.Console.Web/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/src/Fengling.Console.Web/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/src/Fengling.Console.Web/README.md b/src/Fengling.Console.Web/README.md deleted file mode 100644 index dc5acd8..0000000 --- a/src/Fengling.Console.Web/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# Fengling Console.Web - 风铃运管中心前端 - -## 项目概述 - -统一运维管理平台前端,用于管理网关配置、OAuth应用、用户等。 - -## 技术栈 - -- Vue 3.4 -- TypeScript -- Vite 5.4 -- Element Plus -- Pinia -- Vue Router 4 -- Axios - -## 功能模块 - -### 1. 认证模块 -- OAuth2 授权码流登录 -- 密码流登录 -- Token 自动刷新 -- 登出功能 - -### 2. 网关管理 -- 租户列表和管理 -- 租户路由配置 -- 集群实例管理 -- 全局路由配置 -- 负载均衡策略 - -### 3. OAuth 应用管理 -- OAuth Client CRUD -- 重定向 URI 配置 -- 授权类型配置 -- Client 状态管理 - -### 4. 用户管理 -- 用户列表 -- 角色分配 -- 租户分配 -- 用户状态管理 - -## 环境变量 - -### 开发环境 (.env.development) -```env -VITE_AUTH_SERVICE_URL=http://localhost:5000 -VITE_GATEWAY_SERVICE_URL=http://localhost:5001 -VITE_CLIENT_ID=fengling-console -VITE_REDIRECT_URI=http://localhost:5173/auth/callback -``` - -### 生产环境 (.env.production) -```env -VITE_AUTH_SERVICE_URL=https://auth.fengling.local -VITE_GATEWAY_SERVICE_URL=https://gateway.fengling.local -``` - -## 开发指南 - -### 安装依赖 -```bash -npm install -``` - -### 启动开发服务器 -```bash -npm run dev -``` - -访问: http://localhost:5173 - -### 构建生产版本 -```bash -npm run build -``` - -### 预览生产构建 -```bash -npm run preview -``` - -## 目录结构 - -``` -src/ -├── api/ -│ ├── auth.ts # AuthService API封装 -│ └── gateway.ts # Gateway API封装 -├── components/ # 公共组件 -├── stores/ -│ └── auth.ts # Pinia store for auth -├── router/ -│ └── index.ts # Vue Router配置 -└── views/ - ├── Auth/ # 认证相关页面 - │ ├── Login.vue - │ └── Callback.vue - ├── Gateway/ # 网关管理页面 - │ └── Dashboard.vue - ├── OAuth/ # OAuth应用管理 - │ └── ClientList.vue - └── Users/ # 用户管理 - └── UserList.vue -``` - -## API代理配置 - -Vite 开发服务器配置了以下 API 代理: - -- `/api/auth/*` → `http://localhost:5000` (AuthService) -- `/api/gateway/*` → `http://localhost:5001` (YarpGateway) - -## OAuth2 集成 - -Client 已在 AuthService 的 SeedData 中预注册: - -```yaml -Client ID: fengling-console -授权类型: authorization_code, refresh_token -重定向 URI: http://console.fengling.local/auth/callback -作用域: api, offline_access -``` - -## 后续开发 - -### 迁移任务 -- [ ] 迁移 TenantList.vue -- [ ] 迁移 TenantRoutes.vue -- [ ] 迁移 ClusterInstances.vue -- [ ] 迁移 GlobalRoutes.vue - -### 功能增强 -- [ ] Token 自动刷新拦截器 -- [ ] 请求错误统一处理 -- [ ] Loading 状态管理 -- [ ] 权限控制 -- [ ] 完善 OAuth2 授权码流 diff --git a/src/Fengling.Console.Web/index.html b/src/Fengling.Console.Web/index.html deleted file mode 100644 index b0b2ada..0000000 --- a/src/Fengling.Console.Web/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - fengling-console-web - - -
- - - diff --git a/src/Fengling.Console.Web/package-lock.json b/src/Fengling.Console.Web/package-lock.json deleted file mode 100644 index a48f4a9..0000000 --- a/src/Fengling.Console.Web/package-lock.json +++ /dev/null @@ -1,1500 +0,0 @@ -{ - "name": "fengling-console-web", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "fengling-console-web", - "version": "0.0.0", - "dependencies": { - "oidc-client-ts": "^3.4.1", - "vue": "^3.5.24" - }, - "devDependencies": { - "@types/node": "^24.10.1", - "@vitejs/plugin-vue": "^6.0.1", - "@vue/tsconfig": "^0.8.1", - "typescript": "~5.9.3", - "vite": "^7.2.4", - "vue-tsc": "^3.1.4" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", - "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", - "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.53" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", - "vue": "^3.2.25" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", - "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.27" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", - "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", - "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.27", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", - "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.27", - "entities": "^7.0.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", - "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", - "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.27", - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", - "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/language-core": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz", - "integrity": "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.27", - "@vue/compiler-dom": "^3.5.0", - "@vue/shared": "^3.5.0", - "alien-signals": "^3.0.0", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1", - "picomatch": "^4.0.2" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", - "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", - "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", - "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/runtime-core": "3.5.27", - "@vue/shared": "3.5.27", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", - "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27" - }, - "peerDependencies": { - "vue": "3.5.27" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", - "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", - "license": "MIT" - }, - "node_modules/@vue/tsconfig": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", - "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": "5.x", - "vue": "^3.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/alien-signals": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", - "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/oidc-client-ts": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz", - "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==", - "license": "Apache-2.0", - "dependencies": { - "jwt-decode": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", - "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-sfc": "3.5.27", - "@vue/runtime-dom": "3.5.27", - "@vue/server-renderer": "3.5.27", - "@vue/shared": "3.5.27" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-tsc": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz", - "integrity": "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "2.4.27", - "@vue/language-core": "3.2.4" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - } - } -} diff --git a/src/Fengling.Console.Web/package.json b/src/Fengling.Console.Web/package.json deleted file mode 100644 index 2f87665..0000000 --- a/src/Fengling.Console.Web/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "fengling-console-web", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc -b && vite build", - "preview": "vite preview" - }, - "dependencies": { - "oidc-client-ts": "^3.4.1", - "vue": "^3.5.24" - }, - "devDependencies": { - "@types/node": "^24.10.1", - "@vitejs/plugin-vue": "^6.0.1", - "@vue/tsconfig": "^0.8.1", - "typescript": "~5.9.3", - "vite": "^7.2.4", - "vue-tsc": "^3.1.4" - } -} diff --git a/src/Fengling.Console.Web/public/silent-renew.html b/src/Fengling.Console.Web/public/silent-renew.html deleted file mode 100644 index 1761eb6..0000000 --- a/src/Fengling.Console.Web/public/silent-renew.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Silent Renew - - - - - - diff --git a/src/Fengling.Console.Web/public/vite.svg b/src/Fengling.Console.Web/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/src/Fengling.Console.Web/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Fengling.Console.Web/src/App.vue b/src/Fengling.Console.Web/src/App.vue deleted file mode 100644 index aff3dbb..0000000 --- a/src/Fengling.Console.Web/src/App.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/api/gateway.ts b/src/Fengling.Console.Web/src/api/gateway.ts deleted file mode 100644 index a5f89f8..0000000 --- a/src/Fengling.Console.Web/src/api/gateway.ts +++ /dev/null @@ -1,88 +0,0 @@ -import axios from 'axios' - -const gatewayAxios = axios.create({ - baseURL: '/api/gateway', - timeout: 10000, - headers: { - 'Content-Type': 'application/json', - }, -}) - -gatewayAxios.interceptors.request.use( - config => { - const token = localStorage.getItem('access_token') - if (token) { - config.headers.Authorization = `Bearer ${token}` - } - return config - }, - error => { - return Promise.reject(error) - } -) - -export const gatewayService = { - async getTenants() { - const response = await gatewayAxios.get('/tenants') - return response.data - }, - - async getTenant(id: number) { - const response = await gatewayAxios.get(`/tenants/${id}`) - return response.data - }, - - async createTenant(tenant: any) { - const response = await gatewayAxios.post('/tenants', tenant) - return response.data - }, - - async updateTenant(id: number, tenant: any) { - const response = await gatewayAxios.put(`/tenants/${id}`, tenant) - return response.data - }, - - async deleteTenant(id: number) { - await gatewayAxios.delete(`/tenants/${id}`) - }, - - async getTenantRoutes(tenantId: number) { - const response = await gatewayAxios.get(`/tenants/${tenantId}/routes`) - return response.data - }, - - async createTenantRoute(tenantId: number, route: any) { - const response = await gatewayAxios.post(`/tenants/${tenantId}/routes`, route) - return response.data - }, - - async updateTenantRoute(id: number, route: any) { - const response = await gatewayAxios.put(`/routes/${id}`, route) - return response.data - }, - - async deleteTenantRoute(id: number) { - await gatewayAxios.delete(`/routes/${id}`) - }, - - async getClusterInstances(clusterId: number) { - const response = await gatewayAxios.get(`/clusters/${clusterId}/instances`) - return response.data - }, - - async createClusterInstance(clusterId: number, instance: any) { - const response = await gatewayAxios.post(`/clusters/${clusterId}/instances`, instance) - return response.data - }, - - async updateClusterInstance(id: number, instance: any) { - const response = await gatewayAxios.put(`/instances/${id}`, instance) - return response.data - }, - - async deleteClusterInstance(id: number) { - await gatewayAxios.delete(`/instances/${id}`) - }, -} - -export default gatewayAxios diff --git a/src/Fengling.Console.Web/src/api/index.ts b/src/Fengling.Console.Web/src/api/index.ts deleted file mode 100644 index f838ddb..0000000 --- a/src/Fengling.Console.Web/src/api/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import axios from 'axios' - -const api = axios.create({ - baseURL: '/api', - timeout: 30000, -}) - -api.interceptors.request.use( - (config) => { - const token = localStorage.getItem('access_token') - if (token) { - config.headers.Authorization = `Bearer ${token}` - } - return config - }, - (error) => { - return Promise.reject(error) - } -) - -api.interceptors.response.use( - (response) => response, - async (error) => { - const originalRequest = error.config - - if (error.response?.status === 401 && !originalRequest._retry) { - originalRequest._retry = true - - try { - const { useAuthStore } = await import('@/stores/auth') - const authStore = useAuthStore() - await authStore.refresh() - - originalRequest.headers.Authorization = `Bearer ${localStorage.getItem('access_token')}` - return api(originalRequest) - } catch (refreshError) { - localStorage.removeItem('access_token') - localStorage.removeItem('refresh_token') - localStorage.removeItem('user_info') - window.location.href = '/login' - return Promise.reject(error) - } - } - - return Promise.reject(error) - } -) - -export default api diff --git a/src/Fengling.Console.Web/src/assets/vue.svg b/src/Fengling.Console.Web/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/src/Fengling.Console.Web/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Fengling.Console.Web/src/components/HelloWorld.vue b/src/Fengling.Console.Web/src/components/HelloWorld.vue deleted file mode 100644 index b58e52b..0000000 --- a/src/Fengling.Console.Web/src/components/HelloWorld.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/main.ts b/src/Fengling.Console.Web/src/main.ts deleted file mode 100644 index a6da29f..0000000 --- a/src/Fengling.Console.Web/src/main.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createApp } from 'vue' -import { createPinia } from 'pinia' -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import './style.css' -import App from './App.vue' -import router from './router' - -const app = createApp(App) - -app.use(createPinia()) -app.use(router) -app.use(ElementPlus) - -app.mount('#app') diff --git a/src/Fengling.Console.Web/src/router/index.ts b/src/Fengling.Console.Web/src/router/index.ts deleted file mode 100644 index dad6f53..0000000 --- a/src/Fengling.Console.Web/src/router/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { createRouter, createWebHistory } from 'vue-router' -import type { RouteRecordRaw } from 'vue-router' -import { useAuthStore } from '@/stores/auth' - -const routes: Array = [ - { - path: '/login', - name: 'Login', - component: () => import('@/views/Auth/Login.vue'), - meta: { requiresAuth: false }, - }, - { - path: '/auth/callback', - name: 'AuthCallback', - component: () => import('@/views/Auth/Callback.vue'), - meta: { requiresAuth: false }, - }, - { - path: '/', - component: () => import('@/views/Gateway/Dashboard.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'Dashboard', - component: () => import('@/views/Dashboard/Dashboard.vue'), - }, - { - path: 'oauth/clients', - name: 'OAuthClients', - component: () => import('@/views/OAuth/ClientList.vue'), - }, - { - path: 'users', - name: 'UserList', - component: () => import('@/views/Users/UserList.vue'), - }, - { - path: 'roles', - name: 'RoleList', - component: () => import('@/views/Users/RoleList.vue'), - }, - { - path: 'tenants', - name: 'TenantList', - component: () => import('@/views/Users/TenantList.vue'), - }, - { - path: 'logs/access', - name: 'AccessLog', - component: () => import('@/views/Audit/AccessLog.vue'), - }, - { - path: 'logs/audit', - name: 'AuditLog', - component: () => import('@/views/Audit/AuditLog.vue'), - }, - ], - }, -] - -const router = createRouter({ - history: createWebHistory(), - routes, -}) - -router.beforeEach(async (to, _from, next) => { - const authStore = useAuthStore() - - if (!authStore.accessToken && !authStore.user) { - await authStore.loadFromStorage() - } - - if (to.meta.requiresAuth && !authStore.isAuthenticated) { - next({ name: 'Login' }) - } else if (to.name === 'Login' && authStore.isAuthenticated) { - next({ name: 'Dashboard' }) - } else { - next() - } -}) - -export default router diff --git a/src/Fengling.Console.Web/src/services/oidc.ts b/src/Fengling.Console.Web/src/services/oidc.ts deleted file mode 100644 index 7e48a70..0000000 --- a/src/Fengling.Console.Web/src/services/oidc.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { UserManager, User } from 'oidc-client-ts' - -const AUTH_SERVER = import.meta.env.VITE_AUTH_SERVER_URL || 'http://localhost:5000' -const REDIRECT_URI = import.meta.env.VITE_REDIRECT_URI || window.location.origin + '/auth/callback' -const CLIENT_ID = import.meta.env.VITE_CLIENT_ID || 'fengling-console' - -const userManager = new UserManager({ - authority: AUTH_SERVER, - client_id: CLIENT_ID, - redirect_uri: REDIRECT_URI, - response_type: 'code', - scope: 'openid profile api offline_access', - automaticSilentRenew: true, - silent_redirect_uri: `${window.location.origin}/silent-renew.html`, - loadUserInfo: true, -}) - -export interface UserInfo { - sub?: string - name?: string - email?: string - tenant_id?: string - role?: string | string[] - preferred_username?: string -} - -export interface AuthUser { - access_token: string - refresh_token: string - token_type: string - expires_at: number - profile: UserInfo | null - expired: boolean -} - -function mapUserToAuthUser(user: User | null): AuthUser | null { - if (!user) return null - - return { - access_token: user.access_token, - refresh_token: user.refresh_token || '', - token_type: user.token_type || 'Bearer', - expires_at: user.expires_at || 0, - profile: (user.profile as UserInfo) || null, - expired: user.expired ?? false, - } -} - -export const authService = { - async login(): Promise { - await userManager.signinRedirect() - }, - - async handleCallback(): Promise { - const user = await userManager.signinCallback() - if (!user) { - throw new Error('登录失败:无效的用户信息') - } - return mapUserToAuthUser(user)! - }, - - async getUser(): Promise { - const user = await userManager.getUser() - return mapUserToAuthUser(user) - }, - - async refresh(): Promise { - const user = await userManager.signinSilent() - if (!user) { - throw new Error('刷新令牌失败') - } - return mapUserToAuthUser(user)! - }, - - async logout(): Promise { - await userManager.signoutRedirect() - }, - - async removeUser(): Promise { - await userManager.removeUser() - }, - - async isAuthenticated(): Promise { - const user = await userManager.getUser() - return user !== null && !user.expired - }, -} diff --git a/src/Fengling.Console.Web/src/stores/auth.ts b/src/Fengling.Console.Web/src/stores/auth.ts deleted file mode 100644 index 6bd23e5..0000000 --- a/src/Fengling.Console.Web/src/stores/auth.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import { authService, type AuthUser } from '@/services/oidc' - -interface User { - id: string - userName: string - email: string - tenantId: string - roles: string[] -} - -export const useAuthStore = defineStore('auth', () => { - const accessToken = ref('') - const refreshToken = ref('') - const user = ref(null) - const isAuthenticated = computed(() => !!accessToken.value && !!user.value) - - function setTokens(authUser: AuthUser) { - accessToken.value = authUser.access_token - refreshToken.value = authUser.refresh_token - - if (authUser.profile) { - user.value = { - id: authUser.profile.sub || '', - userName: authUser.profile.preferred_username || authUser.profile.name || '', - email: authUser.profile.email || '', - tenantId: authUser.profile.tenant_id || '1', - roles: Array.isArray(authUser.profile.role) ? authUser.profile.role : (authUser.profile.role ? [authUser.profile.role] : []), - } - } - - localStorage.setItem('access_token', authUser.access_token) - localStorage.setItem('refresh_token', authUser.refresh_token) - localStorage.setItem('user_info', JSON.stringify(user.value)) - } - - async function login() { - await authService.login() - } - - async function refresh() { - try { - const authUser = await authService.refresh() - setTokens(authUser) - } catch (error) { - console.error('Token refresh failed:', error) - clearAuth() - throw error - } - } - - async function logout() { - await authService.logout() - clearAuth() - } - - function clearAuth() { - accessToken.value = '' - refreshToken.value = '' - user.value = null - localStorage.removeItem('access_token') - localStorage.removeItem('refresh_token') - localStorage.removeItem('user_info') - } - - async function loadFromStorage() { - const storedToken = localStorage.getItem('access_token') - const storedRefreshToken = localStorage.getItem('refresh_token') - const storedUser = localStorage.getItem('user_info') - - if (storedToken) { - accessToken.value = storedToken - } - if (storedRefreshToken) { - refreshToken.value = storedRefreshToken - } - if (storedUser) { - try { - user.value = JSON.parse(storedUser) - } catch { - user.value = null - } - } - - const authUser = await authService.getUser() - if (authUser && !authUser.expired) { - setTokens(authUser) - } - } - - return { - accessToken, - refreshToken, - user, - isAuthenticated, - login, - refresh, - logout, - setTokens, - clearAuth, - loadFromStorage, - } -}) diff --git a/src/Fengling.Console.Web/src/style.css b/src/Fengling.Console.Web/src/style.css deleted file mode 100644 index f691315..0000000 --- a/src/Fengling.Console.Web/src/style.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -.card { - padding: 2em; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/Fengling.Console.Web/src/views/Audit/AccessLog.vue b/src/Fengling.Console.Web/src/views/Audit/AccessLog.vue deleted file mode 100644 index 8dabfbe..0000000 --- a/src/Fengling.Console.Web/src/views/Audit/AccessLog.vue +++ /dev/null @@ -1,346 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Audit/AuditLog.vue b/src/Fengling.Console.Web/src/views/Audit/AuditLog.vue deleted file mode 100644 index f810d2c..0000000 --- a/src/Fengling.Console.Web/src/views/Audit/AuditLog.vue +++ /dev/null @@ -1,377 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Auth/Callback.vue b/src/Fengling.Console.Web/src/views/Auth/Callback.vue deleted file mode 100644 index b469b93..0000000 --- a/src/Fengling.Console.Web/src/views/Auth/Callback.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Auth/Login.vue b/src/Fengling.Console.Web/src/views/Auth/Login.vue deleted file mode 100644 index b00beb0..0000000 --- a/src/Fengling.Console.Web/src/views/Auth/Login.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Dashboard/Dashboard.vue b/src/Fengling.Console.Web/src/views/Dashboard/Dashboard.vue deleted file mode 100644 index f6053ff..0000000 --- a/src/Fengling.Console.Web/src/views/Dashboard/Dashboard.vue +++ /dev/null @@ -1,257 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Gateway/Dashboard.vue b/src/Fengling.Console.Web/src/views/Gateway/Dashboard.vue deleted file mode 100644 index 8fbf011..0000000 --- a/src/Fengling.Console.Web/src/views/Gateway/Dashboard.vue +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - diff --git a/src/Fengling.Console.Web/src/views/OAuth/ClientList.vue b/src/Fengling.Console.Web/src/views/OAuth/ClientList.vue deleted file mode 100644 index 7bd32d7..0000000 --- a/src/Fengling.Console.Web/src/views/OAuth/ClientList.vue +++ /dev/null @@ -1,496 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Users/RoleList.vue b/src/Fengling.Console.Web/src/views/Users/RoleList.vue deleted file mode 100644 index 1f856b9..0000000 --- a/src/Fengling.Console.Web/src/views/Users/RoleList.vue +++ /dev/null @@ -1,404 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Users/TenantList.vue b/src/Fengling.Console.Web/src/views/Users/TenantList.vue deleted file mode 100644 index 1273115..0000000 --- a/src/Fengling.Console.Web/src/views/Users/TenantList.vue +++ /dev/null @@ -1,575 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/src/views/Users/UserList.vue b/src/Fengling.Console.Web/src/views/Users/UserList.vue deleted file mode 100644 index 8674a84..0000000 --- a/src/Fengling.Console.Web/src/views/Users/UserList.vue +++ /dev/null @@ -1,490 +0,0 @@ - - - - - diff --git a/src/Fengling.Console.Web/tsconfig.app.json b/src/Fengling.Console.Web/tsconfig.app.json deleted file mode 100644 index 44b0bf6..0000000 --- a/src/Fengling.Console.Web/tsconfig.app.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@vue/tsconfig/tsconfig.dom.json", - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "types": ["vite/client"], - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/src/Fengling.Console.Web/tsconfig.json b/src/Fengling.Console.Web/tsconfig.json deleted file mode 100644 index 1ffef60..0000000 --- a/src/Fengling.Console.Web/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/src/Fengling.Console.Web/tsconfig.node.json b/src/Fengling.Console.Web/tsconfig.node.json deleted file mode 100644 index 8a67f62..0000000 --- a/src/Fengling.Console.Web/tsconfig.node.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", - "lib": ["ES2023"], - "module": "ESNext", - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/src/Fengling.Console.Web/vite.config.ts b/src/Fengling.Console.Web/vite.config.ts deleted file mode 100644 index 5c4a516..0000000 --- a/src/Fengling.Console.Web/vite.config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import path from 'path' - -export default defineConfig(() => ({ - plugins: [vue()], - resolve: { - alias: { - '@': path.resolve(__dirname, 'src') - } - }, - server: { - port: 5173, - proxy: { - '/.well-known': { - target: 'http://localhost:5132', - changeOrigin: true, - }, - '/connect': { - target: 'http://localhost:5132', - changeOrigin: true, - }, - '/api': { - target: 'http://localhost:5132', - changeOrigin: true, - }, - } - } -})) diff --git a/src/YarpGateway.Admin/.env.development b/src/YarpGateway.Admin/.env.development deleted file mode 100644 index a8cf54a..0000000 --- a/src/YarpGateway.Admin/.env.development +++ /dev/null @@ -1 +0,0 @@ -VITE_API_BASE_URL=http://localhost:8080 diff --git a/src/YarpGateway.Admin/.gitignore b/src/YarpGateway.Admin/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/src/YarpGateway.Admin/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/src/YarpGateway.Admin/.vscode/extensions.json b/src/YarpGateway.Admin/.vscode/extensions.json deleted file mode 100644 index a7cea0b..0000000 --- a/src/YarpGateway.Admin/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["Vue.volar"] -} diff --git a/src/YarpGateway.Admin/Dockerfile b/src/YarpGateway.Admin/Dockerfile deleted file mode 100644 index ef1fffa..0000000 --- a/src/YarpGateway.Admin/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM node:20-alpine AS builder - -WORKDIR /app - -COPY package*.json ./ -RUN npm install - -COPY . . -RUN npm run build - -FROM nginx:alpine - -COPY --from=builder /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/src/YarpGateway.Admin/README.md b/src/YarpGateway.Admin/README.md deleted file mode 100644 index 9a0c91a..0000000 --- a/src/YarpGateway.Admin/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# YARP Gateway Admin - -基于Vue3 + Element Plus的YARP网关管理界面。 - -## 功能特性 - -- ✅ 租户管理(CRUD) -- ✅ 租户路由配置 -- ✅ 服务实例管理 -- ✅ 加权负载均衡配置 -- ✅ 配置热重载 -- ✅ 实时监控仪表板 -- ✅ TypeScript类型支持 - -## 快速开始 - -### 安装依赖 - -```bash -npm install -``` - -### 启动开发服务器 - -```bash -npm run dev -``` - -访问: http://localhost:5173 - -### 构建生产版本 - -```bash -npm run build -``` - -### 预览生产构建 - -```bash -npm run preview -``` - -## 项目结构 - -``` -src/ -├── api/ # API接口定义 -│ └── index.ts -├── components/ # 公共组件 -│ └── Layout.vue -├── router/ # 路由配置 -│ └── index.ts -├── stores/ # Pinia状态管理 -│ └── tenant.ts -├── views/ # 页面组件 -│ ├── TenantList.vue # 租户列表 -│ ├── TenantRoutes.vue # 租户路由 -│ ├── ClusterInstances.vue # 实例管理 -│ └── Dashboard.vue # 仪表板 -├── App.vue # 根组件 -└── main.ts # 入口文件 -``` - -## 页面说明 - -### 租户列表 (/tenants) -- 查看所有租户 -- 创建新租户 -- 删除租户 -- 查看租户路由 - -### 租户路由 (/tenants/:tenantCode/routes) -- 查看租户的所有服务路由 -- 创建新路由 -- 删除路由 -- 查看服务实例 - -### 服务实例 (/clusters/:clusterId/instances) -- 查看Cluster下的所有实例 -- 添加新实例 -- 设置权重 -- 删除实例 - -### 监控仪表板 (/dashboard) -- 总租户数 -- 总路由数 -- 总Cluster数 -- 总实例数 -- 快速操作 - -## 环境变量 - -`.env.development`: -``` -VITE_API_BASE_URL=http://localhost:8080 -``` - -## API接口配置 - -开发环境通过Vite代理转发到后端: - -```typescript -server: { - proxy: { - '/api': { - target: 'http://localhost:8080', - changeOrigin: true - } - } -} -``` - -## 技术栈 - -- **Vue 3** - 渐进式JavaScript框架 -- **TypeScript** - 类型安全 -- **Vite** - 构建工具 -- **Element Plus** - UI组件库 -- **Pinia** - 状态管理 -- **Vue Router** - 路由管理 -- **Axios** - HTTP客户端 - -## 开发建议 - -### 添加新页面 - -1. 在 `src/views/` 创建Vue组件 -2. 在 `src/router/index.ts` 添加路由 -3. 在 `Layout.vue` 添加菜单项 - -### 添加API接口 - -在 `src/api/index.ts` 添加: - -```typescript -export const api = { - yourModule: { - yourMethod: (params) => request.get('/api/your-endpoint', { params }) - } -} -``` - -### 添加状态管理 - -在 `src/stores/` 创建Pinia store: - -```typescript -import { defineStore } from 'pinia' - -export const useYourStore = defineStore('your', () => { - // state, actions -}) -``` - -## Docker部署 - -```bash -# 构建镜像 -docker build -t yarp-gateway-admin . - -# 运行容器 -docker run -d -p 5173:80 yarp-gateway-admin -``` - -## License - -MIT diff --git a/src/YarpGateway.Admin/index.html b/src/YarpGateway.Admin/index.html deleted file mode 100644 index 98ddb94..0000000 --- a/src/YarpGateway.Admin/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - yarpgateway-admin - - -
- - - diff --git a/src/YarpGateway.Admin/nginx.conf b/src/YarpGateway.Admin/nginx.conf deleted file mode 100644 index 324ece5..0000000 --- a/src/YarpGateway.Admin/nginx.conf +++ /dev/null @@ -1,21 +0,0 @@ -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - location /api { - proxy_pass http://yarp-gateway:8080; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; -} diff --git a/src/YarpGateway.Admin/package-lock.json b/src/YarpGateway.Admin/package-lock.json deleted file mode 100644 index 4e947d8..0000000 --- a/src/YarpGateway.Admin/package-lock.json +++ /dev/null @@ -1,2560 +0,0 @@ -{ - "name": "yarpgateway-admin", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "yarpgateway-admin", - "version": "0.0.0", - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "axios": "^1.13.4", - "element-plus": "^2.13.2", - "pinia": "^3.0.4", - "vue": "^3.5.24", - "vue-router": "^5.0.1" - }, - "devDependencies": { - "@types/node": "^24.10.1", - "@vitejs/plugin-vue": "^6.0.1", - "@vue/tsconfig": "^0.8.1", - "typescript": "~5.9.3", - "vite": "^7.2.4", - "vue-tsc": "^3.1.4" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", - "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", - "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@element-plus/icons-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", - "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@popperjs/core": { - "name": "@sxzz/popperjs-es", - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", - "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", - "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", - "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.53" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", - "vue": "^3.2.25" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", - "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.27" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", - "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", - "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.27", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vue-macros/common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", - "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", - "license": "MIT", - "dependencies": { - "@vue/compiler-sfc": "^3.5.22", - "ast-kit": "^2.1.2", - "local-pkg": "^1.1.2", - "magic-string-ast": "^1.0.2", - "unplugin-utils": "^0.3.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/vue-macros" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.2.25" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", - "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.27", - "entities": "^7.0.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", - "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", - "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.27", - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", - "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.9" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/language-core": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz", - "integrity": "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.27", - "@vue/compiler-dom": "^3.5.0", - "@vue/shared": "^3.5.0", - "alien-signals": "^3.0.0", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1", - "picomatch": "^4.0.2" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", - "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", - "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", - "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/runtime-core": "3.5.27", - "@vue/shared": "3.5.27", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", - "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27" - }, - "peerDependencies": { - "vue": "3.5.27" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", - "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", - "license": "MIT" - }, - "node_modules/@vue/tsconfig": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", - "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": "5.x", - "vue": "^3.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/alien-signals": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", - "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ast-kit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", - "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "pathe": "^2.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/ast-walker-scope": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", - "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.4", - "ast-kit": "^2.1.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/element-plus": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.2.tgz", - "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", - "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^3.4.1", - "@element-plus/icons-vue": "^2.3.2", - "@floating-ui/dom": "^1.0.1", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", - "@types/lodash": "^4.17.20", - "@types/lodash-es": "^4.17.12", - "@vueuse/core": "^10.11.0", - "async-validator": "^4.2.5", - "dayjs": "^1.11.19", - "lodash": "^4.17.23", - "lodash-es": "^4.17.23", - "lodash-unified": "^1.0.3", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.2.0" - }, - "peerDependencies": { - "vue": "^3.3.0" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash-unified": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", - "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", - "license": "MIT", - "peerDependencies": { - "@types/lodash-es": "*", - "lodash": "*", - "lodash-es": "*" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magic-string-ast": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", - "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.19" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-wheel-es": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", - "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", - "license": "BSD-3-Clause" - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pinia": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", - "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^7.7.7" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.5.0", - "vue": "^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", - "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-sfc": "3.5.27", - "@vue/runtime-dom": "3.5.27", - "@vue/server-renderer": "3.5.27", - "@vue/shared": "3.5.27" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-router": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.1.tgz", - "integrity": "sha512-t+lFugGXMdaq8lbn+vXG4j2H9UlsP205Tszz1wcDk9FyxqItBzcdJQ06IhpkQ2mHOfiTOHZeBshkskzPzHJkCw==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^7.28.6", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.0.0", - "ast-walker-scope": "^0.8.3", - "chokidar": "^5.0.0", - "json5": "^2.2.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.0", - "muggle-string": "^0.4.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "scule": "^1.3.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1", - "yaml": "^2.8.2" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "@pinia/colada": "^0.18.1", - "@vue/compiler-sfc": "^3.5.17", - "pinia": "^3.0.4", - "vue": "^3.5.0" - }, - "peerDependenciesMeta": { - "@pinia/colada": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "pinia": { - "optional": true - } - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-api": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.5.tgz", - "integrity": "sha512-DgVcW8H/Nral7LgZEecYFFYXnAvGuN9C3L3DtWekAncFBedBczpNW8iHKExfaM559Zm8wQWrwtYZ9lXthEHtDw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.0.5" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-kit": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", - "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.0.5", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^2.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-shared": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", - "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/vue-router/node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "license": "MIT" - }, - "node_modules/vue-tsc": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz", - "integrity": "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "2.4.27", - "@vue/language-core": "3.2.4" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - } - } -} diff --git a/src/YarpGateway.Admin/package.json b/src/YarpGateway.Admin/package.json deleted file mode 100644 index cc04817..0000000 --- a/src/YarpGateway.Admin/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "yarpgateway-admin", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc -b && vite build", - "preview": "vite preview" - }, - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "axios": "^1.13.4", - "element-plus": "^2.13.2", - "pinia": "^3.0.4", - "vue": "^3.5.24", - "vue-router": "^5.0.1" - }, - "devDependencies": { - "@types/node": "^24.10.1", - "@vitejs/plugin-vue": "^6.0.1", - "@vue/tsconfig": "^0.8.1", - "typescript": "~5.9.3", - "vite": "^7.2.4", - "vue-tsc": "^3.1.4" - } -} diff --git a/src/YarpGateway.Admin/public/vite.svg b/src/YarpGateway.Admin/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/src/YarpGateway.Admin/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/YarpGateway.Admin/src/App.vue b/src/YarpGateway.Admin/src/App.vue deleted file mode 100644 index a39b083..0000000 --- a/src/YarpGateway.Admin/src/App.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/api/index.ts b/src/YarpGateway.Admin/src/api/index.ts deleted file mode 100644 index 4891441..0000000 --- a/src/YarpGateway.Admin/src/api/index.ts +++ /dev/null @@ -1,92 +0,0 @@ -import axios from 'axios' - -const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080' - -const request = axios.create({ - baseURL, - timeout: 10000 -}) - -request.interceptors.response.use( - response => response.data, - error => { - console.error('API Error:', error) - return Promise.reject(error) - } -) - -export interface Tenant { - id: number - tenantCode: string - tenantName: string - status: number - createdBy: number | null - createdTime: string - updatedBy: number | null - updatedTime: string | null - isDeleted: boolean - version: number -} - -export interface TenantRoute { - id: number - tenantCode: string - serviceName: string - clusterId: string - pathPattern: string - priority: number - status: number - isGlobal: boolean - createdBy: number | null - createdTime: string - updatedBy: number | null - updatedTime: string | null - isDeleted: boolean - version: number -} - -export interface ServiceInstance { - id: number - clusterId: string - destinationId: string - address: string - health: number - weight: number - status: number - createdBy: number | null - createdTime: string - updatedBy: number | null - updatedTime: string | null - isDeleted: boolean - version: number -} - -export const api = { - tenants: { - list: () => request.get('/api/gateway/tenants'), - create: (data: { tenantCode: string; tenantName: string }) => - request.post('/api/gateway/tenants', data), - delete: (id: number) => request.delete(`/api/gateway/tenants/${id}`), - getRoutes: (tenantCode: string) => - request.get(`/api/gateway/tenants/${tenantCode}/routes`), - createRoute: (tenantCode: string, data: { serviceName: string; pathPattern: string }) => - request.post(`/api/gateway/tenants/${tenantCode}/routes`, data), - deleteRoute: (id: number) => request.delete(`/api/gateway/routes/${id}`) - }, - routes: { - listGlobal: () => request.get('/api/gateway/routes/global'), - createGlobal: (data: { serviceName: string; clusterId: string; pathPattern: string }) => - request.post('/api/gateway/routes/global', data), - delete: (id: number) => request.delete(`/api/gateway/routes/${id}`) - }, - clusters: { - getInstances: (clusterId: string) => - request.get(`/api/gateway/clusters/${clusterId}/instances`), - addInstance: (clusterId: string, data: { destinationId: string; address: string; weight: number }) => - request.post(`/api/gateway/clusters/${clusterId}/instances`, data), - deleteInstance: (id: number) => request.delete(`/api/gateway/instances/${id}`) - }, - config: { - reload: () => request.post('/api/gateway/reload') - } -} diff --git a/src/YarpGateway.Admin/src/assets/vue.svg b/src/YarpGateway.Admin/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/src/YarpGateway.Admin/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/YarpGateway.Admin/src/components/HelloWorld.vue b/src/YarpGateway.Admin/src/components/HelloWorld.vue deleted file mode 100644 index b58e52b..0000000 --- a/src/YarpGateway.Admin/src/components/HelloWorld.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/components/Layout.vue b/src/YarpGateway.Admin/src/components/Layout.vue deleted file mode 100644 index 8ad8289..0000000 --- a/src/YarpGateway.Admin/src/components/Layout.vue +++ /dev/null @@ -1,132 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/main.ts b/src/YarpGateway.Admin/src/main.ts deleted file mode 100644 index b7402f4..0000000 --- a/src/YarpGateway.Admin/src/main.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createApp } from 'vue' -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import * as ElementPlusIconsVue from '@element-plus/icons-vue' -import { createPinia } from 'pinia' -import router from './router' -import App from './App.vue' -import './style.css' - -const app = createApp(App) - -for (const [key, component] of Object.entries(ElementPlusIconsVue)) { - app.component(key, component) -} - -app.use(createPinia()) -app.use(router) -app.use(ElementPlus) - -app.mount('#app') diff --git a/src/YarpGateway.Admin/src/router/index.ts b/src/YarpGateway.Admin/src/router/index.ts deleted file mode 100644 index 8769dd9..0000000 --- a/src/YarpGateway.Admin/src/router/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { createRouter, createWebHistory } from 'vue-router' -import type { RouteRecordRaw } from 'vue-router' - -const routes: RouteRecordRaw[] = [ - { - path: '/', - name: 'Layout', - component: () => import('../components/Layout.vue'), - redirect: '/tenants', - children: [ - { - path: 'tenants', - name: 'Tenants', - component: () => import('../views/TenantList.vue') - }, - { - path: 'tenants/:tenantCode/routes', - name: 'TenantRoutes', - component: () => import('../views/TenantRoutes.vue') - }, - { - path: 'routes/global', - name: 'GlobalRoutes', - component: () => import('../views/GlobalRoutes.vue') - }, - { - path: 'clusters/:clusterId/instances', - name: 'ClusterInstances', - component: () => import('../views/ClusterInstances.vue') - }, - { - path: 'dashboard', - name: 'Dashboard', - component: () => import('../views/Dashboard.vue') - } - ] - } -] - -const router = createRouter({ - history: createWebHistory(), - routes -}) - -export default router diff --git a/src/YarpGateway.Admin/src/stores/tenant.ts b/src/YarpGateway.Admin/src/stores/tenant.ts deleted file mode 100644 index d67656d..0000000 --- a/src/YarpGateway.Admin/src/stores/tenant.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import { api, type Tenant } from '../api' - -export const useTenantStore = defineStore('tenant', () => { - const tenants = ref([]) - const loading = ref(false) - - const tenantMap = computed(() => { - const map = new Map() - tenants.value.forEach(t => map.set(t.tenantCode, t)) - return map - }) - - async function loadTenants() { - loading.value = true - try { - tenants.value = await api.tenants.list() - } finally { - loading.value = false - } - } - - async function createTenant(data: { tenantCode: string; tenantName: string }) { - const tenant = await api.tenants.create(data) - tenants.value.push(tenant) - return tenant - } - - async function deleteTenant(id: number) { - await api.tenants.delete(id) - tenants.value = tenants.value.filter(t => t.id !== id) - } - - return { - tenants, - loading, - tenantMap, - loadTenants, - createTenant, - deleteTenant - } -}) diff --git a/src/YarpGateway.Admin/src/style.css b/src/YarpGateway.Admin/src/style.css deleted file mode 100644 index 4b243b2..0000000 --- a/src/YarpGateway.Admin/src/style.css +++ /dev/null @@ -1,23 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body { - margin: 0; - padding: 0; - width: 100%; - height: 100%; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -#app { - width: 100%; - height: 100vh; -} diff --git a/src/YarpGateway.Admin/src/views/ClusterInstances.vue b/src/YarpGateway.Admin/src/views/ClusterInstances.vue deleted file mode 100644 index 22f8ca9..0000000 --- a/src/YarpGateway.Admin/src/views/ClusterInstances.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/views/Dashboard.vue b/src/YarpGateway.Admin/src/views/Dashboard.vue deleted file mode 100644 index ab9b91c..0000000 --- a/src/YarpGateway.Admin/src/views/Dashboard.vue +++ /dev/null @@ -1,226 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/views/GlobalRoutes.vue b/src/YarpGateway.Admin/src/views/GlobalRoutes.vue deleted file mode 100644 index 3f3f909..0000000 --- a/src/YarpGateway.Admin/src/views/GlobalRoutes.vue +++ /dev/null @@ -1,128 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/views/TenantList.vue b/src/YarpGateway.Admin/src/views/TenantList.vue deleted file mode 100644 index f34438a..0000000 --- a/src/YarpGateway.Admin/src/views/TenantList.vue +++ /dev/null @@ -1,120 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/src/views/TenantRoutes.vue b/src/YarpGateway.Admin/src/views/TenantRoutes.vue deleted file mode 100644 index f9f8e02..0000000 --- a/src/YarpGateway.Admin/src/views/TenantRoutes.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - - - diff --git a/src/YarpGateway.Admin/tsconfig.app.json b/src/YarpGateway.Admin/tsconfig.app.json deleted file mode 100644 index 8d16e42..0000000 --- a/src/YarpGateway.Admin/tsconfig.app.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "@vue/tsconfig/tsconfig.dom.json", - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "types": ["vite/client"], - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/src/YarpGateway.Admin/tsconfig.json b/src/YarpGateway.Admin/tsconfig.json deleted file mode 100644 index 1ffef60..0000000 --- a/src/YarpGateway.Admin/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/src/YarpGateway.Admin/tsconfig.node.json b/src/YarpGateway.Admin/tsconfig.node.json deleted file mode 100644 index 8a67f62..0000000 --- a/src/YarpGateway.Admin/tsconfig.node.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", - "lib": ["ES2023"], - "module": "ESNext", - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/src/YarpGateway.Admin/vite.config.ts b/src/YarpGateway.Admin/vite.config.ts deleted file mode 100644 index 3f75bd1..0000000 --- a/src/YarpGateway.Admin/vite.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import path from 'path' - -export default defineConfig({ - plugins: [vue()], - resolve: { - alias: { - '@': path.resolve(__dirname, 'src') - } - }, - server: { - port: 5173, - proxy: { - '/api': { - target: 'http://localhost:8080', - changeOrigin: true - } - } - } -}) diff --git a/src/YarpGateway/Config/DatabaseClusterConfigProvider.cs b/src/YarpGateway/Config/DatabaseClusterConfigProvider.cs deleted file mode 100644 index 5fb53c4..0000000 --- a/src/YarpGateway/Config/DatabaseClusterConfigProvider.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Yarp.ReverseProxy.Configuration; -using Microsoft.EntityFrameworkCore; -using System.Collections.Concurrent; -using YarpGateway.Data; -using YarpGateway.Models; - -namespace YarpGateway.Config; - -public class DatabaseClusterConfigProvider -{ - private readonly IDbContextFactory _dbContextFactory; - private readonly ConcurrentDictionary _clusters = new(); - private readonly SemaphoreSlim _lock = new(1, 1); - private readonly ILogger _logger; - - public DatabaseClusterConfigProvider(IDbContextFactory dbContextFactory, ILogger logger) - { - _dbContextFactory = dbContextFactory; - _logger = logger; - _ = LoadConfigAsync(); - } - - public IReadOnlyList GetClusters() - { - return _clusters.Values.ToList().AsReadOnly(); - } - - public async Task ReloadAsync() - { - await _lock.WaitAsync(); - try - { - await LoadConfigInternalAsync(); - } - finally - { - _lock.Release(); - } - } - - private async Task LoadConfigAsync() - { - await LoadConfigInternalAsync(); - } - - private async Task LoadConfigInternalAsync() - { - await using var dbContext = _dbContextFactory.CreateDbContext(); - - var instances = await dbContext.ServiceInstances - .Where(i => i.Status == 1 && !i.IsDeleted) - .GroupBy(i => i.ClusterId) - .ToListAsync(); - - var newClusters = new ConcurrentDictionary(); - - foreach (var group in instances) - { - var destinations = new Dictionary(); - foreach (var instance in group) - { - destinations[instance.DestinationId] = new DestinationConfig - { - Address = instance.Address, - Metadata = new Dictionary - { - ["Weight"] = instance.Weight.ToString() - } - }; - } - - var config = new ClusterConfig - { - ClusterId = group.Key, - Destinations = destinations, - LoadBalancingPolicy = "DistributedWeightedRoundRobin", - HealthCheck = new HealthCheckConfig - { - Active = new ActiveHealthCheckConfig - { - Enabled = true, - Interval = TimeSpan.FromSeconds(30), - Timeout = TimeSpan.FromSeconds(5), - Path = "/health" - } - } - }; - newClusters[group.Key] = config; - } - - _clusters.Clear(); - foreach (var cluster in newClusters) - { - _clusters[cluster.Key] = cluster.Value; - } - - _logger.LogInformation("Loaded {Count} clusters from database", _clusters.Count); - } -} diff --git a/src/YarpGateway/Config/DatabaseRouteConfigProvider.cs b/src/YarpGateway/Config/DatabaseRouteConfigProvider.cs deleted file mode 100644 index f0e4b7d..0000000 --- a/src/YarpGateway/Config/DatabaseRouteConfigProvider.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Collections.Concurrent; -using Microsoft.EntityFrameworkCore; -using Yarp.ReverseProxy.Configuration; -using YarpGateway.Data; -using YarpGateway.Models; - -namespace YarpGateway.Config; - -public class DatabaseRouteConfigProvider -{ - private readonly IDbContextFactory _dbContextFactory; - private readonly ConcurrentDictionary _routes = new(); - private readonly SemaphoreSlim _lock = new(1, 1); - private readonly ILogger _logger; - - public DatabaseRouteConfigProvider( - IDbContextFactory dbContextFactory, - ILogger logger - ) - { - _dbContextFactory = dbContextFactory; - _logger = logger; - _ = LoadConfigAsync(); - } - - public IReadOnlyList GetRoutes() - { - return _routes.Values.ToList().AsReadOnly(); - } - - public async Task ReloadAsync() - { - await _lock.WaitAsync(); - try - { - await LoadConfigInternalAsync(); - } - finally - { - _lock.Release(); - } - } - - private async Task LoadConfigAsync() - { - await LoadConfigInternalAsync(); - } - - private async Task LoadConfigInternalAsync() - { - await using var dbContext = _dbContextFactory.CreateDbContext(); - - var routes = await dbContext - .TenantRoutes.Where(r => r.Status == 1 && !r.IsDeleted) - .ToListAsync(); - - var newRoutes = new ConcurrentDictionary(); - - foreach (var route in routes) - { - var config = new RouteConfig - { - RouteId = route.Id.ToString(), - ClusterId = route.ClusterId, - Match = new RouteMatch { Path = route.PathPattern }, - Metadata = new Dictionary - { - ["TenantCode"] = route.TenantCode, - ["ServiceName"] = route.ServiceName, - }, - }; - newRoutes[route.Id.ToString()] = config; - } - - _routes.Clear(); - foreach (var route in newRoutes) - { - _routes[route.Key] = route.Value; - } - - _logger.LogInformation("Loaded {Count} routes from database", _routes.Count); - } -} diff --git a/src/YarpGateway/Config/JwtConfig.cs b/src/YarpGateway/Config/JwtConfig.cs deleted file mode 100644 index bd7550f..0000000 --- a/src/YarpGateway/Config/JwtConfig.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YarpGateway.Config; - -public class JwtConfig -{ - public string Authority { get; set; } = string.Empty; - public string Audience { get; set; } = string.Empty; - public bool ValidateIssuer { get; set; } = true; - public bool ValidateAudience { get; set; } = true; -} diff --git a/src/YarpGateway/Config/RedisConfig.cs b/src/YarpGateway/Config/RedisConfig.cs deleted file mode 100644 index 28acc8a..0000000 --- a/src/YarpGateway/Config/RedisConfig.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace YarpGateway.Config; - -public class RedisConfig -{ - public string ConnectionString { get; set; } = "localhost:6379"; - public int Database { get; set; } = 0; - public string InstanceName { get; set; } = "YarpGateway"; -} diff --git a/src/YarpGateway/Controllers/GatewayConfigController.cs b/src/YarpGateway/Controllers/GatewayConfigController.cs deleted file mode 100644 index 5286f18..0000000 --- a/src/YarpGateway/Controllers/GatewayConfigController.cs +++ /dev/null @@ -1,272 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using YarpGateway.Data; -using YarpGateway.Config; -using YarpGateway.Models; -using YarpGateway.Services; -using Yarp.ReverseProxy.Configuration; - -namespace YarpGateway.Controllers; - -[ApiController] -[Route("api/gateway")] -public class GatewayConfigController : ControllerBase -{ - private readonly IDbContextFactory _dbContextFactory; - private readonly DatabaseRouteConfigProvider _routeProvider; - private readonly DatabaseClusterConfigProvider _clusterProvider; - private readonly IRouteCache _routeCache; - - public GatewayConfigController( - IDbContextFactory dbContextFactory, - DatabaseRouteConfigProvider routeProvider, - DatabaseClusterConfigProvider clusterProvider, - IRouteCache routeCache) - { - _dbContextFactory = dbContextFactory; - _routeProvider = routeProvider; - _clusterProvider = clusterProvider; - _routeCache = routeCache; - } - - [HttpGet("tenants")] - public async Task GetTenants() - { - await using var db = _dbContextFactory.CreateDbContext(); - var tenants = await db.Tenants - .Where(t => !t.IsDeleted) - .ToListAsync(); - return Ok(tenants); - } - - [HttpPost("tenants")] - public async Task CreateTenant([FromBody] CreateTenantDto dto) - { - await using var db = _dbContextFactory.CreateDbContext(); - var existing = await db.Tenants - .FirstOrDefaultAsync(t => t.TenantCode == dto.TenantCode); - if (existing != null) - { - return BadRequest($"Tenant code {dto.TenantCode} already exists"); - } - - var tenant = new GwTenant - { - Id = GenerateId(), - TenantCode = dto.TenantCode, - TenantName = dto.TenantName, - Status = 1 - }; - await db.Tenants.AddAsync(tenant); - await db.SaveChangesAsync(); - - return Ok(tenant); - } - - [HttpDelete("tenants/{id}")] - public async Task DeleteTenant(long id) - { - await using var db = _dbContextFactory.CreateDbContext(); - var tenant = await db.Tenants.FindAsync(id); - if (tenant == null) - return NotFound(); - - tenant.IsDeleted = true; - await db.SaveChangesAsync(); - - return Ok(); - } - - [HttpGet("tenants/{tenantCode}/routes")] - public async Task GetTenantRoutes(string tenantCode) - { - await using var db = _dbContextFactory.CreateDbContext(); - var routes = await db.TenantRoutes - .Where(r => r.TenantCode == tenantCode && !r.IsDeleted) - .ToListAsync(); - return Ok(routes); - } - - [HttpPost("tenants/{tenantCode}/routes")] - public async Task CreateTenantRoute(string tenantCode, [FromBody] CreateTenantRouteDto dto) - { - await using var db = _dbContextFactory.CreateDbContext(); - var tenant = await db.Tenants - .FirstOrDefaultAsync(t => t.TenantCode == tenantCode); - if (tenant == null) - return BadRequest($"Tenant {tenantCode} not found"); - - var clusterId = $"{tenantCode}-{dto.ServiceName}"; - var existing = await db.TenantRoutes - .FirstOrDefaultAsync(r => r.ClusterId == clusterId); - if (existing != null) - return BadRequest($"Route for {tenantCode}/{dto.ServiceName} already exists"); - - var route = new GwTenantRoute - { - Id = GenerateId(), - TenantCode = tenantCode, - ServiceName = dto.ServiceName, - ClusterId = clusterId, - PathPattern = dto.PathPattern, - Priority = 10, - Status = 1, - IsGlobal = false - }; - await db.TenantRoutes.AddAsync(route); - await db.SaveChangesAsync(); - - await _routeCache.ReloadAsync(); - - return Ok(route); - } - - [HttpGet("routes/global")] - public async Task GetGlobalRoutes() - { - await using var db = _dbContextFactory.CreateDbContext(); - var routes = await db.TenantRoutes - .Where(r => r.IsGlobal && !r.IsDeleted) - .ToListAsync(); - return Ok(routes); - } - - [HttpPost("routes/global")] - public async Task CreateGlobalRoute([FromBody] CreateGlobalRouteDto dto) - { - await using var db = _dbContextFactory.CreateDbContext(); - var existing = await db.TenantRoutes - .FirstOrDefaultAsync(r => r.ServiceName == dto.ServiceName && r.IsGlobal); - if (existing != null) - { - return BadRequest($"Global route for {dto.ServiceName} already exists"); - } - - var route = new GwTenantRoute - { - Id = GenerateId(), - TenantCode = string.Empty, - ServiceName = dto.ServiceName, - ClusterId = dto.ClusterId, - PathPattern = dto.PathPattern, - Priority = 0, - Status = 1, - IsGlobal = true - }; - await db.TenantRoutes.AddAsync(route); - await db.SaveChangesAsync(); - - await _routeCache.ReloadAsync(); - - return Ok(route); - } - - [HttpDelete("routes/{id}")] - public async Task DeleteRoute(long id) - { - await using var db = _dbContextFactory.CreateDbContext(); - var route = await db.TenantRoutes.FindAsync(id); - if (route == null) - return NotFound(); - - route.IsDeleted = true; - await db.SaveChangesAsync(); - - await _routeCache.ReloadAsync(); - - return Ok(); - } - - [HttpGet("clusters/{clusterId}/instances")] - public async Task GetInstances(string clusterId) - { - await using var db = _dbContextFactory.CreateDbContext(); - var instances = await db.ServiceInstances - .Where(i => i.ClusterId == clusterId && !i.IsDeleted) - .ToListAsync(); - return Ok(instances); - } - - [HttpPost("clusters/{clusterId}/instances")] - public async Task AddInstance(string clusterId, [FromBody] CreateInstanceDto dto) - { - await using var db = _dbContextFactory.CreateDbContext(); - var existing = await db.ServiceInstances - .FirstOrDefaultAsync(i => i.ClusterId == clusterId && i.DestinationId == dto.DestinationId); - if (existing != null) - return BadRequest($"Instance {dto.DestinationId} already exists in cluster {clusterId}"); - - var instance = new GwServiceInstance - { - Id = GenerateId(), - ClusterId = clusterId, - DestinationId = dto.DestinationId, - Address = dto.Address, - Weight = dto.Weight, - Health = 1, - Status = 1 - }; - await db.ServiceInstances.AddAsync(instance); - await db.SaveChangesAsync(); - - await _clusterProvider.ReloadAsync(); - - return Ok(instance); - } - - [HttpDelete("instances/{id}")] - public async Task DeleteInstance(long id) - { - await using var db = _dbContextFactory.CreateDbContext(); - var instance = await db.ServiceInstances.FindAsync(id); - if (instance == null) - return NotFound(); - - instance.IsDeleted = true; - await db.SaveChangesAsync(); - - await _clusterProvider.ReloadAsync(); - - return Ok(); - } - - [HttpPost("reload")] - public async Task ReloadConfig() - { - await _routeCache.ReloadAsync(); - await _routeProvider.ReloadAsync(); - await _clusterProvider.ReloadAsync(); - return Ok(new { message = "Config reloaded successfully" }); - } - - private long GenerateId() - { - return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - } - - public class CreateTenantDto - { - public string TenantCode { get; set; } = string.Empty; - public string TenantName { get; set; } = string.Empty; - } - - public class CreateTenantRouteDto - { - public string ServiceName { get; set; } = string.Empty; - public string PathPattern { get; set; } = string.Empty; - } - - public class CreateGlobalRouteDto - { - public string ServiceName { get; set; } = string.Empty; - public string ClusterId { get; set; } = string.Empty; - public string PathPattern { get; set; } = string.Empty; - } - - public class CreateInstanceDto - { - public string DestinationId { get; set; } = string.Empty; - public string Address { get; set; } = string.Empty; - public int Weight { get; set; } = 1; - } -} diff --git a/src/YarpGateway/Data/GatewayDbContext.cs b/src/YarpGateway/Data/GatewayDbContext.cs deleted file mode 100644 index a581d6f..0000000 --- a/src/YarpGateway/Data/GatewayDbContext.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using YarpGateway.Models; - -namespace YarpGateway.Data; - -public class GatewayDbContext : DbContext -{ - public GatewayDbContext(DbContextOptions options) - : base(options) - { - } - - public DbSet Tenants => Set(); - public DbSet TenantRoutes => Set(); - public DbSet ServiceInstances => Set(); - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.Property(e => e.TenantCode).HasMaxLength(50).IsRequired(); - entity.Property(e => e.TenantName).HasMaxLength(100).IsRequired(); - entity.HasIndex(e => e.TenantCode).IsUnique(); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.Property(e => e.TenantCode).HasMaxLength(50); - entity.Property(e => e.ServiceName).HasMaxLength(100).IsRequired(); - entity.Property(e => e.ClusterId).HasMaxLength(100).IsRequired(); - entity.Property(e => e.PathPattern).HasMaxLength(200).IsRequired(); - entity.HasIndex(e => e.TenantCode); - entity.HasIndex(e => e.ServiceName); - entity.HasIndex(e => e.ClusterId); - entity.HasIndex(e => new { e.ServiceName, e.IsGlobal, e.Status }); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.Property(e => e.ClusterId).HasMaxLength(100).IsRequired(); - entity.Property(e => e.DestinationId).HasMaxLength(100).IsRequired(); - entity.Property(e => e.Address).HasMaxLength(200).IsRequired(); - entity.HasIndex(e => new { e.ClusterId, e.DestinationId }).IsUnique(); - entity.HasIndex(e => e.Health); - }); - - base.OnModelCreating(modelBuilder); - } -} diff --git a/src/YarpGateway/Data/GatewayDbContextFactory.cs b/src/YarpGateway/Data/GatewayDbContextFactory.cs deleted file mode 100644 index 1dba2f2..0000000 --- a/src/YarpGateway/Data/GatewayDbContextFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Configuration; - -namespace YarpGateway.Data; - -public class GatewayDbContextFactory : IDesignTimeDbContextFactory -{ - public GatewayDbContext CreateDbContext(string[] args) - { - var configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false) - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - var connectionString = configuration.GetConnectionString("DefaultConnection"); - optionsBuilder.UseNpgsql(connectionString); - - return new GatewayDbContext(optionsBuilder.Options); - } -} diff --git a/src/YarpGateway/Dockerfile b/src/YarpGateway/Dockerfile deleted file mode 100644 index 1fca072..0000000 --- a/src/YarpGateway/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base -WORKDIR /app -EXPOSE 8080 - -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src -COPY . . -RUN dotnet restore -RUN dotnet publish -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -COPY --from=build /app/publish . -ENTRYPOINT ["dotnet", "YarpGateway.dll"] diff --git a/src/YarpGateway/DynamicProxy/DynamicProxyConfigProvider.cs b/src/YarpGateway/DynamicProxy/DynamicProxyConfigProvider.cs deleted file mode 100644 index 8cb68c7..0000000 --- a/src/YarpGateway/DynamicProxy/DynamicProxyConfigProvider.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.Extensions.Primitives; -using Yarp.ReverseProxy.Configuration; -using YarpGateway.Config; - -namespace YarpGateway.DynamicProxy; - -public class DynamicProxyConfigProvider : IProxyConfigProvider -{ - private volatile IProxyConfig _config; - private readonly DatabaseRouteConfigProvider _routeProvider; - private readonly DatabaseClusterConfigProvider _clusterProvider; - private readonly object _lock = new(); - - public DynamicProxyConfigProvider( - DatabaseRouteConfigProvider routeProvider, - DatabaseClusterConfigProvider clusterProvider) - { - _routeProvider = routeProvider; - _clusterProvider = clusterProvider; - UpdateConfig(); - } - - public IProxyConfig GetConfig() - { - return _config; - } - - public void UpdateConfig() - { - lock (_lock) - { - var routes = _routeProvider.GetRoutes(); - var clusters = _clusterProvider.GetClusters(); - - _config = new InMemoryProxyConfig( - routes, - clusters, - Array.Empty>() - ); - } - } - - public async Task ReloadAsync() - { - await _routeProvider.ReloadAsync(); - await _clusterProvider.ReloadAsync(); - UpdateConfig(); - } - - private class InMemoryProxyConfig : IProxyConfig - { - private static readonly CancellationChangeToken _nullChangeToken = new(new CancellationToken()); - - public InMemoryProxyConfig( - IReadOnlyList routes, - IReadOnlyList clusters, - IReadOnlyList> transforms) - { - Routes = routes; - Clusters = clusters; - Transforms = transforms; - } - - public IReadOnlyList Routes { get; } - public IReadOnlyList Clusters { get; } - public IReadOnlyList> Transforms { get; } - public IChangeToken ChangeToken => _nullChangeToken; - } -} diff --git a/src/YarpGateway/LoadBalancing/DistributedWeightedRoundRobinPolicy.cs b/src/YarpGateway/LoadBalancing/DistributedWeightedRoundRobinPolicy.cs deleted file mode 100644 index 9921ffa..0000000 --- a/src/YarpGateway/LoadBalancing/DistributedWeightedRoundRobinPolicy.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System.Text.Json; -using Microsoft.Extensions.Logging; -using StackExchange.Redis; -using Yarp.ReverseProxy.LoadBalancing; -using Yarp.ReverseProxy.Model; -using YarpGateway.Config; - -namespace YarpGateway.LoadBalancing; - -public class DistributedWeightedRoundRobinPolicy : ILoadBalancingPolicy -{ - private readonly IConnectionMultiplexer _redis; - private readonly RedisConfig _config; - private readonly ILogger _logger; - - public string Name => "DistributedWeightedRoundRobin"; - - public DistributedWeightedRoundRobinPolicy( - IConnectionMultiplexer redis, - RedisConfig config, - ILogger logger - ) - { - _redis = redis; - _config = config; - _logger = logger; - } - - public DestinationState? PickDestination( - HttpContext context, - ClusterState cluster, - IReadOnlyList availableDestinations - ) - { - if (availableDestinations.Count == 0) - return null; - - if (availableDestinations.Count == 1) - return availableDestinations[0]; - - var clusterId = cluster.ClusterId; - var db = _redis.GetDatabase(); - - var lockKey = $"lock:{_config.InstanceName}:{clusterId}"; - var stateKey = $"lb:{_config.InstanceName}:{clusterId}:state"; - - var lockValue = Guid.NewGuid().ToString(); - var lockAcquired = db.StringSet( - lockKey, - lockValue, - TimeSpan.FromMilliseconds(500), - When.NotExists - ); - - if (!lockAcquired) - { - _logger.LogDebug( - "Lock busy for cluster {Cluster}, using fallback selection", - clusterId - ); - return FallbackSelection(availableDestinations); - } - - try - { - var state = GetOrCreateLoadBalancingState(db, stateKey, availableDestinations); - - var selectedDestination = SelectByWeight(state, availableDestinations); - - UpdateCurrentWeights(db, stateKey, state, selectedDestination); - - _logger.LogDebug( - "Selected {Destination} for cluster {Cluster}", - selectedDestination?.DestinationId, - clusterId - ); - - return selectedDestination; - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error in distributed load balancing for cluster {Cluster}", - clusterId - ); - return availableDestinations[0]; - } - finally - { - var script = - @" - if redis.call('GET', KEYS[1]) == ARGV[1] then - return redis.call('DEL', KEYS[1]) - else - return 0 - end"; - db.ScriptEvaluate(script, new RedisKey[] { lockKey }, new RedisValue[] { lockValue }); - } - } - - private LoadBalancingState GetOrCreateLoadBalancingState( - IDatabase db, - string stateKey, - IReadOnlyList destinations - ) - { - var existingState = db.StringGet(stateKey); - - if (existingState.HasValue) - { - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - ; - var parsedState = (LoadBalancingState?) - System.Text.Json.JsonSerializer.Deserialize( - existingState.ToString(), - options - ); - var version = ComputeConfigHash(destinations); - - if ( - parsedState != null - && parsedState.ConfigHash == version - && parsedState.CurrentWeights != null - ) - { - return parsedState; - } - } - - var newState = new LoadBalancingState - { - ConfigHash = ComputeConfigHash(destinations), - CurrentWeights = new Dictionary(), - }; - - foreach (var dest in destinations) - { - var weight = GetWeight(dest); - newState.CurrentWeights[dest.DestinationId] = 0; - } - - var json = System.Text.Json.JsonSerializer.Serialize(newState); - db.StringSet(stateKey, json, TimeSpan.FromHours(1)); - - return newState; - } - - private long ComputeConfigHash(IReadOnlyList destinations) - { - var hash = 0L; - foreach (var dest in destinations.OrderBy(d => d.DestinationId)) - { - var weight = GetWeight(dest); - hash = HashCode.Combine(hash, dest.DestinationId.GetHashCode()); - hash = HashCode.Combine(hash, weight); - } - return hash; - } - - private void UpdateCurrentWeights( - IDatabase db, - string stateKey, - LoadBalancingState state, - DestinationState? selected - ) - { - if (selected == null) - return; - - var json = JsonSerializer.Serialize(state); - db.StringSet(stateKey, json, TimeSpan.FromHours(1)); - } - - private DestinationState? SelectByWeight( - LoadBalancingState state, - IReadOnlyList destinations - ) - { - int maxWeight = int.MinValue; - int totalWeight = 0; - DestinationState? selected = null; - - foreach (var dest in destinations) - { - if (!state.CurrentWeights.ContainsKey(dest.DestinationId)) - { - state.CurrentWeights[dest.DestinationId] = 0; - } - - var weight = GetWeight(dest); - var currentWeight = state.CurrentWeights[dest.DestinationId]; - - var newWeight = currentWeight + weight; - state.CurrentWeights[dest.DestinationId] = newWeight; - totalWeight += weight; - - if (newWeight > maxWeight) - { - maxWeight = newWeight; - selected = dest; - } - } - - if (selected != null) - { - state.CurrentWeights[selected.DestinationId] = maxWeight - totalWeight; - } - - return selected; - } - - private DestinationState? FallbackSelection(IReadOnlyList destinations) - { - var hash = ComputeRequestHash(); - var index = Math.Abs(hash % destinations.Count); - return destinations[index]; - } - - private int ComputeRequestHash() - { - var now = DateTime.UtcNow; - return HashCode.Combine(now.Second.GetHashCode(), now.Millisecond.GetHashCode()); - } - - private int GetWeight(DestinationState destination) - { - if ( - destination.Model?.Config?.Metadata?.TryGetValue("Weight", out var weightStr) == true - && int.TryParse(weightStr, out var weight) - ) - { - return weight; - } - return 1; - } - - private class LoadBalancingState - { - public long ConfigHash { get; set; } - public Dictionary CurrentWeights { get; set; } = new(); - } -} diff --git a/src/YarpGateway/Metrics/GatewayMetrics.cs b/src/YarpGateway/Metrics/GatewayMetrics.cs deleted file mode 100644 index 48af459..0000000 --- a/src/YarpGateway/Metrics/GatewayMetrics.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Diagnostics.Metrics; - -namespace YarpGateway.Metrics; - -public class GatewayMetrics -{ - private readonly Counter _requestsTotal; - private readonly Histogram _requestDuration; - - public GatewayMetrics(IMeterFactory meterFactory) - { - var meter = meterFactory.Create("fengling.gateway"); - _requestsTotal = meter.CreateCounter( - "gateway_requests_total", - "Total number of requests"); - _requestDuration = meter.CreateHistogram( - "gateway_request_duration_seconds", - "Request duration in seconds"); - } - - public void RecordRequest(string tenant, string service, int statusCode, double duration) - { - var tag = new KeyValuePair("tenant", tenant); - var tag2 = new KeyValuePair("service", service); - var tag3 = new KeyValuePair("status", statusCode.ToString()); - - _requestsTotal.Add(1, tag, tag2, tag3); - _requestDuration.Record(duration, tag, tag2); - } -} diff --git a/src/YarpGateway/Middleware/JwtTransformMiddleware.cs b/src/YarpGateway/Middleware/JwtTransformMiddleware.cs deleted file mode 100644 index 19998c2..0000000 --- a/src/YarpGateway/Middleware/JwtTransformMiddleware.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.Options; -using YarpGateway.Config; - -namespace YarpGateway.Middleware; - -public class JwtTransformMiddleware -{ - private readonly RequestDelegate _next; - private readonly JwtConfig _jwtConfig; - private readonly ILogger _logger; - - public JwtTransformMiddleware( - RequestDelegate next, - IOptions jwtConfig, - ILogger logger - ) - { - _next = next; - _jwtConfig = jwtConfig.Value; - _logger = logger; - } - - public async Task InvokeAsync(HttpContext context) - { - var authHeader = context.Request.Headers["Authorization"].FirstOrDefault(); - if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) - { - await _next(context); - return; - } - - var token = authHeader.Substring("Bearer ".Length).Trim(); - - try - { - var jwtHandler = new JwtSecurityTokenHandler(); - var jwtToken = jwtHandler.ReadJwtToken(token); - - var tenantId = jwtToken.Claims.FirstOrDefault(c => c.Type == "tenant")?.Value; - var userId = jwtToken - .Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier) - ?.Value; - var userName = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; - var roles = jwtToken - .Claims.Where(c => c.Type == ClaimTypes.Role) - .Select(c => c.Value) - .ToList(); - - if (!string.IsNullOrEmpty(tenantId)) - { - context.Request.Headers["X-Tenant-Id"] = tenantId; - - if (!string.IsNullOrEmpty(userId)) - context.Request.Headers["X-User-Id"] = userId; - - if (!string.IsNullOrEmpty(userName)) - context.Request.Headers["X-User-Name"] = userName; - - if (roles.Any()) - context.Request.Headers["X-Roles"] = string.Join(",", roles); - - _logger.LogInformation( - "JWT transformed - Tenant: {Tenant}, User: {User}", - tenantId, - userId - ); - } - else - { - _logger.LogWarning("JWT missing tenant claim"); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to parse JWT token"); - } - - await _next(context); - } -} diff --git a/src/YarpGateway/Middleware/TenantRoutingMiddleware.cs b/src/YarpGateway/Middleware/TenantRoutingMiddleware.cs deleted file mode 100644 index ab0301d..0000000 --- a/src/YarpGateway/Middleware/TenantRoutingMiddleware.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.Options; -using System.Text.RegularExpressions; -using YarpGateway.Services; - -namespace YarpGateway.Middleware; - -public class TenantRoutingMiddleware -{ - private readonly RequestDelegate _next; - private readonly IRouteCache _routeCache; - private readonly ILogger _logger; - - public TenantRoutingMiddleware( - RequestDelegate next, - IRouteCache routeCache, - ILogger logger) - { - _next = next; - _routeCache = routeCache; - _logger = logger; - } - - public async Task InvokeAsync(HttpContext context) - { - var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault(); - if (string.IsNullOrEmpty(tenantId)) - { - await _next(context); - return; - } - - var path = context.Request.Path.Value ?? string.Empty; - var serviceName = ExtractServiceName(path); - - if (string.IsNullOrEmpty(serviceName)) - { - await _next(context); - return; - } - - var route = _routeCache.GetRoute(tenantId, serviceName); - if (route == null) - { - _logger.LogWarning("Route not found - Tenant: {Tenant}, Service: {Service}", tenantId, serviceName); - await _next(context); - return; - } - - context.Items["DynamicClusterId"] = route.ClusterId; - - var routeType = route.IsGlobal ? "global" : "tenant-specific"; - _logger.LogInformation("Tenant routing - Tenant: {Tenant}, Service: {Service}, Cluster: {Cluster}, Type: {Type}", - tenantId, serviceName, route.ClusterId, routeType); - - await _next(context); - } - - private string ExtractServiceName(string path) - { - var match = Regex.Match(path, @"/api/(\w+)/?"); - return match.Success ? match.Groups[1].Value : string.Empty; - } -} diff --git a/src/YarpGateway/Migrations/20260201120312_InitialCreate.Designer.cs b/src/YarpGateway/Migrations/20260201120312_InitialCreate.Designer.cs deleted file mode 100644 index 3f76f9c..0000000 --- a/src/YarpGateway/Migrations/20260201120312_InitialCreate.Designer.cs +++ /dev/null @@ -1,209 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using YarpGateway.Data; - -#nullable disable - -namespace YarpGateway.Migrations -{ - [DbContext(typeof(GatewayDbContext))] - [Migration("20260201120312_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("YarpGateway.Models.GwServiceInstance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClusterId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("DestinationId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Health") - .HasColumnType("integer"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Health"); - - b.HasIndex("ClusterId", "DestinationId") - .IsUnique(); - - b.ToTable("ServiceInstances"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantCode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TenantCode") - .IsUnique(); - - b.ToTable("Tenants"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenantRoute", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClusterId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("PathPattern") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ServiceName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantCode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ClusterId"); - - b.HasIndex("TenantCode", "ServiceName") - .IsUnique(); - - b.ToTable("TenantRoutes"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenantRoute", b => - { - b.HasOne("YarpGateway.Models.GwTenant", null) - .WithMany() - .HasForeignKey("TenantCode") - .HasPrincipalKey("TenantCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/YarpGateway/Migrations/20260201120312_InitialCreate.cs b/src/YarpGateway/Migrations/20260201120312_InitialCreate.cs deleted file mode 100644 index 870368f..0000000 --- a/src/YarpGateway/Migrations/20260201120312_InitialCreate.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace YarpGateway.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ServiceInstances", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ClusterId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - DestinationId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - Address = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Health = table.Column(type: "integer", nullable: false), - Weight = table.Column(type: "integer", nullable: false), - Status = table.Column(type: "integer", nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: true), - CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedBy = table.Column(type: "bigint", nullable: true), - UpdatedTime = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false), - Version = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ServiceInstances", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Tenants", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - TenantCode = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - TenantName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - Status = table.Column(type: "integer", nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: true), - CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedBy = table.Column(type: "bigint", nullable: true), - UpdatedTime = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false), - Version = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tenants", x => x.Id); - table.UniqueConstraint("AK_Tenants_TenantCode", x => x.TenantCode); - }); - - migrationBuilder.CreateTable( - name: "TenantRoutes", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - TenantCode = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - ServiceName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - ClusterId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - PathPattern = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Priority = table.Column(type: "integer", nullable: false), - Status = table.Column(type: "integer", nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: true), - CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedBy = table.Column(type: "bigint", nullable: true), - UpdatedTime = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false), - Version = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TenantRoutes", x => x.Id); - table.ForeignKey( - name: "FK_TenantRoutes_Tenants_TenantCode", - column: x => x.TenantCode, - principalTable: "Tenants", - principalColumn: "TenantCode", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_ServiceInstances_ClusterId_DestinationId", - table: "ServiceInstances", - columns: new[] { "ClusterId", "DestinationId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ServiceInstances_Health", - table: "ServiceInstances", - column: "Health"); - - migrationBuilder.CreateIndex( - name: "IX_TenantRoutes_ClusterId", - table: "TenantRoutes", - column: "ClusterId"); - - migrationBuilder.CreateIndex( - name: "IX_TenantRoutes_TenantCode_ServiceName", - table: "TenantRoutes", - columns: new[] { "TenantCode", "ServiceName" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Tenants_TenantCode", - table: "Tenants", - column: "TenantCode", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ServiceInstances"); - - migrationBuilder.DropTable( - name: "TenantRoutes"); - - migrationBuilder.DropTable( - name: "Tenants"); - } - } -} diff --git a/src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.Designer.cs b/src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.Designer.cs deleted file mode 100644 index 1466a7c..0000000 --- a/src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.Designer.cs +++ /dev/null @@ -1,205 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using YarpGateway.Data; - -#nullable disable - -namespace YarpGateway.Migrations -{ - [DbContext(typeof(GatewayDbContext))] - [Migration("20260201133826_AddIsGlobalToTenantRoute")] - partial class AddIsGlobalToTenantRoute - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("YarpGateway.Models.GwServiceInstance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClusterId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("DestinationId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Health") - .HasColumnType("integer"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Health"); - - b.HasIndex("ClusterId", "DestinationId") - .IsUnique(); - - b.ToTable("ServiceInstances"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantCode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TenantCode") - .IsUnique(); - - b.ToTable("Tenants"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenantRoute", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClusterId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("IsGlobal") - .HasColumnType("boolean"); - - b.Property("PathPattern") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ServiceName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantCode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ClusterId"); - - b.HasIndex("ServiceName"); - - b.HasIndex("TenantCode"); - - b.HasIndex("ServiceName", "IsGlobal", "Status"); - - b.ToTable("TenantRoutes"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.cs b/src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.cs deleted file mode 100644 index b2959df..0000000 --- a/src/YarpGateway/Migrations/20260201133826_AddIsGlobalToTenantRoute.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace YarpGateway.Migrations -{ - /// - public partial class AddIsGlobalToTenantRoute : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_TenantRoutes_Tenants_TenantCode", - table: "TenantRoutes"); - - migrationBuilder.DropUniqueConstraint( - name: "AK_Tenants_TenantCode", - table: "Tenants"); - - migrationBuilder.DropIndex( - name: "IX_TenantRoutes_TenantCode_ServiceName", - table: "TenantRoutes"); - - migrationBuilder.AddColumn( - name: "IsGlobal", - table: "TenantRoutes", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateIndex( - name: "IX_TenantRoutes_ServiceName", - table: "TenantRoutes", - column: "ServiceName"); - - migrationBuilder.CreateIndex( - name: "IX_TenantRoutes_ServiceName_IsGlobal_Status", - table: "TenantRoutes", - columns: new[] { "ServiceName", "IsGlobal", "Status" }); - - migrationBuilder.CreateIndex( - name: "IX_TenantRoutes_TenantCode", - table: "TenantRoutes", - column: "TenantCode"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_TenantRoutes_ServiceName", - table: "TenantRoutes"); - - migrationBuilder.DropIndex( - name: "IX_TenantRoutes_ServiceName_IsGlobal_Status", - table: "TenantRoutes"); - - migrationBuilder.DropIndex( - name: "IX_TenantRoutes_TenantCode", - table: "TenantRoutes"); - - migrationBuilder.DropColumn( - name: "IsGlobal", - table: "TenantRoutes"); - - migrationBuilder.AddUniqueConstraint( - name: "AK_Tenants_TenantCode", - table: "Tenants", - column: "TenantCode"); - - migrationBuilder.CreateIndex( - name: "IX_TenantRoutes_TenantCode_ServiceName", - table: "TenantRoutes", - columns: new[] { "TenantCode", "ServiceName" }, - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_TenantRoutes_Tenants_TenantCode", - table: "TenantRoutes", - column: "TenantCode", - principalTable: "Tenants", - principalColumn: "TenantCode", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/YarpGateway/Migrations/GatewayDbContextModelSnapshot.cs b/src/YarpGateway/Migrations/GatewayDbContextModelSnapshot.cs deleted file mode 100644 index 9ff7b99..0000000 --- a/src/YarpGateway/Migrations/GatewayDbContextModelSnapshot.cs +++ /dev/null @@ -1,202 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using YarpGateway.Data; - -#nullable disable - -namespace YarpGateway.Migrations -{ - [DbContext(typeof(GatewayDbContext))] - partial class GatewayDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("YarpGateway.Models.GwServiceInstance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClusterId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("DestinationId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Health") - .HasColumnType("integer"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Health"); - - b.HasIndex("ClusterId", "DestinationId") - .IsUnique(); - - b.ToTable("ServiceInstances"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantCode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TenantCode") - .IsUnique(); - - b.ToTable("Tenants"); - }); - - modelBuilder.Entity("YarpGateway.Models.GwTenantRoute", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClusterId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("IsDeleted") - .HasColumnType("boolean"); - - b.Property("IsGlobal") - .HasColumnType("boolean"); - - b.Property("PathPattern") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ServiceName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantCode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedBy") - .HasColumnType("bigint"); - - b.Property("UpdatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ClusterId"); - - b.HasIndex("ServiceName"); - - b.HasIndex("TenantCode"); - - b.HasIndex("ServiceName", "IsGlobal", "Status"); - - b.ToTable("TenantRoutes"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/YarpGateway/Migrations/script.sql b/src/YarpGateway/Migrations/script.sql deleted file mode 100644 index 9a8b36d..0000000 --- a/src/YarpGateway/Migrations/script.sql +++ /dev/null @@ -1,89 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -START TRANSACTION; -CREATE TABLE "ServiceInstances" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "ClusterId" character varying(100) NOT NULL, - "DestinationId" character varying(100) NOT NULL, - "Address" character varying(200) NOT NULL, - "Health" integer NOT NULL, - "Weight" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_ServiceInstances" PRIMARY KEY ("Id") -); - -CREATE TABLE "Tenants" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "TenantName" character varying(100) NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_Tenants" PRIMARY KEY ("Id"), - CONSTRAINT "AK_Tenants_TenantCode" UNIQUE ("TenantCode") -); - -CREATE TABLE "TenantRoutes" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "ServiceName" character varying(100) NOT NULL, - "ClusterId" character varying(100) NOT NULL, - "PathPattern" character varying(200) NOT NULL, - "Priority" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_TenantRoutes" PRIMARY KEY ("Id"), - CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode" FOREIGN KEY ("TenantCode") REFERENCES "Tenants" ("TenantCode") ON DELETE RESTRICT -); - -CREATE UNIQUE INDEX "IX_ServiceInstances_ClusterId_DestinationId" ON "ServiceInstances" ("ClusterId", "DestinationId"); - -CREATE INDEX "IX_ServiceInstances_Health" ON "ServiceInstances" ("Health"); - -CREATE INDEX "IX_TenantRoutes_ClusterId" ON "TenantRoutes" ("ClusterId"); - -CREATE UNIQUE INDEX "IX_TenantRoutes_TenantCode_ServiceName" ON "TenantRoutes" ("TenantCode", "ServiceName"); - -CREATE UNIQUE INDEX "IX_Tenants_TenantCode" ON "Tenants" ("TenantCode"); - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260201120312_InitialCreate', '9.0.0'); - -ALTER TABLE "TenantRoutes" DROP CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode"; - -ALTER TABLE "Tenants" DROP CONSTRAINT "AK_Tenants_TenantCode"; - -DROP INDEX "IX_TenantRoutes_TenantCode_ServiceName"; - -ALTER TABLE "TenantRoutes" ADD "IsGlobal" boolean NOT NULL DEFAULT FALSE; - -CREATE INDEX "IX_TenantRoutes_ServiceName" ON "TenantRoutes" ("ServiceName"); - -CREATE INDEX "IX_TenantRoutes_ServiceName_IsGlobal_Status" ON "TenantRoutes" ("ServiceName", "IsGlobal", "Status"); - -CREATE INDEX "IX_TenantRoutes_TenantCode" ON "TenantRoutes" ("TenantCode"); - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260201133826_AddIsGlobalToTenantRoute', '9.0.0'); - -COMMIT; - diff --git a/src/YarpGateway/Models/GwServiceInstance.cs b/src/YarpGateway/Models/GwServiceInstance.cs deleted file mode 100644 index d6a7a79..0000000 --- a/src/YarpGateway/Models/GwServiceInstance.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace YarpGateway.Models; - -public class GwServiceInstance -{ - public long Id { get; set; } - public string ClusterId { get; set; } = string.Empty; - public string DestinationId { get; set; } = string.Empty; - public string Address { get; set; } = string.Empty; - public int Health { get; set; } = 1; - public int Weight { get; set; } = 1; - public int Status { get; set; } = 1; - public long? CreatedBy { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public long? UpdatedBy { get; set; } - public DateTime? UpdatedTime { get; set; } - public bool IsDeleted { get; set; } = false; - public int Version { get; set; } = 0; -} diff --git a/src/YarpGateway/Models/GwTenant.cs b/src/YarpGateway/Models/GwTenant.cs deleted file mode 100644 index 7ce5997..0000000 --- a/src/YarpGateway/Models/GwTenant.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace YarpGateway.Models; - -public class GwTenant -{ - public long Id { get; set; } - public string TenantCode { get; set; } = string.Empty; - public string TenantName { get; set; } = string.Empty; - public int Status { get; set; } = 1; - public long? CreatedBy { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public long? UpdatedBy { get; set; } - public DateTime? UpdatedTime { get; set; } - public bool IsDeleted { get; set; } = false; - public int Version { get; set; } = 0; -} diff --git a/src/YarpGateway/Models/GwTenantRoute.cs b/src/YarpGateway/Models/GwTenantRoute.cs deleted file mode 100644 index 6a6a918..0000000 --- a/src/YarpGateway/Models/GwTenantRoute.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace YarpGateway.Models; - -public class GwTenantRoute -{ - public long Id { get; set; } - public string TenantCode { get; set; } = string.Empty; - public string ServiceName { get; set; } = string.Empty; - public string ClusterId { get; set; } = string.Empty; - public string PathPattern { get; set; } = string.Empty; - public int Priority { get; set; } = 0; - public int Status { get; set; } = 1; - public bool IsGlobal { get; set; } = false; - public long? CreatedBy { get; set; } - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; - public long? UpdatedBy { get; set; } - public DateTime? UpdatedTime { get; set; } - public bool IsDeleted { get; set; } = false; - public int Version { get; set; } = 0; -} diff --git a/src/YarpGateway/Program.cs b/src/YarpGateway/Program.cs deleted file mode 100644 index dd2983a..0000000 --- a/src/YarpGateway/Program.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Serilog; -using Yarp.ReverseProxy.Configuration; -using Yarp.ReverseProxy.LoadBalancing; -using YarpGateway.Config; -using YarpGateway.Data; -using YarpGateway.DynamicProxy; -using YarpGateway.LoadBalancing; -using YarpGateway.Middleware; -using YarpGateway.Services; -using StackExchange.Redis; - -var builder = WebApplication.CreateBuilder(args); - -builder.Host.UseSerilog( - (context, services, configuration) => - configuration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext() -); - -builder.Services.Configure(builder.Configuration.GetSection("Jwt")); -builder.Services.Configure(builder.Configuration.GetSection("Redis")); -builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); - -builder.Services.AddDbContextFactory(options => - options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")) -); - -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); - -builder.Services.AddSingleton(); -builder.Services.AddSingleton(sp => -{ - var config = sp.GetRequiredService(); - var connectionOptions = ConfigurationOptions.Parse(config.ConnectionString); - connectionOptions.AbortOnConnectFail = false; - connectionOptions.ConnectRetry = 3; - connectionOptions.ConnectTimeout = 5000; - connectionOptions.SyncTimeout = 3000; - connectionOptions.DefaultDatabase = config.Database; - - var connection = ConnectionMultiplexer.Connect(connectionOptions); - connection.ConnectionFailed += (sender, e) => - { - Serilog.Log.Error(e.Exception, "Redis connection failed"); - }; - connection.ConnectionRestored += (sender, e) => - { - Serilog.Log.Information("Redis connection restored"); - }; - - return connection; -}); - -builder.Services.AddSingleton(); - -builder.Services.AddSingleton(); -builder.Services.AddSingleton(sp => sp.GetRequiredService()); - -var corsSettings = builder.Configuration.GetSection("Cors"); -builder.Services.AddCors(options => -{ - var allowAnyOrigin = corsSettings.GetValue("AllowAnyOrigin"); - var allowedOrigins = corsSettings.GetSection("AllowedOrigins").Get() ?? Array.Empty(); - - options.AddPolicy("AllowFrontend", policy => - { - if (allowAnyOrigin) - { - policy.AllowAnyOrigin(); - } - else - { - policy.WithOrigins(allowedOrigins); - } - - policy.AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials(); - }); -}); - -builder.Services.AddControllers(); - -var app = builder.Build(); - -app.UseCors("AllowFrontend"); -app.UseMiddleware(); -app.UseMiddleware(); - -app.MapControllers(); -app.MapReverseProxy(); - -await app.Services.GetRequiredService().InitializeAsync(); - -try -{ - Log.Information("Starting YARP Gateway"); - app.Run(); -} -catch (Exception ex) -{ - Log.Fatal(ex, "Application terminated unexpectedly"); -} -finally -{ - Log.CloseAndFlush(); -} diff --git a/src/YarpGateway/Properties/launchSettings.json b/src/YarpGateway/Properties/launchSettings.json deleted file mode 100644 index 149e051..0000000 --- a/src/YarpGateway/Properties/launchSettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5046", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/YarpGateway/Services/RedisConnectionManager.cs b/src/YarpGateway/Services/RedisConnectionManager.cs deleted file mode 100644 index e393bc6..0000000 --- a/src/YarpGateway/Services/RedisConnectionManager.cs +++ /dev/null @@ -1,138 +0,0 @@ -using StackExchange.Redis; -using System.Diagnostics; -using Microsoft.Extensions.Logging; -using YarpGateway.Config; - -namespace YarpGateway.Services; - -public interface IRedisConnectionManager -{ - IConnectionMultiplexer GetConnection(); - Task AcquireLockAsync(string key, TimeSpan? expiry = null); - Task ExecuteInLockAsync(string key, Func> func, TimeSpan? expiry = null); -} - -public class RedisConnectionManager : IRedisConnectionManager -{ - private readonly Lazy _lazyConnection; - private readonly RedisConfig _config; - private readonly ILogger _logger; - - public RedisConnectionManager(RedisConfig config, ILogger logger) - { - _config = config; - _logger = logger; - _lazyConnection = new Lazy(() => - { - var configuration = ConfigurationOptions.Parse(_config.ConnectionString); - configuration.AbortOnConnectFail = false; - configuration.ConnectRetry = 3; - configuration.ConnectTimeout = 5000; - configuration.SyncTimeout = 3000; - configuration.DefaultDatabase = _config.Database; - - var connection = ConnectionMultiplexer.Connect(configuration); - connection.ConnectionRestored += (sender, e) => - { - _logger.LogInformation("Redis connection restored"); - }; - connection.ConnectionFailed += (sender, e) => - { - _logger.LogError(e.Exception, "Redis connection failed"); - }; - - _logger.LogInformation("Connected to Redis at {ConnectionString}", _config.ConnectionString); - return connection; - }); - } - - public IConnectionMultiplexer GetConnection() - { - return _lazyConnection.Value; - } - - public async Task AcquireLockAsync(string key, TimeSpan? expiry = null) - { - var expiryTime = expiry ?? TimeSpan.FromSeconds(10); - var redis = GetConnection(); - var db = redis.GetDatabase(); - var lockKey = $"lock:{_config.InstanceName}:{key}"; - - var lockValue = Environment.MachineName + ":" + Process.GetCurrentProcess().Id; - var acquired = await db.StringSetAsync(lockKey, lockValue, expiryTime, When.NotExists); - - if (!acquired) - { - var backoff = TimeSpan.FromMilliseconds(100); - var retryCount = 0; - const int maxRetries = 50; - - while (!acquired && retryCount < maxRetries) - { - await Task.Delay(backoff); - acquired = await db.StringSetAsync(lockKey, lockValue, expiryTime, When.NotExists); - retryCount++; - if (retryCount < 10) - { - backoff = TimeSpan.FromMilliseconds(100 * (retryCount + 1)); - } - } - - if (!acquired) - { - throw new TimeoutException($"Failed to acquire lock for key: {lockKey}"); - } - } - - return new RedisLock(db, lockKey, lockValue, _logger); - } - - public async Task ExecuteInLockAsync(string key, Func> func, TimeSpan? expiry = null) - { - using var @lock = await AcquireLockAsync(key, expiry); - return await func(); - } - - private class RedisLock : IDisposable - { - private readonly IDatabase _db; - private readonly string _key; - private readonly string _value; - private readonly ILogger _logger; - private bool _disposed; - - public RedisLock(IDatabase db, string key, string value, ILogger logger) - { - _db = db; - _key = key; - _value = value; - _logger = logger; - } - - public void Dispose() - { - if (_disposed) return; - - try - { - var script = @" - if redis.call('GET', KEYS[1]) == ARGV[1] then - return redis.call('DEL', KEYS[1]) - else - return 0 - end"; - - _db.ScriptEvaluate(script, new RedisKey[] { _key }, new RedisValue[] { _value }); - _logger.LogDebug("Released lock for key: {Key}", _key); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to release lock for key: {Key}", _key); - } - finally - { - _disposed = true; - } - } - } -} diff --git a/src/YarpGateway/Services/RouteCache.cs b/src/YarpGateway/Services/RouteCache.cs deleted file mode 100644 index aa9a026..0000000 --- a/src/YarpGateway/Services/RouteCache.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Collections.Concurrent; -using YarpGateway.Models; -using YarpGateway.Data; -using Microsoft.Extensions.Logging; -using Microsoft.EntityFrameworkCore; - -namespace YarpGateway.Services; - -public class RouteInfo -{ - public long Id { get; set; } - public string ClusterId { get; set; } = string.Empty; - public string PathPattern { get; set; } = string.Empty; - public int Priority { get; set; } - public bool IsGlobal { get; set; } -} - -public interface IRouteCache -{ - Task InitializeAsync(); - Task ReloadAsync(); - RouteInfo? GetRoute(string tenantCode, string serviceName); - RouteInfo? GetRouteByPath(string path); -} - -public class RouteCache : IRouteCache -{ - private readonly IDbContextFactory _dbContextFactory; - private readonly ILogger _logger; - - private readonly ConcurrentDictionary _globalRoutes = new(); - private readonly ConcurrentDictionary> _tenantRoutes = new(); - private readonly ConcurrentDictionary _pathRoutes = new(); - private readonly ReaderWriterLockSlim _lock = new(); - - public RouteCache(IDbContextFactory dbContextFactory, ILogger logger) - { - _dbContextFactory = dbContextFactory; - _logger = logger; - } - - public async Task InitializeAsync() - { - _logger.LogInformation("Initializing route cache from database..."); - await LoadFromDatabaseAsync(); - _logger.LogInformation("Route cache initialized: {GlobalCount} global routes, {TenantCount} tenant routes", - _globalRoutes.Count, _tenantRoutes.Count); - } - - public async Task ReloadAsync() - { - _logger.LogInformation("Reloading route cache..."); - await LoadFromDatabaseAsync(); - _logger.LogInformation("Route cache reloaded"); - } - - public RouteInfo? GetRoute(string tenantCode, string serviceName) - { - _lock.EnterUpgradeableReadLock(); - try - { - // 1. 优先查找租户专用路由 - if (_tenantRoutes.TryGetValue(tenantCode, out var tenantRouteMap) && - tenantRouteMap.TryGetValue(serviceName, out var tenantRoute)) - { - _logger.LogDebug("Found tenant-specific route: {Tenant}/{Service} -> {Cluster}", - tenantCode, serviceName, tenantRoute.ClusterId); - return tenantRoute; - } - - // 2. 查找全局路由 - if (_globalRoutes.TryGetValue(serviceName, out var globalRoute)) - { - _logger.LogDebug("Found global route: {Service} -> {Cluster} for tenant {Tenant}", - serviceName, globalRoute.ClusterId, tenantCode); - return globalRoute; - } - - // 3. 没找到 - _logger.LogWarning("No route found for: {Tenant}/{Service}", tenantCode, serviceName); - return null; - } - finally - { - _lock.ExitUpgradeableReadLock(); - } - } - - public RouteInfo? GetRouteByPath(string path) - { - return _pathRoutes.TryGetValue(path, out var route) ? route : null; - } - - private async Task LoadFromDatabaseAsync() - { - using var db = _dbContextFactory.CreateDbContext(); - - var routes = await db.TenantRoutes - .Where(r => r.Status == 1 && !r.IsDeleted) - .ToListAsync(); - - _lock.EnterWriteLock(); - try - { - _globalRoutes.Clear(); - _tenantRoutes.Clear(); - _pathRoutes.Clear(); - - foreach (var route in routes) - { - var routeInfo = new RouteInfo - { - Id = route.Id, - ClusterId = route.ClusterId, - PathPattern = route.PathPattern, - Priority = route.Priority, - IsGlobal = route.IsGlobal - }; - - if (route.IsGlobal) - { - _globalRoutes[route.ServiceName] = routeInfo; - _pathRoutes[route.PathPattern] = routeInfo; - } - else if (!string.IsNullOrEmpty(route.TenantCode)) - { - _tenantRoutes.GetOrAdd(route.TenantCode, _ => new()) - [route.ServiceName] = routeInfo; - _pathRoutes[route.PathPattern] = routeInfo; - } - } - } - finally - { - _lock.ExitWriteLock(); - } - } -} diff --git a/src/YarpGateway/YarpGateway.csproj b/src/YarpGateway/YarpGateway.csproj deleted file mode 100644 index a1dcd34..0000000 --- a/src/YarpGateway/YarpGateway.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net10.0 - enable - enable - - - - - - runtime; build; native; contentfiles, analyzers; buildtransitive - all - - - - - - - - - - diff --git a/src/YarpGateway/appsettings.Development.json b/src/YarpGateway/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/src/YarpGateway/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/src/YarpGateway/appsettings.json b/src/YarpGateway/appsettings.json deleted file mode 100644 index 18088e8..0000000 --- a/src/YarpGateway/appsettings.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Yarp.ReverseProxy": "Information" - } - }, - "AllowedHosts": "*", - "Cors": { - "AllowedOrigins": [ - "http://localhost:5173", - "http://127.0.0.1:5173", - "http://localhost:5174" - ], - "AllowAnyOrigin": false - }, - "ConnectionStrings": { - "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_gateway;Username=movingsam;Password=sl52788542" - }, - "Jwt": { - "Authority": "https://your-auth-server.com", - "Audience": "fengling-gateway", - "ValidateIssuer": true, - "ValidateAudience": true - }, - "Redis": { - "ConnectionString": "192.168.100.10:6379", - "Database": 0, - "InstanceName": "YarpGateway" - }, - "ReverseProxy": { - "Routes": {}, - "Clusters": {} - }, - "Serilog": { - "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"], - "MinimumLevel": "Information", - "WriteTo": [ - { - "Name": "Console", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" - } - }, - { - "Name": "File", - "Args": { - "path": "logs/gateway-.log", - "rollingInterval": "Day", - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}" - } - } - ], - "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"] - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://0.0.0.0:8080" - } - } - } -} diff --git a/src/YarpGateway/logs/gateway-20260201.log b/src/YarpGateway/logs/gateway-20260201.log deleted file mode 100644 index 520548f..0000000 --- a/src/YarpGateway/logs/gateway-20260201.log +++ /dev/null @@ -1,1867 +0,0 @@ -2026-02-01 20:28:38.367 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 20:28:38.526 +08:00 [INF] Loading proxy data from config. -2026-02-01 20:28:38.538 +08:00 [INF] Starting YARP Gateway -2026-02-01 20:28:38.547 +08:00 [INF] Creating key {608f3e1e-2f33-4a15-9ede-21571a370d97} with creation date 2026-02-01 12:28:38Z, activation date 2026-02-01 12:28:38Z, and expiration date 2026-05-02 12:28:38Z. -2026-02-01 20:28:38.548 +08:00 [WRN] No XML encryptor configured. Key {608f3e1e-2f33-4a15-9ede-21571a370d97} may be persisted to storage in unencrypted form. -2026-02-01 20:28:38.549 +08:00 [INF] Writing data to file '/Users/movingsam/.aspnet/DataProtection-Keys/key-608f3e1e-2f33-4a15-9ede-21571a370d97.xml'. -2026-02-01 20:28:38.561 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 20:28:38.565 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 20:28:38.565 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 20:28:38.565 +08:00 [INF] Hosting environment: Development -2026-02-01 20:28:38.565 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 20:28:45.468 +08:00 [INF] Request starting HTTP/1.1 GET http://0.0.0.0:8080/ - null null -2026-02-01 20:28:45.491 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 20:28:45.492 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:28:45.493 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:28:45.494 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 20:28:45.498 +08:00 [INF] Request finished HTTP/1.1 GET http://0.0.0.0:8080/ - 503 0 null 30.4891ms -2026-02-01 20:28:49.620 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/ - null null -2026-02-01 20:28:49.622 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 20:28:49.623 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:28:49.623 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:28:49.623 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 20:28:49.624 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/ - 503 0 null 4.0712ms -2026-02-01 20:29:02.911 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/ - null null -2026-02-01 20:29:02.914 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 20:29:02.914 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:29:02.915 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:29:02.915 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 20:29:02.915 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/ - 503 0 null 3.9ms -2026-02-01 20:29:30.023 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:29:30.026 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:29:30.046 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:29:30.506 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 20:29:30.506 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:29:30.506 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 20:29:30.512 +08:00 [INF] Loaded 0 clusters from database -2026-02-01 20:29:30.512 +08:00 [INF] Loaded 0 routes from database -2026-02-01 20:29:30.518 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:29:30.527 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 480.0852ms -2026-02-01 20:29:30.527 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:29:30.528 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 505.1274ms -2026-02-01 20:29:30.748 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/favicon.ico - null null -2026-02-01 20:29:30.748 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 20:29:30.749 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:29:30.749 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:29:30.749 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 20:29:30.749 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/favicon.ico - 503 0 null 1.0967ms -2026-02-01 20:29:42.256 +08:00 [INF] Application is shutting down... -2026-02-01 20:49:15.886 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 20:49:16.066 +08:00 [INF] Loading proxy data from config. -2026-02-01 20:49:16.080 +08:00 [INF] Starting YARP Gateway -2026-02-01 20:49:16.102 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 20:49:16.105 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 20:49:16.106 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 20:49:16.106 +08:00 [INF] Hosting environment: Development -2026-02-01 20:49:16.106 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 20:49:41.074 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:49:41.087 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:49:41.105 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:49:41.639 +08:00 [INF] Executed DbCommand (11ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:49:41.639 +08:00 [INF] Executed DbCommand (11ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 20:49:41.645 +08:00 [INF] Loaded 0 clusters from database -2026-02-01 20:49:41.649 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:49:41.661 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 555.0812ms -2026-02-01 20:49:41.663 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:49:41.664 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 591.0773ms -2026-02-01 20:49:41.679 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 20:49:41.680 +08:00 [INF] Loaded 0 routes from database -2026-02-01 20:49:49.313 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:49:49.316 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:49:49.317 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:49:49.329 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:49:49.330 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:49:49.330 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 12.3257ms -2026-02-01 20:49:49.330 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:49:49.331 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 17.6625ms -2026-02-01 20:49:59.040 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:49:59.041 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:49:59.041 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:49:59.048 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:49:59.049 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:49:59.050 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 8.1249ms -2026-02-01 20:49:59.050 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:49:59.050 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 10.4396ms -2026-02-01 20:50:08.685 +08:00 [INF] Request starting HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:50:08.685 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 20:50:08.689 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:50:08.695 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 20:50:08.695 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 20:50:08.696 +08:00 [INF] Request finished HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/tenants - 503 0 null 11.3412ms -2026-02-01 20:51:44.258 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:51:44.263 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:51:44.264 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:51:44.280 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:51:44.281 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:51:44.281 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 17.172ms -2026-02-01 20:51:44.282 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:51:44.282 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 24.3958ms -2026-02-01 20:52:31.061 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:52:31.063 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:52:31.063 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:52:31.070 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:52:31.071 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:52:31.072 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 8.8603ms -2026-02-01 20:52:31.073 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:52:31.076 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 11.8326ms -2026-02-01 20:53:25.683 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:53:25.686 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:53:25.686 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:53:25.706 +08:00 [INF] Executed DbCommand (12ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:53:25.707 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:53:25.710 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 23.3473ms -2026-02-01 20:53:25.752 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:53:25.756 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 69.8792ms -2026-02-01 20:55:24.569 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:55:24.621 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:55:24.631 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:55:24.676 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:55:24.676 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:55:24.677 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 45.3671ms -2026-02-01 20:55:24.678 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:55:24.678 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 110.0931ms -2026-02-01 20:55:36.183 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:55:36.184 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:55:36.184 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:55:36.196 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:55:36.197 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:55:36.197 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 13.3423ms -2026-02-01 20:55:36.198 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:55:36.198 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 15.0201ms -2026-02-01 20:55:47.693 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:55:47.693 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:55:47.694 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:55:47.701 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:55:47.703 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:55:47.706 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 11.9807ms -2026-02-01 20:55:47.706 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:55:47.707 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 14.045ms -2026-02-01 20:56:32.634 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:56:32.635 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:56:32.635 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:56:32.642 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:56:32.643 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:56:32.643 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.646ms -2026-02-01 20:56:32.643 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:56:32.644 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.6731ms -2026-02-01 20:56:35.189 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:56:35.189 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:56:35.190 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:56:35.194 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:56:35.195 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:56:35.196 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.898ms -2026-02-01 20:56:35.196 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:56:35.196 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.9241ms -2026-02-01 20:56:58.530 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 20:56:58.533 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:56:58.534 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 20:56:58.555 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 20:56:58.558 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 20:56:58.558 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 23.7127ms -2026-02-01 20:56:58.558 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 20:56:58.558 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 28.0375ms -2026-02-01 21:00:23.871 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:00:23.872 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:00:23.872 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:00:23.887 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:00:23.888 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:00:23.889 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 16.3404ms -2026-02-01 21:00:23.893 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:00:23.894 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 24.0711ms -2026-02-01 21:02:01.116 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:02:01.118 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:01.120 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:01.162 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:02:01.163 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:02:01.163 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 43.0102ms -2026-02-01 21:02:01.163 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:01.163 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 48.3748ms -2026-02-01 21:02:02.971 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:02:02.972 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:02.972 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:02.979 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:02:02.980 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:02:02.980 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 8.03ms -2026-02-01 21:02:02.980 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:02.981 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.244ms -2026-02-01 21:02:06.331 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:02:06.331 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:06.332 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:06.338 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:02:06.339 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:02:06.340 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.8184ms -2026-02-01 21:02:06.340 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:06.340 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.4572ms -2026-02-01 21:02:11.767 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:02:11.767 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:11.768 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:11.774 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:02:11.775 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:02:11.775 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.011ms -2026-02-01 21:02:11.775 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:11.775 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 7.786ms -2026-02-01 21:02:13.215 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:02:13.216 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:13.216 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:13.221 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:02:13.222 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:02:13.222 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.6281ms -2026-02-01 21:02:13.222 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:13.222 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.8845ms -2026-02-01 21:02:14.142 +08:00 [INF] Request starting HTTP/1.1 POST http://localhost:8080/api/gateway/reload - null 0 -2026-02-01 21:02:14.143 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway)' -2026-02-01 21:02:14.154 +08:00 [INF] Route matched with {action = "ReloadConfig", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] ReloadConfig() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:14.166 +08:00 [INF] Executed DbCommand (9ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 21:02:14.167 +08:00 [INF] Loaded 0 routes from database -2026-02-01 21:02:14.174 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 21:02:14.174 +08:00 [INF] Loaded 0 clusters from database -2026-02-01 21:02:14.175 +08:00 [INF] Executing OkObjectResult, writing value of type '<>f__AnonymousType0`1[[System.String, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -2026-02-01 21:02:14.181 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway) in 27.4397ms -2026-02-01 21:02:14.181 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway)' -2026-02-01 21:02:14.182 +08:00 [INF] Request finished HTTP/1.1 POST http://localhost:8080/api/gateway/reload - 200 null application/json; charset=utf-8 39.1772ms -2026-02-01 21:02:20.366 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:02:20.366 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:20.366 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:02:20.371 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:02:20.372 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:02:20.374 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.9321ms -2026-02-01 21:02:20.375 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:02:20.375 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.3938ms -2026-02-01 21:03:51.216 +08:00 [INF] Application is shutting down... -2026-02-01 21:03:55.492 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 21:03:55.661 +08:00 [INF] Loading proxy data from config. -2026-02-01 21:03:55.672 +08:00 [INF] Starting YARP Gateway -2026-02-01 21:03:55.718 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 21:03:55.724 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 21:03:55.724 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 21:03:55.724 +08:00 [INF] Hosting environment: Development -2026-02-01 21:03:55.724 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 21:04:01.694 +08:00 [INF] Loading proxy data from config. -2026-02-01 21:04:42.773 +08:00 [INF] Application is shutting down... -2026-02-01 21:04:47.139 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 21:04:47.330 +08:00 [INF] Loading proxy data from config. -2026-02-01 21:04:47.346 +08:00 [INF] Starting YARP Gateway -2026-02-01 21:04:47.369 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 21:04:47.374 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 21:04:47.374 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 21:04:47.375 +08:00 [INF] Hosting environment: Development -2026-02-01 21:04:47.375 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 21:04:58.300 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:04:58.380 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:04:58.392 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:04:58.408 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:04:59.210 +08:00 [INF] Executed DbCommand (11ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 21:04:59.210 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 21:04:59.216 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:04:59.218 +08:00 [INF] Loaded 0 routes from database -2026-02-01 21:04:59.218 +08:00 [INF] Loaded 0 clusters from database -2026-02-01 21:04:59.221 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:04:59.239 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:04:59.240 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 829.6275ms -2026-02-01 21:04:59.240 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:04:59.241 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:04:59.241 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:04:59.242 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:04:59.243 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 947.8535ms -2026-02-01 21:04:59.247 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:04:59.247 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:04:59.248 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.222ms -2026-02-01 21:04:59.248 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:04:59.248 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 8.802ms -2026-02-01 21:04:59.331 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:04:59.332 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:04:59.333 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:04:59.333 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:04:59.339 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:04:59.342 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:04:59.342 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 8.9976ms -2026-02-01 21:04:59.343 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:04:59.343 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 12.6168ms -2026-02-01 21:05:08.512 +08:00 [INF] Request starting HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:05:08.512 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:08.513 +08:00 [INF] Request finished HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/tenants - 204 null null 1.3091ms -2026-02-01 21:05:08.515 +08:00 [INF] Request starting HTTP/1.1 POST http://localhost:8080/api/gateway/tenants - application/json 47 -2026-02-01 21:05:08.516 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:08.516 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.CreateTenant (YarpGateway)' -2026-02-01 21:05:08.529 +08:00 [INF] Route matched with {action = "CreateTenant", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] CreateTenant(CreateTenantDto) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:05:08.574 +08:00 [INF] Executed DbCommand (19ms) [Parameters=[@__dto_TenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE t."TenantCode" = @__dto_TenantCode_0 -LIMIT 1 -2026-02-01 21:05:08.640 +08:00 [INF] Executed DbCommand (11ms) [Parameters=[@p0='?' (DbType = Int64), @p1='?' (DbType = Int64), @p2='?' (DbType = DateTime), @p3='?' (DbType = Boolean), @p4='?' (DbType = Int32), @p5='?', @p6='?', @p7='?' (DbType = Int64), @p8='?' (DbType = DateTime), @p9='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30'] -INSERT INTO "Tenants" ("Id", "CreatedBy", "CreatedTime", "IsDeleted", "Status", "TenantCode", "TenantName", "UpdatedBy", "UpdatedTime", "Version") -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9); -2026-02-01 21:05:08.643 +08:00 [INF] Executing OkObjectResult, writing value of type 'YarpGateway.Models.GwTenant'. -2026-02-01 21:05:08.644 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.CreateTenant (YarpGateway) in 114.6718ms -2026-02-01 21:05:08.644 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.CreateTenant (YarpGateway)' -2026-02-01 21:05:08.644 +08:00 [INF] Request finished HTTP/1.1 POST http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 129.8694ms -2026-02-01 21:05:10.106 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:05:10.107 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:10.107 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:05:10.122 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:05:10.143 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:05:10.143 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:05:10.145 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 23.0227ms -2026-02-01 21:05:10.146 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:05:10.146 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 40.263ms -2026-02-01 21:05:53.153 +08:00 [INF] Request starting HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:05:53.154 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:53.154 +08:00 [INF] Request finished HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/tenants/1668/routes - 204 null null 0.8999ms -2026-02-01 21:05:53.156 +08:00 [INF] Request starting HTTP/1.1 POST http://localhost:8080/api/gateway/tenants/1668/routes - application/json 64 -2026-02-01 21:05:53.156 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:53.156 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.CreateRoute (YarpGateway)' -2026-02-01 21:05:53.164 +08:00 [INF] Route matched with {action = "CreateRoute", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] CreateRoute(System.String, CreateRouteDto) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:05:53.191 +08:00 [INF] Executed DbCommand (8ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE t."TenantCode" = @__tenantCode_0 -LIMIT 1 -2026-02-01 21:05:53.204 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."ClusterId" = @__clusterId_0 -LIMIT 1 -2026-02-01 21:05:53.244 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[@p0='?' (DbType = Int64), @p1='?', @p2='?' (DbType = Int64), @p3='?' (DbType = DateTime), @p4='?' (DbType = Boolean), @p5='?', @p6='?' (DbType = Int32), @p7='?', @p8='?' (DbType = Int32), @p9='?', @p10='?' (DbType = Int64), @p11='?' (DbType = DateTime), @p12='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30'] -INSERT INTO "TenantRoutes" ("Id", "ClusterId", "CreatedBy", "CreatedTime", "IsDeleted", "PathPattern", "Priority", "ServiceName", "Status", "TenantCode", "UpdatedBy", "UpdatedTime", "Version") -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12); -2026-02-01 21:05:53.251 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 21:05:53.252 +08:00 [INF] Loaded 1 routes from database -2026-02-01 21:05:53.252 +08:00 [INF] Executing OkObjectResult, writing value of type 'YarpGateway.Models.GwTenantRoute'. -2026-02-01 21:05:53.253 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.CreateRoute (YarpGateway) in 89.3866ms -2026-02-01 21:05:53.253 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.CreateRoute (YarpGateway)' -2026-02-01 21:05:53.253 +08:00 [INF] Request finished HTTP/1.1 POST http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 97.9256ms -2026-02-01 21:05:53.258 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:05:53.259 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:53.259 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:05:53.259 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:05:53.265 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:05:53.266 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:05:53.267 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 7.4562ms -2026-02-01 21:05:53.267 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:05:53.267 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 8.7162ms -2026-02-01 21:05:56.402 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:05:56.402 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:05:56.403 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:05:56.435 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:05:56.448 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:05:56.450 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:05:56.451 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 15.5198ms -2026-02-01 21:05:56.451 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:05:56.451 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 49.2768ms -2026-02-01 21:06:14.850 +08:00 [INF] Request starting HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:06:14.851 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:14.851 +08:00 [INF] Request finished HTTP/1.1 OPTIONS http://localhost:8080/api/gateway/clusters/1668-backend/instances - 204 null null 1.24ms -2026-02-01 21:06:14.853 +08:00 [INF] Request starting HTTP/1.1 POST http://localhost:8080/api/gateway/clusters/1668-backend/instances - application/json 73 -2026-02-01 21:06:14.853 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:14.853 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.AddInstance (YarpGateway)' -2026-02-01 21:06:14.860 +08:00 [INF] Route matched with {action = "AddInstance", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] AddInstance(System.String, CreateInstanceDto) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:14.876 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__clusterId_0='?', @__dto_DestinationId_1='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND s."DestinationId" = @__dto_DestinationId_1 -LIMIT 1 -2026-02-01 21:06:14.902 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[@p0='?' (DbType = Int64), @p1='?', @p2='?', @p3='?' (DbType = Int64), @p4='?' (DbType = DateTime), @p5='?', @p6='?' (DbType = Int32), @p7='?' (DbType = Boolean), @p8='?' (DbType = Int32), @p9='?' (DbType = Int64), @p10='?' (DbType = DateTime), @p11='?' (DbType = Int32), @p12='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30'] -INSERT INTO "ServiceInstances" ("Id", "Address", "ClusterId", "CreatedBy", "CreatedTime", "DestinationId", "Health", "IsDeleted", "Status", "UpdatedBy", "UpdatedTime", "Version", "Weight") -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12); -2026-02-01 21:06:14.908 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 21:06:14.909 +08:00 [INF] Loaded 1 clusters from database -2026-02-01 21:06:14.909 +08:00 [INF] Executing OkObjectResult, writing value of type 'YarpGateway.Models.GwServiceInstance'. -2026-02-01 21:06:14.910 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.AddInstance (YarpGateway) in 50.4462ms -2026-02-01 21:06:14.910 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.AddInstance (YarpGateway)' -2026-02-01 21:06:14.910 +08:00 [INF] Request finished HTTP/1.1 POST http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 57.6265ms -2026-02-01 21:06:14.914 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:06:14.914 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:14.914 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:14.915 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:14.918 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:06:14.919 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:14.919 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 4.0322ms -2026-02-01 21:06:14.919 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:14.919 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 4.7505ms -2026-02-01 21:06:17.150 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:06:17.150 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:17.151 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:17.151 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:17.156 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:06:17.161 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:17.162 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 10.6559ms -2026-02-01 21:06:17.162 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:17.163 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 12.4738ms -2026-02-01 21:06:19.111 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:06:19.111 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:19.111 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:19.111 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:19.117 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:06:19.120 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:19.120 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 8.9071ms -2026-02-01 21:06:19.121 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:19.121 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 9.9959ms -2026-02-01 21:06:20.175 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:06:20.175 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:20.175 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:20.175 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:20.189 +08:00 [INF] Executed DbCommand (11ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:06:20.189 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:20.190 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 14.274ms -2026-02-01 21:06:20.190 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:20.190 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 15.5078ms -2026-02-01 21:06:23.521 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:06:23.522 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:23.522 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:23.522 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:23.527 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:06:23.529 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:23.530 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.7563ms -2026-02-01 21:06:23.530 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:23.531 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.1482ms -2026-02-01 21:06:24.313 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:06:24.313 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:24.314 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:24.314 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:24.327 +08:00 [INF] Executed DbCommand (13ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:06:24.328 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:24.329 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 14.6627ms -2026-02-01 21:06:24.329 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:24.329 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 15.7096ms -2026-02-01 21:06:24.338 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:06:24.339 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:24.344 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:24.345 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:24.351 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:06:24.352 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:24.352 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 7.2453ms -2026-02-01 21:06:24.352 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:24.352 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 14.2265ms -2026-02-01 21:06:24.355 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:06:24.355 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:24.355 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:24.355 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:24.360 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:06:24.361 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:24.361 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 5.8417ms -2026-02-01 21:06:24.362 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:24.363 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 8.1657ms -2026-02-01 21:06:27.399 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:06:27.399 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:27.400 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:27.400 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:27.404 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:06:27.405 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:27.406 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.7784ms -2026-02-01 21:06:27.406 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:27.406 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 7.1823ms -2026-02-01 21:06:28.983 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:06:28.983 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:28.983 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:28.983 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:28.989 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:06:28.990 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:28.991 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.4782ms -2026-02-01 21:06:28.991 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:06:28.991 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 8.8219ms -2026-02-01 21:06:29.001 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:06:29.002 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:29.002 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:29.002 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:29.008 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:06:29.009 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:29.009 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 6.6596ms -2026-02-01 21:06:29.009 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:29.009 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 7.7495ms -2026-02-01 21:06:29.011 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:06:29.011 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:29.011 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:29.012 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:29.016 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:06:29.016 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:29.017 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 4.9486ms -2026-02-01 21:06:29.017 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:29.017 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 5.6986ms -2026-02-01 21:06:36.589 +08:00 [INF] Request starting HTTP/1.1 POST http://localhost:8080/api/gateway/reload - null 0 -2026-02-01 21:06:36.590 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:36.590 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway)' -2026-02-01 21:06:36.606 +08:00 [INF] Route matched with {action = "ReloadConfig", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] ReloadConfig() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:36.640 +08:00 [INF] Executed DbCommand (26ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 21:06:36.641 +08:00 [INF] Loaded 1 routes from database -2026-02-01 21:06:36.660 +08:00 [INF] Executed DbCommand (17ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 21:06:36.661 +08:00 [INF] Loaded 1 clusters from database -2026-02-01 21:06:36.661 +08:00 [INF] Executing OkObjectResult, writing value of type '<>f__AnonymousType0`1[[System.String, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -2026-02-01 21:06:36.668 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway) in 61.9397ms -2026-02-01 21:06:36.668 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway)' -2026-02-01 21:06:36.669 +08:00 [INF] Request finished HTTP/1.1 POST http://localhost:8080/api/gateway/reload - 200 null application/json; charset=utf-8 79.8832ms -2026-02-01 21:06:36.674 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:06:36.674 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:36.675 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:36.675 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:36.686 +08:00 [INF] Executed DbCommand (8ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:06:36.688 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:36.688 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 13.2311ms -2026-02-01 21:06:36.688 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:06:36.688 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 14.6415ms -2026-02-01 21:06:36.693 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:06:36.693 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:06:36.693 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:36.694 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:06:36.699 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:06:36.700 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:06:36.701 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 6.8118ms -2026-02-01 21:06:36.701 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:06:36.701 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 7.9798ms -2026-02-01 21:13:26.437 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:13:26.440 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:26.440 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:13:26.441 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:26.478 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:13:26.479 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:26.479 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 38.2422ms -2026-02-01 21:13:26.479 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:13:26.479 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 42.6309ms -2026-02-01 21:13:28.393 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:13:28.394 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:28.394 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:13:28.394 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:28.405 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:13:28.406 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:28.406 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 11.854ms -2026-02-01 21:13:28.407 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:13:28.407 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 13.359ms -2026-02-01 21:13:31.010 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:13:31.010 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:31.011 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:13:31.011 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:31.015 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:13:31.016 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:31.016 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.5402ms -2026-02-01 21:13:31.016 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:13:31.017 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.5055ms -2026-02-01 21:13:33.878 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:13:33.878 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:33.879 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:13:33.879 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:33.884 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:13:33.885 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:33.885 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 5.7937ms -2026-02-01 21:13:33.885 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:13:33.886 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 7.3867ms -2026-02-01 21:13:34.998 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:13:34.998 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:34.998 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:13:34.999 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:35.003 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:13:35.004 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:35.004 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 5.6441ms -2026-02-01 21:13:35.005 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:13:35.005 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 6.7441ms -2026-02-01 21:13:40.963 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:13:40.964 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:40.964 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:13:40.964 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:40.970 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:13:40.971 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:40.971 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 6.8734ms -2026-02-01 21:13:40.971 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:13:40.972 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 8.4311ms -2026-02-01 21:13:41.818 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:13:41.818 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:13:41.819 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:13:41.819 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:13:41.824 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:13:41.825 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:13:41.825 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 6.3963ms -2026-02-01 21:13:41.826 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:13:41.826 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 8.1552ms -2026-02-01 21:14:14.883 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:14:14.884 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:14:14.884 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:14:14.884 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:14:14.891 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:14:14.892 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:14:14.893 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 8.6189ms -2026-02-01 21:14:14.893 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:14:14.894 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 10.1815ms -2026-02-01 21:14:14.901 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 21:14:14.902 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:14:14.902 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:14:14.903 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:14:14.908 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 21:14:14.909 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:14:14.909 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 6.047ms -2026-02-01 21:14:14.909 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 21:14:14.909 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 7.7971ms -2026-02-01 21:14:14.911 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 21:14:14.911 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:14:14.911 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:14:14.911 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:14:14.916 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 21:14:14.917 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:14:14.917 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 5.5654ms -2026-02-01 21:14:14.917 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 21:14:14.917 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 6.8468ms -2026-02-01 21:14:15.476 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 21:14:15.477 +08:00 [INF] CORS policy execution successful. -2026-02-01 21:14:15.477 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:14:15.477 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 21:14:15.482 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 21:14:15.483 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 21:14:15.483 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 6.0338ms -2026-02-01 21:14:15.483 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 21:14:15.484 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 7.183ms -2026-02-01 21:24:38.446 +08:00 [INF] Application is shutting down... -2026-02-01 22:03:24.445 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:03:24.605 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:03:48.070 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:03:48.232 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:04:34.173 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:04:34.333 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:04:34.365 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:04:34.801 +08:00 [ERR] Failed executing DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:04:34.809 +08:00 [ERR] An exception occurred while iterating over the results of a query for context type 'YarpGateway.Data.GatewayDbContext'. -Npgsql.PostgresException (0x80004005): 42703: column t.IsGlobal does not exist - -POSITION: 78 - at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) - at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) - at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() - Exception data: - Severity: ERROR - SqlState: 42703 - MessageText: column t.IsGlobal does not exist - Position: 78 - File: parse_relation.c - Line: 3822 - Routine: errorMissingColumn -Npgsql.PostgresException (0x80004005): 42703: column t.IsGlobal does not exist - -POSITION: 78 - at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) - at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) - at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() - Exception data: - Severity: ERROR - SqlState: 42703 - MessageText: column t.IsGlobal does not exist - Position: 78 - File: parse_relation.c - Line: 3822 - Routine: errorMissingColumn -2026-02-01 22:06:29.393 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:06:29.561 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:06:29.594 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:06:30.039 +08:00 [ERR] Failed executing DbCommand (11ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:06:30.047 +08:00 [ERR] An exception occurred while iterating over the results of a query for context type 'YarpGateway.Data.GatewayDbContext'. -Npgsql.PostgresException (0x80004005): 42703: column t.IsGlobal does not exist - -POSITION: 78 - at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) - at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) - at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() - Exception data: - Severity: ERROR - SqlState: 42703 - MessageText: column t.IsGlobal does not exist - Position: 78 - File: parse_relation.c - Line: 3822 - Routine: errorMissingColumn -Npgsql.PostgresException (0x80004005): 42703: column t.IsGlobal does not exist - -POSITION: 78 - at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) - at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken) - at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) - at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) - at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() - Exception data: - Severity: ERROR - SqlState: 42703 - MessageText: column t.IsGlobal does not exist - Position: 78 - File: parse_relation.c - Line: 3822 - Routine: errorMissingColumn -2026-02-01 22:06:49.708 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:06:49.873 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:06:49.897 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:06:50.321 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:06:50.359 +08:00 [INF] Route cache initialized: 0 global routes, 1 tenant routes -2026-02-01 22:06:50.359 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:06:50.376 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:06:50.377 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 22:06:50.377 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 22:06:50.377 +08:00 [INF] Hosting environment: Development -2026-02-01 22:06:50.377 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 22:06:58.544 +08:00 [INF] Application is shutting down... -2026-02-01 22:07:12.683 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:07:12.845 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:07:12.873 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:07:13.307 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:07:13.344 +08:00 [INF] Route cache initialized: 0 global routes, 1 tenant routes -2026-02-01 22:07:13.344 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:07:13.360 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:07:13.361 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 22:07:13.361 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 22:07:13.361 +08:00 [INF] Hosting environment: Development -2026-02-01 22:07:13.362 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 22:07:19.889 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/ - null null -2026-02-01 22:07:19.900 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 22:07:19.901 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:19.901 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:19.902 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 22:07:19.902 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/ - 503 0 null 14.2547ms -2026-02-01 22:07:23.258 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes - null null -2026-02-01 22:07:23.259 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 22:07:23.259 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:23.259 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:23.259 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 22:07:23.259 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes - 503 0 null 1.7208ms -2026-02-01 22:07:32.615 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - null null -2026-02-01 22:07:32.617 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 22:07:32.617 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:32.617 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:32.617 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 22:07:32.617 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - 503 0 null 1.5037ms -2026-02-01 22:07:34.387 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - null null -2026-02-01 22:07:34.387 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 22:07:34.387 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:34.387 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:07:34.387 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 22:07:34.387 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - 503 0 null 0.4997ms -2026-02-01 22:11:38.559 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/tenants - null null -2026-02-01 22:11:38.560 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:11:38.567 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:11:38.589 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:11:38.589 +08:00 [INF] Loaded 1 routes from database -2026-02-01 22:11:38.605 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:11:38.612 +08:00 [INF] Loaded 1 clusters from database -2026-02-01 22:11:38.632 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:11:38.640 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:11:38.651 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 82.7537ms -2026-02-01 22:11:38.651 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:11:38.651 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 91.9911ms -2026-02-01 22:11:43.971 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - null null -2026-02-01 22:11:43.971 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:11:43.974 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:11:43.980 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:11:43.980 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:11:43.982 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 8.1183ms -2026-02-01 22:11:43.982 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:11:43.982 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 11.0256ms -2026-02-01 22:11:46.387 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - null null -2026-02-01 22:11:46.387 +08:00 [INF] Executing endpoint 'catch-all-route' -2026-02-01 22:11:46.387 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:11:46.387 +08:00 [WRN] No available destinations after load balancing for cluster 'dynamic-cluster'. -2026-02-01 22:11:46.387 +08:00 [INF] Executed endpoint 'catch-all-route' -2026-02-01 22:11:46.387 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - 503 0 null 0.282ms -2026-02-01 22:12:03.947 +08:00 [INF] Loading proxy data from config. -2026-02-01 22:12:36.795 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - null null -2026-02-01 22:12:36.795 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - 404 0 null 0.4624ms -2026-02-01 22:12:36.795 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/gateway/routes, Response status code: 404 -2026-02-01 22:12:38.943 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - null null -2026-02-01 22:12:38.943 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes - 404 0 null 0.5529ms -2026-02-01 22:12:38.943 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/gateway/routes, Response status code: 404 -2026-02-01 22:12:48.629 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - null null -2026-02-01 22:12:48.630 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:12:48.630 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:12:48.634 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:12:48.635 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:12:48.635 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 4.8835ms -2026-02-01 22:12:48.635 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:12:48.635 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 5.3341ms -2026-02-01 22:13:08.032 +08:00 [INF] Request starting HTTP/1.1 POST http://127.0.0.1:8080/api/gateway/routes/global - application/json 93 -2026-02-01 22:13:08.032 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.CreateGlobalRoute (YarpGateway)' -2026-02-01 22:13:08.036 +08:00 [INF] Route matched with {action = "CreateGlobalRoute", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] CreateGlobalRoute(CreateGlobalRouteDto) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:13:08.055 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[@__dto_ServiceName_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."ServiceName" = @__dto_ServiceName_0 AND t."IsGlobal" -LIMIT 1 -2026-02-01 22:13:08.094 +08:00 [INF] Executed DbCommand (9ms) [Parameters=[@p0='?' (DbType = Int64), @p1='?', @p2='?' (DbType = Int64), @p3='?' (DbType = DateTime), @p4='?' (DbType = Boolean), @p5='?' (DbType = Boolean), @p6='?', @p7='?' (DbType = Int32), @p8='?', @p9='?' (DbType = Int32), @p10='?', @p11='?' (DbType = Int64), @p12='?' (DbType = DateTime), @p13='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30'] -INSERT INTO "TenantRoutes" ("Id", "ClusterId", "CreatedBy", "CreatedTime", "IsDeleted", "IsGlobal", "PathPattern", "Priority", "ServiceName", "Status", "TenantCode", "UpdatedBy", "UpdatedTime", "Version") -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13); -2026-02-01 22:13:08.097 +08:00 [INF] Reloading route cache... -2026-02-01 22:13:08.101 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:13:08.102 +08:00 [INF] Route cache reloaded -2026-02-01 22:13:08.102 +08:00 [INF] Executing OkObjectResult, writing value of type 'YarpGateway.Models.GwTenantRoute'. -2026-02-01 22:13:08.102 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.CreateGlobalRoute (YarpGateway) in 66.3013ms -2026-02-01 22:13:08.102 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.CreateGlobalRoute (YarpGateway)' -2026-02-01 22:13:08.102 +08:00 [INF] Request finished HTTP/1.1 POST http://127.0.0.1:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 70.5429ms -2026-02-01 22:13:10.304 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - null null -2026-02-01 22:13:10.304 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:13:10.304 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:13:10.308 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:13:10.308 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:13:10.309 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 4.2081ms -2026-02-01 22:13:10.309 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:13:10.309 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 4.4907ms -2026-02-01 22:13:51.464 +08:00 [INF] Request starting HTTP/1.1 POST http://127.0.0.1:8080/api/gateway/clusters/product-cluster/instances - application/json 79 -2026-02-01 22:13:51.465 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.AddInstance (YarpGateway)' -2026-02-01 22:13:51.467 +08:00 [INF] Route matched with {action = "AddInstance", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] AddInstance(System.String, CreateInstanceDto) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:13:51.476 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__clusterId_0='?', @__dto_DestinationId_1='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND s."DestinationId" = @__dto_DestinationId_1 -LIMIT 1 -2026-02-01 22:13:51.486 +08:00 [INF] Executed DbCommand (8ms) [Parameters=[@p0='?' (DbType = Int64), @p1='?', @p2='?', @p3='?' (DbType = Int64), @p4='?' (DbType = DateTime), @p5='?', @p6='?' (DbType = Int32), @p7='?' (DbType = Boolean), @p8='?' (DbType = Int32), @p9='?' (DbType = Int64), @p10='?' (DbType = DateTime), @p11='?' (DbType = Int32), @p12='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30'] -INSERT INTO "ServiceInstances" ("Id", "Address", "ClusterId", "CreatedBy", "CreatedTime", "DestinationId", "Health", "IsDeleted", "Status", "UpdatedBy", "UpdatedTime", "Version", "Weight") -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12); -2026-02-01 22:13:51.490 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:13:51.490 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:13:51.490 +08:00 [INF] Executing OkObjectResult, writing value of type 'YarpGateway.Models.GwServiceInstance'. -2026-02-01 22:13:51.491 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.AddInstance (YarpGateway) in 23.8754ms -2026-02-01 22:13:51.491 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.AddInstance (YarpGateway)' -2026-02-01 22:13:51.491 +08:00 [INF] Request finished HTTP/1.1 POST http://127.0.0.1:8080/api/gateway/clusters/product-cluster/instances - 200 null application/json; charset=utf-8 26.9521ms -2026-02-01 22:14:14.187 +08:00 [INF] Request starting HTTP/1.1 POST http://127.0.0.1:8080/api/gateway/reload - null null -2026-02-01 22:14:14.188 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway)' -2026-02-01 22:14:14.190 +08:00 [INF] Route matched with {action = "ReloadConfig", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] ReloadConfig() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:14:14.190 +08:00 [INF] Reloading route cache... -2026-02-01 22:14:14.194 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:14:14.194 +08:00 [INF] Route cache reloaded -2026-02-01 22:14:14.200 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:14:14.200 +08:00 [INF] Loaded 2 routes from database -2026-02-01 22:14:14.204 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:14:14.204 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:14:14.204 +08:00 [INF] Executing OkObjectResult, writing value of type '<>f__AnonymousType0`1[[System.String, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -2026-02-01 22:14:14.205 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway) in 15.273ms -2026-02-01 22:14:14.205 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.ReloadConfig (YarpGateway)' -2026-02-01 22:14:14.205 +08:00 [INF] Request finished HTTP/1.1 POST http://127.0.0.1:8080/api/gateway/reload - 200 null application/json; charset=utf-8 18.1485ms -2026-02-01 22:14:17.161 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - null null -2026-02-01 22:14:17.161 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - 404 0 null 0.1455ms -2026-02-01 22:14:17.161 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/product/test, Response status code: 404 -2026-02-01 22:14:32.651 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/clusters/product-cluster/instances - null null -2026-02-01 22:14:32.651 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:14:32.654 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:14:32.660 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:14:32.660 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:14:32.660 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 6.4256ms -2026-02-01 22:14:32.660 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:14:32.660 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/clusters/product-cluster/instances - 200 null application/json; charset=utf-8 9.4366ms -2026-02-01 22:16:32.786 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - null null -2026-02-01 22:16:32.786 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:16:32.786 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:16:32.791 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:16:32.791 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:16:32.791 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 5.1332ms -2026-02-01 22:16:32.791 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:16:32.792 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 5.8393ms -2026-02-01 22:16:35.327 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - null null -2026-02-01 22:16:35.327 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - 404 0 null 0.1327ms -2026-02-01 22:16:35.327 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/product/test, Response status code: 404 -2026-02-01 22:17:17.995 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - null null -2026-02-01 22:17:17.995 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - 404 0 null 0.7272ms -2026-02-01 22:17:17.996 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/product/test, Response status code: 404 -", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:16:37.617 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:16:37.618 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:16:37.654 +08:00 [INF] Loaded 2 routes from database -2026-02-01 22:16:37.654 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:16:37.656 +08:00 [INF] Route cache initialized: 1 global routes, 1 tenant routes -2026-02-01 22:16:37.656 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:16:37.670 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:16:37.672 +08:00 [ERR] Hosting failed to start -System.IO.IOException: Failed to bind to address http://0.0.0.0:8080: address already in use. - ---> Microsoft.AspNetCore.Connections.AddressInUseException: Address already in use - ---> System.Net.Sockets.SocketException (48): Address already in use - at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) - at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) - at System.Net.Sockets.Socket.Bind(EndPoint localEP) - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() ---- End of stack trace from previous location --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) - at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) - at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) -2026-02-01 22:17:21.898 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:17:22.466 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:17:22.492 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:17:22.492 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:17:22.498 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:17:22.529 +08:00 [INF] Loaded 2 routes from database -2026-02-01 22:17:22.529 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:17:22.531 +08:00 [INF] Route cache initialized: 1 global routes, 1 tenant routes -2026-02-01 22:17:22.531 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:17:22.545 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:17:22.547 +08:00 [ERR] Hosting failed to start -System.IO.IOException: Failed to bind to address http://0.0.0.0:8080: address already in use. - ---> Microsoft.AspNetCore.Connections.AddressInUseException: Address already in use - ---> System.Net.Sockets.SocketException (48): Address already in use - at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) - at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) - at System.Net.Sockets.Socket.Bind(EndPoint localEP) - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() ---- End of stack trace from previous location --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) - at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) - at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) -2026-02-01 22:17:49.266 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:17:49.839 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:17:49.863 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:17:49.863 +08:00 [INF] Executed DbCommand (10ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:17:49.867 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:17:49.902 +08:00 [INF] Loaded 2 routes from database -2026-02-01 22:17:49.902 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:17:49.903 +08:00 [INF] Route cache initialized: 1 global routes, 1 tenant routes -2026-02-01 22:17:49.903 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:17:49.918 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:17:49.919 +08:00 [ERR] Hosting failed to start -System.IO.IOException: Failed to bind to address http://0.0.0.0:8080: address already in use. - ---> Microsoft.AspNetCore.Connections.AddressInUseException: Address already in use - ---> System.Net.Sockets.SocketException (48): Address already in use - at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) - at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) - at System.Net.Sockets.Socket.Bind(EndPoint localEP) - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() - at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() ---- End of stack trace from previous location --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) - at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) - at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) -2026-02-01 22:18:29.315 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:18:29.898 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:18:29.922 +08:00 [INF] Executed DbCommand (9ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:18:29.922 +08:00 [INF] Executed DbCommand (9ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:18:29.930 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:18:29.959 +08:00 [INF] Loaded 2 routes from database -2026-02-01 22:18:29.959 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:18:29.961 +08:00 [INF] Route cache initialized: 1 global routes, 1 tenant routes -2026-02-01 22:18:29.961 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:18:29.975 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:18:29.977 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 22:18:29.977 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 22:18:29.977 +08:00 [INF] Hosting environment: Development -2026-02-01 22:18:29.977 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 22:19:00.199 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - null null -2026-02-01 22:19:00.212 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - 404 0 null 13.5757ms -2026-02-01 22:19:00.215 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/product/test, Response status code: 404 -2026-02-01 22:19:01.451 +08:00 [INF] Request starting HTTP/1.1 GET http://0.0.0.0:8080/ - null null -2026-02-01 22:19:01.453 +08:00 [INF] Request finished HTTP/1.1 GET http://0.0.0.0:8080/ - 404 0 null 1.7052ms -2026-02-01 22:19:01.454 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://0.0.0.0:8080/, Response status code: 404 -2026-02-01 22:19:07.472 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/ - null null -2026-02-01 22:19:07.474 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/ - 404 0 null 1.3807ms -2026-02-01 22:19:07.474 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://localhost:8080/, Response status code: 404 -2026-02-01 22:19:10.624 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:19:10.626 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:19:10.636 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:19:10.646 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:19:10.657 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:19:10.669 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 31.0448ms -2026-02-01 22:19:10.669 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:19:10.670 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 45.8155ms -2026-02-01 22:19:10.873 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/favicon.ico - null null -2026-02-01 22:19:10.874 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/favicon.ico - 404 0 null 0.4219ms -2026-02-01 22:19:10.874 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://localhost:8080/favicon.ico, Response status code: 404 -icrosoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() ---- End of stack trace from previous location --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - --- End of inner exception stack trace --- - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) - at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) - at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) - at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) - at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) -2026-02-01 22:20:22.965 +08:00 [INF] User profile is available. Using '/Users/movingsam/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -2026-02-01 22:20:23.632 +08:00 [INF] Initializing route cache from database... -2026-02-01 22:20:23.651 +08:00 [INF] Executed DbCommand (9ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:20:23.651 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT s."ClusterId", s."Id", s."Address", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."Status" = 1 AND NOT (s."IsDeleted") -ORDER BY s."ClusterId" -2026-02-01 22:20:23.662 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."Status" = 1 AND NOT (t."IsDeleted") -2026-02-01 22:20:23.688 +08:00 [INF] Loaded 2 routes from database -2026-02-01 22:20:23.688 +08:00 [INF] Loaded 2 clusters from database -2026-02-01 22:20:23.689 +08:00 [INF] Route cache initialized: 1 global routes, 1 tenant routes -2026-02-01 22:20:23.690 +08:00 [INF] Starting YARP Gateway -2026-02-01 22:20:23.705 +08:00 [WRN] Overriding address(es) 'http://localhost:5046'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -2026-02-01 22:20:23.706 +08:00 [INF] Now listening on: http://0.0.0.0:8080 -2026-02-01 22:20:23.707 +08:00 [INF] Application started. Press Ctrl+C to shut down. -2026-02-01 22:20:23.707 +08:00 [INF] Hosting environment: Development -2026-02-01 22:20:23.707 +08:00 [INF] Content root path: /Users/movingsam/Fengling.Refactory.Buiding/src/YarpGateway -2026-02-01 22:20:29.093 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/tenants - null null -2026-02-01 22:20:29.106 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:20:29.111 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:20:29.119 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:20:29.126 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:20:29.138 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 25.8851ms -2026-02-01 22:20:29.139 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:20:29.140 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 47.3449ms -2026-02-01 22:20:35.542 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - null null -2026-02-01 22:20:35.543 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:20:35.546 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:20:35.553 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:20:35.554 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:20:35.557 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 10.873ms -2026-02-01 22:20:35.557 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:20:35.557 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 14.4363ms -2026-02-01 22:21:01.471 +08:00 [INF] Request starting HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - null null -2026-02-01 22:21:01.473 +08:00 [INF] Request finished HTTP/1.1 GET http://127.0.0.1:8080/api/product/test - 404 0 null 1.7851ms -2026-02-01 22:21:01.474 +08:00 [INF] Request reached the end of the middleware pipeline without being handled by application code. Request path: GET http://127.0.0.1:8080/api/product/test, Response status code: 404 -2026-02-01 22:21:10.880 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:21:10.882 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:21:10.882 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:21:10.882 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:21:10.888 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:21:10.888 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:21:10.889 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 6.5091ms -2026-02-01 22:21:10.889 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:21:10.889 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.3552ms -2026-02-01 22:21:12.638 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:21:12.639 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:21:12.640 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:21:12.641 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:21:12.647 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:21:12.648 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:21:12.648 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.2865ms -2026-02-01 22:21:12.649 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:21:12.650 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 11.8347ms -2026-02-01 22:21:12.653 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 22:21:12.654 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:21:12.654 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:21:12.666 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:21:12.689 +08:00 [INF] Executed DbCommand (11ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 22:21:12.689 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:21:12.689 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 23.2372ms -2026-02-01 22:21:12.690 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:21:12.690 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 36.4262ms -2026-02-01 22:21:12.691 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 22:21:12.691 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:21:12.691 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:21:12.694 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:21:12.704 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:21:12.706 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:21:12.707 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 12.8864ms -2026-02-01 22:21:12.707 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:21:12.707 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 16.0741ms -2026-02-01 22:21:13.505 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:21:13.506 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:21:13.507 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:21:13.507 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:21:13.515 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:21:13.516 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:21:13.516 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 9.397ms -2026-02-01 22:21:13.516 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:21:13.517 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 11.5291ms -2026-02-01 22:25:30.498 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:25:30.498 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:25:30.498 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:25:30.498 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:25:30.533 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:25:30.534 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:25:30.535 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 36.8297ms -2026-02-01 22:25:30.535 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:25:30.535 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 37.6909ms -2026-02-01 22:26:22.245 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:26:22.245 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:26:22.245 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:26:22.245 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:26:22.249 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:26:22.250 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:26:22.250 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.209ms -2026-02-01 22:26:22.251 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:26:22.251 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.6294ms -2026-02-01 22:31:41.925 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:31:41.926 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:41.926 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:41.926 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:41.958 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:31:41.958 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:41.958 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 31.8244ms -2026-02-01 22:31:41.958 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:41.958 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 32.8773ms -2026-02-01 22:31:43.595 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:31:43.595 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:43.595 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:43.596 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:43.604 +08:00 [INF] Executed DbCommand (7ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:31:43.605 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:43.605 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 9.008ms -2026-02-01 22:31:43.605 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:43.606 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 10.8715ms -2026-02-01 22:31:44.652 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:31:44.652 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:44.652 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:44.652 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:44.657 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:31:44.657 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:44.657 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 5.4168ms -2026-02-01 22:31:44.658 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:44.658 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 6.1249ms -2026-02-01 22:31:46.251 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:31:46.251 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:46.251 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:46.251 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:46.256 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:31:46.257 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:46.257 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.5403ms -2026-02-01 22:31:46.257 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:46.257 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.5585ms -2026-02-01 22:31:46.745 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:31:46.746 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:46.746 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:46.746 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:46.751 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:31:46.752 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:46.752 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 5.6117ms -2026-02-01 22:31:46.752 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:46.752 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 6.6457ms -2026-02-01 22:31:48.657 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/product-cluster/instances - null null -2026-02-01 22:31:48.657 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:48.658 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:31:48.659 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:48.666 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:31:48.666 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:48.666 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 7.116ms -2026-02-01 22:31:48.666 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:31:48.666 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/product-cluster/instances - 200 null application/json; charset=utf-8 9.4147ms -2026-02-01 22:31:49.924 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:31:49.925 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:49.925 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:49.925 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:49.930 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:31:49.930 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:49.931 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.2451ms -2026-02-01 22:31:49.931 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:49.931 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.2486ms -2026-02-01 22:31:51.612 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:31:51.612 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:51.612 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:51.612 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:51.617 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:31:51.617 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:51.618 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 5.4269ms -2026-02-01 22:31:51.618 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:51.618 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 6.0851ms -2026-02-01 22:31:51.951 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:31:51.952 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:51.952 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:51.952 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:51.957 +08:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:31:51.958 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:51.958 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 6.4318ms -2026-02-01 22:31:51.960 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:31:51.961 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 9.0549ms -2026-02-01 22:31:54.041 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 22:31:54.041 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:54.042 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:31:54.042 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:54.047 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 22:31:54.047 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:54.047 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 5.2779ms -2026-02-01 22:31:54.047 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:31:54.047 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 6.3321ms -2026-02-01 22:31:55.255 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 22:31:55.255 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:55.256 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:31:55.256 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:55.261 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:31:55.261 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:55.262 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 5.9988ms -2026-02-01 22:31:55.262 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:31:55.262 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 6.751ms -2026-02-01 22:31:56.595 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:31:56.595 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:31:56.595 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:56.595 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:31:56.600 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:31:56.600 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:31:56.601 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 5.0439ms -2026-02-01 22:31:56.601 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:31:56.601 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 6.1072ms -2026-02-01 22:32:01.664 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/product-cluster/instances - null null -2026-02-01 22:32:01.664 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:01.664 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:01.664 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:01.668 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:32:01.669 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:01.669 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 4.9407ms -2026-02-01 22:32:01.669 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:01.670 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/product-cluster/instances - 200 null application/json; charset=utf-8 5.639ms -2026-02-01 22:32:04.216 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:32:04.216 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:04.216 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:04.217 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:04.222 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:32:04.222 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:04.223 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.4752ms -2026-02-01 22:32:04.223 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:04.223 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.7163ms -2026-02-01 22:32:04.591 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:32:04.591 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:04.591 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:04.591 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:04.595 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:32:04.595 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:04.596 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 4.5298ms -2026-02-01 22:32:04.596 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:04.596 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 5.4523ms -2026-02-01 22:32:12.562 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:32:12.562 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:12.562 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:12.563 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:12.568 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:32:12.568 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:12.569 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 6.2462ms -2026-02-01 22:32:12.569 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:12.569 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 7.22ms -2026-02-01 22:32:13.960 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 22:32:13.960 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:13.960 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:32:13.960 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:13.965 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 22:32:13.965 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:13.966 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 5.5422ms -2026-02-01 22:32:13.966 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:32:13.966 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 6.2859ms -2026-02-01 22:32:17.041 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 22:32:17.041 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:17.041 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:17.041 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:17.046 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:32:17.046 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:17.047 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 5.0143ms -2026-02-01 22:32:17.047 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:17.047 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 5.7629ms -2026-02-01 22:32:21.920 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:32:21.920 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:21.921 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:21.921 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:21.925 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:32:21.925 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:21.925 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 4.7163ms -2026-02-01 22:32:21.926 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:21.926 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 5.3019ms -2026-02-01 22:32:24.298 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:32:24.298 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:24.298 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:24.298 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:24.303 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:32:24.303 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:24.304 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.0583ms -2026-02-01 22:32:24.304 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:24.304 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 6.0625ms -2026-02-01 22:32:24.762 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:32:24.762 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:24.762 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:24.762 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:24.768 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:32:24.768 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:24.769 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 6.0394ms -2026-02-01 22:32:24.769 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:24.769 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 6.762ms -2026-02-01 22:32:25.372 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:32:25.373 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:25.373 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:25.373 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:25.379 +08:00 [INF] Executed DbCommand (6ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:32:25.380 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:25.380 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 7.209ms -2026-02-01 22:32:25.380 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:25.380 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 7.8575ms -2026-02-01 22:32:25.383 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:32:25.384 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:25.384 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:25.384 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:25.387 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:32:25.388 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:25.388 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 4.2493ms -2026-02-01 22:32:25.388 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:25.388 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 4.7142ms -2026-02-01 22:32:25.389 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 22:32:25.389 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:25.390 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:32:25.390 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:25.394 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 22:32:25.394 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:25.394 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 4.3996ms -2026-02-01 22:32:25.394 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:32:25.394 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 4.9442ms -2026-02-01 22:32:25.395 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 22:32:25.395 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:25.395 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:25.395 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:25.398 +08:00 [INF] Executed DbCommand (2ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:32:25.398 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:25.399 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 3.4243ms -2026-02-01 22:32:25.399 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:25.399 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 3.7777ms -2026-02-01 22:32:29.352 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:32:29.352 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:29.352 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:29.352 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:29.357 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:32:29.357 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:29.357 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 5.0185ms -2026-02-01 22:32:29.357 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:29.357 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 5.594ms -2026-02-01 22:32:31.246 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:32:31.246 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:31.246 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:31.246 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:31.250 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:32:31.250 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:31.251 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 4.5497ms -2026-02-01 22:32:31.251 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:31.251 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 4.979ms -2026-02-01 22:32:31.255 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - null null -2026-02-01 22:32:31.255 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:31.255 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:31.255 +08:00 [INF] Route matched with {action = "GetGlobalRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetGlobalRoutes() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:31.259 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."IsGlobal" AND NOT (t."IsDeleted") -2026-02-01 22:32:31.259 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:31.259 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway) in 4.2887ms -2026-02-01 22:32:31.259 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetGlobalRoutes (YarpGateway)' -2026-02-01 22:32:31.259 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/routes/global - 200 null application/json; charset=utf-8 4.6967ms -2026-02-01 22:32:31.260 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - null null -2026-02-01 22:32:31.260 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:31.261 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:32:31.261 +08:00 [INF] Route matched with {action = "GetTenantRoutes", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenantRoutes(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:31.265 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__tenantCode_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."ClusterId", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."IsGlobal", t."PathPattern", t."Priority", t."ServiceName", t."Status", t."TenantCode", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "TenantRoutes" AS t -WHERE t."TenantCode" = @__tenantCode_0 AND NOT (t."IsDeleted") -2026-02-01 22:32:31.265 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenantRoute, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:31.266 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway) in 4.8276ms -2026-02-01 22:32:31.266 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenantRoutes (YarpGateway)' -2026-02-01 22:32:31.266 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants/1668/routes - 200 null application/json; charset=utf-8 5.2765ms -2026-02-01 22:32:31.266 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - null null -2026-02-01 22:32:31.267 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:31.267 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:31.267 +08:00 [INF] Route matched with {action = "GetInstances", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetInstances(System.String) on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:31.271 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[@__clusterId_0='?'], CommandType='"Text"', CommandTimeout='30'] -SELECT s."Id", s."Address", s."ClusterId", s."CreatedBy", s."CreatedTime", s."DestinationId", s."Health", s."IsDeleted", s."Status", s."UpdatedBy", s."UpdatedTime", s."Version", s."Weight" -FROM "ServiceInstances" AS s -WHERE s."ClusterId" = @__clusterId_0 AND NOT (s."IsDeleted") -2026-02-01 22:32:31.271 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwServiceInstance, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:31.272 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway) in 4.6903ms -2026-02-01 22:32:31.272 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetInstances (YarpGateway)' -2026-02-01 22:32:31.272 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/clusters/1668-backend/instances - 200 null application/json; charset=utf-8 5.1732ms -2026-02-01 22:32:31.994 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 22:32:31.994 +08:00 [INF] CORS policy execution successful. -2026-02-01 22:32:31.994 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:31.994 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 22:32:31.999 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 22:32:32.000 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 22:32:32.000 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.5837ms -2026-02-01 22:32:32.000 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 22:32:32.000 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 5.9601ms -2026-02-01 23:02:32.878 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 23:02:32.879 +08:00 [INF] CORS policy execution successful. -2026-02-01 23:02:32.879 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 23:02:32.879 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 23:02:32.907 +08:00 [INF] Executed DbCommand (4ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 23:02:32.908 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 23:02:32.908 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 28.7787ms -2026-02-01 23:02:32.908 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 23:02:32.908 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 30.5865ms -2026-02-01 23:02:32.909 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 23:02:32.909 +08:00 [INF] CORS policy execution successful. -2026-02-01 23:02:32.909 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 23:02:32.909 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 23:02:32.913 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 23:02:32.913 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 23:02:32.914 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 5.3412ms -2026-02-01 23:02:32.914 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 23:02:32.914 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 499 null application/json; charset=utf-8 5.6739ms -2026-02-01 23:02:33.168 +08:00 [INF] Request starting HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - null null -2026-02-01 23:02:33.168 +08:00 [INF] CORS policy execution successful. -2026-02-01 23:02:33.168 +08:00 [INF] Executing endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 23:02:33.168 +08:00 [INF] Route matched with {action = "GetTenants", controller = "GatewayConfig"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] GetTenants() on controller YarpGateway.Controllers.GatewayConfigController (YarpGateway). -2026-02-01 23:02:33.172 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30'] -SELECT t."Id", t."CreatedBy", t."CreatedTime", t."IsDeleted", t."Status", t."TenantCode", t."TenantName", t."UpdatedBy", t."UpdatedTime", t."Version" -FROM "Tenants" AS t -WHERE NOT (t."IsDeleted") -2026-02-01 23:02:33.172 +08:00 [INF] Executing OkObjectResult, writing value of type 'System.Collections.Generic.List`1[[YarpGateway.Models.GwTenant, YarpGateway, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. -2026-02-01 23:02:33.172 +08:00 [INF] Executed action YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway) in 3.8746ms -2026-02-01 23:02:33.172 +08:00 [INF] Executed endpoint 'YarpGateway.Controllers.GatewayConfigController.GetTenants (YarpGateway)' -2026-02-01 23:02:33.172 +08:00 [INF] Request finished HTTP/1.1 GET http://localhost:8080/api/gateway/tenants - 200 null application/json; charset=utf-8 4.2812ms diff --git a/src/YarpGateway/sql/init.sql b/src/YarpGateway/sql/init.sql deleted file mode 100644 index 050e4d4..0000000 --- a/src/YarpGateway/sql/init.sql +++ /dev/null @@ -1,72 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -START TRANSACTION; -CREATE TABLE "ServiceInstances" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "ClusterId" character varying(100) NOT NULL, - "DestinationId" character varying(100) NOT NULL, - "Address" character varying(200) NOT NULL, - "Health" integer NOT NULL, - "Weight" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_ServiceInstances" PRIMARY KEY ("Id") -); - -CREATE TABLE "Tenants" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "TenantName" character varying(100) NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_Tenants" PRIMARY KEY ("Id"), - CONSTRAINT "AK_Tenants_TenantCode" UNIQUE ("TenantCode") -); - -CREATE TABLE "TenantRoutes" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "ServiceName" character varying(100) NOT NULL, - "ClusterId" character varying(100) NOT NULL, - "PathPattern" character varying(200) NOT NULL, - "Priority" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_TenantRoutes" PRIMARY KEY ("Id"), - CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode" FOREIGN KEY ("TenantCode") REFERENCES "Tenants" ("TenantCode") ON DELETE RESTRICT -); - -CREATE UNIQUE INDEX "IX_ServiceInstances_ClusterId_DestinationId" ON "ServiceInstances" ("ClusterId", "DestinationId"); - -CREATE INDEX "IX_ServiceInstances_Health" ON "ServiceInstances" ("Health"); - -CREATE INDEX "IX_TenantRoutes_ClusterId" ON "TenantRoutes" ("ClusterId"); - -CREATE UNIQUE INDEX "IX_TenantRoutes_TenantCode_ServiceName" ON "TenantRoutes" ("TenantCode", "ServiceName"); - -CREATE UNIQUE INDEX "IX_Tenants_TenantCode" ON "Tenants" ("TenantCode"); - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260201120312_InitialCreate', '9.0.0'); - -COMMIT; - diff --git a/src/YarpGateway/sql/update-isglobal.sql b/src/YarpGateway/sql/update-isglobal.sql deleted file mode 100644 index ba25c48..0000000 --- a/src/YarpGateway/sql/update-isglobal.sql +++ /dev/null @@ -1,174 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -START TRANSACTION; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE TABLE "ServiceInstances" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "ClusterId" character varying(100) NOT NULL, - "DestinationId" character varying(100) NOT NULL, - "Address" character varying(200) NOT NULL, - "Health" integer NOT NULL, - "Weight" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_ServiceInstances" PRIMARY KEY ("Id") - ); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE TABLE "Tenants" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "TenantName" character varying(100) NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_Tenants" PRIMARY KEY ("Id"), - CONSTRAINT "AK_Tenants_TenantCode" UNIQUE ("TenantCode") - ); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE TABLE "TenantRoutes" ( - "Id" bigint GENERATED BY DEFAULT AS IDENTITY, - "TenantCode" character varying(50) NOT NULL, - "ServiceName" character varying(100) NOT NULL, - "ClusterId" character varying(100) NOT NULL, - "PathPattern" character varying(200) NOT NULL, - "Priority" integer NOT NULL, - "Status" integer NOT NULL, - "CreatedBy" bigint, - "CreatedTime" timestamp with time zone NOT NULL, - "UpdatedBy" bigint, - "UpdatedTime" timestamp with time zone, - "IsDeleted" boolean NOT NULL, - "Version" integer NOT NULL, - CONSTRAINT "PK_TenantRoutes" PRIMARY KEY ("Id"), - CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode" FOREIGN KEY ("TenantCode") REFERENCES "Tenants" ("TenantCode") ON DELETE RESTRICT - ); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE UNIQUE INDEX "IX_ServiceInstances_ClusterId_DestinationId" ON "ServiceInstances" ("ClusterId", "DestinationId"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE INDEX "IX_ServiceInstances_Health" ON "ServiceInstances" ("Health"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE INDEX "IX_TenantRoutes_ClusterId" ON "TenantRoutes" ("ClusterId"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE UNIQUE INDEX "IX_TenantRoutes_TenantCode_ServiceName" ON "TenantRoutes" ("TenantCode", "ServiceName"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - CREATE UNIQUE INDEX "IX_Tenants_TenantCode" ON "Tenants" ("TenantCode"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201120312_InitialCreate') THEN - INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") - VALUES ('20260201120312_InitialCreate', '9.0.0'); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - ALTER TABLE "TenantRoutes" DROP CONSTRAINT "FK_TenantRoutes_Tenants_TenantCode"; - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - ALTER TABLE "Tenants" DROP CONSTRAINT "AK_Tenants_TenantCode"; - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - DROP INDEX "IX_TenantRoutes_TenantCode_ServiceName"; - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - ALTER TABLE "TenantRoutes" ADD "IsGlobal" boolean NOT NULL DEFAULT FALSE; - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - CREATE INDEX "IX_TenantRoutes_ServiceName" ON "TenantRoutes" ("ServiceName"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - CREATE INDEX "IX_TenantRoutes_ServiceName_IsGlobal_Status" ON "TenantRoutes" ("ServiceName", "IsGlobal", "Status"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - CREATE INDEX "IX_TenantRoutes_TenantCode" ON "TenantRoutes" ("TenantCode"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20260201133826_AddIsGlobalToTenantRoute') THEN - INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") - VALUES ('20260201133826_AddIsGlobalToTenantRoute', '9.0.0'); - END IF; -END $EF$; -COMMIT; - diff --git a/test/Fengling.AuthService.Tests/AccountIntegrationTests.cs b/test/Fengling.AuthService.Tests/AccountIntegrationTests.cs deleted file mode 100644 index 001df92..0000000 --- a/test/Fengling.AuthService.Tests/AccountIntegrationTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Testing; -using FluentAssertions; -using Xunit; -using System.Net; -using System.Net.Http.Json; -using Fengling.AuthService.ViewModels; - -namespace Fengling.AuthService.Tests; - -public class AccountIntegrationTests : IClassFixture> -{ - private readonly WebApplicationFactory _factory; - private readonly HttpClient _client; - - public AccountIntegrationTests(WebApplicationFactory factory) - { - _factory = factory.WithWebHostBuilder(builder => - { - builder.UseEnvironment("Testing"); - }); - _client = factory.CreateClient(new WebApplicationFactoryClientOptions - { - AllowAutoRedirect = false - }); - } - - [Fact] - public async Task Login_Get_ReturnsLoginPage() - { - var response = await _client.GetAsync("/account/login"); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - var content = await response.Content.ReadAsStringAsync(); - content.Should().Contain("欢迎回来"); - content.Should().Contain("登录到 Fengling Auth"); - content.Should().Contain("用户名"); - content.Should().Contain("密码"); - } - - [Fact] - public async Task Login_Get_WithReturnUrl_ReturnsLoginPageWithReturnUrl() - { - var response = await _client.GetAsync("/account/login?returnUrl=/dashboard"); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - var content = await response.Content.ReadAsStringAsync(); - content.Should().Contain("value=\"/dashboard\""); - } - - [Fact] - public async Task Register_Get_ReturnsRegisterPage() - { - var response = await _client.GetAsync("/account/register"); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - var content = await response.Content.ReadAsStringAsync(); - content.Should().Contain("创建账号"); - content.Should().Contain("加入 Fengling Auth"); - content.Should().Contain("用户名"); - content.Should().Contain("邮箱"); - content.Should().Contain("密码"); - content.Should().Contain("确认密码"); - } - - [Fact] - public async Task Register_Get_WithReturnUrl_ReturnsRegisterPageWithReturnUrl() - { - var response = await _client.GetAsync("/account/register?returnUrl=/dashboard"); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - var content = await response.Content.ReadAsStringAsync(); - content.Should().Contain("value=\"/dashboard\""); - } - - [Fact] - public async Task Dashboard_Index_ReturnsLoginPageWhenNotAuthenticated() - { - var response = await _client.GetAsync("/dashboard"); - - response.StatusCode.Should().Be(HttpStatusCode.Redirect); - var location = response.Headers.Location.ToString(); - location.Should().StartWith("/account/login"); - location.Should().Contain("dashboard"); - } - - [Fact] - public async Task Dashboard_Profile_ReturnsLoginPageWhenNotAuthenticated() - { - var response = await _client.GetAsync("/dashboard/profile"); - - response.StatusCode.Should().Be(HttpStatusCode.Redirect); - var location = response.Headers.Location.ToString(); - location.Should().StartWith("/account/login"); - location.Should().Contain("dashboard%2Fprofile"); - } - - [Fact] - public async Task Dashboard_Settings_ReturnsLoginPageWhenNotAuthenticated() - { - var response = await _client.GetAsync("/dashboard/settings"); - - response.StatusCode.Should().Be(HttpStatusCode.Redirect); - var location = response.Headers.Location.ToString(); - location.Should().StartWith("/account/login"); - location.Should().Contain("dashboard%2Fsettings"); - } -} diff --git a/test/Fengling.AuthService.Tests/Fengling.AuthService.Tests.csproj b/test/Fengling.AuthService.Tests/Fengling.AuthService.Tests.csproj deleted file mode 100644 index c1cc282..0000000 --- a/test/Fengling.AuthService.Tests/Fengling.AuthService.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net10.0 - enable - enable - false - true - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - -