chore: 完善 .gitignore + 清理 IDE/构建产物

- .gitignore 补充 .idea/.vscode/.vs/ 等 IDE 目录
- 从 git 移除已误提交的 .idea/ 目录
- 移除 bin/obj 缓存
This commit is contained in:
xiangning 2026-07-02 17:58:52 +08:00
parent 52383d83f3
commit 386280a4c4
17 changed files with 65 additions and 359 deletions

22
.gitignore vendored
View File

@ -1,12 +1,32 @@
# ===== .NET 构建产物 =====
**/bin/
**/obj/
*.user
*.suo
*.userosscache
*.userprefs
*.lscache
# ===== IDE / 编辑器 =====
.idea/
.vscode/
.vs/
*.swp
*.swo
*~
# ===== 敏感配置 =====
.env
.env.*
!.env.example
**/appsettings.Development.json
**/appsettings.Production.json
*.lscache
**/secrets.json
# ===== 前端 =====
node_modules/
dist/
# ===== 系统 =====
.DS_Store
Thumbs.db

View File

@ -1 +0,0 @@
Admin

View File

@ -1,272 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoGeneratedRunConfigurationManager">
<projectFile>src/Admin/Admin.csproj</projectFile>
<projectFile>src/modules/file/Api/FileSystem.Api.csproj</projectFile>
<projectFile profileName="http">src/modules/im/Api/IM.Api.csproj</projectFile>
<projectFile>src/modules/monitor/Monitor.Api/Monitor.Api.csproj</projectFile>
<projectFile profileName="http">src/modules/order/Order.Api/Order.Api.csproj</projectFile>
<projectFile profileName="http">src/modules/rag/Api/RAG.Api.csproj</projectFile>
<projectFile profileName="http">src/modules/sso/Api/SSO.Api.csproj</projectFile>
<projectFile profileName="https">src/modules/sso/Api/SSO.Api.csproj</projectFile>
<projectFile>src/modules/workflow/Api/Workflow.Api.csproj</projectFile>
</component>
<component name="ChangeListManager">
<list default="true" id="75422575-b6e2-4ec3-8b59-7ca42bb82b14" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Buckets/CreateBucketEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Buckets/CreateBucketRequest.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Buckets/DeleteBucketEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Buckets/DeleteBucketRequest.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Buckets/ListBucketsEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Folders/CreateFolderEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Folders/CreateFolderRequest.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Folders/DeleteFolderEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Folders/DeleteFolderRequest.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/file/Api/Endpoints/Folders/GetFolderTreeEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/im/Api/Endpoints/Chat/AddGroupMemberEndpoint.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/im/Application/Chat/Commands/AddGroupMemberCommand.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/im/Application/Chat/DTOs/AddGroupMemberRequest.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/im/Application/Chat/DTOs/MessageMetadataDto.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/im/Application/Chat/DTOs/RecallMessageRequest.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/im/Application/Chat/Handlers/AddGroupMemberCommandHandler.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/workflow/Application/BuildFlow/Applications/Commands/CreateBuildFlowApplicationCommand.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/workflow/Application/BuildFlow/Applications/DTOs/BuildFlowApplicationDto.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/workflow/Application/Features/Notifications/GetNotificationsQuery.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/modules/workflow/Domain/StateMachine/InstanceStateMachine.cs" root0="SKIP_HIGHLIGHTING" />
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="Toolset" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 1
}</component>
<component name="ProjectId" id="3FinWLLpUIj0MaP1bx6rE2BSFKP" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;codeWithMe.voiceChat.enabledByDefault&quot;: &quot;false&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/wen/project/rag/admin/Admin.slnx&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="RunManager" selected=".NET 项目.Admin">
<configuration name="Admin" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="ENV_FILE_PATHS" value="" />
<option name="REDIRECT_INPUT_PATH" value="" />
<option name="MIXED_MODE_DEBUG" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/src/Admin/Admin.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="FileSystem.Api" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="ENV_FILE_PATHS" value="" />
<option name="REDIRECT_INPUT_PATH" value="" />
<option name="MIXED_MODE_DEBUG" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/src/modules/file/Api/FileSystem.Api.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="Monitor.Api" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="ENV_FILE_PATHS" value="" />
<option name="REDIRECT_INPUT_PATH" value="" />
<option name="MIXED_MODE_DEBUG" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/src/modules/monitor/Monitor.Api/Monitor.Api.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="Workflow.Api" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="ENV_FILE_PATHS" value="" />
<option name="REDIRECT_INPUT_PATH" value="" />
<option name="MIXED_MODE_DEBUG" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/src/modules/workflow/Api/Workflow.Api.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="IM.Api: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/src/modules/im/Api/IM.Api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="Order.Api: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/src/modules/order/Order.Api/Order.Api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="RAG.Api: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/src/modules/rag/Api/RAG.Api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="SSO.Api: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/src/modules/sso/Api/SSO.Api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="SSO.Api: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/src/modules/sso/Api/SSO.Api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="75422575-b6e2-4ec3-8b59-7ca42bb82b14" name="更改" comment="" />
<created>1782566787625</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1782566787625</updated>
<workItem from="1782566790245" duration="7864000" />
<workItem from="1782798132000" duration="2183000" />
<workItem from="1782800359006" duration="2924000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="System.OperationCanceledException" breakIfHandledByOtherCode="false" displayValue="System.OperationCanceledException" />
<option name="timeStamp" value="1" />
</breakpoint>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="System.Threading.Tasks.TaskCanceledException" breakIfHandledByOtherCode="false" displayValue="System.Threading.Tasks.TaskCanceledException" />
<option name="timeStamp" value="2" />
</breakpoint>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="System.Threading.ThreadAbortException" breakIfHandledByOtherCode="false" displayValue="System.Threading.ThreadAbortException" />
<option name="timeStamp" value="3" />
</breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>

