From 750346fdb2ea31fac814e75652c7a9355d5745ba Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Fri, 20 Feb 2026 16:23:29 +0800 Subject: [PATCH] feat(api): add tenant files upload endpoint --- .../Requests/FileUploadFormRequest.cs | 27 +++++++ .../Controllers/FilesController.cs | 79 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/Api/TakeoutSaaS.TenantApi/Contracts/Requests/FileUploadFormRequest.cs create mode 100644 src/Api/TakeoutSaaS.TenantApi/Controllers/FilesController.cs diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Requests/FileUploadFormRequest.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Requests/FileUploadFormRequest.cs new file mode 100644 index 0000000..83018c8 --- /dev/null +++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Requests/FileUploadFormRequest.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Http; + +namespace TakeoutSaaS.TenantApi.Contracts.Requests; + +/// +/// 文件上传表单请求。 +/// +public sealed record FileUploadFormRequest +{ + /// + /// 上传文件。 + /// + [Required] + public required IFormFile File { get; init; } + + /// + /// 租户 ID。 + /// + [Required] + public long? TenantId { get; init; } + + /// + /// 上传类型。 + /// + public string? Type { get; init; } +} diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/FilesController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/FilesController.cs new file mode 100644 index 0000000..e4ebeba --- /dev/null +++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/FilesController.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +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.Abstractions.Tenancy; +using TakeoutSaaS.Shared.Web.Api; +using TakeoutSaaS.TenantApi.Contracts.Requests; + +namespace TakeoutSaaS.TenantApi.Controllers; + +/// +/// 租户端文件上传。 +/// +[ApiVersion("1.0")] +[Authorize] +[Route("api/tenant/v{version:apiVersion}/files")] +public sealed class FilesController( + IFileStorageService fileStorageService, + ITenantProvider tenantProvider) : BaseApiController +{ + /// + /// 上传图片或文件。 + /// + /// 文件上传响应信息。 + [HttpPost("upload")] + [Consumes("multipart/form-data")] + [RequestFormLimits(MultipartBodyLengthLimit = 30 * 1024 * 1024)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status403Forbidden)] + 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 (!request.TenantId.HasValue || request.TenantId.Value <= 0) + { + return ApiResponse.Error(ErrorCodes.BadRequest, "TenantId 不能为空"); + } + + // 3. 校验当前租户上下文 + var currentTenantId = tenantProvider.GetCurrentTenantId(); + if (currentTenantId <= 0) + { + return ApiResponse.Error(ErrorCodes.BadRequest, "缺少租户标识"); + } + + if (request.TenantId.Value != currentTenantId) + { + return ApiResponse.Error(ErrorCodes.Forbidden, "禁止跨租户上传文件"); + } + + // 4. 解析上传类型 + if (!UploadFileTypeParser.TryParse(request.Type, out var uploadType)) + { + return ApiResponse.Error(ErrorCodes.BadRequest, "上传类型不合法"); + } + + // 5. 提取请求来源 + var origin = Request.Headers["Origin"].FirstOrDefault() ?? Request.Headers["Referer"].FirstOrDefault(); + await using var stream = request.File.OpenReadStream(); + + // 6. 调用存储服务执行上传 + var result = await fileStorageService.UploadAsync( + new UploadFileRequest(uploadType, stream, request.File.FileName, request.File.ContentType ?? string.Empty, request.File.Length, origin), + cancellationToken); + + // 7. 返回上传结果 + return ApiResponse.Ok(result); + } +}