This commit is contained in:
fuenmao
2026-08-28 11:11:22 +08:00
parent 80940c60a5
commit 58804be611
8 changed files with 267 additions and 57 deletions
@@ -17,11 +17,16 @@ namespace SimpleAdmin.System;
/// </summary>
public class FileService : DbRepository<SysFile>, IFileService
{
private static readonly HttpClient OssPublicHttpClient = new();
private readonly IConfigService _configService;
private readonly IAliyunOssClientProvider _ossClientProvider;
public FileService(IConfigService configService)
public FileService(
IConfigService configService,
IAliyunOssClientProvider ossClientProvider)
{
_configService = configService;
_ossClientProvider = ossClientProvider;
}
/// <inheritdoc/>
@@ -258,7 +263,7 @@ public class FileService : DbRepository<SysFile>, IFileService
metadata.ContentType = file.ContentType;
await using var stream = file.OpenReadStream();
var client = CreateOssClient(setting);
var client = await _ossClientProvider.GetClientAsync();
client.PutObject(setting.Bucket, objectName, stream, metadata);
var downloadUrl = string.IsNullOrWhiteSpace(setting.OSSUrl)
@@ -274,22 +279,63 @@ public class FileService : DbRepository<SysFile>, IFileService
private async Task<FileStreamResult> GetFileStreamResultFromAliyunOss(SysFile file)
{
var setting = GetOssSetting();
var client = CreateOssClient(setting);
var ossObject = client.GetObject(file.Bucket, file.ObjName);
var memory = new MemoryStream();
await using (ossObject.Content)
try
{
await ossObject.Content.CopyToAsync(memory);
var client = await _ossClientProvider.GetClientAsync();
var ossObject = client.GetObject(file.Bucket, file.ObjName);
await using (ossObject.Content)
{
return await BuildFileStreamResult(ossObject.Content, file.Name);
}
}
catch
{
// STS/OSS SDK 暂时不可用时,只通过已公开的对象地址读取文件。
// 后台下载接口仍由系统鉴权保护,且该降级过程不会使用或暴露长期 AK。
var publicUrl = BuildOssPublicUrl(file, setting);
using var response = await OssPublicHttpClient.GetAsync(
publicUrl,
HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
await using var content = await response.Content.ReadAsStreamAsync();
return await BuildFileStreamResult(content, file.Name);
}
}
/// <summary>
/// 将远程文件流复制到独立内存流,避免远程响应释放后影响文件下载。
/// </summary>
private static async Task<FileStreamResult> BuildFileStreamResult(
Stream source,
string originalFileName)
{
var memory = new MemoryStream();
await source.CopyToAsync(memory);
memory.Position = 0;
var fileName = HttpUtility.UrlEncode(file.Name, Encoding.GetEncoding("UTF-8"));
var fileName = HttpUtility.UrlEncode(
originalFileName,
Encoding.GetEncoding("UTF-8"));
return new FileStreamResult(memory, "application/octet-stream")
{
FileDownloadName = fileName
};
}
/// <summary>
/// 根据受信任的 OSS 配置生成公开对象地址,不使用数据库中的任意外部地址。
/// </summary>
private static string BuildOssPublicUrl(SysFile file, OssSettingOptions setting)
{
var baseUrl = string.IsNullOrWhiteSpace(setting.OSSUrl)
? $"https://{file.Bucket}.{setting.EndPoint.Trim().TrimEnd('/')}"
: setting.OSSUrl.Trim().TrimEnd('/');
var objectPath = string.Join(
"/",
file.ObjName.Split('/', StringSplitOptions.RemoveEmptyEntries)
.Select(Uri.EscapeDataString));
return $"{baseUrl}/{objectPath}";
}
/// <summary>
/// 读取并校验阿里云 OSS 配置。
/// </summary>
@@ -307,47 +353,6 @@ public class FileService : DbRepository<SysFile>, IFileService
return setting;
}
/// <summary>
/// 通过 STS 临时凭证创建阿里云 OSS 客户端。
/// </summary>
private static OssClient CreateOssClient(OssSettingOptions setting)
{
var endpoint = setting.EndPoint.Trim();
if (!endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
&& !endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
endpoint = "https://" + endpoint;
}
if (string.IsNullOrWhiteSpace(setting.RoleArn))
return new OssClient(endpoint, setting.AccessKeyID, setting.AccessKeySecret);
var stsConfig = new AlibabaCloud.OpenApiClient.Models.Config
{
AccessKeyId = setting.AccessKeyID,
AccessKeySecret = setting.AccessKeySecret,
Endpoint = setting.StsEndpoint
};
var stsClient = new AlibabaCloud.SDK.Sts20150401.Client(stsConfig);
var request = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest
{
RoleArn = setting.RoleArn,
RoleSessionName = string.IsNullOrWhiteSpace(setting.RoleSessionName)
? "onboardx-server"
: setting.RoleSessionName,
DurationSeconds = 3600
};
var credentials = stsClient.AssumeRole(request).Body.Credentials;
if (credentials == null)
throw Oops.Bah("获取阿里云 OSS 临时凭证失败");
return new OssClient(
endpoint,
credentials.AccessKeyId,
credentials.AccessKeySecret,
credentials.SecurityToken);
}
/// <summary>
/// 获取文件大小
/// </summary>