feat: 新增 /file-storage/stream 供面单预览鉴权读取
All checks were successful
Build and Deploy / deploy (push) Successful in 1m46s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-09-11 19:14:26 +08:00
parent d1d31fee8a
commit 40997f7d71

View File

@ -108,6 +108,55 @@ public class FileStorageService : ApplicationService
var key = FileStorage.ToStorageKey(input.PathOrUrl);
return new NormalizeKeyOutput { Key = key };
}
/// <summary>
/// 按 key / URL 流式读取文件(供面单预览等场景,避免前端跨域 iframe 空白)。
/// </summary>
[HttpGet("/file-storage/stream")]
public async Task<IActionResult> StreamAsync([FromQuery] string key, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(key))
{
throw new BusinessException("key 不能为空");
}
string storageKey;
try
{
storageKey = FileStorage.ToStorageKey(key) ?? FileStoragePath.NormalizeKey(key);
}
catch (Exception)
{
throw new BusinessException("无法解析文件 key");
}
if (!await FileStorage.ExistsAsync(storageKey, cancellationToken))
{
throw new BusinessException("文件不存在或已过期");
}
var stream = await FileStorage.OpenReadAsync(storageKey, cancellationToken);
return new FileStreamResult(stream, GuessStreamContentType(storageKey))
{
EnableRangeProcessing = true,
};
}
private static string GuessStreamContentType(string storageKey)
{
if (storageKey.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
{
return "application/pdf";
}
if (storageKey.EndsWith(".html", StringComparison.OrdinalIgnoreCase)
|| storageKey.EndsWith(".htm", StringComparison.OrdinalIgnoreCase))
{
return "text/html; charset=utf-8";
}
return "application/octet-stream";
}
}
public class PresignUploadInput