View File

@ -1,15 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# Rider 忽略的文件
/projectSettingsUpdater.xml
/contentModel.xml
/modules.xml
/.idea.BuildFlow.iml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1 +0,0 @@
BuildFlow

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,15 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# Rider 忽略的文件
/contentModel.xml
/modules.xml
/.idea.build-flow.iml
/projectSettingsUpdater.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1 +0,0 @@
build-flow

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,18 +1,38 @@
using File.Application.Features.Files.DTOs;
using System.Net.Mime;
using File.Application.Features.Files.Queries;
using MediatR;
namespace BuildFlow.Host.Endpoints.file.Files;
/// <summary>下载文件端点。GET /api/files/download,按对象键返回流数据。</summary>
public class DownloadFileEndpoint(IMediator mediator) : Endpoint<DownloadFileRequest, DownloadFileResult>
/// <summary>
/// 下载文件端点。GET /api/files/download,按对象键流式返回文件内容。
/// 直接把 S3 响应流透传到 HTTP 响应体,整文件不进内存;Content-Disposition 触发浏览器下载。
/// 该路径已在 ApiResponseMiddlewareOptions.PassthroughPrefixes 注册,绕过响应包裹中间件的缓冲。
/// </summary>
[Authorize]
public class DownloadFileEndpoint(IMediator mediator) : Endpoint<DownloadFileRequest>
{
public override void Configure() => Get("/files/download");
public override async Task HandleAsync(DownloadFileRequest req, CancellationToken ct)
{
var result = await mediator.Send(new DownloadFileQuery(req.BucketName, req.ObjectKey), ct);
await Send.OkAsync(result, ct);
// 设置响应头:真实 ContentType + 触发下载的文件名
HttpContext.Response.StatusCode = 200;
HttpContext.Response.ContentType = result.ContentType;
var disposition = new ContentDisposition { FileName = result.FileName, Inline = false };
HttpContext.Response.Headers.ContentDisposition = disposition.ToString();
// 流式透传:S3 流 → HTTP 响应体,零内存拷贝
try
{
await result.Stream.CopyToAsync(HttpContext.Response.Body, ct);
await HttpContext.Response.Body.FlushAsync(ct);
}
finally
{
await result.Stream.DisposeAsync();
}
}
}

View File

@ -24,7 +24,7 @@ public class NotificationHub : Hub
public static string UserGroup(Guid userId) => $"user:{userId}";
/// <summary>从 ClaimsPrincipal 解析用户 Id(sub claim)。</summary>
internal static Guid? ResolveUserId(ClaimsPrincipal? user)
private static Guid? ResolveUserId(ClaimsPrincipal? user)
{
if (user?.Identity?.IsAuthenticated != true) return null;
var sub = user.FindFirstValue(JwtRegisteredClaimNames.Sub)

View File

@ -2,5 +2,9 @@
namespace File.Application.Features.Files.DTOs;
/// <summary>文件下载结果 DTO(字节流、ContentType、文件名)。</summary>
public record DownloadFileResult(byte[] Data, string ContentType, string FileName);
/// <summary>
/// 文件下载结果 DTO(流、ContentType、文件名)。
/// 持有 S3 返回的原始 Stream,由 Endpoint 直接透传到响应输出流,避免整文件进内存。
/// 调用方负责在使用后 Dispose Stream。
/// </summary>
public record DownloadFileResult(Stream Stream, string ContentType, string FileName);

View File

@ -5,15 +5,19 @@ using MediatR;
namespace File.Application.Features.Files.Handlers;
/// <summary>下载文件查询处理器。从 S3 拉流并拷贝到内存返回。</summary>
/// <summary>
/// 下载文件查询处理器。从 S3 拉取响应流,原样透传给 Endpoint 写入 HTTP 响应体,
/// 不再拷贝到内存,大文件零内存压力。真实 ContentType 由 S3 HeadObject/GetObject 返回。
/// </summary>
public class DownloadFileQueryHandler(IS3StorageService s3) : IRequestHandler<DownloadFileQuery, DownloadFileResult>
{
public async Task<DownloadFileResult> Handle(DownloadFileQuery req, CancellationToken ct)
{
var (stream, _) = await s3.DownloadFileAsync(req.BucketName, req.ObjectKey, ct);
using var ms = new MemoryStream();
await stream.CopyToAsync(ms, ct);
await stream.DisposeAsync();
return new DownloadFileResult(ms.ToArray(), "application/octet-stream", req.ObjectKey);
var (stream, contentType) = await s3.DownloadFileAsync(req.BucketName, req.ObjectKey, ct);
// 文件名取 ObjectKey 最后一段(S3 key 可能含目录前缀)
var fileName = req.ObjectKey.Contains('/')
? req.ObjectKey[(req.ObjectKey.LastIndexOf('/') + 1)..]
: req.ObjectKey;
return new DownloadFileResult(stream, contentType, fileName);
}
}