feat: finalize core modules and gateway
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Aliyun.OSS;
|
||||
using Aliyun.OSS.Util;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TakeoutSaaS.Module.Storage.Abstractions;
|
||||
using TakeoutSaaS.Module.Storage.Models;
|
||||
using TakeoutSaaS.Module.Storage.Options;
|
||||
|
||||
namespace TakeoutSaaS.Module.Storage.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// 阿里云 OSS 存储提供商实现。
|
||||
/// </summary>
|
||||
public sealed class AliyunOssStorageProvider(IOptionsMonitor<StorageOptions> optionsMonitor) : IObjectStorageProvider, IDisposable
|
||||
{
|
||||
private OssClient? _client;
|
||||
private bool _disposed;
|
||||
|
||||
private StorageOptions CurrentOptions => optionsMonitor.CurrentValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public StorageProviderKind Kind => StorageProviderKind.AliyunOss;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<StorageUploadResult> UploadAsync(StorageUploadRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CurrentOptions;
|
||||
var metadata = new ObjectMetadata
|
||||
{
|
||||
ContentLength = request.ContentLength,
|
||||
ContentType = request.ContentType
|
||||
};
|
||||
|
||||
foreach (var kv in request.Metadata)
|
||||
{
|
||||
metadata.UserMetadata[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
// Aliyun OSS SDK 支持异步方法,如未支持将同步封装为任务。
|
||||
await PutObjectAsync(options.AliyunOss.Bucket, request.ObjectKey, request.Content, metadata, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var signedUrl = request.GenerateSignedUrl
|
||||
? await GenerateDownloadUrlAsync(request.ObjectKey, request.SignedUrlExpires, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
return new StorageUploadResult
|
||||
{
|
||||
ObjectKey = request.ObjectKey,
|
||||
Url = signedUrl ?? BuildPublicUrl(request.ObjectKey),
|
||||
SignedUrl = signedUrl,
|
||||
FileSize = request.ContentLength,
|
||||
ContentType = request.ContentType
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<StorageDirectUploadResult> CreateDirectUploadAsync(StorageDirectUploadRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var expiresAt = DateTimeOffset.UtcNow.Add(request.Expires);
|
||||
var uploadUrl = GeneratePresignedUrl(request.ObjectKey, request.Expires, SignHttpMethod.Put, request.ContentType);
|
||||
var downloadUrl = GeneratePresignedUrl(request.ObjectKey, request.Expires, SignHttpMethod.Get, null);
|
||||
|
||||
var result = new StorageDirectUploadResult
|
||||
{
|
||||
UploadUrl = uploadUrl,
|
||||
FormFields = new Dictionary<string, string>(),
|
||||
ExpiresAt = expiresAt,
|
||||
ObjectKey = request.ObjectKey,
|
||||
SignedDownloadUrl = downloadUrl
|
||||
};
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GenerateDownloadUrlAsync(string objectKey, TimeSpan expires, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var url = GeneratePresignedUrl(objectKey, expires, SignHttpMethod.Get, null);
|
||||
return Task.FromResult(url);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string BuildPublicUrl(string objectKey)
|
||||
{
|
||||
var cdn = CurrentOptions.AliyunOss.CdnBaseUrl ?? CurrentOptions.CdnBaseUrl;
|
||||
if (!string.IsNullOrWhiteSpace(cdn))
|
||||
{
|
||||
return $"{cdn!.TrimEnd('/')}/{objectKey}";
|
||||
}
|
||||
|
||||
var endpoint = CurrentOptions.AliyunOss.Endpoint.TrimEnd('/');
|
||||
var scheme = CurrentOptions.AliyunOss.UseHttps ? "https" : "http";
|
||||
// Endpoint 可能已包含协议,若没有则补充。
|
||||
if (!endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
endpoint = $"{scheme}://{endpoint}";
|
||||
}
|
||||
|
||||
return $"{endpoint}/{CurrentOptions.AliyunOss.Bucket}/{objectKey}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传对象到 OSS。
|
||||
/// </summary>
|
||||
private async Task PutObjectAsync(string bucket, string key, Stream content, ObjectMetadata metadata, CancellationToken cancellationToken)
|
||||
{
|
||||
var client = EnsureClient();
|
||||
await Task.Run(() => client.PutObject(bucket, key, content, metadata), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成预签名 URL。
|
||||
/// </summary>
|
||||
private string GeneratePresignedUrl(string objectKey, TimeSpan expires, SignHttpMethod method, string? contentType)
|
||||
{
|
||||
var request = new GeneratePresignedUriRequest(CurrentOptions.AliyunOss.Bucket, objectKey, method)
|
||||
{
|
||||
Expiration = DateTime.Now.Add(expires)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(contentType))
|
||||
{
|
||||
request.ContentType = contentType;
|
||||
}
|
||||
|
||||
var uri = EnsureClient().GeneratePresignedUri(request);
|
||||
return uri.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建或复用 OSS 客户端。
|
||||
/// </summary>
|
||||
private OssClient EnsureClient()
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
return _client;
|
||||
}
|
||||
|
||||
var options = CurrentOptions.AliyunOss;
|
||||
_client = new OssClient(options.Endpoint, options.AccessKeyId, options.AccessKeySecret);
|
||||
return _client;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user