From 0ae2589b515b8d9e7e0a82ce66e22608b8590439 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 29 Jan 2026 03:33:06 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=AB=AF=E4=B8=8E=E7=BD=91=E5=85=B3=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TakeoutSaaS.sln | 33 - .../Requests/DictionaryExportRequest.cs | 12 - .../Requests/DictionaryImportFormRequest.cs | 26 - .../Requests/FileUploadFormRequest.cs | 20 - .../Requests/SearchTenantBillsRequest.cs | 34 - .../Controllers/AppAnnouncementsController.cs | 120 ---- .../Controllers/AuthController.cs | 197 ------ .../Controllers/BillingsController.cs | 303 --------- .../Controllers/CacheMetricsController.cs | 65 -- .../Controllers/DeliveriesController.cs | 148 ----- .../Controllers/DictionaryController.cs | 167 ----- .../Controllers/DictionaryGroupsController.cs | 185 ------ .../Controllers/DictionaryItemsController.cs | 77 --- .../DictionaryLabelOverridesController.cs | 176 ------ .../DictionaryOverridesController.cs | 167 ----- .../Controllers/FilesController.cs | 52 -- .../Controllers/HealthController.cs | 30 - .../Controllers/InventoryController.cs | 149 ----- .../Controllers/MenusController.cs | 94 --- .../MerchantCategoriesController.cs | 95 --- .../Controllers/MerchantsController.cs | 447 -------------- .../Controllers/OrdersController.cs | 137 ----- .../Controllers/PaymentsController.cs | 132 ---- .../Controllers/PermissionsController.cs | 107 ---- .../PlatformAnnouncementsController.cs | 278 --------- .../Controllers/ProductsController.cs | 274 --------- .../PublicTenantPackagesController.cs | 39 -- .../PublicTenantSubscriptionsController.cs | 49 -- .../Controllers/PublicTenantsController.cs | 76 --- .../Controllers/QuotaPackagesController.cs | 198 ------ .../Controllers/RoleTemplatesController.cs | 217 ------- .../Controllers/StatisticsController.cs | 96 --- .../Controllers/StoreAuditsController.cs | 222 ------- .../Controllers/StorePickupController.cs | 114 ---- .../StoreQualificationsController.cs | 60 -- .../Controllers/StoreShiftsController.cs | 99 --- .../Controllers/StoreStaffsController.cs | 98 --- .../Controllers/StoreTableAreasController.cs | 87 --- .../Controllers/StoreTablesController.cs | 121 ---- .../Controllers/StoresController.cs | 581 ------------------ .../Controllers/SubscriptionsController.cs | 210 ------- .../Controllers/SystemParametersController.cs | 134 ---- .../TenantAnnouncementsController.cs | 394 ------------ .../Controllers/TenantBillingsController.cs | 107 ---- .../TenantNotificationsController.cs | 58 -- .../Controllers/TenantPackagesController.cs | 177 ------ .../Controllers/TenantRolesController.cs | 220 ------- .../Controllers/TenantsController.cs | 439 ------------- .../Controllers/UserPermissionsController.cs | 75 --- .../Controllers/UsersController.cs | 212 ------- src/Api/TakeoutSaaS.AdminApi/Dockerfile | 37 -- src/Api/TakeoutSaaS.AdminApi/Program.cs | 199 ------ .../Properties/launchSettings.json | 24 - .../TakeoutSaaS.AdminApi.csproj | 40 -- .../appsettings.Development.json | 195 ------ .../appsettings.Production.json | 195 ------ .../appsettings.Seed.Development.json | 475 -------------- .../GatewayOpenTelemetryOptions.cs | 22 - .../Configuration/GatewayRateLimitOptions.cs | 27 - src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile | 25 - src/Gateway/TakeoutSaaS.ApiGateway/Program.cs | 215 ------- .../Properties/launchSettings.json | 12 - .../TakeoutSaaS.ApiGateway.csproj | Bin 2130 -> 0 bytes .../appsettings.Development.json | 38 -- .../TakeoutSaaS.ApiGateway/appsettings.json | 72 --- 65 files changed, 9184 deletions(-) delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/FileUploadFormRequest.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/SearchTenantBillsRequest.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/AppAnnouncementsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryItemsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryLabelOverridesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryOverridesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/FilesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/HealthController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/InventoryController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantCategoriesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PlatformAnnouncementsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantPackagesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantSubscriptionsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/QuotaPackagesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/RoleTemplatesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StatisticsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoreAuditsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StorePickupController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoreQualificationsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoreShiftsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoreStaffsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTableAreasController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTablesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/SubscriptionsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/SystemParametersController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/TenantAnnouncementsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/TenantBillingsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/TenantNotificationsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/TenantPackagesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/UserPermissionsController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Dockerfile delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Program.cs delete mode 100644 src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json delete mode 100644 src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj delete mode 100644 src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json delete mode 100644 src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json delete mode 100644 src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayOpenTelemetryOptions.cs delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayRateLimitOptions.cs delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/Program.cs delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/appsettings.Development.json delete mode 100644 src/Gateway/TakeoutSaaS.ApiGateway/appsettings.json diff --git a/TakeoutSaaS.sln b/TakeoutSaaS.sln index 593dd42..1c33d44 100644 --- a/TakeoutSaaS.sln +++ b/TakeoutSaaS.sln @@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{81034408-37C8-1011-444E-4C15C2FADA8E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.AdminApi", "src\Api\TakeoutSaaS.AdminApi\TakeoutSaaS.AdminApi.csproj", "{0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{8D626EA8-CB54-BC41-363A-217881BEBA6E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Shared.Web", "TakeoutSaaS.BuildingBlocks\src\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj", "{022FCF39-EC48-46EA-AC08-FA2EAD1548B7}" @@ -37,10 +35,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.MiniApi", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.UserApi", "src\Api\TakeoutSaaS.UserApi\TakeoutSaaS.UserApi.csproj", "{1C0BCC51-AF18-44F3-A1E6-A693F74276B5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "Gateway", "{6306A8FB-679E-111F-6585-8F70E0EE6013}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.ApiGateway", "src\Gateway\TakeoutSaaS.ApiGateway\TakeoutSaaS.ApiGateway.csproj", "{A2620200-D487-49A7-ABAF-9B84951F81DD}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Shared.Kernel", "TakeoutSaaS.BuildingBlocks\src\Core\TakeoutSaaS.Shared.Kernel\TakeoutSaaS.Shared.Kernel.csproj", "{BBC99B58-ECA8-42C3-9070-9AA058D778D3}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Module.Storage", "src\Modules\TakeoutSaaS.Module.Storage\TakeoutSaaS.Module.Storage.csproj", "{05058F44-6FB7-43AF-8648-8BF538E283EF}" @@ -71,18 +65,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x64.ActiveCfg = Debug|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x64.Build.0 = Debug|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x86.ActiveCfg = Debug|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x86.Build.0 = Debug|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|Any CPU.Build.0 = Release|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x64.ActiveCfg = Release|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x64.Build.0 = Release|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x86.ActiveCfg = Release|Any CPU - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x86.Build.0 = Release|Any CPU {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|Any CPU.Build.0 = Debug|Any CPU {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -191,18 +173,6 @@ Global {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x64.Build.0 = Release|Any CPU {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x86.ActiveCfg = Release|Any CPU {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x86.Build.0 = Release|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x64.ActiveCfg = Debug|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x64.Build.0 = Debug|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x86.ActiveCfg = Debug|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x86.Build.0 = Debug|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|Any CPU.Build.0 = Release|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x64.ActiveCfg = Release|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x64.Build.0 = Release|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x86.ActiveCfg = Release|Any CPU - {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x86.Build.0 = Release|Any CPU {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -317,7 +287,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {81034408-37C8-1011-444E-4C15C2FADA8E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954} = {81034408-37C8-1011-444E-4C15C2FADA8E} {8D626EA8-CB54-BC41-363A-217881BEBA6E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {022FCF39-EC48-46EA-AC08-FA2EAD1548B7} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} {0DA03B31-E718-4424-A1F0-9989E79FFE81} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} @@ -332,8 +301,6 @@ Global {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D} = {81034408-37C8-1011-444E-4C15C2FADA8E} {1C0BCC51-AF18-44F3-A1E6-A693F74276B5} = {81034408-37C8-1011-444E-4C15C2FADA8E} - {6306A8FB-679E-111F-6585-8F70E0EE6013} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {A2620200-D487-49A7-ABAF-9B84951F81DD} = {6306A8FB-679E-111F-6585-8F70E0EE6013} {BBC99B58-ECA8-42C3-9070-9AA058D778D3} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} {05058F44-6FB7-43AF-8648-8BF538E283EF} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {5C12177E-6C25-4F78-BFD4-AA073CFC0650} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} diff --git a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs deleted file mode 100644 index e8f59dc..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace TakeoutSaaS.AdminApi.Contracts.Requests; - -/// -/// 字典导出请求。 -/// -public sealed record DictionaryExportRequest -{ - /// - /// 导出格式(csv/json)。 - /// - public string? Format { get; init; } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs deleted file mode 100644 index 7d24ccc..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Http; - -namespace TakeoutSaaS.AdminApi.Contracts.Requests; - -/// -/// 字典导入表单请求。 -/// -public sealed record DictionaryImportFormRequest -{ - /// - /// 导入文件。 - /// - [Required] - public required IFormFile File { get; init; } - - /// - /// 冲突解决模式(Skip/Overwrite/Append)。 - /// - public string? ConflictMode { get; init; } - - /// - /// 文件格式(csv/json)。 - /// - public string? Format { get; init; } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/FileUploadFormRequest.cs b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/FileUploadFormRequest.cs deleted file mode 100644 index 056faa3..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/FileUploadFormRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Http; - -namespace TakeoutSaaS.AdminApi.Contracts.Requests; - -/// -/// 文件上传表单请求。 -/// -public sealed record FileUploadFormRequest -{ - /// - /// 上传文件。 - /// - [Required] - public required IFormFile File { get; init; } - /// - /// 上传类型。 - /// - public string? Type { get; init; } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/SearchTenantBillsRequest.cs b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/SearchTenantBillsRequest.cs deleted file mode 100644 index d99fcad..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/SearchTenantBillsRequest.cs +++ /dev/null @@ -1,34 +0,0 @@ -using TakeoutSaaS.Domain.Tenants.Enums; - -namespace TakeoutSaaS.AdminApi.Contracts.Requests; - -/// -/// 租户账单分页查询请求(QueryString 参数)。 -/// -public sealed record SearchTenantBillsRequest -{ - /// - /// 账单状态筛选。 - /// - public TenantBillingStatus? Status { get; init; } - - /// - /// 账单起始时间(UTC)筛选。 - /// - public DateTime? From { get; init; } - - /// - /// 账单结束时间(UTC)筛选。 - /// - public DateTime? To { get; init; } - - /// - /// 页码(从 1 开始)。 - /// - public int Page { get; init; } = 1; - - /// - /// 每页条数。 - /// - public int PageSize { get; init; } = 20; -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/AppAnnouncementsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/AppAnnouncementsController.cs deleted file mode 100644 index eec915a..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/AppAnnouncementsController.cs +++ /dev/null @@ -1,120 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 应用端公告(面向已认证用户)。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/app/announcements")] -public sealed class AppAnnouncementsController(IMediator mediator) : BaseApiController -{ - /// - /// 获取当前用户可见的公告列表(已发布/有效期内)。 - /// - /// - /// 示例: - /// - /// GET /api/app/announcements?page=1&pageSize=20 - /// Header: Authorization: Bearer <JWT> - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "items": [], - /// "page": 1, - /// "pageSize": 20, - /// "totalCount": 0 - /// } - /// } - /// - /// - [HttpGet] - [SwaggerOperation(Summary = "获取可见公告列表", Description = "仅返回已发布且在有效期内的公告(含平台公告)。")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)] - public async Task>> List([FromQuery] GetTenantsAnnouncementsQuery query, CancellationToken cancellationToken) - { - var request = query with - { - Status = AnnouncementStatus.Published, - IsActive = true, - OnlyEffective = true - }; - - var result = await mediator.Send(request, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 获取当前用户未读公告。 - /// - /// - /// 示例: - /// - /// GET /api/app/announcements/unread?page=1&pageSize=20 - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "items": [], - /// "page": 1, - /// "pageSize": 20, - /// "totalCount": 0 - /// } - /// } - /// - /// - [HttpGet("unread")] - [SwaggerOperation(Summary = "获取未读公告", Description = "仅返回未读且在有效期内的已发布公告。")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)] - public async Task>> GetUnread([FromQuery] GetUnreadAnnouncementsQuery query, CancellationToken cancellationToken) - { - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 标记公告已读。 - /// - /// - /// 示例: - /// - /// POST /api/app/announcements/900123456789012345/mark-read - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "isRead": true - /// } - /// } - /// - /// - [HttpPost("{announcementId:long}/mark-read")] - [SwaggerOperation(Summary = "标记公告已读", Description = "仅已发布且可见的公告允许标记已读。")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)] - public async Task> MarkRead(long announcementId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new MarkAnnouncementAsReadCommand { AnnouncementId = announcementId }, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs deleted file mode 100644 index 2545af7..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs +++ /dev/null @@ -1,197 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Identity.Abstractions; -using TakeoutSaaS.Application.Identity.Commands; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; -using TakeoutSaaS.Shared.Web.Security; - -namespace TakeoutSaaS.AdminApi.Controllers; -/// -/// 管理后台认证接口 -/// -/// 提供登录、刷新 Token 以及用户权限查询能力。 -/// 认证服务 -/// 中介者。 -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/auth")] -public sealed class AuthController(IAdminAuthService authService, IMediator mediator) : BaseApiController -{ - /// - /// 登录获取 Token - /// - /// 登录请求。 - /// 取消标记。 - /// 包含访问令牌与刷新令牌的响应。 - [HttpPost("login")] - [AllowAnonymous] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Login([FromBody] AdminLoginRequest request, CancellationToken cancellationToken) - { - var response = await authService.LoginAsync(request, cancellationToken); - return ApiResponse.Ok(response); - } - - /// - /// 免租户号登录(仅账号+密码)。 - /// - /// 登录请求。 - /// 取消标记。 - /// 包含访问令牌与刷新令牌的响应。 - /// 用于前端简化登录,无需额外传递租户号。 - [HttpPost("login/simple")] - [AllowAnonymous] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> LoginSimple([FromBody] AdminLoginRequest request, CancellationToken cancellationToken) - { - var response = await authService.LoginSimpleAsync(request, cancellationToken); - return ApiResponse.Ok(response); - } - - /// - /// 刷新 Token - /// - /// 刷新令牌请求。 - /// 取消标记。 - /// 新的访问令牌与刷新令牌。 - [HttpPost("refresh")] - [AllowAnonymous] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> RefreshToken([FromBody] RefreshTokenRequest request, CancellationToken cancellationToken) - { - var response = await authService.RefreshTokenAsync(request, cancellationToken); - return ApiResponse.Ok(response); - } - - /// - /// 通过重置链接令牌重置管理员密码。 - /// - /// 令牌为一次性使用;成功后即可使用新密码登录。 - [HttpPost("reset-password")] - [AllowAnonymous] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ResetPassword([FromBody] ResetAdminPasswordRequest request, CancellationToken cancellationToken) - { - // 1. 通过令牌重置密码 - await mediator.Send(new ResetAdminPasswordByTokenCommand - { - Token = request.Token, - NewPassword = request.NewPassword - }, cancellationToken); - - // 2. 返回成功 - return ApiResponse.Success("密码重置成功"); - } - - /// - /// 获取当前用户信息 - /// - /// - /// 示例: - /// - /// GET /api/admin/v1/auth/profile - /// Header: Authorization: Bearer <JWT> - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "message": "操作成功", - /// "data": { - /// "userId": "900123456789012345", - /// "account": "admin@tenant1", - /// "displayName": "租户管理员", - /// "tenantId": "100000000000000001", - /// "merchantId": null, - /// "roles": ["TenantAdmin"], - /// "permissions": ["identity:permission:read", "merchant:read", "order:read"], - /// "avatar": "https://cdn.example.com/avatar.png" - /// } - /// } - /// - /// - /// 取消标记。 - /// 当前用户档案信息。 - [HttpGet("profile")] - [PermissionAuthorize("identity:profile:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)] - public async Task> GetProfile(CancellationToken cancellationToken) - { - // 1. 从 JWT 中获取当前用户标识 - var userId = User.GetUserId(); - if (userId == 0) - { - return ApiResponse.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识"); - } - - // 2. 读取用户档案并返回 - var profile = await authService.GetProfileAsync(userId, cancellationToken); - return ApiResponse.Ok(profile); - } - - /// - /// 获取当前用户的菜单树(按权限过滤)。 - /// - /// 取消标记。 - /// 当前用户可见的菜单树。 - [HttpGet("menu")] - [PermissionAuthorize("identity:profile:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetMenuTree(CancellationToken cancellationToken) - { - // 1. 获取当前用户标识 - var userId = User.GetUserId(); - if (userId == 0) - { - return ApiResponse>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识"); - } - // 2. 生成菜单树 - var menu = await authService.GetMenuTreeAsync(userId, cancellationToken); - return ApiResponse>.Ok(menu); - } - - /// - /// 查询指定用户的角色与权限概览(当前租户范围)。 - /// - /// - /// 示例: - /// - /// GET /api/admin/v1/auth/permissions/900123456789012346 - /// Header: Authorization: Bearer <JWT> - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "userId": "900123456789012346", - /// "tenantId": "100000000000000001", - /// "merchantId": "200000000000000001", - /// "account": "ops.manager", - /// "displayName": "运营经理", - /// "roles": ["OpsManager", "Reporter"], - /// "permissions": ["delivery:read", "order:read", "payment:read"], - /// "createdAt": "2025-12-01T08:30:00Z" - /// } - /// } - /// - /// - /// 目标用户 ID。 - /// 取消标记。 - /// 用户权限概览,未找到则返回 404。 - [HttpGet("permissions/{userId:long}")] - [PermissionAuthorize("identity:permission:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> GetUserPermissions(long userId, CancellationToken cancellationToken) - { - var result = await authService.GetUserPermissionsAsync(userId, cancellationToken); - return result is null - ? ApiResponse.Error(ErrorCodes.NotFound, "用户不存在或不属于当前租户") - : ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs deleted file mode 100644 index b4aa0e8..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs +++ /dev/null @@ -1,303 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Billings.Commands; -using TakeoutSaaS.Application.App.Billings.Dto; -using TakeoutSaaS.Application.App.Billings.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 账单管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/billings")] -public sealed class BillingsController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询账单列表。 - /// - /// 账单分页结果。 - [HttpGet] - [PermissionAuthorize("bill:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetList([FromQuery] GetBillingListQuery query, CancellationToken cancellationToken) - { - // 1. 查询账单列表 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取账单详情。 - /// - /// 账单 ID。 - /// 取消标记。 - /// 账单详情。 - [HttpGet("{id:long}")] - [PermissionAuthorize("bill:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> GetDetail(long id, CancellationToken cancellationToken) - { - // 1. 查询账单详情(若不存在则抛出业务异常,由全局异常处理转换为 404) - var result = await mediator.Send(new GetBillingDetailQuery { BillingId = id }, cancellationToken); - - // 2. 返回详情 - return ApiResponse.Ok(result); - } - - /// - /// 手动创建账单。 - /// - /// 创建账单命令。 - /// 取消标记。 - /// 创建的账单信息。 - [HttpPost] - [PermissionAuthorize("bill:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreateBillingCommand command, CancellationToken cancellationToken) - { - // 1. 创建账单 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 更新账单状态。 - /// - /// 账单 ID。 - /// 更新状态命令。 - /// 取消标记。 - /// 更新结果。 - [HttpPut("{id:long}/status")] - [PermissionAuthorize("bill:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateStatus(long id, [FromBody, Required] UpdateBillingStatusCommand command, CancellationToken cancellationToken) - { - // 1. 绑定账单标识 - command = command with { BillingId = id }; - - // 2. 更新账单状态(若不存在则抛出业务异常,由全局异常处理转换为 404) - await mediator.Send(command, cancellationToken); - - // 3. 返回成功结果 - return ApiResponse.Ok(null); - } - - /// - /// 取消账单。 - /// - /// 账单 ID。 - /// 取消原因(可选)。 - /// 取消标记。 - /// 取消结果。 - [HttpDelete("{id:long}")] - [PermissionAuthorize("bill:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Cancel(long id, [FromQuery] string? reason, CancellationToken cancellationToken) - { - // 1. 取消账单(取消原因支持可选) - await mediator.Send(new CancelBillingCommand { BillingId = id, Reason = reason ?? string.Empty }, cancellationToken); - - // 2. 返回成功结果 - return ApiResponse.Ok(null); - } - - /// - /// 获取账单支付记录。 - /// - /// 账单 ID。 - /// 取消标记。 - /// 支付记录列表。 - [HttpGet("{id:long}/payments")] - [PermissionAuthorize("bill:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetPayments(long id, CancellationToken cancellationToken) - { - // 1. 查询支付记录 - var result = await mediator.Send(new GetBillingPaymentsQuery { BillingId = id }, cancellationToken); - - // 2. 返回列表 - return ApiResponse>.Ok(result); - } - - /// - /// 记录支付(线下支付确认)。 - /// - /// 账单 ID。 - /// 记录支付命令。 - /// 取消标记。 - /// 支付记录信息。 - [HttpPost("{id:long}/payments")] - [PermissionAuthorize("bill:pay")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> RecordPayment(long id, [FromBody, Required] RecordPaymentCommand command, CancellationToken cancellationToken) - { - // 1. 绑定账单标识 - command = command with { BillingId = id }; - - // 2. 记录支付 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回支付记录 - return ApiResponse.Ok(result); - } - - /// - /// 一键确认收款(记录支付 + 立即审核通过)。 - /// - /// 账单 ID。 - /// 确认收款命令。 - /// 取消标记。 - /// 确认后的支付记录。 - [HttpPost("{id:long}/payments/confirm")] - [PermissionAuthorize("bill:pay")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> ConfirmPayment(long id, [FromBody, Required] ConfirmPaymentCommand command, CancellationToken cancellationToken) - { - // 1. 绑定账单标识 - command = command with { BillingId = id }; - - // 2. 一键确认收款(含:写入 VerifiedBy/VerifiedAt,并同步更新账单已收金额/状态) - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 审核支付记录。 - /// - /// 支付记录 ID。 - /// 审核参数。 - /// 取消标记。 - /// 审核后的支付记录。 - [HttpPut("payments/{paymentId:long}/verify")] - [PermissionAuthorize("bill:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> VerifyPayment(long paymentId, [FromBody, Required] VerifyPaymentCommand command, CancellationToken cancellationToken) - { - // 1. 绑定支付记录标识 - command = command with { PaymentId = paymentId }; - - // 2. 审核支付记录 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回审核结果 - return ApiResponse.Ok(result); - } - - /// - /// 批量更新账单状态。 - /// - /// 批量更新命令。 - /// 取消标记。 - /// 更新条数。 - [HttpPost("batch/status")] - [PermissionAuthorize("bill:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> BatchUpdateStatus([FromBody, Required] BatchUpdateStatusCommand command, CancellationToken cancellationToken) - { - // 1. 执行批量更新 - var affected = await mediator.Send(command, cancellationToken); - - // 2. 返回更新条数 - return ApiResponse.Ok(affected); - } - - /// - /// 导出账单(Excel/PDF/CSV)。 - /// - /// 导出请求。 - /// 取消标记。 - /// 导出文件。 - [HttpPost("export")] - [PermissionAuthorize("bill:read")] - [Produces("application/octet-stream")] - [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] - public async Task Export([FromBody, Required] ExportBillingsQuery query, CancellationToken cancellationToken) - { - // 1. 执行导出 - var bytes = await mediator.Send(query, cancellationToken); - - // 2. 解析格式并生成文件名 - var extension = ResolveExportFileExtension(query.Format); - var fileName = $"billings_{DateTime.UtcNow:yyyyMMdd_HHmmss}.{extension}"; - - // 3. 显式写入 Content-Disposition,确保浏览器以附件形式下载 - Response.Headers[HeaderNames.ContentDisposition] = new ContentDispositionHeaderValue("attachment") - { - FileName = fileName, - FileNameStar = fileName - }.ToString(); - - // 4. 返回二进制流(统一 octet-stream,避免被默认 JSON Produces 影响) - return File(bytes, "application/octet-stream"); - } - - /// - /// 获取账单统计数据。 - /// - /// 统计查询参数。 - /// 取消标记。 - /// 统计结果。 - [HttpGet("statistics")] - [PermissionAuthorize("bill:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Statistics([FromQuery] GetBillingStatisticsQuery query, CancellationToken cancellationToken) - { - // 1. 查询统计数据 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回统计结果 - return ApiResponse.Ok(result); - } - - /// - /// 获取逾期账单列表。 - /// - /// 逾期列表查询参数。 - /// 取消标记。 - /// 逾期账单分页结果。 - [HttpGet("overdue")] - [PermissionAuthorize("bill:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Overdue([FromQuery] GetOverdueBillingsQuery query, CancellationToken cancellationToken) - { - // 1. 查询逾期账单分页列表 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - private static string ResolveExportFileExtension(string? format) - { - // 1. 归一化导出格式 - var normalized = (format ?? string.Empty).Trim(); - - // 2. 映射扩展名 - return normalized.ToUpperInvariant() switch - { - "PDF" => "pdf", - "CSV" => "csv", - _ => "xlsx" - }; - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs deleted file mode 100644 index 31e66d1..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Domain.Dictionary.Entities; -using TakeoutSaaS.Domain.Dictionary.Repositories; -using TakeoutSaaS.Infrastructure.Dictionary.Caching; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 缓存监控指标接口。 -/// -[ApiVersion("1.0")] -[Authorize(Roles = "PlatformAdmin")] -[Route("api/admin/v{version:apiVersion}/dictionary/metrics")] -public sealed class CacheMetricsController( - CacheMetricsCollector metricsCollector, - ICacheInvalidationLogRepository invalidationLogRepository) - : BaseApiController -{ - /// - /// 获取缓存统计信息。 - /// - [HttpGet("cache-stats")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public ApiResponse GetCacheStats([FromQuery] string? timeRange = "1h") - { - var window = timeRange?.ToLowerInvariant() switch - { - "24h" => TimeSpan.FromHours(24), - "7d" => TimeSpan.FromDays(7), - _ => TimeSpan.FromHours(1) - }; - - var snapshot = metricsCollector.GetSnapshot(window); - return ApiResponse.Ok(snapshot); - } - - /// - /// 获取缓存失效事件列表。 - /// - [HttpGet("invalidation-events")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetInvalidationEvents( - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] DateTime? startDate = null, - [FromQuery] DateTime? endDate = null, - CancellationToken cancellationToken = default) - { - var safePage = page <= 0 ? 1 : page; - var safePageSize = pageSize <= 0 ? 20 : pageSize; - - var (items, total) = await invalidationLogRepository.GetPagedAsync( - safePage, - safePageSize, - startDate, - endDate, - cancellationToken); - - var result = new PagedResult(items, safePage, safePageSize, total); - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs deleted file mode 100644 index 6f0f953..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs +++ /dev/null @@ -1,148 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Deliveries.Commands; -using TakeoutSaaS.Application.App.Deliveries.Dto; -using TakeoutSaaS.Application.App.Deliveries.Queries; -using TakeoutSaaS.Domain.Deliveries.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 配送单管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/deliveries")] -public sealed class DeliveriesController(IMediator mediator) : BaseApiController -{ - /// - /// 创建配送单。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建后的配送单。 - [HttpPost] - [PermissionAuthorize("delivery:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateDeliveryOrderCommand command, CancellationToken cancellationToken) - { - // 1. 创建配送单 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询配送单列表。 - /// - /// 订单 ID。 - /// 配送状态。 - /// 页码。 - /// 每页大小。 - /// 排序字段。 - /// 是否倒序。 - /// 取消标记。 - /// 配送单分页列表。 - [HttpGet] - [PermissionAuthorize("delivery:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] long? orderId, - [FromQuery] DeliveryStatus? status, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, - CancellationToken cancellationToken = default) - { - // 1. 组装查询参数 - var result = await mediator.Send(new SearchDeliveryOrdersQuery - { - OrderId = orderId, - Status = status, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取配送单详情。 - /// - /// 配送单 ID。 - /// 取消标记。 - /// 配送单详情或未找到。 - [HttpGet("{deliveryOrderId:long}")] - [PermissionAuthorize("delivery:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long deliveryOrderId, CancellationToken cancellationToken) - { - // 1. 查询配送单详情 - var result = await mediator.Send(new GetDeliveryOrderByIdQuery { DeliveryOrderId = deliveryOrderId }, cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "配送单不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新配送单。 - /// - /// 配送单 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的配送单或未找到。 - [HttpPut("{deliveryOrderId:long}")] - [PermissionAuthorize("delivery:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long deliveryOrderId, [FromBody] UpdateDeliveryOrderCommand command, CancellationToken cancellationToken) - { - // 1. 确保命令携带配送单标识 - if (command.DeliveryOrderId == 0) - { - command = command with { DeliveryOrderId = deliveryOrderId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "配送单不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除配送单。 - /// - /// 配送单 ID。 - /// 取消标记。 - /// 删除结果,未找到则返回错误。 - [HttpDelete("{deliveryOrderId:long}")] - [PermissionAuthorize("delivery:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long deliveryOrderId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteDeliveryOrderCommand { DeliveryOrderId = deliveryOrderId }, cancellationToken); - - // 2. 返回结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "配送单不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs deleted file mode 100644 index f2cc347..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Dictionary.Abstractions; -using TakeoutSaaS.Application.Dictionary.Contracts; -using TakeoutSaaS.Application.Dictionary.Models; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 参数字典管理。 -/// -/// 字典服务 -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/dictionaries")] -public sealed class DictionaryController(IDictionaryAppService dictionaryAppService) : BaseApiController -{ - /// - /// 查询字典分组。 - /// - /// 分组查询条件。 - /// 取消标记。 - /// 分组列表。 - [HttpGet] - [PermissionAuthorize("dictionary:group:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetGroups([FromQuery] DictionaryGroupQuery query, CancellationToken cancellationToken) - { - // 1. 查询字典分组 - var groups = await dictionaryAppService.SearchGroupsAsync(query, cancellationToken); - - // 2. 返回分组列表 - return ApiResponse>.Ok(groups); - } - - /// - /// 创建字典分组。 - /// - /// 创建分组请求。 - /// 取消标记。 - /// 创建后的分组。 - [HttpPost] - [PermissionAuthorize("dictionary:group:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateGroup([FromBody] CreateDictionaryGroupRequest request, CancellationToken cancellationToken) - { - // 1. 创建字典分组 - var group = await dictionaryAppService.CreateGroupAsync(request, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(group); - } - - /// - /// 更新字典分组。 - /// - /// 分组 ID。 - /// 更新请求。 - /// 取消标记。 - /// 更新后的分组。 - [HttpPut("{groupId:long}")] - [PermissionAuthorize("dictionary:group:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateGroup(long groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken) - { - // 1. 更新字典分组 - var group = await dictionaryAppService.UpdateGroupAsync(groupId, request, cancellationToken); - - // 2. 返回更新结果 - return ApiResponse.Ok(group); - } - - /// - /// 删除字典分组。 - /// - /// 分组 ID。 - /// 取消标记。 - /// 操作结果。 - [HttpDelete("{groupId:long}")] - [PermissionAuthorize("dictionary:group:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> DeleteGroup(long groupId, CancellationToken cancellationToken) - { - // 1. 删除字典分组 - await dictionaryAppService.DeleteGroupAsync(groupId, cancellationToken); - - // 2. 返回成功响应 - return ApiResponse.Success(); - } - - /// - /// 创建字典项。 - /// - /// 分组 ID。 - /// 创建请求。 - /// 取消标记。 - /// 创建的字典项。 - [HttpPost("{groupId:long}/items")] - [PermissionAuthorize("dictionary:item:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateItem(long groupId, [FromBody] CreateDictionaryItemRequest request, CancellationToken cancellationToken) - { - // 1. 绑定分组标识 - request.GroupId = groupId; - - // 2. 创建字典项 - var item = await dictionaryAppService.CreateItemAsync(request, cancellationToken); - return ApiResponse.Ok(item); - } - - /// - /// 更新字典项。 - /// - /// 字典项 ID。 - /// 更新请求。 - /// 取消标记。 - /// 更新后的字典项。 - [HttpPut("items/{itemId:long}")] - [PermissionAuthorize("dictionary:item:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateItem(long itemId, [FromBody] UpdateDictionaryItemRequest request, CancellationToken cancellationToken) - { - // 1. 更新字典项 - var item = await dictionaryAppService.UpdateItemAsync(itemId, request, cancellationToken); - - // 2. 返回更新结果 - return ApiResponse.Ok(item); - } - - /// - /// 删除字典项。 - /// - /// 字典项 ID。 - /// 取消标记。 - /// 操作结果。 - [HttpDelete("items/{itemId:long}")] - [PermissionAuthorize("dictionary:item:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> DeleteItem(long itemId, CancellationToken cancellationToken) - { - // 1. 删除字典项 - await dictionaryAppService.DeleteItemAsync(itemId, cancellationToken); - - // 2. 返回成功响应 - return ApiResponse.Success(); - } - - /// - /// 批量获取字典项(命中缓存)。 - /// - /// 批量查询请求。 - /// 取消标记。 - /// 分组编码到字典项列表的映射。 - [HttpPost("batch")] - [ProducesResponseType(typeof(ApiResponse>>), StatusCodes.Status200OK)] - public async Task>>> BatchGet([FromBody] DictionaryBatchQueryRequest request, CancellationToken cancellationToken) - { - // 1. 批量读取并命中缓存 - var dictionaries = await dictionaryAppService.GetCachedItemsAsync(request, cancellationToken); - - // 2. 返回批量结果 - return ApiResponse>>.Ok(dictionaries); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs deleted file mode 100644 index d9f5b3a..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs +++ /dev/null @@ -1,185 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; -using System.Net.Mime; -using TakeoutSaaS.AdminApi.Contracts.Requests; -using TakeoutSaaS.Application.Dictionary.Contracts; -using TakeoutSaaS.Application.Dictionary.Models; -using TakeoutSaaS.Application.Dictionary.Services; -using TakeoutSaaS.Domain.Dictionary.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 字典分组管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/dictionary/groups")] -public sealed class DictionaryGroupsController( - DictionaryCommandService commandService, - DictionaryQueryService queryService, - DictionaryImportExportService importExportService) - : BaseApiController -{ - /// - /// 查询字典分组。 - /// - [HttpGet] - [PermissionAuthorize("dictionary:group:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] DictionaryGroupQuery query, CancellationToken cancellationToken) - { - var result = await queryService.GetGroupsAsync(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 获取字典分组详情。 - /// - [HttpGet("{groupId:long}")] - [PermissionAuthorize("dictionary:group:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long groupId, CancellationToken cancellationToken) - { - var result = await queryService.GetGroupByIdAsync(groupId, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "字典分组不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建字典分组。 - /// - [HttpPost] - [PermissionAuthorize("dictionary:group:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateDictionaryGroupRequest request, CancellationToken cancellationToken) - { - var result = await commandService.CreateGroupAsync(request, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新字典分组。 - /// - [HttpPut("{groupId:long}")] - [PermissionAuthorize("dictionary:group:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Update(long groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken) - { - var result = await commandService.UpdateGroupAsync(groupId, request, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 删除字典分组。 - /// - [HttpDelete("{groupId:long}")] - [PermissionAuthorize("dictionary:group:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long groupId, CancellationToken cancellationToken) - { - var success = await commandService.DeleteGroupAsync(groupId, cancellationToken); - return success - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "字典分组不存在"); - } - - /// - /// 导出字典分组数据。 - /// - [HttpPost("{groupId:long}/export")] - [PermissionAuthorize("dictionary:group:read")] - [Produces("application/octet-stream")] - [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] - public async Task Export(long groupId, [FromBody] DictionaryExportRequest request, CancellationToken cancellationToken) - { - var format = NormalizeFormat(request.Format); - await using var stream = new MemoryStream(); - - if (format == "json") - { - await importExportService.ExportToJsonAsync(groupId, stream, cancellationToken); - } - else - { - await importExportService.ExportToCsvAsync(groupId, stream, cancellationToken); - } - - var extension = format == "json" ? "json" : "csv"; - var fileName = $"dictionary_{groupId}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.{extension}"; - Response.Headers[HeaderNames.ContentDisposition] = new ContentDispositionHeaderValue("attachment") - { - FileName = fileName, - FileNameStar = fileName - }.ToString(); - - var contentType = format == "json" ? MediaTypeNames.Application.Json : "text/csv"; - return File(stream.ToArray(), contentType); - } - - /// - /// 导入字典分组数据。 - /// - [HttpPost("{groupId:long}/import")] - [PermissionAuthorize("dictionary:item:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Import( - long groupId, - [FromForm] DictionaryImportFormRequest request, - CancellationToken cancellationToken) - { - if (request.File.Length > 10 * 1024 * 1024) - { - return ApiResponse.Error(ErrorCodes.BadRequest, "导入文件不能超过 10MB"); - } - - var format = NormalizeFormat(request.Format); - var conflictMode = ParseConflictMode(request.ConflictMode); - - await using var stream = request.File.OpenReadStream(); - var importRequest = new DictionaryImportRequest - { - GroupId = groupId, - FileName = request.File.FileName, - FileSize = request.File.Length, - ConflictMode = conflictMode, - FileStream = stream - }; - - var result = format == "json" - ? await importExportService.ImportFromJsonAsync(importRequest, cancellationToken) - : await importExportService.ImportFromCsvAsync(importRequest, cancellationToken); - - return ApiResponse.Ok(result); - } - - private static string NormalizeFormat(string? format) - { - if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) - { - return "json"; - } - - return "csv"; - } - - private static ConflictResolutionMode ParseConflictMode(string? conflictMode) - { - if (string.IsNullOrWhiteSpace(conflictMode)) - { - return ConflictResolutionMode.Skip; - } - - return Enum.TryParse(conflictMode, ignoreCase: true, out var mode) - ? mode - : ConflictResolutionMode.Skip; - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryItemsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryItemsController.cs deleted file mode 100644 index 9663260..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryItemsController.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Dictionary.Contracts; -using TakeoutSaaS.Application.Dictionary.Models; -using TakeoutSaaS.Application.Dictionary.Services; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 字典项管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/dictionary/groups/{groupId:long}/items")] -public sealed class DictionaryItemsController( - DictionaryCommandService commandService, - DictionaryQueryService queryService) - : BaseApiController -{ - /// - /// 查询字典项列表。 - /// - [HttpGet] - [PermissionAuthorize("dictionary:group:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List(long groupId, CancellationToken cancellationToken) - { - var result = await queryService.GetItemsByGroupIdAsync(groupId, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 创建字典项。 - /// - [HttpPost] - [PermissionAuthorize("dictionary:item:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create(long groupId, [FromBody] CreateDictionaryItemRequest request, CancellationToken cancellationToken) - { - request.GroupId = groupId; - var result = await commandService.CreateItemAsync(request, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新字典项。 - /// - [HttpPut("{itemId:long}")] - [PermissionAuthorize("dictionary:item:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Update(long groupId, long itemId, [FromBody] UpdateDictionaryItemRequest request, CancellationToken cancellationToken) - { - _ = groupId; - var result = await commandService.UpdateItemAsync(itemId, request, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 删除字典项。 - /// - [HttpDelete("{itemId:long}")] - [PermissionAuthorize("dictionary:item:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long groupId, long itemId, CancellationToken cancellationToken) - { - _ = groupId; - var success = await commandService.DeleteItemAsync(itemId, cancellationToken); - return success - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "字典项不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryLabelOverridesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryLabelOverridesController.cs deleted file mode 100644 index d031058..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryLabelOverridesController.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Dictionary.Models; -using TakeoutSaaS.Application.Dictionary.Services; -using TakeoutSaaS.Domain.Dictionary.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Security; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 字典标签覆盖管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/dictionary/label-overrides")] -public sealed class DictionaryLabelOverridesController( - DictionaryLabelOverrideService labelOverrideService, - ITenantProvider tenantProvider, - ICurrentUserAccessor currentUserAccessor) - : BaseApiController -{ - private const string TenantIdHeaderName = "X-Tenant-Id"; - - #region 租户端 API(租户覆盖系统字典) - - /// - /// 获取当前租户的标签覆盖列表。 - /// - [HttpGet("tenant")] - [PermissionAuthorize("dictionary:override:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListTenantOverrides( - [FromQuery] OverrideType? overrideType, - CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader>(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var result = await labelOverrideService.GetOverridesAsync(tenantId, overrideType, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 租户覆盖系统字典项的标签。 - /// - [HttpPost("tenant")] - [PermissionAuthorize("dictionary:override:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateTenantOverride( - [FromBody] UpsertLabelOverrideRequest request, - CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var operatorId = currentUserAccessor.UserId; - var result = await labelOverrideService.UpsertTenantOverrideAsync(tenantId, request, operatorId, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 租户删除自己的标签覆盖。 - /// - [HttpDelete("tenant/{dictionaryItemId:long}")] - [PermissionAuthorize("dictionary:override:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> DeleteTenantOverride(long dictionaryItemId, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var operatorId = currentUserAccessor.UserId; - var success = await labelOverrideService.DeleteOverrideAsync( - tenantId, - dictionaryItemId, - operatorId, - allowPlatformEnforcement: false, - cancellationToken); - return success - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在"); - } - - #endregion - - #region 平台端 API(平台管理所有租户的覆盖) - - /// - /// 获取指定租户的所有标签覆盖(平台管理员用)。 - /// - [HttpGet("platform/{targetTenantId:long}")] - [PermissionAuthorize("dictionary:override:platform:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListPlatformOverrides( - long targetTenantId, - [FromQuery] OverrideType? overrideType, - CancellationToken cancellationToken) - { - var result = await labelOverrideService.GetOverridesAsync(targetTenantId, overrideType, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 平台强制覆盖租户字典项的标签。 - /// - [HttpPost("platform/{targetTenantId:long}")] - [PermissionAuthorize("dictionary:override:platform:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreatePlatformOverride( - long targetTenantId, - [FromBody] UpsertLabelOverrideRequest request, - CancellationToken cancellationToken) - { - var operatorId = currentUserAccessor.UserId; - var result = await labelOverrideService.UpsertPlatformOverrideAsync(targetTenantId, request, operatorId, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 平台删除对租户的强制覆盖。 - /// - [HttpDelete("platform/{targetTenantId:long}/{dictionaryItemId:long}")] - [PermissionAuthorize("dictionary:override:platform:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> DeletePlatformOverride( - long targetTenantId, - long dictionaryItemId, - CancellationToken cancellationToken) - { - var operatorId = currentUserAccessor.UserId; - var success = await labelOverrideService.DeleteOverrideAsync( - targetTenantId, - dictionaryItemId, - operatorId, - cancellationToken: cancellationToken); - return success - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在"); - } - - #endregion - - private ApiResponse? EnsureTenantHeader() - { - if (!Request.Headers.TryGetValue(TenantIdHeaderName, out var tenantHeader) || string.IsNullOrWhiteSpace(tenantHeader)) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, $"缺少租户标识,请在请求头 {TenantIdHeaderName} 指定租户"); - } - - if (!long.TryParse(tenantHeader.FirstOrDefault(), out _)) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, $"租户标识无效,请在请求头 {TenantIdHeaderName} 指定正确的租户 ID"); - } - - return null; - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryOverridesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryOverridesController.cs deleted file mode 100644 index 43295ee..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryOverridesController.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Dictionary.Contracts; -using TakeoutSaaS.Application.Dictionary.Models; -using TakeoutSaaS.Application.Dictionary.Services; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户字典覆盖配置管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/dictionary/overrides")] -public sealed class DictionaryOverridesController( - DictionaryOverrideService overrideService, - ITenantProvider tenantProvider) - : BaseApiController -{ - private const string TenantIdHeaderName = "X-Tenant-Id"; - - /// - /// 获取当前租户的覆盖配置列表。 - /// - [HttpGet] - [PermissionAuthorize("dictionary:override:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List(CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader>(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var result = await overrideService.GetOverridesAsync(tenantId, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 获取指定字典分组的覆盖配置。 - /// - [HttpGet("{groupCode}")] - [PermissionAuthorize("dictionary:override:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(string groupCode, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var result = await overrideService.GetOverrideAsync(tenantId, groupCode, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在") - : ApiResponse.Ok(result); - } - - /// - /// 启用覆盖模式。 - /// - [HttpPost("{groupCode}/enable")] - [PermissionAuthorize("dictionary:override:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Enable(string groupCode, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var result = await overrideService.EnableOverrideAsync(tenantId, groupCode, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 禁用覆盖模式。 - /// - [HttpPost("{groupCode}/disable")] - [PermissionAuthorize("dictionary:override:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Disable(string groupCode, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var success = await overrideService.DisableOverrideAsync(tenantId, groupCode, cancellationToken); - return success - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在"); - } - - /// - /// 更新隐藏的系统字典项。 - /// - [HttpPut("{groupCode}/hidden-items")] - [PermissionAuthorize("dictionary:override:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateHiddenItems( - string groupCode, - [FromBody] DictionaryOverrideHiddenItemsRequest request, - CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var result = await overrideService.UpdateHiddenItemsAsync(tenantId, groupCode, request.HiddenItemIds, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新自定义排序。 - /// - [HttpPut("{groupCode}/sort-order")] - [PermissionAuthorize("dictionary:override:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateSortOrder( - string groupCode, - [FromBody] DictionaryOverrideSortOrderRequest request, - CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var tenantId = tenantProvider.GetCurrentTenantId(); - var result = await overrideService.UpdateCustomSortOrderAsync(tenantId, groupCode, request.SortOrder, cancellationToken); - return ApiResponse.Ok(result); - } - - private ApiResponse? EnsureTenantHeader() - { - if (!Request.Headers.TryGetValue(TenantIdHeaderName, out var tenantHeader) || string.IsNullOrWhiteSpace(tenantHeader)) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, $"缺少租户标识,请在请求头 {TenantIdHeaderName} 指定租户"); - } - - if (!long.TryParse(tenantHeader.FirstOrDefault(), out _)) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, $"租户标识无效,请在请求头 {TenantIdHeaderName} 指定正确的租户 ID"); - } - - return null; - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/FilesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/FilesController.cs deleted file mode 100644 index 35f57f5..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/FilesController.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.AdminApi.Contracts.Requests; -using TakeoutSaaS.Application.Storage.Abstractions; -using TakeoutSaaS.Application.Storage.Contracts; -using TakeoutSaaS.Application.Storage.Extensions; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 管理后台文件上传。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/files")] -public sealed class FilesController(IFileStorageService fileStorageService) : BaseApiController -{ - /// - /// 上传图片或文件。 - /// - /// 文件上传响应信息。 - [HttpPost("upload")] - [Consumes("multipart/form-data")] - [RequestFormLimits(MultipartBodyLengthLimit = 30 * 1024 * 1024)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] - public async Task> Upload([FromForm] FileUploadFormRequest request, CancellationToken cancellationToken) - { - // 1. 校验文件有效性 - if (request.File is null || request.File.Length == 0) - { - return ApiResponse.Error(ErrorCodes.BadRequest, "文件不能为空"); - } - // 2. 解析上传类型 - if (!UploadFileTypeParser.TryParse(request.Type, out var uploadType)) - { - return ApiResponse.Error(ErrorCodes.BadRequest, "上传类型不合法"); - } - // 3. 提取请求来源 - var origin = Request.Headers["Origin"].FirstOrDefault() ?? Request.Headers["Referer"].FirstOrDefault(); - await using var stream = request.File.OpenReadStream(); - // 4. 调用存储服务执行上传 - var result = await fileStorageService.UploadAsync( - new UploadFileRequest(uploadType, stream, request.File.FileName, request.File.ContentType ?? string.Empty, request.File.Length, origin), - cancellationToken); - // 5. 返回上传结果 - return ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/HealthController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/HealthController.cs deleted file mode 100644 index 90dc606..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/HealthController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 管理后台 - 健康检查。 -/// -[ApiVersion("1.0")] -[Route("api/admin/v{version:apiVersion}/[controller]")] -public class HealthController : BaseApiController -{ - /// - /// 获取服务健康状态。 - /// - /// 健康状态 - [HttpGet] - [AllowAnonymous] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public ApiResponse Get() - { - // 1. 构造健康状态 - var payload = new { status = "OK", service = "AdminApi", time = DateTime.UtcNow }; - - // 2. 返回健康响应 - return ApiResponse.Ok(payload); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/InventoryController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/InventoryController.cs deleted file mode 100644 index 410d5f2..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/InventoryController.cs +++ /dev/null @@ -1,149 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Inventory.Commands; -using TakeoutSaaS.Application.App.Inventory.Dto; -using TakeoutSaaS.Application.App.Inventory.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 库存管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores/{storeId:long}/inventory")] -public sealed class InventoryController(IMediator mediator) : BaseApiController -{ - /// - /// 查询库存。 - /// - [HttpGet("{productSkuId:long}")] - [PermissionAuthorize("inventory:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Get(long storeId, long productSkuId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetInventoryItemQuery { StoreId = storeId, ProductSkuId = productSkuId }, cancellationToken); - return result is null - ? ApiResponse.Error(ErrorCodes.NotFound, "库存不存在") - : ApiResponse.Ok(result); - } - - /// - /// 调整库存(入库/盘点/报损)。 - /// - [HttpPost("adjust")] - [PermissionAuthorize("inventory:adjust")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Adjust(long storeId, [FromBody] AdjustInventoryCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 锁定库存(下单占用)。 - /// - [HttpPost("lock")] - [PermissionAuthorize("inventory:lock")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Lock(long storeId, [FromBody] LockInventoryCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 释放库存(取消订单等)。 - /// - [HttpPost("release")] - [PermissionAuthorize("inventory:release")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Release(long storeId, [FromBody] ReleaseInventoryCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 扣减库存(支付或履约成功)。 - /// - [HttpPost("deduct")] - [PermissionAuthorize("inventory:deduct")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Deduct(long storeId, [FromBody] DeductInventoryCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 查询批次列表。 - /// - [HttpGet("{productSkuId:long}/batches")] - [PermissionAuthorize("inventory:batch:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetBatches(long storeId, long productSkuId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetInventoryBatchesQuery - { - StoreId = storeId, - ProductSkuId = productSkuId - }, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 新增或更新批次。 - /// - [HttpPost("{productSkuId:long}/batches")] - [PermissionAuthorize("inventory:batch:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpsertBatch(long storeId, long productSkuId, [FromBody] UpsertInventoryBatchCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.ProductSkuId == 0) - { - command = command with { StoreId = storeId, ProductSkuId = productSkuId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 释放过期锁定。 - /// - [HttpPost("locks/expire")] - [PermissionAuthorize("inventory:release")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ReleaseExpiredLocks(CancellationToken cancellationToken) - { - var count = await mediator.Send(new ReleaseExpiredInventoryLocksCommand(), cancellationToken); - return ApiResponse.Ok(count); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs deleted file mode 100644 index 572943a..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs +++ /dev/null @@ -1,94 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.Identity.Commands; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Application.Identity.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 菜单管理(可增删改查)。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/menus")] -public sealed class MenusController(IMediator mediator) : BaseApiController -{ - /// - /// 获取当前租户的菜单列表(平铺)。 - /// - [HttpGet] - [PermissionAuthorize("identity:menu:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List(CancellationToken cancellationToken) - { - // 1. 查询菜单列表 - var result = await mediator.Send(new ListMenusQuery(), cancellationToken); - - // 2. 返回数据 - return ApiResponse>.Ok(result); - } - - /// - /// 获取菜单详情。 - /// - [HttpGet("{menuId:long}")] - [PermissionAuthorize("identity:menu:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long menuId, CancellationToken cancellationToken) - { - // 1. 查询详情 - var result = await mediator.Send(new MenuDetailQuery { Id = menuId }, cancellationToken); - - // 2. 返回或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "菜单不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建菜单。 - /// - [HttpPost] - [PermissionAuthorize("identity:menu:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreateMenuCommand command, CancellationToken cancellationToken) - { - // 1. 菜单已固定,禁止新增 - return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "菜单已固定,禁止新增")); - } - - /// - /// 更新菜单。 - /// - [HttpPut("{menuId:long}")] - [PermissionAuthorize("identity:menu:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update( - long menuId, - [FromBody, Required] UpdateMenuCommand command, - CancellationToken cancellationToken) - { - // 1. 菜单已固定,禁止修改 - return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "菜单已固定,禁止修改")); - } - - /// - /// 删除菜单。 - /// - [HttpDelete("{menuId:long}")] - [PermissionAuthorize("identity:menu:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Delete(long menuId, CancellationToken cancellationToken) - { - // 1. 菜单已固定,禁止删除 - return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "菜单已固定,禁止删除")); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantCategoriesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantCategoriesController.cs deleted file mode 100644 index 497cc87..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantCategoriesController.cs +++ /dev/null @@ -1,95 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Merchants.Commands; -using TakeoutSaaS.Application.App.Merchants.Dto; -using TakeoutSaaS.Application.App.Merchants.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 商户类目管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/merchant-categories")] -public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiController -{ - /// - /// 列出所有类目。 - /// - /// 取消标记。 - /// 类目列表。 - [HttpGet] - [PermissionAuthorize("merchant_category:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List(CancellationToken cancellationToken) - { - // 1. 查询所有类目 - var result = await mediator.Send(new ListMerchantCategoriesQuery(), cancellationToken); - - // 2. 返回类目列表 - return ApiResponse>.Ok(result); - } - - /// - /// 新增类目。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建的类目。 - [HttpPost] - [PermissionAuthorize("merchant_category:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateMerchantCategoryCommand command, CancellationToken cancellationToken) - { - // 1. 创建类目 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 删除类目。 - /// - /// 类目 ID。 - /// 取消标记。 - /// 删除结果,未找到则返回错误。 - [HttpDelete("{categoryId:long}")] - [PermissionAuthorize("merchant_category:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long categoryId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteMerchantCategoryCommand(categoryId), cancellationToken); - - // 2. 返回删除结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "类目不存在"); - } - - /// - /// 批量调整类目排序。 - /// - /// 排序命令。 - /// 取消标记。 - /// 执行结果。 - [HttpPost("reorder")] - [PermissionAuthorize("merchant_category:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Reorder([FromBody] ReorderMerchantCategoriesCommand command, CancellationToken cancellationToken) - { - // 1. 执行排序调整 - await mediator.Send(command, cancellationToken); - - // 2. 返回成功结果 - return ApiResponse.Ok(null); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs deleted file mode 100644 index 98125ac..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs +++ /dev/null @@ -1,447 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; -using TakeoutSaaS.Application.App.Merchants.Commands; -using TakeoutSaaS.Application.App.Merchants.Dto; -using TakeoutSaaS.Application.App.Merchants.Queries; -using TakeoutSaaS.Domain.Merchants.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 商户管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/merchants")] -public sealed class MerchantsController(IMediator mediator) : BaseApiController -{ - /// - /// 创建商户。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建后的商户。 - [HttpPost] - [PermissionAuthorize("merchant:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateMerchantCommand command, CancellationToken cancellationToken) - { - // 1. 创建商户 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询商户列表。 - /// - /// 查询参数。 - /// 取消标记。 - /// 商户分页结果。 - [HttpGet] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] GetMerchantListQuery query, - CancellationToken cancellationToken = default) - { - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 待审核商户列表。 - /// - [HttpGet("pending-review")] - [PermissionAuthorize("merchant:review")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> PendingReviewList( - [FromQuery] GetPendingReviewListQuery query, - CancellationToken cancellationToken) - { - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 更新商户。 - /// - /// 商户 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的商户或未找到。 - [HttpPut("{merchantId:long}")] - [PermissionAuthorize("merchant:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long merchantId, [FromBody] UpdateMerchantCommand command, CancellationToken cancellationToken) - { - if (command.MerchantId != 0 && command.MerchantId != merchantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 merchantId 与请求体 merchantId 不一致"); - } - - command = command with { MerchantId = merchantId }; - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - if (result == null) - { - return ApiResponse.Error(ErrorCodes.NotFound, "商户不存在"); - } - - if (result.RequiresReview) - { - return ApiResponse.Error( - ErrorCodes.ValidationFailed, - "关键信息修改,商户已进入待审核状态,业务已冻结") - with { Data = result }; - } - - return ApiResponse.Ok(result); - } - - /// - /// 删除商户。 - /// - /// 商户 ID。 - /// 取消标记。 - /// 删除结果。 - [HttpDelete("{merchantId:long}")] - [PermissionAuthorize("merchant:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long merchantId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteMerchantCommand { MerchantId = merchantId }, cancellationToken); - - // 2. 返回删除结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "商户不存在"); - } - - /// - /// 获取商户概览。 - /// - /// 商户 ID。 - /// 取消标记。 - /// 商户概览或未找到。 - [HttpGet("{merchantId:long}")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long merchantId, CancellationToken cancellationToken) - { - // 1. 查询商户概览 - var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken); - - // 2. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 获取审核领取信息。 - /// - [HttpGet("{merchantId:long}/review/claim")] - [PermissionAuthorize("merchant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetReviewClaim(long merchantId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetMerchantReviewClaimQuery(merchantId), cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 领取审核。 - /// - [HttpPost("{merchantId:long}/review/claim")] - [PermissionAuthorize("merchant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ClaimReview(long merchantId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ClaimMerchantReviewCommand { MerchantId = merchantId }, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 释放审核领取。 - /// - [HttpDelete("{merchantId:long}/review/claim")] - [PermissionAuthorize("merchant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ReleaseReviewClaim(long merchantId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ReleaseClaimCommand { MerchantId = merchantId }, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 获取商户详细资料(含证照、合同)。 - /// - /// 创建的证照信息。 - [HttpGet("{merchantId:long}/detail")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> FullDetail(long merchantId, CancellationToken cancellationToken) - { - // 1. 查询商户详细资料 - var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken); - - // 2. 返回详情 - return ApiResponse.Ok(result); - } - - /// - /// 上传商户证照信息(先通过文件上传接口获取 COS 地址)。 - /// - /// 创建的证照信息。 - [HttpPost("{merchantId:long}/documents")] - [PermissionAuthorize("merchant:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateDocument( - long merchantId, - [FromBody] AddMerchantDocumentCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定商户标识 - var command = body with { MerchantId = merchantId }; - - // 2. 创建证照记录 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 商户证照列表。 - /// - /// 商户证照列表。 - [HttpGet("{merchantId:long}/documents")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Documents(long merchantId, CancellationToken cancellationToken) - { - // 1. 查询证照列表 - var result = await mediator.Send(new GetMerchantDocumentsQuery(merchantId), cancellationToken); - - // 2. 返回证照集合 - return ApiResponse>.Ok(result); - } - - /// - /// 审核指定证照。 - /// - /// 审核后的证照信息。 - [HttpPost("{merchantId:long}/documents/{documentId:long}/review")] - [PermissionAuthorize("merchant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ReviewDocument( - long merchantId, - long documentId, - [FromBody] ReviewMerchantDocumentCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定商户与证照标识 - var command = body with { MerchantId = merchantId, DocumentId = documentId }; - - // 2. 执行审核 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 新增商户合同。 - /// - /// 创建的合同信息。 - [HttpPost("{merchantId:long}/contracts")] - [PermissionAuthorize("merchant:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateContract( - long merchantId, - [FromBody] CreateMerchantContractCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定商户标识 - var command = body with { MerchantId = merchantId }; - - // 2. 创建合同 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 合同列表。 - /// - /// 商户合同列表。 - [HttpGet("{merchantId:long}/contracts")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Contracts(long merchantId, CancellationToken cancellationToken) - { - // 1. 查询合同列表 - var result = await mediator.Send(new GetMerchantContractsQuery(merchantId), cancellationToken); - - // 2. 返回合同集合 - return ApiResponse>.Ok(result); - } - - /// - /// 更新合同状态(生效/终止等)。 - /// - /// 更新后的合同信息。 - [HttpPut("{merchantId:long}/contracts/{contractId:long}/status")] - [PermissionAuthorize("merchant:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateContractStatus( - long merchantId, - long contractId, - [FromBody] UpdateMerchantContractStatusCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定商户与合同标识 - var command = body with { MerchantId = merchantId, ContractId = contractId }; - - // 2. 更新合同状态 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 审核商户(通过/驳回)。 - /// - /// 审核后的商户信息。 - [HttpPost("{merchantId:long}/review")] - [PermissionAuthorize("merchant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Review(long merchantId, [FromBody] ReviewMerchantCommand body, CancellationToken cancellationToken) - { - // 1. 绑定商户标识 - var command = body with { MerchantId = merchantId }; - - // 2. 执行审核 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 撤销审核。 - /// - [HttpPost("{merchantId:long}/review/revoke")] - [PermissionAuthorize("merchant:review:revoke")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> RevokeReview( - long merchantId, - [FromBody] RevokeMerchantReviewCommand body, - CancellationToken cancellationToken) - { - if (body.MerchantId != 0 && body.MerchantId != merchantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 merchantId 与请求体 merchantId 不一致"); - } - - var command = new RevokeMerchantReviewCommand - { - MerchantId = merchantId, - Reason = body.Reason - }; - await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(null); - } - - /// - /// 审核历史。 - /// - [HttpGet("{merchantId:long}/audit-history")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> AuditHistory( - long merchantId, - CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetMerchantAuditHistoryQuery(merchantId), cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 变更历史。 - /// - [HttpGet("{merchantId:long}/change-history")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ChangeHistory( - long merchantId, - [FromQuery] string? fieldName, - CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetMerchantChangeHistoryQuery(merchantId, fieldName), cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 审核日志。 - /// - /// 商户审核日志分页结果。 - [HttpGet("{merchantId:long}/audits")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> AuditLogs( - long merchantId, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - CancellationToken cancellationToken = default) - { - // 1. 查询审核日志 - var result = await mediator.Send(new GetMerchantAuditLogsQuery(merchantId, page, pageSize), cancellationToken); - - // 2. 返回日志分页 - return ApiResponse>.Ok(result); - } - - /// - /// 导出商户 PDF。 - /// - [HttpGet("{merchantId:long}/export-pdf")] - [PermissionAuthorize("merchant:read")] - [Produces("application/pdf")] - [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] - public async Task ExportPdf(long merchantId, CancellationToken cancellationToken) - { - var bytes = await mediator.Send(new ExportMerchantPdfQuery(merchantId), cancellationToken); - var fileName = $"merchant_{merchantId}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.pdf"; - - Response.Headers[HeaderNames.ContentDisposition] = new ContentDispositionHeaderValue("attachment") - { - FileName = fileName, - FileNameStar = fileName - }.ToString(); - - return File(bytes, "application/pdf"); - } - - /// - /// 可选商户类目列表。 - /// - /// 可选的商户类目列表。 - [HttpGet("categories")] - [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Categories(CancellationToken cancellationToken) - { - // 1. 查询可选类目 - var result = await mediator.Send(new GetMerchantCategoriesQuery(), cancellationToken); - - // 2. 返回类目列表 - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs deleted file mode 100644 index d190e96..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs +++ /dev/null @@ -1,137 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Orders.Commands; -using TakeoutSaaS.Application.App.Orders.Dto; -using TakeoutSaaS.Application.App.Orders.Queries; -using TakeoutSaaS.Domain.Orders.Enums; -using TakeoutSaaS.Domain.Payments.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 订单管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/orders")] -public sealed class OrdersController(IMediator mediator) : BaseApiController -{ - /// - /// 创建订单。 - /// - /// 创建的订单信息。 - [HttpPost] - [PermissionAuthorize("order:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateOrderCommand command, CancellationToken cancellationToken) - { - // 1. 创建订单 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询订单列表。 - /// - /// 订单分页列表。 - [HttpGet] - [PermissionAuthorize("order:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] long? storeId, - [FromQuery] OrderStatus? status, - [FromQuery] PaymentStatus? paymentStatus, - [FromQuery] string? orderNo, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, - CancellationToken cancellationToken = default) - { - // 1. 组装查询参数并执行查询 - var result = await mediator.Send(new SearchOrdersQuery - { - StoreId = storeId, - Status = status, - PaymentStatus = paymentStatus, - OrderNo = orderNo, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取订单详情。 - /// - /// 订单详情。 - [HttpGet("{orderId:long}")] - [PermissionAuthorize("order:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long orderId, CancellationToken cancellationToken) - { - // 1. 查询订单详情 - var result = await mediator.Send(new GetOrderByIdQuery { OrderId = orderId }, cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "订单不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新订单。 - /// - /// 更新后的订单信息。 - [HttpPut("{orderId:long}")] - [PermissionAuthorize("order:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long orderId, [FromBody] UpdateOrderCommand command, CancellationToken cancellationToken) - { - // 1. 确保命令包含订单标识 - if (command.OrderId == 0) - { - command = command with { OrderId = orderId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "订单不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除订单。 - /// - /// 删除结果。 - [HttpDelete("{orderId:long}")] - [PermissionAuthorize("order:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long orderId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteOrderCommand { OrderId = orderId }, cancellationToken); - - // 2. 返回结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "订单不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs deleted file mode 100644 index 287f893..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs +++ /dev/null @@ -1,132 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Payments.Commands; -using TakeoutSaaS.Application.App.Payments.Dto; -using TakeoutSaaS.Application.App.Payments.Queries; -using TakeoutSaaS.Domain.Payments.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 支付记录管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/payments")] -public sealed class PaymentsController(IMediator mediator) : BaseApiController -{ - /// - /// 创建支付记录。 - /// - /// 创建的支付记录信息。 - [HttpPost] - [PermissionAuthorize("payment:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreatePaymentCommand command, CancellationToken cancellationToken) - { - // 1. 创建支付记录 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询支付记录列表。 - /// - /// 支付记录分页列表。 - [HttpGet] - [PermissionAuthorize("payment:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] long? orderId, - [FromQuery] PaymentStatus? status, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, - CancellationToken cancellationToken = default) - { - // 1. 组装查询参数并执行查询 - var result = await mediator.Send(new SearchPaymentsQuery - { - OrderId = orderId, - Status = status, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取支付记录详情。 - /// - /// 支付记录详情。 - [HttpGet("{paymentId:long}")] - [PermissionAuthorize("payment:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long paymentId, CancellationToken cancellationToken) - { - // 1. 查询支付记录详情 - var result = await mediator.Send(new GetPaymentByIdQuery { PaymentId = paymentId }, cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "支付记录不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新支付记录。 - /// - /// 更新后的支付记录信息。 - [HttpPut("{paymentId:long}")] - [PermissionAuthorize("payment:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long paymentId, [FromBody] UpdatePaymentCommand command, CancellationToken cancellationToken) - { - // 1. 确保命令包含支付记录标识 - if (command.PaymentId == 0) - { - command = command with { PaymentId = paymentId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "支付记录不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除支付记录。 - /// - /// 删除结果。 - [HttpDelete("{paymentId:long}")] - [PermissionAuthorize("payment:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long paymentId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeletePaymentCommand { PaymentId = paymentId }, cancellationToken); - - // 2. 返回结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "支付记录不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs deleted file mode 100644 index a32d48c..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Identity.Commands; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Application.Identity.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 权限管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/permissions")] -public sealed class PermissionsController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询权限。 - /// - /// - /// 示例:GET /api/admin/v1/permissions?keyword=order&page=1&pageSize=20 - /// - /// 查询条件。 - /// 取消标记。 - /// 权限的分页结果。 - [HttpGet] - [PermissionAuthorize("identity:permission:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Search([FromQuery] SearchPermissionsQuery query, CancellationToken cancellationToken) - { - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 获取权限树。 - /// - /// 关键字(可选)。 - /// 取消标记。 - /// 权限树列表。 - [HttpGet("tree")] - [PermissionAuthorize("identity:permission:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Tree([FromQuery] string? keyword, CancellationToken cancellationToken) - { - // 1. 构造查询对象 - var query = new PermissionTreeQuery { Keyword = keyword }; - - // 2. 查询权限树 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 创建权限。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建的权限。 - [HttpPost] - [PermissionAuthorize("identity:permission:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken) - { - // 1. 权限已固定,禁止新增 - return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止新增")); - } - - /// - /// 更新权限。 - /// - /// 权限 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的权限,未找到时返回 404。 - [HttpPut("{permissionId:long}")] - [PermissionAuthorize("identity:permission:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken) - { - // 1. 权限已固定,禁止修改 - return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止修改")); - } - - /// - /// 删除权限。 - /// - /// 权限 ID。 - /// 取消标记。 - /// 删除结果。 - [HttpDelete("{permissionId:long}")] - [PermissionAuthorize("identity:permission:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Delete(long permissionId, CancellationToken cancellationToken) - { - // 1. 权限已固定,禁止删除 - return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止删除")); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PlatformAnnouncementsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PlatformAnnouncementsController.cs deleted file mode 100644 index e2af709..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PlatformAnnouncementsController.cs +++ /dev/null @@ -1,278 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 平台公告管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/platform/announcements")] -[Route("api/admin/v{version:apiVersion}/platform/announcements")] -public sealed class PlatformAnnouncementsController(IMediator mediator, ITenantContextAccessor tenantContextAccessor) : BaseApiController -{ - /// - /// 创建平台公告。 - /// - /// - /// 示例: - /// - /// POST /api/platform/announcements - /// Header: Authorization: Bearer <JWT> - /// Body: - /// { - /// "title": "平台升级通知", - /// "content": "系统将于今晚 23:00 维护。", - /// "announcementType": 0, - /// "priority": 10, - /// "effectiveFrom": "2025-12-20T00:00:00Z", - /// "effectiveTo": null, - /// "targetType": "all", - /// "targetParameters": null - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "tenantId": "0", - /// "title": "平台升级通知", - /// "status": "Draft" - /// } - /// } - /// - /// - [HttpPost] - [PermissionAuthorize("platform-announcement:create")] - [SwaggerOperation(Summary = "创建平台公告", Description = "需要权限:platform-announcement:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Create([FromBody, Required] CreateTenantAnnouncementCommand command, CancellationToken cancellationToken) - { - command = command with - { - TenantId = 0, - PublisherScope = PublisherScope.Platform - }; - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 查询平台公告列表。 - /// - /// - /// 示例: - /// - /// GET /api/platform/announcements?page=1&pageSize=20&status=Published - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "items": [], - /// "page": 1, - /// "pageSize": 20, - /// "totalCount": 0 - /// } - /// } - /// - /// - [HttpGet] - [PermissionAuthorize("platform-announcement:read", "platform-announcement:create")] - [SwaggerOperation(Summary = "查询平台公告列表", Description = "需要权限:platform-announcement:read 或 platform-announcement:create")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task>> List([FromQuery] GetTenantsAnnouncementsQuery query, CancellationToken cancellationToken) - { - var request = query with { TenantId = 0 }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken)); - return ApiResponse>.Ok(result); - } - - /// - /// 获取平台公告详情。 - /// - /// - /// 示例: - /// - /// GET /api/platform/announcements/900123456789012345 - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "tenantId": "0", - /// "title": "平台升级通知", - /// "status": "Draft" - /// } - /// } - /// - /// - [HttpGet("{announcementId:long}")] - [PermissionAuthorize("platform-announcement:read", "platform-announcement:create")] - [SwaggerOperation(Summary = "获取平台公告详情", Description = "需要权限:platform-announcement:read 或 platform-announcement:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Detail(long announcementId, CancellationToken cancellationToken) - { - var result = await ExecuteAsPlatformAsync(() => - mediator.Send(new GetAnnouncementByIdQuery { TenantId = 0, AnnouncementId = announcementId }, cancellationToken)); - - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新平台公告(仅草稿)。 - /// - /// - /// 示例: - /// - /// PUT /api/platform/announcements/900123456789012345 - /// Body: - /// { - /// "title": "平台升级通知(更新)", - /// "content": "维护时间调整为 23:30。", - /// "targetType": "all", - /// "targetParameters": null, - /// "rowVersion": "AAAAAAAAB9E=" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "status": "Draft" - /// } - /// } - /// - /// - [HttpPut("{announcementId:long}")] - [PermissionAuthorize("platform-announcement:create")] - [SwaggerOperation(Summary = "更新平台公告", Description = "仅草稿可更新;需要权限:platform-announcement:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Update(long announcementId, [FromBody, Required] UpdateTenantAnnouncementCommand command, CancellationToken cancellationToken) - { - command = command with { TenantId = 0, AnnouncementId = announcementId }; - var result = await mediator.Send(command, cancellationToken); - - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 发布平台公告。 - /// - /// - /// 示例: - /// - /// POST /api/platform/announcements/900123456789012345/publish - /// Body: - /// { - /// "rowVersion": "AAAAAAAAB9E=" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "status": "Published" - /// } - /// } - /// - /// - [HttpPost("{announcementId:long}/publish")] - [PermissionAuthorize("platform-announcement:publish")] - [SwaggerOperation(Summary = "发布平台公告", Description = "需要权限:platform-announcement:publish")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Publish(long announcementId, [FromBody, Required] PublishAnnouncementCommand command, CancellationToken cancellationToken) - { - command = command with { AnnouncementId = announcementId }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(command, cancellationToken)); - - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 撤销平台公告。 - /// - /// - /// 示例: - /// - /// POST /api/platform/announcements/900123456789012345/revoke - /// Body: - /// { - /// "rowVersion": "AAAAAAAAB9E=" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "status": "Revoked" - /// } - /// } - /// - /// - [HttpPost("{announcementId:long}/revoke")] - [PermissionAuthorize("platform-announcement:revoke")] - [SwaggerOperation(Summary = "撤销平台公告", Description = "需要权限:platform-announcement:revoke")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Revoke(long announcementId, [FromBody, Required] RevokeAnnouncementCommand command, CancellationToken cancellationToken) - { - command = command with { AnnouncementId = announcementId }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(command, cancellationToken)); - - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - private async Task ExecuteAsPlatformAsync(Func> action) - { - var original = tenantContextAccessor.Current; - tenantContextAccessor.Current = new TenantContext(0, null, "platform"); - try - { - return await action(); - } - finally - { - tenantContextAccessor.Current = original; - } - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs deleted file mode 100644 index 0dfc911..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs +++ /dev/null @@ -1,274 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Products.Commands; -using TakeoutSaaS.Application.App.Products.Dto; -using TakeoutSaaS.Application.App.Products.Queries; -using TakeoutSaaS.Domain.Products.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 商品管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/products")] -public sealed class ProductsController(IMediator mediator) : BaseApiController -{ - /// - /// 创建商品。 - /// - /// 创建的商品信息。 - [HttpPost] - [PermissionAuthorize("product:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateProductCommand command, CancellationToken cancellationToken) - { - // 1. 创建商品 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询商品列表。 - /// - /// 商品分页列表。 - [HttpGet] - [PermissionAuthorize("product:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] long? storeId, - [FromQuery] long? categoryId, - [FromQuery] ProductStatus? status, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, - CancellationToken cancellationToken = default) - { - // 1. 组装查询参数并执行查询 - var result = await mediator.Send(new SearchProductsQuery - { - StoreId = storeId, - CategoryId = categoryId, - Status = status, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取商品详情。 - /// - /// 商品详情。 - [HttpGet("{productId:long}")] - [PermissionAuthorize("product:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long productId, CancellationToken cancellationToken) - { - // 1. 查询商品详情 - var result = await mediator.Send(new GetProductByIdQuery { ProductId = productId }, cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商品不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新商品。 - /// - /// 更新后的商品信息。 - [HttpPut("{productId:long}")] - [PermissionAuthorize("product:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long productId, [FromBody] UpdateProductCommand command, CancellationToken cancellationToken) - { - // 1. 确保命令包含商品标识 - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商品不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除商品。 - /// - /// 删除结果。 - [HttpDelete("{productId:long}")] - [PermissionAuthorize("product:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long productId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteProductCommand { ProductId = productId }, cancellationToken); - - // 2. 返回结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "商品不存在"); - } - - /// - /// 获取商品全量详情。 - /// - [HttpGet("{productId:long}/detail")] - [PermissionAuthorize("product:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> FullDetail(long productId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetProductDetailQuery { ProductId = productId }, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商品不存在") - : ApiResponse.Ok(result); - } - - /// - /// 上架商品。 - /// - [HttpPost("{productId:long}/publish")] - [PermissionAuthorize("product:publish")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Publish(long productId, [FromBody] PublishProductCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商品不存在") - : ApiResponse.Ok(result); - } - - /// - /// 下架商品。 - /// - [HttpPost("{productId:long}/unpublish")] - [PermissionAuthorize("product:publish")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Unpublish(long productId, [FromBody] UnpublishProductCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商品不存在") - : ApiResponse.Ok(result); - } - - /// - /// 替换商品 SKU。 - /// - [HttpPut("{productId:long}/skus")] - [PermissionAuthorize("product-sku:update")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ReplaceSkus(long productId, [FromBody] ReplaceProductSkusCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 替换商品规格。 - /// - [HttpPut("{productId:long}/attributes")] - [PermissionAuthorize("product-attr:update")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ReplaceAttributes(long productId, [FromBody] ReplaceProductAttributesCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 替换商品加料。 - /// - [HttpPut("{productId:long}/addons")] - [PermissionAuthorize("product-addon:update")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ReplaceAddons(long productId, [FromBody] ReplaceProductAddonsCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 替换商品媒资。 - /// - [HttpPut("{productId:long}/media")] - [PermissionAuthorize("product-media:update")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ReplaceMedia(long productId, [FromBody] ReplaceProductMediaCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 替换商品价格策略。 - /// - [HttpPut("{productId:long}/pricing-rules")] - [PermissionAuthorize("product-pricing:update")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ReplacePricingRules(long productId, [FromBody] ReplaceProductPricingRulesCommand command, CancellationToken cancellationToken) - { - if (command.ProductId == 0) - { - command = command with { ProductId = productId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantPackagesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantPackagesController.cs deleted file mode 100644 index 716608d..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantPackagesController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.RateLimiting; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 公共租户套餐查询接口。 -/// -[ApiVersion("1.0")] -[AllowAnonymous] -[EnableRateLimiting("public-self-service")] -[Route("api/public/v{version:apiVersion}/tenant-packages")] -public sealed class PublicTenantPackagesController(IMediator mediator) : BaseApiController -{ - /// - /// 分页获取已启用的租户套餐。 - /// - /// 分页参数。 - /// 取消标记。 - /// 启用套餐的分页列表。 - [HttpGet] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery, Required] GetPublicTenantPackagesQuery query, - CancellationToken cancellationToken) - { - // 1. 执行查询 - var result = await mediator.Send(query, cancellationToken); - // 2. 返回结果 - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantSubscriptionsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantSubscriptionsController.cs deleted file mode 100644 index 3952b9b..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantSubscriptionsController.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.RateLimiting; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 公域租户订阅自助接口(需登录,无权限校验)。 -/// -[ApiVersion("1.0")] -[Authorize] -[EnableRateLimiting("public-self-service")] -[Route("api/public/v{version:apiVersion}/tenants")] -public sealed class PublicTenantSubscriptionsController(IMediator mediator) : BaseApiController -{ - /// - /// 初次绑定租户订阅(默认 0 个月)。 - /// - /// 租户 ID。 - /// 绑定请求。 - /// 取消标记。 - /// 绑定后的订阅信息。 - [HttpPost("{tenantId:long}/subscriptions/initial")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - public async Task> BindInitialSubscription( - long tenantId, - [FromBody, Required] BindInitialTenantSubscriptionCommand body, - CancellationToken cancellationToken) - { - // 1. 合并路由租户标识 - var command = body with { TenantId = tenantId }; - - // 2. 执行初次订阅绑定 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回绑定结果 - return ApiResponse.Ok(result); - } -} - diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantsController.cs deleted file mode 100644 index 05e7764..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PublicTenantsController.cs +++ /dev/null @@ -1,76 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.RateLimiting; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 公域租户自助入住接口。 -/// -[ApiVersion("1.0")] -[AllowAnonymous] -[EnableRateLimiting("public-self-service")] -[Route("api/public/v{version:apiVersion}/tenants")] -public sealed class PublicTenantsController(IMediator mediator) : BaseApiController -{ - /// - /// 自助注册租户并生成初始管理员。 - /// - /// 自助注册命令。 - /// 取消标记。 - /// 注册结果(含临时密码)。 - [HttpPost("self-register")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> SelfRegister( - [FromBody, Required] SelfRegisterTenantCommand command, - CancellationToken cancellationToken) - { - // 1. 执行自助注册 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 自助提交或更新实名资料。 - /// - /// 租户 ID。 - /// 实名资料。 - /// 取消标记。 - /// 实名资料结果。 - [HttpPost("{tenantId:long}/verification")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> SubmitVerification( - long tenantId, - [FromBody, Required] SubmitTenantVerificationCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定租户 ID - var merged = command with { TenantId = tenantId }; - // 2. 提交实名 - var result = await mediator.Send(merged, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 查询租户入住进度。 - /// - /// 租户 ID。 - /// 取消标记。 - /// 入住进度。 - [HttpGet("{tenantId:long}/status")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Progress(long tenantId, CancellationToken cancellationToken) - { - // 1. 查询进度 - var query = new GetTenantProgressQuery { TenantId = tenantId }; - var result = await mediator.Send(query, cancellationToken); - return ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/QuotaPackagesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/QuotaPackagesController.cs deleted file mode 100644 index b6a3ee2..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/QuotaPackagesController.cs +++ /dev/null @@ -1,198 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.QuotaPackages.Commands; -using TakeoutSaaS.Application.App.QuotaPackages.Dto; -using TakeoutSaaS.Application.App.QuotaPackages.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 配额包管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/quota-packages")] -public sealed class QuotaPackagesController(IMediator mediator) : BaseApiController -{ - /// - /// 配额包列表。 - /// - /// 查询条件。 - /// 取消标记。 - /// 配额包分页结果。 - [HttpGet] - [PermissionAuthorize("quota-package:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] GetQuotaPackageListQuery query, CancellationToken cancellationToken) - { - // 1. 查询配额包分页 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 创建配额包。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建后的配额包。 - [HttpPost] - [PermissionAuthorize("quota-package:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreateQuotaPackageCommand command, CancellationToken cancellationToken) - { - // 1. 执行创建 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 更新配额包。 - /// - /// 配额包 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的配额包或未找到。 - [HttpPut("{quotaPackageId:long}")] - [PermissionAuthorize("quota-package:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long quotaPackageId, [FromBody, Required] UpdateQuotaPackageCommand command, CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { QuotaPackageId = quotaPackageId }; - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "配额包不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除配额包。 - /// - /// 配额包 ID。 - /// 取消标记。 - /// 删除结果。 - [HttpDelete("{quotaPackageId:long}")] - [PermissionAuthorize("quota-package:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Delete(long quotaPackageId, CancellationToken cancellationToken) - { - // 1. 构建删除命令 - var command = new DeleteQuotaPackageCommand { QuotaPackageId = quotaPackageId }; - - // 2. 执行删除并返回 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 上架/下架配额包。 - /// - /// 配额包 ID。 - /// 状态更新命令。 - /// 取消标记。 - /// 更新结果。 - [HttpPut("{quotaPackageId:long}/status")] - [PermissionAuthorize("quota-package:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateStatus(long quotaPackageId, [FromBody, Required] UpdateQuotaPackageStatusCommand command, CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { QuotaPackageId = quotaPackageId }; - - // 2. 执行状态更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 为租户购买配额包。 - /// - /// 租户 ID。 - /// 购买命令。 - /// 取消标记。 - /// 购买记录。 - [HttpPost("~/api/admin/v{version:apiVersion}/tenants/{tenantId:long}/quota-packages")] - [PermissionAuthorize("tenant:quota:purchase")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> PurchaseForTenant( - long tenantId, - [FromBody, Required] PurchaseQuotaPackageCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定租户 ID - command = command with { TenantId = tenantId }; - - // 2. 执行购买 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回购买结果 - return ApiResponse.Ok(result); - } - - /// - /// 租户配额使用情况。 - /// - /// 租户 ID。 - /// 查询条件。 - /// 取消标记。 - /// 配额使用情况列表。 - [HttpGet("~/api/admin/v{version:apiVersion}/tenants/{tenantId:long}/quota-usage")] - [PermissionAuthorize("tenant:quota:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetTenantQuotaUsage( - long tenantId, - [FromQuery] GetTenantQuotaUsageQuery query, - CancellationToken cancellationToken) - { - // 1. 绑定租户 ID - query = query with { TenantId = tenantId }; - - // 2. 查询配额使用情况 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 租户配额购买记录。 - /// - /// 租户 ID。 - /// 查询条件。 - /// 取消标记。 - /// 购买记录分页结果。 - [HttpGet("~/api/admin/v{version:apiVersion}/tenants/{tenantId:long}/quota-purchases")] - [PermissionAuthorize("tenant:quota:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetTenantQuotaPurchases( - long tenantId, - [FromQuery] GetTenantQuotaPurchasesQuery query, - CancellationToken cancellationToken) - { - // 1. 绑定租户 ID - query = query with { TenantId = tenantId }; - - // 2. 查询购买记录 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回结果 - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/RoleTemplatesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/RoleTemplatesController.cs deleted file mode 100644 index f5853ac..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/RoleTemplatesController.cs +++ /dev/null @@ -1,217 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.Identity.Commands; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Application.Identity.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 角色模板管理(平台蓝本)。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/role-templates")] -public sealed class RoleTemplatesController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询角色模板。 - /// - /// 是否启用筛选。 - /// 取消标记。 - /// 角色模板列表。 - [HttpGet] - [PermissionAuthorize("identity:role:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] bool? isActive, CancellationToken cancellationToken) - { - // 1. 构造查询参数 - var query = new ListRoleTemplatesQuery { IsActive = isActive }; - - // 2. 查询模板集合 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回模板列表 - return ApiResponse>.Ok(result); - } - - /// - /// 克隆角色模板。 - /// - /// 源模板编码。 - /// 克隆命令。 - /// 取消标记。 - /// 新模板详情。 - [HttpPost("{templateCode}/clone")] - [PermissionAuthorize("role-template:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Clone( - string templateCode, - [FromBody, Required] CloneRoleTemplateCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定源模板编码 - command = command with { SourceTemplateCode = templateCode }; - - // 2. 执行克隆 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回新模板 - return ApiResponse.Ok(result); - } - - /// - /// 获取角色模板详情。 - /// - /// 模板编码。 - /// 取消标记。 - /// 角色模板详情。 - [HttpGet("{templateCode}")] - [PermissionAuthorize("identity:role:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(string templateCode, CancellationToken cancellationToken) - { - // 1. 查询模板详情 - var result = await mediator.Send(new GetRoleTemplateQuery { TemplateCode = templateCode }, cancellationToken); - - // 2. 返回模板或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "角色模板不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建角色模板。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建后的模板。 - [HttpPost] - [PermissionAuthorize("role-template:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreateRoleTemplateCommand command, CancellationToken cancellationToken) - { - // 1. 创建模板 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 获取模板的权限列表。 - /// - /// 模板编码。 - /// 取消标记。 - /// 权限集合。 - [HttpGet("{templateCode}/permissions")] - [PermissionAuthorize("identity:role:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetPermissions(string templateCode, CancellationToken cancellationToken) - { - // 1. 查询模板权限 - var result = await mediator.Send(new RoleTemplatePermissionsQuery { TemplateCode = templateCode }, cancellationToken); - - // 2. 返回权限集合 - return ApiResponse>.Ok(result); - } - - /// - /// 更新角色模板。 - /// - /// 模板编码。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的模板。 - [HttpPut("{templateCode}")] - [PermissionAuthorize("role-template:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update( - string templateCode, - [FromBody, Required] UpdateRoleTemplateCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定模板编码 - command = command with { TemplateCode = templateCode }; - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "角色模板不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除角色模板。 - /// - /// 模板编码。 - /// 取消标记。 - /// 删除结果。 - [HttpDelete("{templateCode}")] - [PermissionAuthorize("role-template:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Delete(string templateCode, CancellationToken cancellationToken) - { - // 1. 执行删除 - var result = await mediator.Send(new DeleteRoleTemplateCommand { TemplateCode = templateCode }, cancellationToken); - - // 2. 返回执行结果 - return ApiResponse.Ok(result); - } - - /// - /// 为当前租户批量初始化预置角色模板。 - /// - /// 初始化命令。 - /// 取消标记。 - /// 生成的租户角色列表。 - [HttpPost("init")] - [PermissionAuthorize("identity:role:create")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Initialize( - [FromBody] InitializeRoleTemplatesCommand? command, - CancellationToken cancellationToken) - { - // 1. 确保命令存在 - command ??= new InitializeRoleTemplatesCommand(); - - // 2. 初始化模板到租户 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回新建的角色列表 - return ApiResponse>.Ok(result); - } - - /// - /// 将单个模板初始化到当前租户。 - /// - /// 模板编码。 - /// 取消标记。 - /// 生成的角色列表。 - [HttpPost("{templateCode}/initialize-tenant")] - [PermissionAuthorize("identity:role:create")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> InitializeSingle(string templateCode, CancellationToken cancellationToken) - { - // 1. 构造初始化命令 - var command = new InitializeRoleTemplatesCommand - { - TemplateCodes = new[] { templateCode } - }; - - // 2. 初始化模板到租户 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回生成的角色列表 - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StatisticsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StatisticsController.cs deleted file mode 100644 index cf4dd7f..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StatisticsController.cs +++ /dev/null @@ -1,96 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Statistics.Dto; -using TakeoutSaaS.Application.App.Statistics.Queries; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 统计数据接口。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/statistics")] -public sealed class StatisticsController(IMediator mediator) : BaseApiController -{ - /// - /// 获取订阅概览统计。 - /// - /// 取消标记。 - /// 订阅概览数据。 - [HttpGet("subscription-overview")] - [PermissionAuthorize("statistics:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetSubscriptionOverview(CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetSubscriptionOverviewQuery(), cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 获取配额使用排行。 - /// - /// 配额类型。 - /// 返回前N条记录,默认10。 - /// 取消标记。 - /// 配额使用排行数据。 - [HttpGet("quota-ranking")] - [PermissionAuthorize("statistics:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetQuotaRanking( - [FromQuery] TenantQuotaType quotaType, - [FromQuery] int topN = 10, - CancellationToken cancellationToken = default) - { - var query = new GetQuotaUsageRankingQuery { QuotaType = quotaType, TopN = topN }; - var result = await mediator.Send(query, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 获取收入统计。 - /// - /// 统计月份数量,默认12个月。 - /// 取消标记。 - /// 收入统计数据。 - [HttpGet("revenue")] - [PermissionAuthorize("statistics:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetRevenue( - [FromQuery] int monthsCount = 12, - CancellationToken cancellationToken = default) - { - var query = new GetRevenueStatisticsQuery { MonthsCount = monthsCount }; - var result = await mediator.Send(query, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 获取即将到期的订阅列表。 - /// - /// 筛选天数,默认7天内到期。 - /// 是否只返回未开启自动续费的订阅。 - /// 取消标记。 - /// 即将到期的订阅列表。 - [HttpGet("expiring-subscriptions")] - [PermissionAuthorize("statistics:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetExpiringSubscriptions( - [FromQuery] int daysAhead = 7, - [FromQuery] bool onlyWithoutAutoRenew = false, - CancellationToken cancellationToken = default) - { - var query = new GetExpiringSubscriptionsQuery - { - DaysAhead = daysAhead, - OnlyWithoutAutoRenew = onlyWithoutAutoRenew - }; - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreAuditsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreAuditsController.cs deleted file mode 100644 index ab376b0..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreAuditsController.cs +++ /dev/null @@ -1,222 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.StoreAudits.Commands; -using TakeoutSaaS.Application.App.StoreAudits.Dto; -using TakeoutSaaS.Application.App.StoreAudits.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店审核与风控管理(平台)。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/platform/store-audits")] -[Route("api/admin/v{version:apiVersion}/platform/store-audits")] -public sealed class StoreAuditsController(IMediator mediator, ITenantContextAccessor tenantContextAccessor) : BaseApiController -{ - /// - /// 查询待审核门店列表。 - /// - /// 待审核门店分页列表。 - [HttpGet("pending")] - [PermissionAuthorize("store-audit:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListPending( - [FromQuery] ListPendingStoreAuditsQuery query, - CancellationToken cancellationToken) - { - // 1. 查询待审核门店列表 - var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken)); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取门店审核详情。 - /// - /// 门店 ID。 - /// 取消标记。 - /// 审核详情。 - [HttpGet("{storeId:long}")] - [PermissionAuthorize("store-audit:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> GetDetail(long storeId, CancellationToken cancellationToken) - { - // 1. 获取审核详情 - var result = await ExecuteAsPlatformAsync(() => - mediator.Send(new GetStoreAuditDetailQuery { StoreId = storeId }, cancellationToken)); - - // 2. 返回详情或未找到 - return result is null - ? ApiResponse.Error(ErrorCodes.NotFound, "门店不存在") - : ApiResponse.Ok(result); - } - - /// - /// 审核通过。 - /// - /// 门店 ID。 - /// 审核命令。 - /// 取消标记。 - /// 操作结果。 - [HttpPost("{storeId:long}/approve")] - [PermissionAuthorize("store-audit:approve")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Approve( - long storeId, - [FromBody, Required] ApproveStoreCommand command, - CancellationToken cancellationToken) - { - // 1. 执行审核通过 - var request = command with { StoreId = storeId }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken)); - - // 2. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 审核驳回。 - /// - /// 门店 ID。 - /// 驳回命令。 - /// 取消标记。 - /// 操作结果。 - [HttpPost("{storeId:long}/reject")] - [PermissionAuthorize("store-audit:reject")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Reject( - long storeId, - [FromBody, Required] RejectStoreCommand command, - CancellationToken cancellationToken) - { - // 1. 执行审核驳回 - var request = command with { StoreId = storeId }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken)); - - // 2. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询审核记录。 - /// - /// 门店 ID。 - /// 页码。 - /// 每页数量。 - /// 取消标记。 - /// 审核记录分页列表。 - [HttpGet("{storeId:long}/records")] - [PermissionAuthorize("store-audit:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListRecords( - long storeId, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - CancellationToken cancellationToken = default) - { - // 1. 执行记录查询 - var query = new ListStoreAuditRecordsQuery - { - StoreId = storeId, - Page = page, - PageSize = pageSize - }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken)); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取审核统计数据。 - /// - /// 查询参数。 - /// 取消标记。 - /// 统计数据。 - [HttpGet("statistics")] - [PermissionAuthorize("store-audit:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetStatistics( - [FromQuery] GetStoreAuditStatisticsQuery query, - CancellationToken cancellationToken) - { - // 1. 执行统计查询 - var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken)); - - // 2. 返回统计结果 - return ApiResponse.Ok(result); - } - - /// - /// 强制关闭门店。 - /// - /// 门店 ID。 - /// 关闭命令。 - /// 取消标记。 - /// 操作结果。 - [HttpPost("{storeId:long}/force-close")] - [PermissionAuthorize("store-audit:force-close")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ForceClose( - long storeId, - [FromBody, Required] ForceCloseStoreCommand command, - CancellationToken cancellationToken) - { - // 1. 执行强制关闭 - var request = command with { StoreId = storeId }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken)); - - // 2. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 解除强制关闭。 - /// - /// 门店 ID。 - /// 解除命令。 - /// 取消标记。 - /// 操作结果。 - [HttpPost("{storeId:long}/reopen")] - [PermissionAuthorize("store-audit:force-close")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Reopen( - long storeId, - [FromBody, Required] ReopenStoreCommand command, - CancellationToken cancellationToken) - { - // 1. 执行解除强制关闭 - var request = command with { StoreId = storeId }; - var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken)); - - // 2. 返回结果 - return ApiResponse.Ok(result); - } - - private async Task ExecuteAsPlatformAsync(Func> action) - { - var original = tenantContextAccessor.Current; - tenantContextAccessor.Current = new TenantContext(0, null, "platform"); - - // 1. (空行后) 切换到平台上下文执行 - try - { - return await action(); - } - finally - { - tenantContextAccessor.Current = original; - } - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StorePickupController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StorePickupController.cs deleted file mode 100644 index f07addd..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StorePickupController.cs +++ /dev/null @@ -1,114 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Commands; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店自提管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores/{storeId:long}/pickup")] -public sealed class StorePickupController(IMediator mediator) : BaseApiController -{ - /// - /// 获取自提配置。 - /// - [HttpGet("settings")] - [PermissionAuthorize("pickup-setting:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetSetting(long storeId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new GetStorePickupSettingQuery { StoreId = storeId }, cancellationToken); - return result is null - ? ApiResponse.Error(ErrorCodes.NotFound, "未配置自提设置") - : ApiResponse.Ok(result); - } - - /// - /// 更新自提配置。 - /// - [HttpPut("settings")] - [PermissionAuthorize("pickup-setting:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpsertSetting(long storeId, [FromBody] UpsertStorePickupSettingCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 查询档期列表。 - /// - [HttpGet("slots")] - [PermissionAuthorize("pickup-slot:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListSlots(long storeId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStorePickupSlotsQuery { StoreId = storeId }, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 创建档期。 - /// - [HttpPost("slots")] - [PermissionAuthorize("pickup-slot:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateSlot(long storeId, [FromBody] CreateStorePickupSlotCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新档期。 - /// - [HttpPut("slots/{slotId:long}")] - [PermissionAuthorize("pickup-slot:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateSlot(long storeId, long slotId, [FromBody] UpdateStorePickupSlotCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.SlotId == 0) - { - command = command with { StoreId = storeId, SlotId = slotId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result is null - ? ApiResponse.Error(ErrorCodes.NotFound, "档期不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除档期。 - /// - [HttpDelete("slots/{slotId:long}")] - [PermissionAuthorize("pickup-slot:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> DeleteSlot(long storeId, long slotId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStorePickupSlotCommand { StoreId = storeId, SlotId = slotId }, cancellationToken); - return success ? ApiResponse.Ok(null) : ApiResponse.Error(ErrorCodes.NotFound, "档期不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreQualificationsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreQualificationsController.cs deleted file mode 100644 index ce56b25..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreQualificationsController.cs +++ /dev/null @@ -1,60 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店资质预警(平台)。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/platform/store-qualifications")] -[Route("api/admin/v{version:apiVersion}/platform/store-qualifications")] -public sealed class StoreQualificationsController( - IMediator mediator, - ITenantContextAccessor tenantContextAccessor) - : BaseApiController -{ - /// - /// 查询资质即将过期/已过期列表。 - /// - /// 查询参数。 - /// 取消标记。 - /// 资质预警分页结果。 - [HttpGet("expiring")] - [PermissionAuthorize("store-qualification:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ListExpiring( - [FromQuery] ListExpiringStoreQualificationsQuery query, - CancellationToken cancellationToken) - { - // 1. 查询资质预警 - var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken)); - - // 2. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - private async Task ExecuteAsPlatformAsync(Func> action) - { - var original = tenantContextAccessor.Current; - tenantContextAccessor.Current = new TenantContext(0, null, "platform"); - - // 1. (空行后) 切换到平台上下文执行 - try - { - return await action(); - } - finally - { - tenantContextAccessor.Current = original; - } - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreShiftsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreShiftsController.cs deleted file mode 100644 index 4b6fa2d..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreShiftsController.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Collections.Generic; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Commands; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店排班管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores/{storeId:long}/shifts")] -public sealed class StoreShiftsController(IMediator mediator) : BaseApiController -{ - /// - /// 查询排班(默认未来 7 天)。 - /// - [HttpGet] - [PermissionAuthorize("store-shift:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - long storeId, - [FromQuery] DateTime? from, - [FromQuery] DateTime? to, - [FromQuery] long? staffId, - CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreEmployeeShiftsQuery - { - StoreId = storeId, - From = from, - To = to, - StaffId = staffId - }, cancellationToken); - - return ApiResponse>.Ok(result); - } - - /// - /// 创建排班。 - /// - [HttpPost] - [PermissionAuthorize("store-shift:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create(long storeId, [FromBody] CreateStoreEmployeeShiftCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新排班。 - /// - [HttpPut("{shiftId:long}")] - [PermissionAuthorize("store-shift:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long storeId, long shiftId, [FromBody] UpdateStoreEmployeeShiftCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.ShiftId == 0) - { - command = command with { StoreId = storeId, ShiftId = shiftId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "排班不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除排班。 - /// - [HttpDelete("{shiftId:long}")] - [PermissionAuthorize("store-shift:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long storeId, long shiftId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreEmployeeShiftCommand { StoreId = storeId, ShiftId = shiftId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "排班不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreStaffsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreStaffsController.cs deleted file mode 100644 index f578d05..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreStaffsController.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Collections.Generic; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Commands; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Domain.Merchants.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店员工管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores/{storeId:long}/staffs")] -public sealed class StoreStaffsController(IMediator mediator) : BaseApiController -{ - /// - /// 查询门店员工列表。 - /// - [HttpGet] - [PermissionAuthorize("store-staff:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - long storeId, - [FromQuery] StaffRoleType? role, - [FromQuery] StaffStatus? status, - CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreStaffQuery - { - StoreId = storeId, - RoleType = role, - Status = status - }, cancellationToken); - - return ApiResponse>.Ok(result); - } - - /// - /// 创建门店员工。 - /// - [HttpPost] - [PermissionAuthorize("store-staff:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create(long storeId, [FromBody] CreateStoreStaffCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新门店员工。 - /// - [HttpPut("{staffId:long}")] - [PermissionAuthorize("store-staff:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long storeId, long staffId, [FromBody] UpdateStoreStaffCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.StaffId == 0) - { - command = command with { StoreId = storeId, StaffId = staffId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "员工不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除门店员工。 - /// - [HttpDelete("{staffId:long}")] - [PermissionAuthorize("store-staff:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long storeId, long staffId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreStaffCommand { StoreId = storeId, StaffId = staffId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "员工不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTableAreasController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTableAreasController.cs deleted file mode 100644 index 26275da..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTableAreasController.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Generic; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Commands; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店桌台区域管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores/{storeId:long}/table-areas")] -public sealed class StoreTableAreasController(IMediator mediator) : BaseApiController -{ - /// - /// 查询区域列表。 - /// - [HttpGet] - [PermissionAuthorize("store-table-area:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List(long storeId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreTableAreasQuery { StoreId = storeId }, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 创建区域。 - /// - [HttpPost] - [PermissionAuthorize("store-table-area:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create(long storeId, [FromBody] CreateStoreTableAreaCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新区域。 - /// - [HttpPut("{areaId:long}")] - [PermissionAuthorize("store-table-area:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long storeId, long areaId, [FromBody] UpdateStoreTableAreaCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.AreaId == 0) - { - command = command with { StoreId = storeId, AreaId = areaId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "桌台区域不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除区域。 - /// - [HttpDelete("{areaId:long}")] - [PermissionAuthorize("store-table-area:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long storeId, long areaId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreTableAreaCommand { StoreId = storeId, AreaId = areaId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "桌台区域不存在或不可删除"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTablesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTablesController.cs deleted file mode 100644 index 01af828..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoreTablesController.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Commands; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Domain.Stores.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店桌码管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores/{storeId:long}/tables")] -public sealed class StoreTablesController(IMediator mediator) : BaseApiController -{ - /// - /// 查询桌码列表。 - /// - [HttpGet] - [PermissionAuthorize("store-table:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - long storeId, - [FromQuery] long? areaId, - [FromQuery] StoreTableStatus? status, - CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreTablesQuery - { - StoreId = storeId, - AreaId = areaId, - Status = status - }, cancellationToken); - - return ApiResponse>.Ok(result); - } - - /// - /// 批量生成桌码。 - /// - [HttpPost] - [PermissionAuthorize("store-table:create")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Generate(long storeId, [FromBody] GenerateStoreTablesCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 更新桌码。 - /// - [HttpPut("{tableId:long}")] - [PermissionAuthorize("store-table:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long storeId, long tableId, [FromBody] UpdateStoreTableCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.TableId == 0) - { - command = command with { StoreId = storeId, TableId = tableId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "桌码不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除桌码。 - /// - [HttpDelete("{tableId:long}")] - [PermissionAuthorize("store-table:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long storeId, long tableId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreTableCommand { StoreId = storeId, TableId = tableId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "桌码不存在"); - } - - /// - /// 导出桌码二维码 ZIP。 - /// - [HttpPost("export")] - [PermissionAuthorize("store-table:export")] - [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task Export(long storeId, [FromBody] ExportStoreTableQRCodesQuery query, CancellationToken cancellationToken) - { - if (query.StoreId == 0) - { - query = query with { StoreId = storeId }; - } - - var result = await mediator.Send(query, cancellationToken); - if (result is null) - { - return Ok(ApiResponse.Error(ErrorCodes.NotFound, "未找到可导出的桌码")); - } - - return File(result.Content, result.ContentType, result.FileName); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs deleted file mode 100644 index 5bb3b1d..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs +++ /dev/null @@ -1,581 +0,0 @@ -using System.Collections.Generic; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Stores.Commands; -using TakeoutSaaS.Application.App.Stores.Dto; -using TakeoutSaaS.Application.App.Stores.Queries; -using TakeoutSaaS.Domain.Stores.Enums; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 门店管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/stores")] -public sealed class StoresController(IMediator mediator) : BaseApiController -{ - /// - /// 创建门店。 - /// - /// 创建的门店信息。 - [HttpPost] - [PermissionAuthorize("store:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateStoreCommand command, CancellationToken cancellationToken) - { - // 1. 创建门店 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询门店列表。 - /// - /// 门店分页列表。 - [HttpGet] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] long? merchantId, - [FromQuery] StoreStatus? status, - [FromQuery] StoreAuditStatus? auditStatus, - [FromQuery] StoreBusinessStatus? businessStatus, - [FromQuery] StoreOwnershipType? ownershipType, - [FromQuery] string? keyword, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, - CancellationToken cancellationToken = default) - { - // 1. 组装查询参数并执行查询 - var result = await mediator.Send(new SearchStoresQuery - { - MerchantId = merchantId, - Status = status, - AuditStatus = auditStatus, - BusinessStatus = businessStatus, - OwnershipType = ownershipType, - Keyword = keyword, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取门店详情。 - /// - /// 门店详情。 - [HttpGet("{storeId:long}")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long storeId, CancellationToken cancellationToken) - { - // 1. 查询门店详情 - var result = await mediator.Send(new GetStoreByIdQuery { StoreId = storeId }, cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "门店不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新门店。 - /// - /// 更新后的门店信息。 - [HttpPut("{storeId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long storeId, [FromBody] UpdateStoreCommand command, CancellationToken cancellationToken) - { - // 1. 确保命令包含门店标识 - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "门店不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除门店。 - /// - /// 删除结果。 - [HttpDelete("{storeId:long}")] - [PermissionAuthorize("store:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long storeId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteStoreCommand { StoreId = storeId }, cancellationToken); - - // 2. 返回结果或 404 - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "门店不存在"); - } - - /// - /// 提交门店审核。 - /// - [HttpPost("{storeId:long}/submit")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> SubmitAudit(long storeId, [FromBody] SubmitStoreAuditCommand command, CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - // 2. (空行后) 执行提交 - var result = await mediator.Send(command, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 切换门店经营状态。 - /// - [HttpPost("{storeId:long}/business-status")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ToggleBusinessStatus(long storeId, [FromBody] ToggleBusinessStatusCommand command, CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - // 2. (空行后) 执行切换 - var result = await mediator.Send(command, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询门店资质列表。 - /// - [HttpGet("{storeId:long}/qualifications")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListQualifications(long storeId, CancellationToken cancellationToken) - { - // 1. 查询资质列表 - var result = await mediator.Send(new ListStoreQualificationsQuery { StoreId = storeId }, cancellationToken); - - // 2. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 检查门店资质完整性。 - /// - [HttpGet("{storeId:long}/qualifications/check")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CheckQualifications(long storeId, CancellationToken cancellationToken) - { - // 1. 执行检查 - var result = await mediator.Send(new CheckStoreQualificationsQuery { StoreId = storeId }, cancellationToken); - - // 2. 返回检查结果 - return ApiResponse.Ok(result); - } - - /// - /// 新增门店资质。 - /// - [HttpPost("{storeId:long}/qualifications")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateQualification(long storeId, [FromBody] CreateStoreQualificationCommand command, CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - // 2. (空行后) 执行创建 - var result = await mediator.Send(command, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 更新门店资质。 - /// - [HttpPut("{storeId:long}/qualifications/{qualificationId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateQualification( - long storeId, - long qualificationId, - [FromBody] UpdateStoreQualificationCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定资质 ID - if (command.StoreId == 0 || command.QualificationId == 0) - { - command = command with { StoreId = storeId, QualificationId = qualificationId }; - } - - // 2. (空行后) 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. (空行后) 返回结果或 404 - return result is null - ? ApiResponse.Error(ErrorCodes.NotFound, "资质不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除门店资质。 - /// - [HttpDelete("{storeId:long}/qualifications/{qualificationId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> DeleteQualification(long storeId, long qualificationId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var result = await mediator.Send(new DeleteStoreQualificationCommand - { - StoreId = storeId, - QualificationId = qualificationId - }, cancellationToken); - - // 2. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 批量更新营业时段。 - /// - [HttpPut("{storeId:long}/business-hours/batch")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> BatchUpdateBusinessHours( - long storeId, - [FromBody] BatchUpdateBusinessHoursCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - // 2. (空行后) 执行批量更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 查询门店营业时段。 - /// - [HttpGet("{storeId:long}/business-hours")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListBusinessHours(long storeId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreBusinessHoursQuery { StoreId = storeId }, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 新增营业时段。 - /// - [HttpPost("{storeId:long}/business-hours")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateBusinessHour(long storeId, [FromBody] CreateStoreBusinessHourCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新营业时段。 - /// - [HttpPut("{storeId:long}/business-hours/{businessHourId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateBusinessHour(long storeId, long businessHourId, [FromBody] UpdateStoreBusinessHourCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.BusinessHourId == 0) - { - command = command with { StoreId = storeId, BusinessHourId = businessHourId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "营业时段不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除营业时段。 - /// - [HttpDelete("{storeId:long}/business-hours/{businessHourId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> DeleteBusinessHour(long storeId, long businessHourId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreBusinessHourCommand { StoreId = storeId, BusinessHourId = businessHourId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "营业时段不存在"); - } - - /// - /// 查询配送区域。 - /// - [HttpGet("{storeId:long}/delivery-zones")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListDeliveryZones(long storeId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreDeliveryZonesQuery { StoreId = storeId }, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 新增配送区域。 - /// - [HttpPost("{storeId:long}/delivery-zones")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateDeliveryZone(long storeId, [FromBody] CreateStoreDeliveryZoneCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新配送区域。 - /// - [HttpPut("{storeId:long}/delivery-zones/{deliveryZoneId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateDeliveryZone(long storeId, long deliveryZoneId, [FromBody] UpdateStoreDeliveryZoneCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.DeliveryZoneId == 0) - { - command = command with { StoreId = storeId, DeliveryZoneId = deliveryZoneId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "配送区域不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除配送区域。 - /// - [HttpDelete("{storeId:long}/delivery-zones/{deliveryZoneId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> DeleteDeliveryZone(long storeId, long deliveryZoneId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreDeliveryZoneCommand { StoreId = storeId, DeliveryZoneId = deliveryZoneId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "配送区域不存在"); - } - - /// - /// 配送范围检测。 - /// - [HttpPost("{storeId:long}/delivery-check")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CheckDeliveryZone( - long storeId, - [FromBody] CheckStoreDeliveryZoneQuery query, - CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (query.StoreId == 0) - { - query = query with { StoreId = storeId }; - } - - // 2. (空行后) 执行检测 - var result = await mediator.Send(query, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 获取门店费用配置。 - /// - [HttpGet("{storeId:long}/fee")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetFee(long storeId, CancellationToken cancellationToken) - { - // 1. 查询费用配置 - var result = await mediator.Send(new GetStoreFeeQuery { StoreId = storeId }, cancellationToken); - - // 2. 返回结果 - return ApiResponse.Ok(result ?? new StoreFeeDto()); - } - - /// - /// 更新门店费用配置。 - /// - [HttpPut("{storeId:long}/fee")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateFee(long storeId, [FromBody] UpdateStoreFeeCommand command, CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - // 2. (空行后) 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 门店费用预览。 - /// - [HttpPost("{storeId:long}/fee/calculate")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CalculateFee( - long storeId, - [FromBody] CalculateStoreFeeQuery query, - CancellationToken cancellationToken) - { - // 1. 绑定门店 ID - if (query.StoreId == 0) - { - query = query with { StoreId = storeId }; - } - - // 2. (空行后) 执行计算 - var result = await mediator.Send(query, cancellationToken); - - // 3. (空行后) 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询门店节假日。 - /// - [HttpGet("{storeId:long}/holidays")] - [PermissionAuthorize("store:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> ListHolidays(long storeId, CancellationToken cancellationToken) - { - var result = await mediator.Send(new ListStoreHolidaysQuery { StoreId = storeId }, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 新增节假日配置。 - /// - [HttpPost("{storeId:long}/holidays")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateHoliday(long storeId, [FromBody] CreateStoreHolidayCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0) - { - command = command with { StoreId = storeId }; - } - - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新节假日配置。 - /// - [HttpPut("{storeId:long}/holidays/{holidayId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateHoliday(long storeId, long holidayId, [FromBody] UpdateStoreHolidayCommand command, CancellationToken cancellationToken) - { - if (command.StoreId == 0 || command.HolidayId == 0) - { - command = command with { StoreId = storeId, HolidayId = holidayId }; - } - - var result = await mediator.Send(command, cancellationToken); - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "节假日配置不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除节假日配置。 - /// - [HttpDelete("{storeId:long}/holidays/{holidayId:long}")] - [PermissionAuthorize("store:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> DeleteHoliday(long storeId, long holidayId, CancellationToken cancellationToken) - { - var success = await mediator.Send(new DeleteStoreHolidayCommand { StoreId = storeId, HolidayId = holidayId }, cancellationToken); - return success - ? ApiResponse.Ok(null) - : ApiResponse.Error(ErrorCodes.NotFound, "节假日配置不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/SubscriptionsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/SubscriptionsController.cs deleted file mode 100644 index 75336d1..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/SubscriptionsController.cs +++ /dev/null @@ -1,210 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Subscriptions.Commands; -using TakeoutSaaS.Application.App.Subscriptions.Dto; -using TakeoutSaaS.Application.App.Subscriptions.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 订阅管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/subscriptions")] -public sealed class SubscriptionsController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询订阅列表(支持按状态、套餐、到期时间筛选)。 - /// - /// 查询条件。 - /// 取消标记。 - /// 订阅分页结果。 - [HttpGet] - [PermissionAuthorize("subscription:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] GetSubscriptionListQuery query, - CancellationToken cancellationToken) - { - // 1. 查询订阅分页 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 查看订阅详情(含套餐信息、配额使用、变更历史)。 - /// - /// 订阅 ID。 - /// 取消标记。 - /// 订阅详情或未找到。 - [HttpGet("{subscriptionId:long}")] - [PermissionAuthorize("subscription:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail( - long subscriptionId, - CancellationToken cancellationToken) - { - // 1. 查询订阅详情 - var result = await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscriptionId }, cancellationToken); - - // 2. 返回查询结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新订阅基础信息(备注、自动续费等)。 - /// - /// 订阅 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的订阅详情或未找到。 - [HttpPut("{subscriptionId:long}")] - [PermissionAuthorize("subscription:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update( - long subscriptionId, - [FromBody, Required] UpdateSubscriptionCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { SubscriptionId = subscriptionId }; - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") - : ApiResponse.Ok(result); - } - - /// - /// 延期订阅(增加订阅时长)。 - /// - /// 订阅 ID。 - /// 延期命令。 - /// 取消标记。 - /// 延期后的订阅详情或未找到。 - [HttpPost("{subscriptionId:long}/extend")] - [PermissionAuthorize("subscription:extend")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Extend( - long subscriptionId, - [FromBody, Required] ExtendSubscriptionCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { SubscriptionId = subscriptionId }; - - // 2. 执行延期 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回延期结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") - : ApiResponse.Ok(result); - } - - /// - /// 变更套餐(支持立即生效或下周期生效)。 - /// - /// 订阅 ID。 - /// 变更套餐命令。 - /// 取消标记。 - /// 变更后的订阅详情或未找到。 - [HttpPost("{subscriptionId:long}/change-plan")] - [PermissionAuthorize("subscription:change-plan")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> ChangePlan( - long subscriptionId, - [FromBody, Required] ChangeSubscriptionPlanCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { SubscriptionId = subscriptionId }; - - // 2. 执行套餐变更 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回变更结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") - : ApiResponse.Ok(result); - } - - /// - /// 变更订阅状态。 - /// - /// 订阅 ID。 - /// 状态变更命令。 - /// 取消标记。 - /// 变更后的订阅详情或未找到。 - [HttpPost("{subscriptionId:long}/status")] - [PermissionAuthorize("subscription:update-status")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateStatus( - long subscriptionId, - [FromBody, Required] UpdateSubscriptionStatusCommand command, - CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { SubscriptionId = subscriptionId }; - - // 2. 执行状态变更 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回变更结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") - : ApiResponse.Ok(result); - } - - /// - /// 批量延期订阅。 - /// - /// 批量延期命令。 - /// 取消标记。 - /// 批量延期结果。 - [HttpPost("batch-extend")] - [PermissionAuthorize("subscription:batch-extend")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> BatchExtend( - [FromBody, Required] BatchExtendSubscriptionsCommand command, - CancellationToken cancellationToken) - { - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 批量发送续费提醒。 - /// - /// 批量发送提醒命令。 - /// 取消标记。 - /// 批量发送提醒结果。 - [HttpPost("batch-remind")] - [PermissionAuthorize("subscription:batch-remind")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> BatchRemind( - [FromBody, Required] BatchSendReminderCommand command, - CancellationToken cancellationToken) - { - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/SystemParametersController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/SystemParametersController.cs deleted file mode 100644 index 7a2fcb2..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/SystemParametersController.cs +++ /dev/null @@ -1,134 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.SystemParameters.Commands; -using TakeoutSaaS.Application.App.SystemParameters.Dto; -using TakeoutSaaS.Application.App.SystemParameters.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 系统参数管理。 -/// -/// -/// 提供参数的新增、修改、查询与删除。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/system-parameters")] -public sealed class SystemParametersController(IMediator mediator) : BaseApiController -{ - /// - /// 创建系统参数。 - /// - /// 创建的系统参数信息。 - [HttpPost] - [PermissionAuthorize("system-parameter:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateSystemParameterCommand command, CancellationToken cancellationToken) - { - // 1. 创建系统参数 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询系统参数列表。 - /// - /// 分页的系统参数列表。 - [HttpGet] - [PermissionAuthorize("system-parameter:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] string? keyword, - [FromQuery] bool? isEnabled, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, - CancellationToken cancellationToken = default) - { - // 1. 组合查询参数 - var result = await mediator.Send(new SearchSystemParametersQuery - { - Keyword = keyword, - IsEnabled = isEnabled, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 获取系统参数详情。 - /// - /// 系统参数详情。 - [HttpGet("{parameterId:long}")] - [PermissionAuthorize("system-parameter:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long parameterId, CancellationToken cancellationToken) - { - // 1. 查询参数详情 - var result = await mediator.Send(new GetSystemParameterByIdQuery(parameterId), cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "系统参数不存在") - : ApiResponse.Ok(result); - } - - /// - /// 更新系统参数。 - /// - /// 更新后的系统参数信息。 - [HttpPut("{parameterId:long}")] - [PermissionAuthorize("system-parameter:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long parameterId, [FromBody] UpdateSystemParameterCommand command, CancellationToken cancellationToken) - { - // 1. 确保命令包含参数标识 - if (command.ParameterId == 0) - { - command = command with { ParameterId = parameterId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "系统参数不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除系统参数。 - /// - /// 删除结果。 - [HttpDelete("{parameterId:long}")] - [PermissionAuthorize("system-parameter:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long parameterId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var success = await mediator.Send(new DeleteSystemParameterCommand { ParameterId = parameterId }, cancellationToken); - - // 2. 返回成功或 404 - return success - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "系统参数不存在"); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantAnnouncementsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantAnnouncementsController.cs deleted file mode 100644 index df6cf1b..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantAnnouncementsController.cs +++ /dev/null @@ -1,394 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户公告管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/announcements")] -public sealed class TenantAnnouncementsController(IMediator mediator, ITenantContextAccessor tenantContextAccessor) : BaseApiController -{ - private const string TenantIdHeaderName = "X-Tenant-Id"; - - /// - /// 分页查询公告。 - /// - /// - /// 示例: - /// - /// GET /api/admin/v1/tenants/100000000000000001/announcements?page=1&pageSize=20 - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "items": [], - /// "page": 1, - /// "pageSize": 20, - /// "totalCount": 0 - /// } - /// } - /// - /// - [HttpGet] - [PermissionAuthorize("tenant-announcement:read")] - [SwaggerOperation(Summary = "查询租户公告列表", Description = "需要权限:tenant-announcement:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task>> Search(long tenantId, [FromQuery] GetTenantsAnnouncementsQuery query, CancellationToken cancellationToken) - { - if (!Request.Headers.TryGetValue(TenantIdHeaderName, out var tenantHeader) || string.IsNullOrWhiteSpace(tenantHeader)) - { - var request = query with { TenantId = 0 }; - var platformResult = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken)); - return ApiResponse>.Ok(platformResult); - } - - var headerError = EnsureTenantHeader>(); - if (headerError != null) - { - return headerError; - } - - query = query with { TenantId = tenantId }; - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 公告详情。 - /// - /// - /// 示例: - /// - /// GET /api/admin/v1/tenants/100000000000000001/announcements/900123456789012345 - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "tenantId": "100000000000000001", - /// "title": "租户公告", - /// "status": "Draft" - /// } - /// } - /// - /// - [HttpGet("{announcementId:long}")] - [PermissionAuthorize("tenant-announcement:read")] - [SwaggerOperation(Summary = "获取公告详情", Description = "需要权限:tenant-announcement:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Detail(long tenantId, long announcementId, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var result = await mediator.Send(new GetAnnouncementByIdQuery { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建公告。 - /// - /// - /// 示例: - /// - /// POST /api/admin/v1/tenants/100000000000000001/announcements - /// Body: - /// { - /// "title": "租户公告", - /// "content": "新品上线提醒", - /// "announcementType": 0, - /// "priority": 5, - /// "effectiveFrom": "2025-12-20T00:00:00Z", - /// "targetType": "roles", - /// "targetParameters": "{\"roles\":[\"OpsManager\"]}" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "tenantId": "100000000000000001", - /// "title": "租户公告", - /// "status": "Draft" - /// } - /// } - /// - /// - [HttpPost] - [PermissionAuthorize("tenant-announcement:create")] - [SwaggerOperation(Summary = "创建租户公告", Description = "需要权限:tenant-announcement:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Create(long tenantId, [FromBody, Required] CreateTenantAnnouncementCommand command, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - command = command with { TenantId = tenantId }; - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 更新公告(仅草稿)。 - /// - /// - /// 示例: - /// - /// PUT /api/admin/v1/tenants/100000000000000001/announcements/900123456789012345 - /// Body: - /// { - /// "title": "租户公告(更新)", - /// "content": "公告内容更新", - /// "targetType": "all", - /// "targetParameters": null, - /// "rowVersion": "AAAAAAAAB9E=" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "status": "Draft" - /// } - /// } - /// - /// - [HttpPut("{announcementId:long}")] - [PermissionAuthorize("tenant-announcement:update")] - [SwaggerOperation(Summary = "更新租户公告", Description = "仅草稿可更新;需要权限:tenant-announcement:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Update(long tenantId, long announcementId, [FromBody, Required] UpdateTenantAnnouncementCommand command, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - command = command with { TenantId = tenantId, AnnouncementId = announcementId }; - var result = await mediator.Send(command, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 发布公告。 - /// - /// - /// 示例: - /// - /// POST /api/admin/v1/tenants/100000000000000001/announcements/900123456789012345/publish - /// Body: - /// { - /// "rowVersion": "AAAAAAAAB9E=" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "status": "Published" - /// } - /// } - /// - /// - [HttpPost("{announcementId:long}/publish")] - [PermissionAuthorize("tenant-announcement:publish")] - [SwaggerOperation(Summary = "发布租户公告", Description = "需要权限:tenant-announcement:publish")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Publish(long tenantId, long announcementId, [FromBody, Required] PublishAnnouncementCommand command, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - command = command with { AnnouncementId = announcementId }; - var result = await mediator.Send(command, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 撤销公告。 - /// - /// - /// 示例: - /// - /// POST /api/admin/v1/tenants/100000000000000001/announcements/900123456789012345/revoke - /// Body: - /// { - /// "rowVersion": "AAAAAAAAB9E=" - /// } - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "status": "Revoked" - /// } - /// } - /// - /// - [HttpPost("{announcementId:long}/revoke")] - [PermissionAuthorize("tenant-announcement:revoke")] - [SwaggerOperation(Summary = "撤销租户公告", Description = "需要权限:tenant-announcement:revoke")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Revoke(long tenantId, long announcementId, [FromBody, Required] RevokeAnnouncementCommand command, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - command = command with { AnnouncementId = announcementId }; - var result = await mediator.Send(command, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除公告。 - /// - /// - /// 示例: - /// - /// DELETE /api/admin/v1/tenants/100000000000000001/announcements/900123456789012345 - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": true - /// } - /// - /// - [HttpDelete("{announcementId:long}")] - [PermissionAuthorize("tenant-announcement:delete")] - [SwaggerOperation(Summary = "删除租户公告", Description = "需要权限:tenant-announcement:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> Delete(long tenantId, long announcementId, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var result = await mediator.Send(new DeleteTenantAnnouncementCommand { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 标记公告已读(兼容旧路径)。 - /// - /// - /// 示例: - /// - /// POST /api/admin/v1/tenants/100000000000000001/announcements/900123456789012345/read - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "id": "900123456789012345", - /// "isRead": true - /// } - /// } - /// - /// - [HttpPost("{announcementId:long}/read")] - [PermissionAuthorize("tenant-announcement:read")] - [SwaggerOperation(Summary = "标记公告已读", Description = "需要权限:tenant-announcement:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] - public async Task> MarkRead(long tenantId, long announcementId, CancellationToken cancellationToken) - { - var headerError = EnsureTenantHeader(); - if (headerError != null) - { - return headerError; - } - - var result = await mediator.Send(new MarkAnnouncementAsReadCommand { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "公告不存在") - : ApiResponse.Ok(result); - } - - private ApiResponse? EnsureTenantHeader() - { - if (!Request.Headers.TryGetValue(TenantIdHeaderName, out var tenantHeader) || string.IsNullOrWhiteSpace(tenantHeader)) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, $"缺少租户标识,请在请求头 {TenantIdHeaderName} 指定租户"); - } - - if (!long.TryParse(tenantHeader.FirstOrDefault(), out _)) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, $"租户标识无效,请在请求头 {TenantIdHeaderName} 指定正确的租户 ID"); - } - - return null; - } - - private async Task ExecuteAsPlatformAsync(Func> action) - { - var original = tenantContextAccessor.Current; - tenantContextAccessor.Current = new TenantContext(0, null, "platform"); - try - { - return await action(); - } - finally - { - tenantContextAccessor.Current = original; - } - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantBillingsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantBillingsController.cs deleted file mode 100644 index 722e6fe..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantBillingsController.cs +++ /dev/null @@ -1,107 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.AdminApi.Contracts.Requests; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户账单管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/billings")] -public sealed class TenantBillingsController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询账单。 - /// - /// 租户账单分页结果。 - [HttpGet] - [PermissionAuthorize("tenant-bill:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Search(long tenantId, [FromQuery] SearchTenantBillsRequest request, CancellationToken cancellationToken) - { - // 1. 组装查询对象(TenantId 仅来自路由,避免与 QueryString 重复) - var query = new SearchTenantBillsQuery - { - TenantId = tenantId, - Status = request.Status, - From = request.From, - To = request.To, - Page = request.Page, - PageSize = request.PageSize, - }; - - // 2. 查询账单列表 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 账单详情。 - /// - /// 租户账单详情。 - [HttpGet("{billingId:long}")] - [PermissionAuthorize("tenant-bill:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long tenantId, long billingId, CancellationToken cancellationToken) - { - // 1. 查询账单详情 - var result = await mediator.Send(new GetTenantBillQuery { TenantId = tenantId, BillingId = billingId }, cancellationToken); - - // 2. 返回详情或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "账单不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建账单。 - /// - /// 创建的账单信息。 - [HttpPost] - [PermissionAuthorize("tenant-bill:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create(long tenantId, [FromBody, Required] CreateTenantBillingCommand command, CancellationToken cancellationToken) - { - // 1. 绑定租户标识 - command = command with { TenantId = tenantId }; - - // 2. 创建账单 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 标记账单已支付。 - /// - /// 标记支付后的账单信息。 - [HttpPost("{billingId:long}/pay")] - [PermissionAuthorize("tenant-bill:pay")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> MarkPaid(long tenantId, long billingId, [FromBody, Required] MarkTenantBillingPaidCommand command, CancellationToken cancellationToken) - { - // 1. 绑定租户与账单标识 - command = command with { TenantId = tenantId, BillingId = billingId }; - - // 2. 标记支付状态 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "账单不存在") - : ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantNotificationsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantNotificationsController.cs deleted file mode 100644 index 18c78ce..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantNotificationsController.cs +++ /dev/null @@ -1,58 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户通知接口。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/notifications")] -public sealed class TenantNotificationsController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询通知。 - /// - /// 租户通知分页结果。 - [HttpGet] - [PermissionAuthorize("tenant-notification:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Search(long tenantId, [FromQuery] SearchTenantNotificationsQuery query, CancellationToken cancellationToken) - { - // 1. 绑定租户标识 - query = query with { TenantId = tenantId }; - - // 2. 查询通知列表 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回分页结果 - return ApiResponse>.Ok(result); - } - - /// - /// 标记通知已读。 - /// - /// 标记已读后的通知信息。 - [HttpPost("{notificationId:long}/read")] - [PermissionAuthorize("tenant-notification:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> MarkRead(long tenantId, long notificationId, CancellationToken cancellationToken) - { - // 1. 标记通知为已读 - var result = await mediator.Send(new MarkTenantNotificationReadCommand { TenantId = tenantId, NotificationId = notificationId }, cancellationToken); - - // 2. 返回结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "通知不存在") - : ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantPackagesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantPackagesController.cs deleted file mode 100644 index a7327d2..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantPackagesController.cs +++ /dev/null @@ -1,177 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户套餐管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/tenant-packages")] -public sealed class TenantPackagesController(IMediator mediator) : BaseApiController -{ - /// - /// 分页查询租户套餐。 - /// - /// 查询条件。 - /// 取消标记。 - /// 租户套餐分页结果。 - [HttpGet] - [PermissionAuthorize("tenant-package:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Search([FromQuery] SearchTenantPackagesQuery query, CancellationToken cancellationToken) - { - // 1. 查询租户套餐分页 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 查询套餐使用统计(订阅关联数量、使用租户数量)。 - /// - /// 套餐 ID 列表(为空表示查询全部)。 - /// 取消标记。 - /// 套餐使用统计列表。 - [HttpGet("usages")] - [PermissionAuthorize("tenant-package:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Usages( - [FromQuery] long[]? tenantPackageIds, - CancellationToken cancellationToken) - { - // 1. 查询使用统计 - var result = await mediator.Send(new GetTenantPackageUsagesQuery { TenantPackageIds = tenantPackageIds }, cancellationToken); - - // 2. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 查询套餐当前使用租户列表(按有效订阅口径)。 - /// - /// 套餐 ID。 - /// 关键词(可选)。 - /// 可选:未来 N 天内到期筛选。 - /// 页码(从 1 开始)。 - /// 每页大小。 - /// 取消标记。 - /// 使用租户分页结果。 - [HttpGet("{tenantPackageId:long}/tenants")] - [PermissionAuthorize("tenant-package:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Tenants( - long tenantPackageId, - [FromQuery] string? keyword, - [FromQuery] int? expiringWithinDays, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - CancellationToken cancellationToken = default) - { - // 1. 查询套餐使用租户分页 - var result = await mediator.Send(new GetTenantPackageTenantsQuery - { - TenantPackageId = tenantPackageId, - Keyword = keyword, - ExpiringWithinDays = expiringWithinDays, - Page = page, - PageSize = pageSize - }, cancellationToken); - - // 2. 返回结果 - return ApiResponse>.Ok(result); - } - - /// - /// 查看套餐详情。 - /// - /// 套餐 ID。 - /// 取消标记。 - /// 套餐详情或未找到。 - [HttpGet("{tenantPackageId:long}")] - [PermissionAuthorize("tenant-package:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long tenantPackageId, CancellationToken cancellationToken) - { - // 1. 查询套餐详情 - var result = await mediator.Send(new GetTenantPackageByIdQuery { TenantPackageId = tenantPackageId }, cancellationToken); - - // 2. 返回查询结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "套餐不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建套餐。 - /// - /// 创建命令。 - /// 取消标记。 - /// 创建后的套餐。 - [HttpPost] - [PermissionAuthorize("tenant-package:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreateTenantPackageCommand command, CancellationToken cancellationToken) - { - // 1. 执行创建 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 更新套餐。 - /// - /// 套餐 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新后的套餐或未找到。 - [HttpPut("{tenantPackageId:long}")] - [PermissionAuthorize("tenant-package:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long tenantPackageId, [FromBody, Required] UpdateTenantPackageCommand command, CancellationToken cancellationToken) - { - // 1. 绑定路由 ID - command = command with { TenantPackageId = tenantPackageId }; - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回更新结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "套餐不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除套餐。 - /// - /// 套餐 ID。 - /// 取消标记。 - /// 删除结果。 - [HttpDelete("{tenantPackageId:long}")] - [PermissionAuthorize("tenant-package:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Delete(long tenantPackageId, CancellationToken cancellationToken) - { - // 1. 构建删除命令 - var command = new DeleteTenantPackageCommand { TenantPackageId = tenantPackageId }; - - // 2. 执行删除并返回 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs deleted file mode 100644 index f932a35..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs +++ /dev/null @@ -1,220 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.Identity.Commands; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Application.Identity.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户角色管理(实例层)。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/roles")] -public sealed class TenantRolesController(IMediator mediator, ITenantProvider tenantProvider) : BaseApiController -{ - private const long PlatformRootTenantId = 1000000000001; - - /// - /// 租户角色分页。 - /// - [HttpGet] - [PermissionAuthorize("identity:role:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - long tenantId, - [FromQuery] SearchRolesQuery query, - CancellationToken cancellationToken) - { - // 1. 校验路由租户与上下文一致(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse>.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 绑定租户并查询角色分页 - var request = new SearchRolesQuery - { - TenantId = tenantId, - Keyword = query.Keyword, - Page = query.Page, - PageSize = query.PageSize, - SortBy = query.SortBy, - SortDescending = query.SortDescending - }; - var result = await mediator.Send(request, cancellationToken); - - // 3. 返回分页数据 - return ApiResponse>.Ok(result); - } - - /// - /// 角色详情(含权限)。 - /// - [HttpGet("{roleId:long}")] - [PermissionAuthorize("identity:role:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long tenantId, long roleId, CancellationToken cancellationToken) - { - // 1. 校验租户上下文(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 查询角色详情 - var result = await mediator.Send(new RoleDetailQuery { RoleId = roleId, TenantId = tenantId }, cancellationToken); - - // 3. 返回数据或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "角色不存在") - : ApiResponse.Ok(result); - } - - /// - /// 创建角色。 - /// - [HttpPost] - [PermissionAuthorize("identity:role:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create( - long tenantId, - [FromBody, Required] CreateRoleCommand command, - CancellationToken cancellationToken) - { - // 1. 校验租户上下文(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 创建角色 - var result = await mediator.Send(command with { TenantId = tenantId }, cancellationToken); - - // 3. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 更新角色。 - /// - [HttpPut("{roleId:long}")] - [PermissionAuthorize("identity:role:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update( - long tenantId, - long roleId, - [FromBody, Required] UpdateRoleCommand command, - CancellationToken cancellationToken) - { - // 1. 校验租户上下文(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 绑定角色 ID - command = command with { RoleId = roleId, TenantId = tenantId }; - - // 3. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 4. 返回结果或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "角色不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除角色。 - /// - [HttpDelete("{roleId:long}")] - [PermissionAuthorize("identity:role:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Delete(long tenantId, long roleId, CancellationToken cancellationToken) - { - // 1. 校验租户上下文(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 执行删除 - var command = new DeleteRoleCommand { RoleId = roleId, TenantId = tenantId }; - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果 - return ApiResponse.Ok(result); - } - - /// - /// 获取角色权限列表。 - /// - [HttpGet("{roleId:long}/permissions")] - [PermissionAuthorize("identity:role:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status404NotFound)] - public async Task>> GetPermissions( - long tenantId, - long roleId, - CancellationToken cancellationToken) - { - // 1. 校验租户上下文(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse>.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 查询角色详情并提取权限 - var detail = await mediator.Send(new RoleDetailQuery { RoleId = roleId, TenantId = tenantId }, cancellationToken); - if (detail is null) - { - return ApiResponse>.Error(StatusCodes.Status404NotFound, "角色不存在"); - } - - // 3. 返回权限集合 - return ApiResponse>.Ok(detail.Permissions); - } - - /// - /// 覆盖角色权限。 - /// - [HttpPut("{roleId:long}/permissions")] - [PermissionAuthorize("identity:role:bind-permission")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> BindPermissions( - long tenantId, - long roleId, - [FromBody, Required] BindRolePermissionsCommand command, - CancellationToken cancellationToken) - { - // 1. 校验租户上下文(超管租户 1000000000001 放行) - var currentTenantId = tenantProvider.GetCurrentTenantId(); - if (currentTenantId != PlatformRootTenantId && tenantId != currentTenantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "租户上下文不一致"); - } - - // 2. 绑定角色 ID - command = command with { RoleId = roleId, TenantId = tenantId }; - - // 3. 覆盖授权 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs deleted file mode 100644 index 3108448..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs +++ /dev/null @@ -1,439 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Dto; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 租户管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/tenants")] -public sealed class TenantsController(IMediator mediator) : BaseApiController -{ - /// - /// 注册租户并初始化套餐。 - /// - /// 注册的租户信息。 - [HttpPost] - [PermissionAuthorize("tenant:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Register([FromBody] RegisterTenantCommand command, CancellationToken cancellationToken) - { - // 1. 注册租户并初始化套餐 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回注册结果 - return ApiResponse.Ok(result); - } - - /// - /// 后台手动新增租户并直接入驻(创建租户 + 认证 + 订阅 + 管理员账号)。 - /// - /// 新增后的租户详情。 - [HttpPost("manual")] - [PermissionAuthorize("tenant:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateManually([FromBody] CreateTenantManuallyCommand command, CancellationToken cancellationToken) - { - // 1. 后台手动新增租户(直接可用) - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 分页查询租户。 - /// - /// 租户分页结果。 - [HttpGet] - [PermissionAuthorize("tenant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Search([FromQuery] SearchTenantsQuery query, CancellationToken cancellationToken) - { - // 1. 查询租户分页 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回分页数据 - return ApiResponse>.Ok(result); - } - - /// - /// 查看租户详情。 - /// - /// 租户详情。 - [HttpGet("{tenantId:long}")] - [PermissionAuthorize("tenant:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Detail(long tenantId, CancellationToken cancellationToken) - { - // 1. 查询租户详情 - var result = await mediator.Send(new GetTenantByIdQuery(tenantId), cancellationToken); - - // 2. 返回租户信息 - return ApiResponse.Ok(result); - } - - /// - /// 更新租户基础信息。 - /// - /// 租户 ID。 - /// 更新命令。 - /// 取消标记。 - /// 更新结果。 - [HttpPut("{tenantId:long}")] - [PermissionAuthorize("tenant:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Update( - long tenantId, - [FromBody, Required] UpdateTenantCommand body, - CancellationToken cancellationToken) - { - // 1. 校验路由与请求体租户标识一致 - if (body.TenantId != 0 && body.TenantId != tenantId) - { - return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 tenantId 与请求体 tenantId 不一致"); - } - - // 2. 绑定租户标识并执行更新(若不存在或冲突则抛出业务异常,由全局异常处理转换为 404/409) - var command = body with { TenantId = tenantId }; - await mediator.Send(command, cancellationToken); - - // 3. 返回成功结果 - return ApiResponse.Ok(null); - } - - /// - /// 提交或更新实名认证资料。 - /// - /// 提交的实名认证信息。 - [HttpPost("{tenantId:long}/verification")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> SubmitVerification( - long tenantId, - [FromBody] SubmitTenantVerificationCommand body, - CancellationToken cancellationToken) - { - // 1. 合并路由中的租户标识 - var command = body with { TenantId = tenantId }; - - // 2. 提交或更新认证资料 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回认证结果 - return ApiResponse.Ok(result); - } - - /// - /// 审核租户。 - /// - /// 审核后的租户信息。 - [HttpPost("{tenantId:long}/review")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Review(long tenantId, [FromBody] ReviewTenantCommand body, CancellationToken cancellationToken) - { - // 1. 绑定租户标识 - var command = body with { TenantId = tenantId }; - - // 2. 执行审核 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回审核结果 - return ApiResponse.Ok(result); - } - - /// - /// 查询当前租户审核领取信息。 - /// - /// 领取信息,未领取返回 null。 - [HttpGet("{tenantId:long}/review/claim")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetReviewClaim(long tenantId, CancellationToken cancellationToken) - { - // 1. 查询领取信息 - var result = await mediator.Send(new GetTenantReviewClaimQuery(tenantId), cancellationToken); - - // 2. 返回领取信息 - return ApiResponse.Ok(result); - } - - /// - /// 领取租户入驻审核(领取后仅领取人可操作审核)。 - /// - /// 领取结果。 - [HttpPost("{tenantId:long}/review/claim")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ClaimReview(long tenantId, CancellationToken cancellationToken) - { - // 1. 执行领取 - var result = await mediator.Send(new ClaimTenantReviewCommand { TenantId = tenantId }, cancellationToken); - - // 2. 返回领取结果 - return ApiResponse.Ok(result); - } - - /// - /// 强制接管租户入驻审核(仅超级管理员可用)。 - /// - /// 接管后的领取信息。 - [HttpPost("{tenantId:long}/review/force-claim")] - [PermissionAuthorize("tenant:review:force-claim")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ForceClaimReview(long tenantId, CancellationToken cancellationToken) - { - // 1. 执行强制接管 - var result = await mediator.Send(new ForceClaimTenantReviewCommand { TenantId = tenantId }, cancellationToken); - - // 2. 返回接管结果 - return ApiResponse.Ok(result); - } - - /// - /// 释放租户入驻审核领取(仅领取人可释放)。 - /// - /// 释放后的领取信息,未领取返回 null。 - [HttpPost("{tenantId:long}/review/release")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ReleaseReview(long tenantId, CancellationToken cancellationToken) - { - // 1. 执行释放 - var result = await mediator.Send(new ReleaseTenantReviewClaimCommand { TenantId = tenantId }, cancellationToken); - - // 2. 返回释放结果 - return ApiResponse.Ok(result); - } - - /// - /// 冻结租户(暂停服务)。 - /// - /// 冻结后的租户信息。 - [HttpPost("{tenantId:long}/freeze")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Freeze( - long tenantId, - [FromBody] FreezeTenantCommand body, - CancellationToken cancellationToken) - { - // 1. 合并路由参数 - var command = body with { TenantId = tenantId }; - - // 2. 执行冻结 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回冻结结果 - return ApiResponse.Ok(result); - } - - /// - /// 解冻租户(恢复服务)。 - /// - /// 解冻后的租户信息。 - [HttpPost("{tenantId:long}/unfreeze")] - [PermissionAuthorize("tenant:review")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Unfreeze( - long tenantId, - [FromBody] UnfreezeTenantCommand body, - CancellationToken cancellationToken) - { - // 1. 合并路由参数 - var command = body with { TenantId = tenantId }; - - // 2. 执行解冻 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回解冻结果 - return ApiResponse.Ok(result); - } - - /// - /// 创建或续费租户订阅。 - /// - /// 创建或续费的订阅信息。 - [HttpPost("{tenantId:long}/subscriptions")] - [PermissionAuthorize("tenant:subscription")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateSubscription( - long tenantId, - [FromBody] CreateTenantSubscriptionCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定租户并创建或续费订阅 - var command = body with { TenantId = tenantId }; - - // 2. 返回订阅结果 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 延期/赠送租户订阅时长(按当前订阅套餐续费)。 - /// - /// 续费后的订阅信息。 - [HttpPost("{tenantId:long}/subscriptions/extend")] - [PermissionAuthorize("tenant:subscription")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ExtendSubscription( - long tenantId, - [FromBody] ExtendTenantSubscriptionCommand body, - CancellationToken cancellationToken) - { - // 1. 合并租户标识 - var command = body with { TenantId = tenantId }; - - // 2. 执行延期/赠送 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回订阅结果 - return ApiResponse.Ok(result); - } - - /// - /// 套餐升降配。 - /// - /// 更新后的订阅信息。 - [HttpPut("{tenantId:long}/subscriptions/{subscriptionId:long}/plan")] - [PermissionAuthorize("tenant:subscription")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ChangePlan( - long tenantId, - long subscriptionId, - [FromBody] ChangeTenantSubscriptionPlanCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定租户与订阅标识 - var command = body with { TenantId = tenantId, TenantSubscriptionId = subscriptionId }; - - // 2. 执行升降配 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回调整后的订阅 - return ApiResponse.Ok(result); - } - - /// - /// 查询审核日志。 - /// - /// 租户审核日志分页结果。 - [HttpGet("{tenantId:long}/audits")] - [PermissionAuthorize("tenant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> AuditLogs( - long tenantId, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - CancellationToken cancellationToken = default) - { - // 1. 构造审核日志查询 - var query = new GetTenantAuditLogsQuery(tenantId, page, pageSize); - - // 2. 查询并返回分页结果 - var result = await mediator.Send(query, cancellationToken); - return ApiResponse>.Ok(result); - } - - /// - /// 伪装登录租户(仅平台超级管理员可用)。 - /// - /// 目标租户主管理员的令牌对。 - [HttpPost("{tenantId:long}/impersonate")] - [PermissionAuthorize("tenant:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Impersonate(long tenantId, CancellationToken cancellationToken) - { - // 1. 执行伪装登录 - var result = await mediator.Send(new ImpersonateTenantCommand { TenantId = tenantId }, cancellationToken); - - // 2. 返回令牌 - return ApiResponse.Ok(result); - } - - /// - /// 生成租户主管理员重置密码链接(仅平台超级管理员可用)。 - /// - /// 链接默认 24 小时有效且仅可使用一次。 - /// 重置密码链接。 - [HttpPost("{tenantId:long}/admin/reset-link")] - [PermissionAuthorize("tenant:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CreateAdminResetLink(long tenantId, CancellationToken cancellationToken) - { - // 1. 生成一次性令牌 - var token = await mediator.Send(new CreateTenantAdminResetLinkTokenCommand { TenantId = tenantId }, cancellationToken); - - // 2. 解析前端来源(优先 Origin,避免拼成 AdminApi 域名) - var origin = Request.Headers.Origin.ToString(); - if (string.IsNullOrWhiteSpace(origin)) - { - origin = $"{Request.Scheme}://{Request.Host}"; - } - - origin = origin.TrimEnd('/'); - var resetUrl = $"{origin}/#/auth/reset-password?token={Uri.EscapeDataString(token)}"; - - // 3. 返回链接 - return ApiResponse.Ok(data: resetUrl); - } - - /// - /// 配额校验并占用额度(门店/账号/短信/配送)。 - /// - /// 需在请求头携带 X-Tenant-Id 对应的租户。 - /// 配额校验结果。 - [HttpPost("{tenantId:long}/quotas/check")] - [PermissionAuthorize("tenant:quota:check")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> CheckQuota( - long tenantId, - [FromBody, Required] CheckTenantQuotaCommand body, - CancellationToken cancellationToken) - { - // 1. 绑定租户标识 - var command = body with { TenantId = tenantId }; - - // 2. 校验并占用配额 - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); - } - - /// - /// 分页查询租户配额使用历史。 - /// - /// 租户 ID。 - /// 查询条件。 - /// 取消标记。 - /// 配额使用历史分页结果。 - [HttpGet("{tenantId:long}/quota-usage-history")] - [PermissionAuthorize("tenant:quota:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetQuotaUsageHistory( - long tenantId, - [FromQuery] GetTenantQuotaUsageHistoryQuery query, - CancellationToken cancellationToken) - { - // 1. 绑定租户标识 - query = query with { TenantId = tenantId }; - - // 2. 查询配额使用历史 - var result = await mediator.Send(query, cancellationToken); - - // 3. 返回分页结果 - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/UserPermissionsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/UserPermissionsController.cs deleted file mode 100644 index c6a4a04..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/UserPermissionsController.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TakeoutSaaS.Application.Identity.Abstractions; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Application.Identity.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 用户权限洞察接口。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/users/permissions")] -public sealed class UserPermissionsController(IAdminAuthService authService) : BaseApiController -{ - /// - /// 分页查询当前租户用户的角色与权限概览。 - /// - /// - /// 示例: - /// - /// GET /api/admin/v1/users/permissions?keyword=ops&page=1&pageSize=20&sortBy=createdAt&sortDescending=true - /// Header: Authorization: Bearer <JWT> - /// 响应: - /// { - /// "success": true, - /// "code": 200, - /// "data": { - /// "items": [ - /// { - /// "userId": "900123456789012346", - /// "tenantId": "100000000000000001", - /// "merchantId": "200000000000000001", - /// "account": "ops.manager", - /// "displayName": "运营经理", - /// "roles": ["OpsManager", "Reporter"], - /// "permissions": ["delivery:read", "order:read", "payment:read"], - /// "createdAt": "2025-12-01T08:30:00Z" - /// } - /// ], - /// "page": 1, - /// "pageSize": 20, - /// "totalCount": 1, - /// "totalPages": 1 - /// } - /// } - /// - /// - /// 搜索条件。 - /// 取消标记。 - /// 分页的用户权限概览。 - [HttpGet] - [PermissionAuthorize("identity:permission:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> Search( - [FromQuery] SearchUserPermissionsQuery query, - CancellationToken cancellationToken) - { - // 1. 查询当前租户的用户权限概览 - var result = await authService.SearchUserPermissionsAsync( - query.Keyword, - query.Page, - query.PageSize, - query.SortBy, - query.SortDescending, - cancellationToken); - - // 2. 返回分页结果 - return ApiResponse>.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs deleted file mode 100644 index 68a3411..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs +++ /dev/null @@ -1,212 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using TakeoutSaaS.Application.Identity.Commands; -using TakeoutSaaS.Application.Identity.Contracts; -using TakeoutSaaS.Application.Identity.Queries; -using TakeoutSaaS.Module.Authorization.Attributes; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Web.Api; - -namespace TakeoutSaaS.AdminApi.Controllers; - -/// -/// 用户管理。 -/// -[ApiVersion("1.0")] -[Authorize] -[Route("api/admin/v{version:apiVersion}/users")] -public sealed class UsersController(IMediator mediator) : BaseApiController -{ - /// - /// 用户分页列表。 - /// - [HttpGet] - [PermissionAuthorize("identity:user:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] SearchIdentityUsersQuery query, - CancellationToken cancellationToken) - { - // 1. 查询用户分页 - var result = await mediator.Send(query, cancellationToken); - - // 2. 返回分页数据 - return ApiResponse>.Ok(result); - } - - /// - /// 用户详情。 - /// - [HttpGet("{userId:long}")] - [PermissionAuthorize("identity:user:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long userId, [FromQuery] bool includeDeleted, CancellationToken cancellationToken) - { - // 1. 查询用户详情 - var result = await mediator.Send(new GetIdentityUserDetailQuery - { - UserId = userId, - IncludeDeleted = includeDeleted - }, cancellationToken); - - // 2. 返回详情或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "用户不存在") - : ApiResponse.Ok(result); - } - - /// - /// 用户权限明细。 - /// - [HttpGet("{userId:long}/permissions")] - [PermissionAuthorize("identity:user:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status404NotFound)] - public async Task>> Permissions(long userId, CancellationToken cancellationToken) - { - // 1. 查询用户详情并提取权限 - var detail = await mediator.Send(new GetIdentityUserDetailQuery { UserId = userId }, cancellationToken); - if (detail == null) - { - return ApiResponse>.Error(ErrorCodes.NotFound, "用户不存在"); - } - - // 2. 返回权限编码列表 - return ApiResponse>.Ok(detail.Permissions); - } - - /// - /// 创建用户。 - /// - [HttpPost] - [PermissionAuthorize("identity:user:create")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Create([FromBody, Required] CreateIdentityUserCommand command, CancellationToken cancellationToken) - { - // 1. 创建用户 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); - } - - /// - /// 更新用户。 - /// - [HttpPut("{userId:long}")] - [PermissionAuthorize("identity:user:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long userId, [FromBody, Required] UpdateIdentityUserCommand command, CancellationToken cancellationToken) - { - // 1. 绑定用户 ID - if (command.UserId == 0) - { - command = command with { UserId = userId }; - } - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "用户不存在") - : ApiResponse.Ok(result); - } - - /// - /// 删除用户。 - /// - [HttpDelete("{userId:long}")] - [PermissionAuthorize("identity:user:delete")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Delete(long userId, CancellationToken cancellationToken) - { - // 1. 执行删除 - var result = await mediator.Send(new DeleteIdentityUserCommand { UserId = userId }, cancellationToken); - - // 2. 返回结果或 404 - return result - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "用户不存在"); - } - - /// - /// 恢复用户。 - /// - [HttpPost("{userId:long}/restore")] - [PermissionAuthorize("identity:user:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Restore(long userId, CancellationToken cancellationToken) - { - // 1. 执行恢复 - var result = await mediator.Send(new RestoreIdentityUserCommand { UserId = userId }, cancellationToken); - - // 2. 返回结果或 404 - return result - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "用户不存在"); - } - - /// - /// 更新用户状态。 - /// - [HttpPut("{userId:long}/status")] - [PermissionAuthorize("identity:user:status")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> ChangeStatus(long userId, [FromBody, Required] ChangeIdentityUserStatusCommand command, CancellationToken cancellationToken) - { - // 1. 绑定用户 ID - if (command.UserId == 0) - { - command = command with { UserId = userId }; - } - - // 2. 执行状态变更 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回结果或 404 - return result - ? ApiResponse.Success() - : ApiResponse.Error(ErrorCodes.NotFound, "用户不存在"); - } - - /// - /// 生成重置密码链接。 - /// - [HttpPost("{userId:long}/password-reset")] - [PermissionAuthorize("identity:user:reset-password")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> ResetPassword(long userId, CancellationToken cancellationToken) - { - // 1. 生成重置令牌 - var result = await mediator.Send(new ResetIdentityUserPasswordCommand { UserId = userId }, cancellationToken); - - // 2. 返回令牌信息 - return ApiResponse.Ok(result); - } - - /// - /// 批量用户操作。 - /// - [HttpPost("batch")] - [PermissionAuthorize("identity:user:batch")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> Batch( - [FromBody, Required] BatchIdentityUserOperationCommand command, - CancellationToken cancellationToken) - { - // 1. 执行批量操作 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回操作结果 - return ApiResponse.Ok(result); - } -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Dockerfile b/src/Api/TakeoutSaaS.AdminApi/Dockerfile deleted file mode 100644 index 72754e9..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src - -# Copy only what's needed for restore first, so `dotnet restore` can be cached. -COPY ["Directory.Build.props", "./"] -COPY ["TakeoutSaaS.sln", "./"] -COPY ["stylecop.json", "./"] -COPY ["src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj", "src/Api/TakeoutSaaS.AdminApi/"] -COPY ["src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj", "src/Application/TakeoutSaaS.Application/"] -COPY ["TakeoutSaaS.BuildingBlocks/src/Core/TakeoutSaaS.Shared.Abstractions/TakeoutSaaS.Shared.Abstractions.csproj", "TakeoutSaaS.BuildingBlocks/src/Core/TakeoutSaaS.Shared.Abstractions/"] -COPY ["TakeoutSaaS.BuildingBlocks/src/Core/TakeoutSaaS.Shared.Kernel/TakeoutSaaS.Shared.Kernel.csproj", "TakeoutSaaS.BuildingBlocks/src/Core/TakeoutSaaS.Shared.Kernel/"] -COPY ["TakeoutSaaS.BuildingBlocks/src/Core/TakeoutSaaS.Shared.Web/TakeoutSaaS.Shared.Web.csproj", "TakeoutSaaS.BuildingBlocks/src/Core/TakeoutSaaS.Shared.Web/"] -COPY ["src/Domain/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj", "src/Domain/TakeoutSaaS.Domain/"] -COPY ["src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj", "src/Infrastructure/TakeoutSaaS.Infrastructure/"] -COPY ["src/Modules/TakeoutSaaS.Module.Authorization/TakeoutSaaS.Module.Authorization.csproj", "src/Modules/TakeoutSaaS.Module.Authorization/"] -COPY ["src/Modules/TakeoutSaaS.Module.Delivery/TakeoutSaaS.Module.Delivery.csproj", "src/Modules/TakeoutSaaS.Module.Delivery/"] -COPY ["src/Modules/TakeoutSaaS.Module.Dictionary/TakeoutSaaS.Module.Dictionary.csproj", "src/Modules/TakeoutSaaS.Module.Dictionary/"] -COPY ["src/Modules/TakeoutSaaS.Module.Messaging/TakeoutSaaS.Module.Messaging.csproj", "src/Modules/TakeoutSaaS.Module.Messaging/"] -COPY ["src/Modules/TakeoutSaaS.Module.Scheduler/TakeoutSaaS.Module.Scheduler.csproj", "src/Modules/TakeoutSaaS.Module.Scheduler/"] -COPY ["src/Modules/TakeoutSaaS.Module.Sms/TakeoutSaaS.Module.Sms.csproj", "src/Modules/TakeoutSaaS.Module.Sms/"] -COPY ["src/Modules/TakeoutSaaS.Module.Storage/TakeoutSaaS.Module.Storage.csproj", "src/Modules/TakeoutSaaS.Module.Storage/"] -COPY ["src/Modules/TakeoutSaaS.Module.Tenancy/TakeoutSaaS.Module.Tenancy.csproj", "src/Modules/TakeoutSaaS.Module.Tenancy/"] - -RUN dotnet restore "src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj" - -# Copy the rest of the source after restore for best cache reuse. -COPY . . - -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish --no-restore - -FROM mcr.microsoft.com/dotnet/aspnet:10.0 -WORKDIR /app -COPY --from=build /app/publish . -EXPOSE 7801 -ENV ASPNETCORE_URLS=http://+:7801 -ENTRYPOINT ["dotnet", "TakeoutSaaS.AdminApi.dll"] diff --git a/src/Api/TakeoutSaaS.AdminApi/Program.cs b/src/Api/TakeoutSaaS.AdminApi/Program.cs deleted file mode 100644 index 5d8837b..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Program.cs +++ /dev/null @@ -1,199 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Cors.Infrastructure; -using Microsoft.AspNetCore.RateLimiting; -using OpenTelemetry.Metrics; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; -using Serilog; -using System.Threading.RateLimiting; -using TakeoutSaaS.Application.App.Extensions; -using TakeoutSaaS.Application.Identity.Extensions; -using TakeoutSaaS.Application.Messaging.Extensions; -using TakeoutSaaS.Application.Sms.Extensions; -using TakeoutSaaS.Application.Storage.Extensions; -using TakeoutSaaS.Infrastructure.App.Extensions; -using TakeoutSaaS.Infrastructure.Identity.Extensions; -using TakeoutSaaS.Infrastructure.Logs.Extensions; -using TakeoutSaaS.Module.Authorization.Extensions; -using TakeoutSaaS.Module.Dictionary.Extensions; -using TakeoutSaaS.Module.Messaging.Extensions; -using TakeoutSaaS.Module.Scheduler.Extensions; -using TakeoutSaaS.Module.Sms.Extensions; -using TakeoutSaaS.Module.Storage.Extensions; -using TakeoutSaaS.Module.Tenancy.Extensions; -using TakeoutSaaS.Shared.Web.Extensions; -using TakeoutSaaS.Shared.Web.Swagger; - -// 1. 创建构建器与日志模板 -var builder = WebApplication.CreateBuilder(args); -const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}"; -var isDevelopment = builder.Environment.IsDevelopment(); - -// 2. 加载种子配置文件 -builder.Configuration - .AddJsonFile("appsettings.Seed.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.Seed.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); - -// 3. 配置 Serilog 输出 -builder.Host.UseSerilog((context, _, configuration) => -{ - configuration - .Enrich.FromLogContext() - .Enrich.WithProperty("Service", "AdminApi") - .WriteTo.Console(outputTemplate: logTemplate) - .WriteTo.File( - "logs/admin-api-.log", - rollingInterval: RollingInterval.Day, - retainedFileCountLimit: 7, - shared: true, - outputTemplate: logTemplate); -}); - -// 4. 注册通用 Web 能力,开发环境启用 Swagger -builder.Services.AddSharedWebCore(); -if (isDevelopment) -{ - builder.Services.AddSharedSwagger(options => - { - options.Title = "外卖SaaS - 管理后台"; - options.Description = "管理后台 API 文档"; - options.EnableAuthorization = true; - }); -} - -// 5. 注册领域与基础设施模块 -builder.Services.AddIdentityApplication(); -builder.Services.AddIdentityInfrastructure(builder.Configuration, enableAdminSeed: true); -builder.Services.AddAppInfrastructure(builder.Configuration); -builder.Services.AddAppApplication(); -builder.Services.AddJwtAuthentication(builder.Configuration); -builder.Services.AddAuthorization(); -builder.Services.AddPermissionAuthorization(); -builder.Services.AddTenantResolution(builder.Configuration); -builder.Services.AddDictionaryModule(builder.Configuration); -builder.Services.AddStorageModule(builder.Configuration); -builder.Services.AddStorageApplication(); -builder.Services.AddSmsModule(builder.Configuration); -builder.Services.AddSmsApplication(builder.Configuration); -builder.Services.AddMessagingModule(builder.Configuration); -builder.Services.AddMessagingApplication(); -builder.Services.AddOperationLogOutbox(builder.Configuration); -builder.Services.AddSchedulerModule(builder.Configuration); -builder.Services.AddHealthChecks(); -builder.Services.AddRateLimiter(options => -{ - options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; - options.AddFixedWindowLimiter("public-self-service", limiterOptions => - { - limiterOptions.PermitLimit = 10; - limiterOptions.Window = TimeSpan.FromMinutes(1); - limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; - limiterOptions.QueueLimit = 2; - }); -}); - -// 6. 配置 OpenTelemetry 采集 -var otelSection = builder.Configuration.GetSection("Otel"); -var otelEndpoint = otelSection.GetValue("Endpoint"); -var useConsoleExporter = otelSection.GetValue("UseConsoleExporter") ?? builder.Environment.IsDevelopment(); -builder.Services.AddOpenTelemetry() - .ConfigureResource(resource => resource.AddService( - serviceName: "TakeoutSaaS.AdminApi", - serviceVersion: "1.0.0", - serviceInstanceId: Environment.MachineName)) - .WithTracing(tracing => - { - tracing - .SetSampler(new ParentBasedSampler(new AlwaysOnSampler())) - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddEntityFrameworkCoreInstrumentation(); - if (!string.IsNullOrWhiteSpace(otelEndpoint)) - { - tracing.AddOtlpExporter(exporter => - { - exporter.Endpoint = new Uri(otelEndpoint); - }); - } - - if (useConsoleExporter) - { - tracing.AddConsoleExporter(); - } - }) - .WithMetrics(metrics => - { - metrics - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation() - .AddPrometheusExporter(); - if (!string.IsNullOrWhiteSpace(otelEndpoint)) - { - metrics.AddOtlpExporter(exporter => - { - exporter.Endpoint = new Uri(otelEndpoint); - }); - } - - if (useConsoleExporter) - { - metrics.AddConsoleExporter(); - } - }); - -// 7. 解析并配置 CORS -var adminOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Admin"); -builder.Services.AddCors(options => -{ - options.AddPolicy("AdminApiCors", policy => - { - ConfigureCorsPolicy(policy, adminOrigins); - }); -}); - -// 8. 构建应用并配置中间件管道 -var app = builder.Build(); -app.UseCors("AdminApiCors"); -app.UseTenantResolution(); -app.UseRateLimiter(); -app.UseSharedWebCore(); -app.UseAuthentication(); -app.UseAuthorization(); -if (app.Environment.IsDevelopment()) -{ - app.UseSharedSwagger(); -} -app.UseSchedulerDashboard(builder.Configuration); -app.MapHealthChecks("/healthz"); -app.MapPrometheusScrapingEndpoint(); -app.MapControllers(); -app.Run(); - -// 9. 解析配置中的 CORS 域名 -static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey) -{ - var origins = configuration.GetSection(sectionKey).Get(); - return origins? - .Where(origin => !string.IsNullOrWhiteSpace(origin)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() ?? []; -} - -// 10. 构建 CORS 策略 -static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins) -{ - if (origins.Length == 0) - { - policy.AllowAnyOrigin(); - } - else - { - policy.WithOrigins(origins) - .AllowCredentials(); - } - - policy - .AllowAnyHeader() - .AllowAnyMethod(); -} diff --git a/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json b/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json deleted file mode 100644 index 20bd1cc..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "profiles": { - "TakeoutSaaS.AdminApi": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:2680" - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "http://localhost:7801/swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "publishAllPorts": false, - "useSSL": false, - "dockerRunOptions": "-p 7801:7801 --name TakeoutAdminApi" - } - } -} \ No newline at end of file diff --git a/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj b/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj deleted file mode 100644 index 212123a..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net10.0 - enable - enable - true - Linux - ../../.. - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json deleted file mode 100644 index 6256ff4..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "ConnectionStrings": { - "Redis": "49.232.6.45:6379,password=MsuMshk112233,abortConnect=false" - }, - "Database": { - "DataSources": { - "AppDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - }, - "IdentityDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - }, - "DictionaryDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - }, - "LogsDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - } - } - }, - "Identity": { - "Jwt": { - "Issuer": "takeout-saas", - "Audience": "takeout-saas-clients", - "Secret": "psZEx_O##]Mq(W.1$?8Aia*LM03sXGGx", - "AccessTokenExpirationMinutes": 120, - "RefreshTokenExpirationMinutes": 10080 - }, - "LoginRateLimit": { - "WindowSeconds": 60, - "MaxAttempts": 5 - }, - "RefreshTokenStore": { - "Prefix": "identity:refresh:" - }, - "AdminSeed": { - "Users": [] - } - }, - "Dictionary": { - "Cache": { - "SlidingExpiration": "00:30:00" - } - }, - "CacheWarmup": { - "DictionaryCodes": [ - "order_status", - "payment_method", - "shipping_method", - "product_category", - "user_role" - ] - }, - "Tenancy": { - "TenantIdHeaderName": "X-Tenant-Id", - "TenantCodeHeaderName": "X-Tenant-Code", - "IgnoredPaths": [ - "/health" - ], - "RootDomain": "" - }, - "Storage": { - "Provider": "TencentCos", - "CdnBaseUrl": "https://image-admin.laosankeji.com", - "TencentCos": { - "SecretId": "AKID52mHageV8ZnnY5NRL3Xq270fAcw2vb5R", - "SecretKey": "B8sPitsiEXcS4ScaMvGMErFOL3ZqsgFa", - "Region": "ap-beijing", - "Bucket": "saas-admin-1388556178", - "Endpoint": "https://cos.ap-beijing.myqcloud.com", - "CdnBaseUrl": "https://image-admin.laosankeji.com", - "UseHttps": true, - "ForcePathStyle": false - }, - "QiniuKodo": { - "AccessKey": "QINIU_ACCESS_KEY", - "SecretKey": "QINIU_SECRET_KEY", - "Bucket": "takeout-files", - "DownloadDomain": "", - "Endpoint": "", - "UseHttps": true, - "SignedUrlExpirationMinutes": 30 - }, - "AliyunOss": { - "AccessKeyId": "OSS_ACCESS_KEY_ID", - "AccessKeySecret": "OSS_ACCESS_KEY_SECRET", - "Endpoint": "https://oss-cn-hangzhou.aliyuncs.com", - "Bucket": "takeout-files", - "CdnBaseUrl": "", - "UseHttps": true - }, - "Security": { - "MaxFileSizeBytes": 10485760, - "AllowedImageExtensions": [ - ".jpg", - ".jpeg", - ".png", - ".webp", - ".gif" - ], - "AllowedFileExtensions": [ - ".jpg", - ".jpeg", - ".png", - ".webp", - ".gif", - ".pdf" - ], - "DefaultUrlExpirationMinutes": 30, - "EnableRefererValidation": false, - "AllowedReferers": [ - "https://admin.example.com", - "https://miniapp.example.com" - ], - "AntiLeechTokenSecret": "ReplaceWithARandomToken" - } - }, - "Sms": { - "Provider": "Tencent", - "DefaultSignName": "外卖SaaS", - "UseMock": true, - "Tencent": { - "SecretId": "TENCENT_SMS_SECRET_ID", - "SecretKey": "TENCENT_SMS_SECRET_KEY", - "SdkAppId": "1400000000", - "SignName": "外卖SaaS", - "Region": "ap-beijing", - "Endpoint": "https://sms.tencentcloudapi.com" - }, - "Aliyun": { - "AccessKeyId": "ALIYUN_SMS_AK", - "AccessKeySecret": "ALIYUN_SMS_SK", - "Endpoint": "dysmsapi.aliyuncs.com", - "SignName": "外卖SaaS", - "Region": "cn-hangzhou" - }, - "SceneTemplates": { - "login": "LOGIN_TEMPLATE_ID", - "register": "REGISTER_TEMPLATE_ID", - "reset": "RESET_TEMPLATE_ID" - }, - "VerificationCode": { - "CodeLength": 6, - "ExpireMinutes": 5, - "CooldownSeconds": 60, - "CachePrefix": "sms:code" - } - }, - "RabbitMQ": { - "Host": "49.232.6.45", - "Port": 5672, - "Username": "Admin", - "Password": "MsuMshk112233", - "VirtualHost": "/", - "Exchange": "takeout.events", - "ExchangeType": "topic", - "PrefetchCount": 20 - }, - "Scheduler": { - "ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_hangfire_db;Username=hangfire_user;Password=HangFire112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "WorkerCount": 5, - "DashboardEnabled": false, - "DashboardPath": "/hangfire" - }, - "Otel": { - "Endpoint": "", - "Sampling": "ParentBasedAlwaysOn", - "UseConsoleExporter": true - } -} - diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json deleted file mode 100644 index 3eb75e2..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "ConnectionStrings": { - "Redis": "49.232.6.45:6379,password=MsuMshk112233,abortConnect=false" - }, - "Database": { - "DataSources": { - "AppDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - }, - "IdentityDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - }, - "DictionaryDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - }, - "LogsDatabase": { - "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "Reads": [ - "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" - ], - "CommandTimeoutSeconds": 30, - "MaxRetryCount": 3, - "MaxRetryDelaySeconds": 5 - } - } - }, - "Identity": { - "Jwt": { - "Issuer": "takeout-saas", - "Audience": "takeout-saas-clients", - "Secret": "psZEx_O##]Mq(W.1$?8Aia*LM03sXGGx", - "AccessTokenExpirationMinutes": 120, - "RefreshTokenExpirationMinutes": 10080 - }, - "LoginRateLimit": { - "WindowSeconds": 60, - "MaxAttempts": 5 - }, - "RefreshTokenStore": { - "Prefix": "identity:refresh:" - }, - "AdminSeed": { - "Users": [] - } - }, - "Dictionary": { - "Cache": { - "SlidingExpiration": "00:30:00" - } - }, - "CacheWarmup": { - "DictionaryCodes": [ - "order_status", - "payment_method", - "shipping_method", - "product_category", - "user_role" - ] - }, - "Tenancy": { - "TenantIdHeaderName": "X-Tenant-Id", - "TenantCodeHeaderName": "X-Tenant-Code", - "IgnoredPaths": [ - "/health" - ], - "RootDomain": "" - }, - "Storage": { - "Provider": "TencentCos", - "CdnBaseUrl": "https://saas2025-1388556178.cos.ap-beijing.myqcloud.com", - "TencentCos": { - "SecretId": "AKID52mHageV8ZnnY5NRL3Xq270fAcw2vb5R", - "SecretKey": "B8sPitsiEXcS4ScaMvGMErFOL3ZqsgFa", - "Region": "ap-beijing", - "Bucket": "saas2025-1388556178", - "Endpoint": "https://saas2025-1388556178.cos.ap-beijing.myqcloud.com", - "CdnBaseUrl": "https://saas2025-1388556178.cos.ap-beijing.myqcloud.com", - "UseHttps": true, - "ForcePathStyle": false - }, - "QiniuKodo": { - "AccessKey": "QINIU_ACCESS_KEY", - "SecretKey": "QINIU_SECRET_KEY", - "Bucket": "takeout-files", - "DownloadDomain": "", - "Endpoint": "", - "UseHttps": true, - "SignedUrlExpirationMinutes": 30 - }, - "AliyunOss": { - "AccessKeyId": "OSS_ACCESS_KEY_ID", - "AccessKeySecret": "OSS_ACCESS_KEY_SECRET", - "Endpoint": "https://oss-cn-hangzhou.aliyuncs.com", - "Bucket": "takeout-files", - "CdnBaseUrl": "", - "UseHttps": true - }, - "Security": { - "MaxFileSizeBytes": 10485760, - "AllowedImageExtensions": [ - ".jpg", - ".jpeg", - ".png", - ".webp", - ".gif" - ], - "AllowedFileExtensions": [ - ".jpg", - ".jpeg", - ".png", - ".webp", - ".gif", - ".pdf" - ], - "DefaultUrlExpirationMinutes": 30, - "EnableRefererValidation": true, - "AllowedReferers": [ - "https://admin.example.com", - "https://miniapp.example.com" - ], - "AntiLeechTokenSecret": "ReplaceWithARandomToken" - } - }, - "Sms": { - "Provider": "Tencent", - "DefaultSignName": "外卖SaaS", - "UseMock": true, - "Tencent": { - "SecretId": "TENCENT_SMS_SECRET_ID", - "SecretKey": "TENCENT_SMS_SECRET_KEY", - "SdkAppId": "1400000000", - "SignName": "外卖SaaS", - "Region": "ap-beijing", - "Endpoint": "https://sms.tencentcloudapi.com" - }, - "Aliyun": { - "AccessKeyId": "ALIYUN_SMS_AK", - "AccessKeySecret": "ALIYUN_SMS_SK", - "Endpoint": "dysmsapi.aliyuncs.com", - "SignName": "外卖SaaS", - "Region": "cn-hangzhou" - }, - "SceneTemplates": { - "login": "LOGIN_TEMPLATE_ID", - "register": "REGISTER_TEMPLATE_ID", - "reset": "RESET_TEMPLATE_ID" - }, - "VerificationCode": { - "CodeLength": 6, - "ExpireMinutes": 5, - "CooldownSeconds": 60, - "CachePrefix": "sms:code" - } - }, - "RabbitMQ": { - "Host": "49.232.6.45", - "Port": 5672, - "Username": "Admin", - "Password": "MsuMshk112233", - "VirtualHost": "/", - "Exchange": "takeout.events", - "ExchangeType": "topic", - "PrefetchCount": 20 - }, - "Scheduler": { - "ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_hangfire_db;Username=hangfire_user;Password=HangFire112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", - "WorkerCount": 5, - "DashboardEnabled": false, - "DashboardPath": "/hangfire" - }, - "Otel": { - "Endpoint": "", - "Sampling": "ParentBasedAlwaysOn", - "UseConsoleExporter": true - } -} - diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json deleted file mode 100644 index a0c31a9..0000000 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json +++ /dev/null @@ -1,475 +0,0 @@ -{ - "App": { - "Seed": { - "Enabled": false, - "DefaultTenant": { - "TenantId": 1000000000001, - "Code": "demo", - "Name": "Demo租户", - "ShortName": "Demo", - "ContactName": "DemoAdmin", - "ContactPhone": "13800000000" - }, - "DictionaryGroups": [ - { - "Code": "order_status", - "Name": "订单状态", - "Scope": "Business", - "Items": [ - { "Key": "pending", "Value": "待支付", "SortOrder": 10 }, - { "Key": "paid", "Value": "已支付", "SortOrder": 20 }, - { "Key": "finished", "Value": "已完成", "SortOrder": 30 } - ] - }, - { - "Code": "store_tags", - "Name": "门店标签", - "Scope": "Business", - "Items": [ - { "Key": "hot", "Value": "热门", "SortOrder": 10 }, - { "Key": "new", "Value": "新店", "SortOrder": 20 } - ] - } - ], - "SystemParameters": [ - { "Key": "site_name", "Value": "外卖SaaS Demo", "Description": "演示环境站点名称", "SortOrder": 10, "IsEnabled": true }, - { "Key": "order_auto_cancel_minutes", "Value": "30", "Description": "待支付自动取消时间(分钟)", "SortOrder": 20, "IsEnabled": true } - ] - } - }, - "Identity": { - "AdminSeed": { - "Enabled": false, - "RoleTemplates": [ - { - "TemplateCode": "platform-admin", - "Name": "平台管理员", - "Description": "平台全量权限", - "IsActive": true, - "Permissions": [ - "identity:profile:read", - "identity:role:read", - "identity:role:create", - "identity:role:update", - "identity:role:delete", - "identity:role:bind-permission", - "identity:permission:read", - "identity:permission:create", - "identity:permission:update", - "identity:permission:delete", - "identity:user:read", - "identity:user:create", - "identity:user:update", - "identity:user:delete", - "identity:user:status", - "identity:user:reset-password", - "identity:user:batch", - "role-template:read", - "role-template:create", - "role-template:update", - "role-template:delete", - "tenant-bill:read", - "tenant-bill:create", - "tenant-bill:pay", - "tenant-announcement:read", - "tenant-announcement:create", - "tenant-announcement:update", - "tenant-announcement:delete", - "tenant-announcement:publish", - "tenant-announcement:revoke", - "platform-announcement:create", - "platform-announcement:publish", - "platform-announcement:revoke", - "tenant-notification:read", - "tenant-notification:update", - "tenant:create", - "tenant:read", - "tenant:review", - "tenant:review:force-claim", - "tenant:subscription", - "tenant:quota:check", - "tenant-package:read", - "tenant-package:create", - "tenant-package:update", - "tenant-package:delete", - "merchant:create", - "merchant:read", - "merchant:update", - "merchant:delete", - "merchant:review", - "merchant_category:read", - "merchant_category:create", - "merchant_category:update", - "merchant_category:delete", - "store:create", - "store:read", - "store:read:all", - "store:update", - "store:delete", - "store-table-area:read", - "store-table-area:create", - "store-table-area:update", - "store-table-area:delete", - "store-table:read", - "store-table:create", - "store-table:update", - "store-table:delete", - "store-table:export", - "store-staff:read", - "store-staff:create", - "store-staff:update", - "store-staff:delete", - "store-shift:read", - "store-shift:create", - "store-shift:update", - "store-shift:delete", - "product:create", - "product:read", - "product:update", - "product:delete", - "product:publish", - "product-sku:read", - "product-sku:update", - "product-attr:read", - "product-attr:update", - "product-addon:read", - "product-addon:update", - "product-media:read", - "product-media:update", - "product-pricing:read", - "product-pricing:update", - "order:create", - "order:read", - "order:update", - "order:delete", - "payment:create", - "payment:read", - "payment:update", - "payment:delete", - "delivery:create", - "delivery:read", - "delivery:update", - "delivery:delete", - "dictionary:group:read", - "dictionary:group:create", - "dictionary:group:update", - "dictionary:group:delete", - "dictionary:item:create", - "dictionary:item:update", - "dictionary:item:delete", - "dictionary:override:read", - "dictionary:override:update", - "system-parameter:create", - "system-parameter:read", - "system-parameter:update", - "system-parameter:delete" - ] - }, - { - "TemplateCode": "tenant-admin", - "Name": "租户管理员", - "Description": "管理本租户的门店、商品、订单与权限", - "IsActive": true, - "Permissions": [ - "identity:profile:read", - "identity:role:read", - "identity:role:create", - "identity:role:update", - "identity:role:delete", - "identity:role:bind-permission", - "identity:permission:read", - "identity:permission:create", - "identity:permission:update", - "identity:permission:delete", - "identity:user:read", - "identity:user:create", - "identity:user:update", - "identity:user:delete", - "identity:user:status", - "identity:user:reset-password", - "identity:user:batch", - "tenant-bill:read", - "tenant-bill:create", - "tenant-bill:pay", - "tenant-announcement:read", - "tenant-announcement:create", - "tenant-announcement:update", - "tenant-announcement:delete", - "tenant-announcement:publish", - "tenant-announcement:revoke", - "tenant-notification:read", - "tenant-notification:update", - "tenant:read", - "tenant:subscription", - "tenant:quota:check", - "merchant:read", - "merchant:update", - "merchant_category:read", - "merchant_category:create", - "merchant_category:update", - "merchant_category:delete", - "store:create", - "store:read", - "store:update", - "store:delete", - "store-table-area:read", - "store-table-area:create", - "store-table-area:update", - "store-table-area:delete", - "store-table:read", - "store-table:create", - "store-table:update", - "store-table:delete", - "store-table:export", - "store-staff:read", - "store-staff:create", - "store-staff:update", - "store-staff:delete", - "store-shift:read", - "store-shift:create", - "store-shift:update", - "store-shift:delete", - "product:create", - "product:read", - "product:update", - "product:delete", - "product:publish", - "product-sku:read", - "product-sku:update", - "product-attr:read", - "product-attr:update", - "product-addon:read", - "product-addon:update", - "product-media:read", - "product-media:update", - "product-pricing:read", - "product-pricing:update", - "inventory:read", - "inventory:adjust", - "inventory:lock", - "inventory:release", - "inventory:deduct", - "inventory:batch:read", - "inventory:batch:update", - "inventory:lock:expire", - "order:create", - "order:read", - "order:update", - "delivery:create", - "delivery:read", - "delivery:update", - "payment:create", - "payment:read", - "payment:update", - "dictionary:group:read", - "dictionary:group:create", - "dictionary:group:update", - "dictionary:group:delete", - "dictionary:item:create", - "dictionary:item:update", - "dictionary:item:delete", - "dictionary:override:read", - "dictionary:override:update", - "system-parameter:read" - ] - }, - { - "TemplateCode": "store-manager", - "Name": "店长", - "Description": "负责门店运营与商品、订单管理", - "IsActive": true, - "Permissions": [ - "identity:profile:read", - "store:read", - "store:update", - "store-table-area:read", - "store-table-area:create", - "store-table-area:update", - "store-table-area:delete", - "store-table:read", - "store-table:create", - "store-table:update", - "store-table:export", - "store-staff:read", - "store-staff:create", - "store-staff:update", - "store-shift:read", - "store-shift:create", - "store-shift:update", - "product:create", - "product:read", - "product:update", - "product:publish", - "product-sku:read", - "product-sku:update", - "product-attr:read", - "product-attr:update", - "product-addon:read", - "product-addon:update", - "product-media:read", - "product-media:update", - "product-pricing:read", - "product-pricing:update", - "inventory:read", - "inventory:adjust", - "inventory:lock", - "inventory:release", - "inventory:deduct", - "inventory:batch:read", - "inventory:batch:update", - "inventory:lock:expire", - "pickup-setting:read", - "pickup-setting:update", - "pickup-slot:read", - "pickup-slot:create", - "pickup-slot:update", - "pickup-slot:delete", - "order:create", - "order:read", - "order:update", - "delivery:read", - "delivery:update", - "payment:read", - "payment:update", - "dictionary:group:read", - "dictionary:item:create", - "dictionary:item:update", - "dictionary:item:delete" - ] - }, - { - "TemplateCode": "store-staff", - "Name": "店员", - "Description": "处理订单履约与收款查询", - "IsActive": true, - "Permissions": [ - "identity:profile:read", - "store:read", - "store-table-area:read", - "store-table:read", - "store-shift:read", - "product:read", - "order:read", - "order:update", - "delivery:read", - "payment:read" - ] - } - ], - "Users": [ - { - "Account": "admin", - "DisplayName": "平台管理员", - "Password": "Admin@123456", - "TenantId": 1000000000001, - "Roles": [ "PlatformAdmin" ], - "Permissions": [ - "identity:profile:read", - "identity:role:read", - "identity:role:create", - "identity:role:update", - "identity:role:delete", - "identity:role:bind-permission", - "identity:permission:read", - "identity:permission:create", - "identity:permission:update", - "identity:permission:delete", - "role-template:read", - "role-template:create", - "role-template:update", - "role-template:delete", - "tenant-bill:read", - "tenant-bill:create", - "tenant-bill:pay", - "tenant-announcement:read", - "tenant-announcement:create", - "tenant-announcement:update", - "tenant-announcement:delete", - "tenant-announcement:publish", - "tenant-announcement:revoke", - "platform-announcement:create", - "platform-announcement:publish", - "platform-announcement:revoke", - "tenant-notification:read", - "tenant-notification:update", - "tenant:create", - "tenant:read", - "tenant:review", - "tenant:subscription", - "tenant:quota:check", - "tenant-package:read", - "tenant-package:create", - "tenant-package:update", - "tenant-package:delete", - "merchant:create", - "merchant:read", - "merchant:update", - "merchant:delete", - "merchant:review", - "merchant_category:read", - "merchant_category:create", - "merchant_category:update", - "merchant_category:delete", - "store:create", - "store:read", - "store:read:all", - "store:update", - "store:delete", - "product:create", - "product:read", - "product:update", - "product:delete", - "product:publish", - "product-sku:read", - "product-sku:update", - "product-attr:read", - "product-attr:update", - "product-addon:read", - "product-addon:update", - "product-media:read", - "product-media:update", - "product-pricing:read", - "product-pricing:update", - "inventory:read", - "inventory:adjust", - "inventory:lock", - "inventory:release", - "inventory:deduct", - "inventory:batch:read", - "inventory:batch:update", - "inventory:lock:expire", - "pickup-setting:read", - "pickup-setting:update", - "pickup-slot:read", - "pickup-slot:create", - "pickup-slot:update", - "pickup-slot:delete", - "order:create", - "order:read", - "order:update", - "order:delete", - "payment:create", - "payment:read", - "payment:update", - "payment:delete", - "delivery:create", - "delivery:read", - "delivery:update", - "delivery:delete", - "dictionary:group:read", - "dictionary:group:create", - "dictionary:group:update", - "dictionary:group:delete", - "dictionary:item:create", - "dictionary:item:update", - "dictionary:item:delete", - "system-parameter:create", - "system-parameter:read", - "system-parameter:update", - "system-parameter:delete" - ] - } - ] - } - } -} diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayOpenTelemetryOptions.cs b/src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayOpenTelemetryOptions.cs deleted file mode 100644 index 97064fb..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayOpenTelemetryOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace TakeoutSaaS.ApiGateway.Configuration; - -/// -/// 网关 OpenTelemetry 导出配置。 -/// -public class GatewayOpenTelemetryOptions -{ - /// - /// 是否启用 OpenTelemetry。 - /// - public bool Enabled { get; set; } = true; - - /// - /// 服务名称,用于 Resource 标识。 - /// - public string ServiceName { get; set; } = "TakeoutSaaS.ApiGateway"; - - /// - /// OTLP 导出端点(http/https)。 - /// - public string? OtlpEndpoint { get; set; } -} diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayRateLimitOptions.cs b/src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayRateLimitOptions.cs deleted file mode 100644 index cf633a4..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/Configuration/GatewayRateLimitOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace TakeoutSaaS.ApiGateway.Configuration; - -/// -/// 网关限流参数配置。 -/// -public class GatewayRateLimitOptions -{ - /// - /// 是否开启固定窗口限流。 - /// - public bool Enabled { get; set; } = true; - - /// - /// 固定窗口内允许的最大请求数。 - /// - public int PermitLimit { get; set; } = 300; - - /// - /// 固定窗口长度(秒)。 - /// - public int WindowSeconds { get; set; } = 60; - - /// - /// 排队等待的最大请求数。 - /// - public int QueueLimit { get; set; } = 100; -} diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile b/src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile deleted file mode 100644 index beac7d4..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src - -# 1. 先只复制 csproj 文件 (利用 Docker 缓存) -COPY ["src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj", "src/Gateway/TakeoutSaaS.ApiGateway/"] - -# 2. 还原依赖 (如果 csproj 没变,这一步会直接使用缓存,瞬间完成) -RUN dotnet restore "src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj" - -# 3. 再复制剩余的所有源代码 -COPY . . - -# 4. 发布 -RUN dotnet publish "src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj" -c Release -o /app/publish - -# --- 运行时环境 --- -FROM mcr.microsoft.com/dotnet/aspnet:10.0 -WORKDIR /app -COPY --from=build /app/publish . - -# 显式声明端口 -EXPOSE 5000 -ENV ASPNETCORE_URLS=http://+:5000 - -ENTRYPOINT ["dotnet", "TakeoutSaaS.ApiGateway.dll"] \ No newline at end of file diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Program.cs b/src/Gateway/TakeoutSaaS.ApiGateway/Program.cs deleted file mode 100644 index 6011bd9..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/Program.cs +++ /dev/null @@ -1,215 +0,0 @@ -using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.HttpOverrides; -using OpenTelemetry.Logs; -using OpenTelemetry.Metrics; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; -using Serilog; -using System.Diagnostics; -using System.Threading.RateLimiting; -using TakeoutSaaS.ApiGateway.Configuration; - -const string CorsPolicyName = "GatewayCors"; - -// 1. 创建构建器并配置 Serilog -var builder = WebApplication.CreateBuilder(args); -builder.Host.UseSerilog((context, services, loggerConfiguration) => -{ - loggerConfiguration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext(); -}); - -// 2. 配置 YARP 反向代理 -builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); - -// 3. 转发头部配置 -builder.Services.Configure(options => -{ - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - options.KnownIPNetworks.Clear(); - options.KnownProxies.Clear(); -}); - -// 4. 配置 CORS -builder.Services.AddCors(options => -{ - options.AddPolicy(CorsPolicyName, policy => - { - policy.AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod(); - }); -}); - -// 5. 配置网关限流 -builder.Services.Configure(builder.Configuration.GetSection("Gateway:RateLimiting")); -var rateLimitOptions = builder.Configuration.GetSection("Gateway:RateLimiting").Get() ?? new(); - -if (rateLimitOptions.Enabled) -{ - builder.Services.AddRateLimiter(options => - { - options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; - options.GlobalLimiter = PartitionedRateLimiter.Create(context => - { - var remoteIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; - return RateLimitPartition.GetFixedWindowLimiter(remoteIp, _ => new FixedWindowRateLimiterOptions - { - PermitLimit = Math.Max(1, rateLimitOptions.PermitLimit), - Window = TimeSpan.FromSeconds(Math.Max(1, rateLimitOptions.WindowSeconds)), - QueueLimit = Math.Max(0, rateLimitOptions.QueueLimit), - QueueProcessingOrder = QueueProcessingOrder.OldestFirst - }); - }); - }); -} - -// 6. 配置 OpenTelemetry -var otelOptions = builder.Configuration.GetSection("OpenTelemetry").Get() ?? new(); -if (otelOptions.Enabled) -{ - builder.Services.AddOpenTelemetry() - // 1. 配置统一的 Resource,便于追踪定位。 - .ConfigureResource(resource => resource.AddService(otelOptions.ServiceName ?? "TakeoutSaaS.ApiGateway")) - .WithMetrics(metrics => - { - metrics.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation(); - - if (!string.IsNullOrWhiteSpace(otelOptions.OtlpEndpoint)) - { - metrics.AddOtlpExporter(options => - { - options.Endpoint = new Uri(otelOptions.OtlpEndpoint); - }); - } - }) - .WithTracing(tracing => - { - tracing.AddAspNetCoreInstrumentation(options => - { - options.RecordException = true; - }) - .AddHttpClientInstrumentation(); - - if (!string.IsNullOrWhiteSpace(otelOptions.OtlpEndpoint)) - { - tracing.AddOtlpExporter(options => - { - options.Endpoint = new Uri(otelOptions.OtlpEndpoint); - }); - } - }); - - builder.Logging.AddOpenTelemetry(logging => - { - logging.IncludeScopes = true; - logging.ParseStateValues = true; - if (!string.IsNullOrWhiteSpace(otelOptions.OtlpEndpoint)) - { - logging.AddOtlpExporter(options => - { - options.Endpoint = new Uri(otelOptions.OtlpEndpoint); - }); - } - }); -} - -// 7. 构建应用 -var app = builder.Build(); - -// 8. 转发头中间件 -app.UseForwardedHeaders(); - -// 9. 全局异常处理中间件 -app.UseExceptionHandler(errorApp => -{ - // 1. 捕获所有未处理异常并返回统一结构。 - errorApp.Run(async context => - { - var feature = context.Features.Get(); - var traceId = Activity.Current?.Id ?? context.TraceIdentifier; - context.Response.StatusCode = StatusCodes.Status500InternalServerError; - context.Response.ContentType = "application/json"; - - var payload = new - { - success = false, - code = 500, - message = "Gateway internal error", - traceId - }; - - var logger = context.RequestServices.GetRequiredService().CreateLogger("Gateway"); - logger.LogError(feature?.Error, "网关异常 {TraceId}", traceId); - await context.Response.WriteAsJsonAsync(payload, cancellationToken: context.RequestAborted); - }); -}); - -// 10. 请求日志 -app.UseSerilogRequestLogging(options => -{ - options.MessageTemplate = "网关请求 {RequestMethod} {RequestPath} => {StatusCode} 用时 {Elapsed:0.000} 秒"; - options.GetLevel = (httpContext, elapsed, ex) => ex is not null ? Serilog.Events.LogEventLevel.Error : Serilog.Events.LogEventLevel.Information; - options.EnrichDiagnosticContext = (diagnosticContext, httpContext) => - { - diagnosticContext.Set("TraceId", Activity.Current?.Id ?? httpContext.TraceIdentifier); - diagnosticContext.Set("ClientIp", httpContext.Connection.RemoteIpAddress?.ToString()); - }; -}); - -// 11. CORS 与限流 -app.UseCors(CorsPolicyName); - -if (rateLimitOptions.Enabled) -{ - app.UseRateLimiter(); -} - -// 12. 透传请求头并保证 Trace -app.Use(async (context, next) => -{ - // 1. 确保请求拥有可追踪的 ID。 - if (!context.Request.Headers.ContainsKey("X-Request-Id")) - { - context.Request.Headers["X-Request-Id"] = Activity.Current?.Id ?? Guid.NewGuid().ToString("N"); - } - - // 2. 透传租户等标识头,方便下游继续使用。 - var tenantId = context.Request.Headers["X-Tenant-Id"]; - if (!string.IsNullOrWhiteSpace(tenantId)) - { - context.Request.Headers["X-Tenant-Id"] = tenantId; - } - - var tenantCode = context.Request.Headers["X-Tenant-Code"]; - if (!string.IsNullOrWhiteSpace(tenantCode)) - { - context.Request.Headers["X-Tenant-Code"] = tenantCode; - } - - await next(context); -}); - -// 13. 映射反向代理与健康接口 -app.MapReverseProxy(); - -app.MapGet("/", () => Results.Json(new -{ - Service = "TakeoutSaaS.ApiGateway", - Status = "OK", - Timestamp = DateTimeOffset.UtcNow -})); - -app.MapGet("/healthz", () => Results.Json(new -{ - Service = "TakeoutSaaS.ApiGateway", - Status = "Healthy", - Timestamp = DateTimeOffset.UtcNow -})); - -app.Run(); diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json b/src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json deleted file mode 100644 index 3956499..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "TakeoutSaaS.ApiGateway": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:2677;http://localhost:2683" - } - } -} \ No newline at end of file diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj b/src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj deleted file mode 100644 index 207e70312c191ac92f77e62c7f21fdb09c6e57e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2130 zcmdUwOHbQS5QWbgiT_}+#}JBGg`ldUg+&38K-Fb%9F!6q%XWDD`Ic|yVn_*bRVh?- z;paT=nKQ3nKik^Vovt-jq#LCwRBEJ&GVN+jEydd886nA(@PB|b)1^zNsx;I(T07|M zsiU?|@G#L2^b@3!x>#Jf_E#(umzHP@k;XdGzIr@^R;+K>UEr&uRjpthJ$cL>Tcvw_ zaUWCVGg?8jr#`YvG%J1PtxsI3ZaJ+0LkHX@X%Ue{)z$`QH(S&wg8w5;;c1M`1@{z> z#!CHV{^m7)_izZJ99v^z1eP;r+xRSB@z$(ed$w}}>&+asb;yY+2o^JVLNp0n^_|62 zw1-@U$C+@C@L_c@hKT{+RwKtmWIXh^8!N_nh0Q%I?^9d3^KT~?{8_9WVyMuzIvab{ zox9b`ifn1yGiY_WF;@j|5rd)6o;suC^aC)K#2LE5kh}-1mK{S?kRQuK#(UpUT1UtF zh%~24EkmC?FX_AC`A^snz+8d;f8qL+92oy2&&0n(-Sw^+(L)pZ`0uhFW@A0|m(yJ@ z@pQdwuBgS>tI%rJI(|_t?x1IzsO7m|4yNz)ML{*ivME<@zVvaD-Z8yi&)60gAGpF+ zoW){uxM{*JXa1gndEh-Arf(Od*6}uXW6nS3K05)M=Tn{Sd1jlUdEPkno)7!lHqry3 ChFZn| diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/appsettings.Development.json b/src/Gateway/TakeoutSaaS.ApiGateway/appsettings.Development.json deleted file mode 100644 index 56dd41a..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/appsettings.Development.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "OpenTelemetry": { - "Enabled": false - }, - "ReverseProxy": { - "Routes": { - "admin-route": { - "ClusterId": "admin", - "Match": { "Path": "/api/admin/{**catch-all}" } - }, - "mini-route": { - "ClusterId": "mini", - "Match": { "Path": "/api/mini/{**catch-all}" } - }, - "user-route": { - "ClusterId": "user", - "Match": { "Path": "/api/user/{**catch-all}" } - } - }, - "Clusters": { - "admin": { - "Destinations": { - "d1": { "Address": "http://localhost:5001/" } - } - }, - "mini": { - "Destinations": { - "d1": { "Address": "http://localhost:5002/" } - } - }, - "user": { - "Destinations": { - "d1": { "Address": "http://localhost:5003/" } - } - } - } - } -} diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/appsettings.json b/src/Gateway/TakeoutSaaS.ApiGateway/appsettings.json deleted file mode 100644 index 0c5d718..0000000 --- a/src/Gateway/TakeoutSaaS.ApiGateway/appsettings.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "Serilog": { - "Using": [ "Serilog.Sinks.Console" ], - "MinimumLevel": { - "Default": "Information", - "Override": { - "Microsoft": "Warning", - "System": "Warning", - "Yarp": "Information" - } - }, - "WriteTo": [ - { - "Name": "Console", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}" - } - } - ], - "Enrich": [ "FromLogContext" ], - "Properties": { - "Application": "TakeoutSaaS.ApiGateway" - } - }, - "Gateway": { - "RateLimiting": { - "Enabled": true, - "PermitLimit": 300, - "WindowSeconds": 60, - "QueueLimit": 100 - } - }, - "OpenTelemetry": { - "Enabled": true, - "ServiceName": "TakeoutSaaS.ApiGateway", - "OtlpEndpoint": "http://localhost:4317" - }, - "ReverseProxy": { - "Routes": { - "admin-route": { - "ClusterId": "admin", - "Match": { "Path": "/api/admin/{**catch-all}" } - }, - "mini-route": { - "ClusterId": "mini", - "Match": { "Path": "/api/mini/{**catch-all}" } - }, - "user-route": { - "ClusterId": "user", - "Match": { "Path": "/api/user/{**catch-all}" } - } - }, - "Clusters": { - "admin": { - "Destinations": { - "primary": { "Address": "http://49.7.179.246:7801/" } - } - }, - "mini": { - "Destinations": { - "primary": { "Address": "http://49.7.179.246:7701/" } - } - }, - "user": { - "Destinations": { - "primary": { "Address": "http://49.7.179.246:7901/" } - } - } - } - }, - "AllowedHosts": "*" -}