1111
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user