From 2fda049fb1f9401a1d5ce94980189f02aff159f7 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 11 May 2022 23:57:55 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat(Storage):=20Add=20GetObject=E3=80=81?= =?UTF-8?q?PutObject=E3=80=81ObjectExists=E3=80=81DeleteObject=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BuildingBlocks/MASA.BuildingBlocks | 2 +- .../BaseClient.cs | 103 +++++++++++++ .../Client.cs | 135 ++++++++++-------- .../Internal/Const.cs | 6 +- .../Internal/Response/UploadObjectResponse.cs | 41 ++++++ ...ontrib.Storage.ObjectStorage.Aliyun.csproj | 2 + .../Options/AliyunStorageOptions.cs | 133 ++++++++++++++++- .../Options/Enum/EndpointMode.cs | 10 ++ .../README.md | 2 +- .../README.zh-CN.md | 2 +- .../ServiceCollectionExtensions.cs | 39 +++-- .../_Imports.cs | 8 +- 12 files changed, 394 insertions(+), 89 deletions(-) create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index d3b46ae3d..fcd4c1f09 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit d3b46ae3d38a9892672725aec7e46af528cdeb01 +Subproject commit fcd4c1f099a80dab68baad6fec343acdbbf97d95 diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs new file mode 100644 index 000000000..4545d1e84 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs @@ -0,0 +1,103 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public abstract class BaseClient +{ + protected readonly AliyunStorageOptions _options; + protected readonly bool _supportSts; + private readonly IMemoryCache _cache; + protected readonly ILogger? _logger; + + public BaseClient(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) + { + _options = options; + _supportSts = !string.IsNullOrEmpty(options.RoleArn) && !string.IsNullOrEmpty(options.RoleSessionName); + _cache = cache; + _logger = logger; + } + + /// + /// Obtain temporary authorization credentials through STS service + /// + /// + public virtual TemporaryCredentialsResponse GetSecurityToken() + { + if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) + { + temporaryCredentials = GetTemporaryCredentials( + _options.RegionId, + _options.AccessKeyId, + _options.AccessKeySecret, + _options.RoleArn, + _options.RoleSessionName, + _options.Policy, + _options.DurationSeconds); + SetTemporaryCredentials(temporaryCredentials); + } + return temporaryCredentials!; + } + + protected virtual TemporaryCredentialsResponse GetTemporaryCredentials( + string regionId, + string accessKeyId, + string accessKeySecret, + string roleArn, + string roleSessionName, + string policy, + long durationSeconds) + { + IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); + DefaultAcsClient client = new DefaultAcsClient(profile); + var request = new AssumeRoleRequest + { + ContentType = AliyunFormatType.JSON, + RoleArn = roleArn, + RoleSessionName = roleSessionName, + DurationSeconds = durationSeconds + }; + if (!string.IsNullOrEmpty(policy)) + request.Policy = policy; + var response = client.GetAcsResponse(request); + if (response.HttpResponse.isSuccess()) + { + return new TemporaryCredentialsResponse( + response.Credentials.AccessKeyId, + response.Credentials.AccessKeySecret, + response.Credentials.SecurityToken, + DateTime.Parse(response.Credentials.Expiration)); + } + + string responseContent = System.Text.Encoding.Default.GetString(response.HttpResponse.Content); + string message = + $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId}, Status: {response.HttpResponse.Status}, Message: {responseContent}"; + _logger?.LogWarning( + "Aliyun.Client: Failed to obtain temporary credentials, RequestId: {RequestId}, Status: {Status}, Message: {Message}", + response.RequestId, response.HttpResponse.Status, responseContent); + + throw new Exception(message); + } + + protected virtual IOss GetClient() + { + var credential = GetCredential(); + return new OssClient(_options.Endpoint, credential.AccessKeyId, credential.AccessKeySecret, credential.SecurityToken); + } + + protected virtual (string AccessKeyId, string AccessKeySecret, string? SecurityToken) GetCredential() + { + if (!_supportSts) + return new(_options.AccessKeyId, _options.AccessKeySecret, null); + + var securityToken = GetSecurityToken(); + return new(securityToken.AccessKeyId, securityToken.AccessKeySecret, securityToken.SessionToken); + } + + protected virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) + { + var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - _options.EarlyExpires; + if (timespan >= 0) + _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs index 618c20af2..99f6c5f21 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs @@ -3,38 +3,26 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; -public class Client : IClient +public class Client : BaseClient, IClient { - private readonly AliyunStorageOptions _options; - private readonly IMemoryCache _cache; - private readonly ILogger? _logger; + private readonly bool _supportCallback; public Client(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) + : base(options, cache, logger) { - _options = options; - _cache = cache; - _logger = logger; + _supportCallback = !string.IsNullOrEmpty(options.CallbackBody) && !string.IsNullOrEmpty(options.CallbackUrl); } /// /// Obtain temporary authorization credentials through STS service /// /// - public TemporaryCredentialsResponse GetSecurityToken() + public override TemporaryCredentialsResponse GetSecurityToken() { - if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) - { - temporaryCredentials = GetTemporaryCredentials( - _options.RegionId, - _options.AccessKeyId, - _options.AccessKeySecret, - _options.RoleArn, - _options.RoleSessionName, - _options.Policy, - _options.DurationSeconds); - SetTemporaryCredentials(temporaryCredentials); - } - return temporaryCredentials!; + if (!_supportSts) + throw new ArgumentNullException($"{nameof(_options.RoleArn)} or {nameof(_options.RoleSessionName)} cannot be empty or null"); + + return base.GetSecurityToken(); } /// @@ -44,46 +32,81 @@ public TemporaryCredentialsResponse GetSecurityToken() /// public string GetToken() => throw new NotSupportedException("GetToken is not supported, please use GetSecurityToken"); - protected virtual TemporaryCredentialsResponse GetTemporaryCredentials( - string regionId, - string accessKeyId, - string accessKeySecret, - string roleArn, - string roleSessionName, - string policy, - long durationSeconds) + public Task GetObjectAsync(string bucketName, string objectName, Action callback, CancellationToken cancellationToken = default) { - IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); - DefaultAcsClient client = new DefaultAcsClient(profile); - var request = new AssumeRoleRequest - { - ContentType = AliyunFormatType.JSON, - RoleArn = roleArn, - RoleSessionName = roleSessionName, - DurationSeconds = durationSeconds - }; - if (!string.IsNullOrEmpty(policy)) - request.Policy = policy; - var response = client.GetAcsResponse(request); - if (response.HttpResponse.isSuccess()) - { - return new TemporaryCredentialsResponse( - response.Credentials.AccessKeyId, - response.Credentials.AccessKeySecret, - response.Credentials.SecurityToken, - DateTime.Parse(response.Credentials.Expiration)); - } + var client = GetClient(); + var result = client.GetObject(bucketName, objectName); + callback.Invoke(result.Content); + return Task.CompletedTask; + } + + public Task GetObjectAsync(string bucketName, string objectName, long offset, long length, Action callback, CancellationToken cancellationToken = default) + { + if (length < 0 && length != -1) + throw new ArgumentOutOfRangeException(nameof(length), $"{length} should be greater than 0 or -1"); + + var client = GetClient(); + var request = new GetObjectRequest(bucketName, objectName); + request.SetRange(offset, length > 0 ? offset + length : length); + var result = client.GetObject(request); + callback.Invoke(result.Content); + return Task.CompletedTask; + } - string message = $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId},Status: {response.HttpResponse.Status}, Message: {System.Text.Encoding.Default.GetString(response.HttpResponse.Content)}"; - _logger?.LogWarning(message); + public Task PutObjectAsync(string bucketName, string objectName, Stream data, CancellationToken cancellationToken = default) + { + var client = GetClient(); + var objectMetadata = _supportCallback ? BuildCallbackMetadata(_options.CallbackUrl, _options.CallbackBody) : null; + var result = !_options.EnableResumableUpload || _options.BigObjectContentLength > data.Length ? + client.PutObject(bucketName, objectName, data, objectMetadata) : + client.ResumableUploadObject(new UploadObjectRequest(bucketName, objectName, data) + { + PartSize = _options.PartSize, + Metadata = objectMetadata + }); + _logger?.LogDebug("----- Upload {ObjectName} from {BucketName} - ({Result})", + objectName, + bucketName, + new UploadObjectResponse(result)); + return Task.CompletedTask; + } + + protected virtual ObjectMetadata BuildCallbackMetadata(string callbackUrl, string callbackBody) + { + string callbackHeaderBuilder = new CallbackHeaderBuilder(callbackUrl, callbackBody).Build(); + var metadata = new ObjectMetadata(); + metadata.AddHeader(HttpHeaders.Callback, callbackHeaderBuilder); + return metadata; + } - throw new Exception(message); + public Task ObjectExistsAsync(string bucketName, string objectName, CancellationToken cancellationToken = default) + { + var client = GetClient(); + var exist = client.DoesObjectExist(bucketName, objectName); + return Task.FromResult(exist); + } + + public async Task DeleteObjectAsync(string bucketName, string objectName, CancellationToken cancellationToken = default) + { + var client = GetClient(); + if (await ObjectExistsAsync(bucketName, objectName, cancellationToken)) + { + var result = client.DeleteObject(bucketName, objectName); + _logger?.LogDebug("----- Delete {ObjectName} from {BucketName} - ({Result})", + objectName, + bucketName, + result); + } } - protected virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) + public Task DeleteObjectAsync(string bucketName, IEnumerable objectNames, CancellationToken cancellationToken = default) { - var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - 10; - if (timespan >= 0) - _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); + var client = GetClient(); + var result = client.DeleteObjects(new DeleteObjectsRequest(bucketName, objectNames.ToList(), _options.Quiet)); + _logger?.LogDebug("----- Delete {ObjectNames} from {BucketName} - ({Result})", + objectNames, + bucketName, + result); + return Task.CompletedTask; } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs index 1fbe2a40a..72ae7037d 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs @@ -7,5 +7,9 @@ internal class Const { public const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.TemporaryCredentials"; - public const string DEFAULT_SECTION = "Aliyun"; + public const string DEFAULT_SECTION = "AliyunOss"; + + public const string INTERNAL_ENDPOINT_SUFFIX = "-internal.aliyuncs.com"; + + public const string PUBLIC_ENDPOINT_DOMAIN_SUFFIX = ".aliyuncs.com"; } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs new file mode 100644 index 000000000..e629fa20f --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs @@ -0,0 +1,41 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal.Response; + +internal class UploadObjectResponse +{ + public string ETag { get; set; } + + public string VersionId { get; set; } + + public string RequestId { get; set; } + + public HttpStatusCode HttpStatusCode { get; set; } + + public long ContentLength { get; set; } + + public string Response { get; set; } + + public IDictionary ResponseMetadata { get; set; } + + public UploadObjectResponse(PutObjectResult result) + { + ETag = result.ETag; + VersionId = result.VersionId; + HttpStatusCode = result.HttpStatusCode; + RequestId = result.RequestId; + ContentLength = result.ContentLength; + Response = GetCallbackResponse(result); + ResponseMetadata = result.ResponseMetadata; + } + + private string GetCallbackResponse(PutObjectResult putObjectResult) + { + using var stream = putObjectResult.ResponseStream; + var buffer = new byte[4 * 1024]; + var bytesRead = stream.Read(buffer, 0, buffer.Length); + string callbackResponse = Encoding.Default.GetString(buffer, 0, bytesRead); + return callbackResponse; + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj index e3aa62b2c..4d2f5efb5 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj @@ -8,6 +8,7 @@ + @@ -16,6 +17,7 @@ + diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs index 0f6a364ec..e34e11791 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs @@ -11,11 +11,13 @@ public class AliyunStorageOptions public string RegionId { get; set; } + public string Endpoint { get; set; } + public string RoleArn { get; set; } public string RoleSessionName { get; set; } - private int _durationSeconds = 3600; + private int _durationSeconds; /// /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. @@ -28,7 +30,7 @@ public int DurationSeconds set { if (value < 900 || value > 43200) - throw new ArgumentOutOfRangeException(nameof(DurationSeconds), "DurationSeconds must be in range of 900-43200"); + throw new ArgumentOutOfRangeException(nameof(DurationSeconds), $"{nameof(DurationSeconds)} must be in range of 900-43200"); _durationSeconds = value; } @@ -39,7 +41,7 @@ public int DurationSeconds /// public string Policy { get; set; } - private string _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; + private string _temporaryCredentialsCacheKey; public string TemporaryCredentialsCacheKey { @@ -47,13 +49,109 @@ public string TemporaryCredentialsCacheKey set => _temporaryCredentialsCacheKey = CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); } - public AliyunStorageOptions() { } + private int _earlyExpires = 10; + + /// + /// Voucher expires early + /// default: 10 + /// unit: second + /// + public int EarlyExpires + { + get => _earlyExpires; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(EarlyExpires), $"{nameof(EarlyExpires)} must be Greater than 0"); + + _earlyExpires = value; + } + } + + /// + /// The server address of the callback request + /// + public string CallbackUrl { get; set; } + + /// + /// The value of the request body when the callback is initiated + /// + public string CallbackBody { get; set; } + + /// + /// Large files enable resume after power failure + /// default: true + /// + public bool EnableResumableUpload { get; set; } + + /// + /// large file length + /// unit: Byte + /// default: 5GB + /// + public long BigObjectContentLength { get; set; } + + /// + /// Gets or sets the size of the part. + /// + /// The size of the part. + public long? PartSize { get; set; } + + /// + /// true: quiet mode; false: detail mode + /// default: true + /// + public bool Quiet { get; set; } + + public AliyunStorageOptions() + { + _durationSeconds = 3600; + _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; + Quiet = true; + CallbackBody = "bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}"; + EnableResumableUpload = true; + BigObjectContentLength = 5 * (long)Math.Pow(1024, 3); + } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string roleArn, string roleSessionName) : this() + private AliyunStorageOptions(string accessKeyId, string accessKeySecret) : this() { AccessKeyId = CheckNullOrEmptyAndReturnValue(accessKeyId, nameof(accessKeyId)); AccessKeySecret = CheckNullOrEmptyAndReturnValue(accessKeySecret, nameof(accessKeySecret)); + } + + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint) + : this(accessKeyId, accessKeySecret) + { + string regionId = GetRegionId(endpoint); + RegionId = CheckNullOrEmptyAndReturnValue(regionId, + () => throw new ArgumentException("Unrecognized endpoint, failed to get RegionId")); + Endpoint = CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); + } + + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, EndpointMode mode) + : this(accessKeyId, accessKeySecret) + { RegionId = CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); + Endpoint = GetEndpoint(regionId, mode); + } + + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint, string roleArn, string roleSessionName) + : this(accessKeyId, accessKeySecret, GetRegionId(endpoint), endpoint, roleArn, roleSessionName) + { + } + + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, EndpointMode mode, string roleArn, + string roleSessionName) + : this(accessKeyId, accessKeySecret, regionId, GetEndpoint(regionId, mode), roleArn, roleSessionName) + { + } + + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string endpoint, string roleArn, + string roleSessionName) + : this(accessKeyId, accessKeySecret) + { + RegionId = CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); + Endpoint = endpoint; RoleArn = CheckNullOrEmptyAndReturnValue(roleArn, nameof(roleArn)); RoleSessionName = CheckNullOrEmptyAndReturnValue(roleSessionName, nameof(roleSessionName)); } @@ -76,6 +174,12 @@ public AliyunStorageOptions SetDurationSeconds(int durationSeconds) return this; } + public AliyunStorageOptions SetEarlyExpires(int earlyExpires) + { + EarlyExpires = earlyExpires; + return this; + } + internal string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) { if (string.IsNullOrEmpty(parameter)) @@ -83,4 +187,23 @@ internal string CheckNullOrEmptyAndReturnValue(string? parameter, string paramet return parameter; } + + private string CheckNullOrEmptyAndReturnValue(string? parameter, Action error) + { + if (string.IsNullOrEmpty(parameter)) + error.Invoke(); + + return parameter!; + } + + private static string GetEndpoint(string regionId, EndpointMode mode) + => regionId + (mode == EndpointMode.Public ? Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX : Const.INTERNAL_ENDPOINT_SUFFIX); + + private static string GetRegionId(string endpoint) + { + if (endpoint.EndsWith(Const.INTERNAL_ENDPOINT_SUFFIX, StringComparison.OrdinalIgnoreCase)) + return endpoint.Remove(endpoint.Length - Const.INTERNAL_ENDPOINT_SUFFIX.Length); + + return endpoint.Remove(endpoint.Length - Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX.Length); + } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs new file mode 100644 index 000000000..ec688d764 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs @@ -0,0 +1,10 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options.Enum; + +public enum EndpointMode +{ + Public = 1, + Internal, +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index ead3764ac..c54c5f7e0 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -16,7 +16,7 @@ support: 1. Configure appsettings.json ```` C# { - "Aliyun": { + "AliyunOss": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", "RegionId": "Replace-With-Your-RegionId", diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index 272a50524..ec54dc310 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -16,7 +16,7 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun 1. 配置appsettings.json ``` C# { - "Aliyun": { + "AliyunOss": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", "RegionId": "Replace-With-Your-RegionId", diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index 46a04715e..422e69e76 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -18,16 +18,15 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic if (string.IsNullOrEmpty(sectionName)) throw new ArgumentException(sectionName, nameof(sectionName)); - return services.AddAliyunStorageCore(() => + services.AddAliyunStorageDepend(); + services.TryAddConfigure(sectionName); + services.TryAddSingleton(serviceProvider => { - services.TryAddConfigure(sectionName); - services.AddSingleton(serviceProvider => - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - CheckAliYunStorageOptions(optionsMonitor.CurrentValue, $"Failed to get {nameof(IOptionsMonitor)}"); - return new Client(optionsMonitor.CurrentValue, GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider)); - }); + var optionsMonitor = serviceProvider.GetRequiredService>(); + CheckAliYunStorageOptions(optionsMonitor.CurrentValue, $"Failed to get {nameof(IOptionsMonitor)}"); + return new Client(optionsMonitor.CurrentValue, GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider)); }); + return services; } public static IServiceCollection AddAliyunStorage(this IServiceCollection services, AliyunStorageOptions options) @@ -44,18 +43,15 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic { ArgumentNullException.ThrowIfNull(func, nameof(func)); - return services.AddAliyunStorageCore(() => services.AddSingleton(serviceProvider - => new Client(func.Invoke(), GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider)))); + services.AddAliyunStorageDepend(); + services.TryAddSingleton(serviceProvider + => new Client(func.Invoke(), GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider))); + return services; } - private static IServiceCollection AddAliyunStorageCore(this IServiceCollection services, Action action) + private static IServiceCollection AddAliyunStorageDepend(this IServiceCollection services) { - if (services.Any(service => service.ImplementationType == typeof(AliyunStorageProvider))) - return services; - - services.AddSingleton(); services.AddMemoryCache(); - action.Invoke(); return services; } @@ -64,9 +60,10 @@ private static IServiceCollection TryAddConfigure( string sectionName) where TOptions : class { - IConfiguration? - configuration = - services.BuildServiceProvider().GetService(); //Todo: Follow-up needs to support IMasaConfiguration + var serviceProvider = services.BuildServiceProvider(); + IConfiguration? configuration = serviceProvider.GetService()?.GetConfiguration(SectionTypes.Local) ?? + serviceProvider.GetService(); + if (configuration == null) return services; @@ -103,8 +100,4 @@ private static void CheckAliYunStorageOptions(AliyunStorageOptions options, stri options.CheckNullOrEmptyAndReturnValue(options.RoleArn, nameof(options.RoleArn)); options.CheckNullOrEmptyAndReturnValue(options.RoleSessionName, nameof(options.RoleSessionName)); } - - private class AliyunStorageProvider - { - } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs index 9372bb316..9c9a45abb 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs @@ -4,15 +4,21 @@ global using Aliyun.Acs.Core; global using Aliyun.Acs.Core.Auth.Sts; global using Aliyun.Acs.Core.Profile; +global using Aliyun.OSS; +global using Aliyun.OSS.Util; +global using Masa.BuildingBlocks.Configuration; global using Masa.BuildingBlocks.Storage.ObjectStorage; global using Masa.BuildingBlocks.Storage.ObjectStorage.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; +global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; +global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options.Enum; global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; +global using System.Net; +global using System.Text; global using AliyunFormatType = Aliyun.Acs.Core.Http.FormatType; - From fc63e293658159dd25d51177a1e6e79cc6f315ac Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 12 May 2022 09:13:27 +0800 Subject: [PATCH 02/12] chore: Format code --- src/BuildingBlocks/MASA.BuildingBlocks | 2 +- .../Client.cs | 35 +++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index fcd4c1f09..8a0e80951 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit fcd4c1f099a80dab68baad6fec343acdbbf97d95 +Subproject commit 8a0e8095147187ff7f0c03de51cdb62642d93df8 diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs index 99f6c5f21..0147532a4 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs @@ -32,7 +32,11 @@ public override TemporaryCredentialsResponse GetSecurityToken() /// public string GetToken() => throw new NotSupportedException("GetToken is not supported, please use GetSecurityToken"); - public Task GetObjectAsync(string bucketName, string objectName, Action callback, CancellationToken cancellationToken = default) + public Task GetObjectAsync( + string bucketName, + string objectName, + Action callback, + CancellationToken cancellationToken = default) { var client = GetClient(); var result = client.GetObject(bucketName, objectName); @@ -40,7 +44,13 @@ public Task GetObjectAsync(string bucketName, string objectName, Action return Task.CompletedTask; } - public Task GetObjectAsync(string bucketName, string objectName, long offset, long length, Action callback, CancellationToken cancellationToken = default) + public Task GetObjectAsync( + string bucketName, + string objectName, + long offset, + long length, + Action callback, + CancellationToken cancellationToken = default) { if (length < 0 && length != -1) throw new ArgumentOutOfRangeException(nameof(length), $"{length} should be greater than 0 or -1"); @@ -53,7 +63,11 @@ public Task GetObjectAsync(string bucketName, string objectName, long offset, lo return Task.CompletedTask; } - public Task PutObjectAsync(string bucketName, string objectName, Stream data, CancellationToken cancellationToken = default) + public Task PutObjectAsync( + string bucketName, + string objectName, + Stream data, + CancellationToken cancellationToken = default) { var client = GetClient(); var objectMetadata = _supportCallback ? BuildCallbackMetadata(_options.CallbackUrl, _options.CallbackBody) : null; @@ -79,14 +93,20 @@ protected virtual ObjectMetadata BuildCallbackMetadata(string callbackUrl, strin return metadata; } - public Task ObjectExistsAsync(string bucketName, string objectName, CancellationToken cancellationToken = default) + public Task ObjectExistsAsync( + string bucketName, + string objectName, + CancellationToken cancellationToken = default) { var client = GetClient(); var exist = client.DoesObjectExist(bucketName, objectName); return Task.FromResult(exist); } - public async Task DeleteObjectAsync(string bucketName, string objectName, CancellationToken cancellationToken = default) + public async Task DeleteObjectAsync( + string bucketName, + string objectName, + CancellationToken cancellationToken = default) { var client = GetClient(); if (await ObjectExistsAsync(bucketName, objectName, cancellationToken)) @@ -99,7 +119,10 @@ public async Task DeleteObjectAsync(string bucketName, string objectName, Cancel } } - public Task DeleteObjectAsync(string bucketName, IEnumerable objectNames, CancellationToken cancellationToken = default) + public Task DeleteObjectAsync( + string bucketName, + IEnumerable objectNames, + CancellationToken cancellationToken = default) { var client = GetClient(); var result = client.DeleteObjects(new DeleteObjectsRequest(bucketName, objectNames.ToList(), _options.Quiet)); From ebaed2596ee3617322f0ce658bed692806b03099 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Mon, 16 May 2022 11:47:29 +0800 Subject: [PATCH 03/12] refactor(Storage): Refactor Storage.Aliyun --- .../BaseClient.cs | 87 +--------- .../Client.cs | 35 ++-- .../DefaultCredentialProvider.cs | 130 ++++++++++++++ .../DefaultOssClientFactory.cs | 16 ++ .../ICredentialProvider.cs | 14 ++ .../IOssClientFactory.cs | 11 ++ .../Internal/Const.cs | 10 +- .../Internal/ObjectStorageExtensions.cs | 18 ++ ...ontrib.Storage.ObjectStorage.Aliyun.csproj | 4 + .../Options/AliyunOptions.cs | 75 ++++++++ .../Options/AliyunStorageConfigureOptions.cs | 9 + .../Options/AliyunStorageOptions.cs | 125 +++----------- .../README.md | 4 +- .../README.zh-CN.md | 7 +- .../ServiceCollectionExtensions.cs | 78 ++++++--- .../_Imports.cs | 1 + .../BaseTest.cs | 21 +++ .../CustomNullClient.cs | 22 --- .../CustomizeClient.cs | 74 ++++++++ ...ient.cs => CustomizeCredentialProvider.cs} | 26 +-- .../CustomizeNullClient.cs | 26 +++ .../TestALiYunStorageOptions.cs | 128 ++++++++++++-- .../TestClient.cs | 161 +++++++++--------- .../TestCredentialProvider.cs | 124 ++++++++++++++ .../TestStorage.cs | 33 ++-- .../_Imports.cs | 5 + .../appsettings.json | 13 +- 27 files changed, 888 insertions(+), 369 deletions(-) create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs create mode 100644 test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs delete mode 100644 test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs create mode 100644 test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs rename test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/{CustomClient.cs => CustomizeCredentialProvider.cs} (58%) create mode 100644 test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs create mode 100644 test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs index 4545d1e84..98c7aca35 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs @@ -5,99 +5,28 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; public abstract class BaseClient { + protected readonly ICredentialProvider _credentialProvider; protected readonly AliyunStorageOptions _options; - protected readonly bool _supportSts; - private readonly IMemoryCache _cache; - protected readonly ILogger? _logger; - public BaseClient(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) + public BaseClient(ICredentialProvider credentialProvider, + AliyunStorageOptions options) { + _credentialProvider = credentialProvider; _options = options; - _supportSts = !string.IsNullOrEmpty(options.RoleArn) && !string.IsNullOrEmpty(options.RoleSessionName); - _cache = cache; - _logger = logger; } - /// - /// Obtain temporary authorization credentials through STS service - /// - /// - public virtual TemporaryCredentialsResponse GetSecurityToken() - { - if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) - { - temporaryCredentials = GetTemporaryCredentials( - _options.RegionId, - _options.AccessKeyId, - _options.AccessKeySecret, - _options.RoleArn, - _options.RoleSessionName, - _options.Policy, - _options.DurationSeconds); - SetTemporaryCredentials(temporaryCredentials); - } - return temporaryCredentials!; - } - - protected virtual TemporaryCredentialsResponse GetTemporaryCredentials( - string regionId, - string accessKeyId, - string accessKeySecret, - string roleArn, - string roleSessionName, - string policy, - long durationSeconds) - { - IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); - DefaultAcsClient client = new DefaultAcsClient(profile); - var request = new AssumeRoleRequest - { - ContentType = AliyunFormatType.JSON, - RoleArn = roleArn, - RoleSessionName = roleSessionName, - DurationSeconds = durationSeconds - }; - if (!string.IsNullOrEmpty(policy)) - request.Policy = policy; - var response = client.GetAcsResponse(request); - if (response.HttpResponse.isSuccess()) - { - return new TemporaryCredentialsResponse( - response.Credentials.AccessKeyId, - response.Credentials.AccessKeySecret, - response.Credentials.SecurityToken, - DateTime.Parse(response.Credentials.Expiration)); - } - - string responseContent = System.Text.Encoding.Default.GetString(response.HttpResponse.Content); - string message = - $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId}, Status: {response.HttpResponse.Status}, Message: {responseContent}"; - _logger?.LogWarning( - "Aliyun.Client: Failed to obtain temporary credentials, RequestId: {RequestId}, Status: {Status}, Message: {Message}", - response.RequestId, response.HttpResponse.Status, responseContent); - - throw new Exception(message); - } - - protected virtual IOss GetClient() + public virtual IOss GetClient() { var credential = GetCredential(); return new OssClient(_options.Endpoint, credential.AccessKeyId, credential.AccessKeySecret, credential.SecurityToken); } - protected virtual (string AccessKeyId, string AccessKeySecret, string? SecurityToken) GetCredential() + public virtual (string AccessKeyId, string AccessKeySecret, string? SecurityToken) GetCredential() { - if (!_supportSts) + if (!_credentialProvider.SupportSts) return new(_options.AccessKeyId, _options.AccessKeySecret, null); - var securityToken = GetSecurityToken(); + var securityToken = _credentialProvider.GetSecurityToken(); return new(securityToken.AccessKeyId, securityToken.AccessKeySecret, securityToken.SessionToken); } - - protected virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) - { - var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - _options.EarlyExpires; - if (timespan >= 0) - _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); - } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs index 0147532a4..630f97ec9 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs @@ -6,23 +6,30 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; public class Client : BaseClient, IClient { private readonly bool _supportCallback; + private readonly ILogger? _logger; - public Client(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) - : base(options, cache, logger) + public Client(ICredentialProvider credentialProvider, AliyunStorageOptions options, ILogger? logger) + : base(credentialProvider, options) { _supportCallback = !string.IsNullOrEmpty(options.CallbackBody) && !string.IsNullOrEmpty(options.CallbackUrl); + _logger = logger; + } + + public Client(ICredentialProvider credentialProvider, IOptionsMonitor options, ILogger? logger) + : this(credentialProvider, options.CurrentValue, logger) + { } /// /// Obtain temporary authorization credentials through STS service /// /// - public override TemporaryCredentialsResponse GetSecurityToken() + public TemporaryCredentialsResponse GetSecurityToken() { - if (!_supportSts) - throw new ArgumentNullException($"{nameof(_options.RoleArn)} or {nameof(_options.RoleSessionName)} cannot be empty or null"); + if (!_credentialProvider.SupportSts) + throw new ArgumentException($"{nameof(_options.RoleArn)} or {nameof(_options.RoleSessionName)} cannot be empty or null"); - return base.GetSecurityToken(); + return _credentialProvider.GetSecurityToken(); } /// @@ -109,14 +116,14 @@ public async Task DeleteObjectAsync( CancellationToken cancellationToken = default) { var client = GetClient(); - if (await ObjectExistsAsync(bucketName, objectName, cancellationToken)) - { - var result = client.DeleteObject(bucketName, objectName); - _logger?.LogDebug("----- Delete {ObjectName} from {BucketName} - ({Result})", - objectName, - bucketName, - result); - } + if (await ObjectExistsAsync(bucketName, objectName, cancellationToken) == false) + return; + + var result = client.DeleteObject(bucketName, objectName); + _logger?.LogDebug("----- Delete {ObjectName} from {BucketName} - ({Result})", + objectName, + bucketName, + result); } public Task DeleteObjectAsync( diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs new file mode 100644 index 000000000..c89e23191 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs @@ -0,0 +1,130 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public class DefaultCredentialProvider : ICredentialProvider +{ + private readonly IOssClientFactory _ossClientFactory; + protected readonly AliyunStorageOptions _options; + private readonly IMemoryCache _cache; + protected readonly ILogger? _logger; + + public bool SupportSts { get; init; } + + public DefaultCredentialProvider( + IOssClientFactory ossClientFactory, + AliyunStorageOptions options, + IMemoryCache cache, + ILogger? logger) + { + _ossClientFactory = ossClientFactory; + _options = options; + SupportSts = !string.IsNullOrEmpty(options.RoleArn) && !string.IsNullOrEmpty(options.RoleSessionName); + _cache = cache; + _logger = logger; + } + + public DefaultCredentialProvider( + IOssClientFactory ossClientFactory, + IOptionsMonitor options, + IMemoryCache cache, + ILogger? logger) + : this(ossClientFactory, options.CurrentValue, cache, logger) + { + } + + public DefaultCredentialProvider( + IOssClientFactory ossClientFactory, + IOptionsMonitor options, + IMemoryCache cache, + ILogger? logger) + : this(ossClientFactory, GetAliyunStorageOptions(options.CurrentValue), cache, logger) + { + } + + /// + /// Obtain temporary authorization credentials through STS service + /// + /// + public virtual TemporaryCredentialsResponse GetSecurityToken() + { + if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) + { + temporaryCredentials = GetTemporaryCredentials( + _options.RegionId, + _options.AccessKeyId, + _options.AccessKeySecret, + _options.RoleArn, + _options.RoleSessionName, + _options.Policy, + _options.GetDurationSeconds()); + SetTemporaryCredentials(temporaryCredentials); + } + return temporaryCredentials!; + } + + public virtual TemporaryCredentialsResponse GetTemporaryCredentials( + string regionId, + string accessKeyId, + string accessKeySecret, + string roleArn, + string roleSessionName, + string policy, + long durationSeconds) + { + IAcsClient client = _ossClientFactory.GetAcsClient(accessKeyId, accessKeySecret, regionId); + var request = new AssumeRoleRequest + { + ContentType = AliyunFormatType.JSON, + RoleArn = roleArn, + RoleSessionName = roleSessionName, + DurationSeconds = durationSeconds + }; + if (!string.IsNullOrEmpty(policy)) + request.Policy = policy; + var response = client.GetAcsResponse(request); + if (response.HttpResponse.isSuccess()) + { + return new TemporaryCredentialsResponse( + response.Credentials.AccessKeyId, + response.Credentials.AccessKeySecret, + response.Credentials.SecurityToken, + DateTime.Parse(response.Credentials.Expiration)); + } + + string responseContent = Encoding.Default.GetString(response.HttpResponse.Content); + string message = + $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId}, Status: {response.HttpResponse.Status}, Message: {responseContent}"; + _logger?.LogWarning( + "Aliyun.Client: Failed to obtain temporary credentials, RequestId: {RequestId}, Status: {Status}, Message: {Message}", + response.RequestId, response.HttpResponse.Status, responseContent); + + throw new Exception(message); + } + + public virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) + { + var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - _options.GetEarlyExpires(); + if (timespan >= 0) _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); + } + + private static AliyunStorageOptions GetAliyunStorageOptions(AliyunStorageConfigureOptions options) + { + AliyunStorageOptions aliyunStorageOptions = options.Storage; + aliyunStorageOptions.AccessKeyId = TryUpdate(options.Storage.AccessKeyId, options.AccessKeyId); + aliyunStorageOptions.AccessKeySecret = TryUpdate(options.Storage.AccessKeySecret, options.AccessKeySecret); + aliyunStorageOptions.RegionId = TryUpdate(options.Storage.RegionId, options.RegionId); + aliyunStorageOptions.Endpoint = TryUpdate(options.Storage.Endpoint, options.Endpoint); + aliyunStorageOptions.DurationSeconds = options.Storage.DurationSeconds ?? options.DurationSeconds ?? options.GetDurationSeconds(); + aliyunStorageOptions.EarlyExpires = options.Storage.EarlyExpires ?? options.EarlyExpires ?? options.GetEarlyExpires(); + return aliyunStorageOptions; + } + + private static string TryUpdate(string source, string destination) + { + if (!string.IsNullOrWhiteSpace(source)) + return source; + return destination; + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs new file mode 100644 index 000000000..fdcbb2036 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs @@ -0,0 +1,16 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public class DefaultOssClientFactory : IOssClientFactory +{ + public IOss GetClient(string accessKeyId, string accessKeySecret, string? securityToken, string endpoint) + => new OssClient(endpoint, accessKeyId, accessKeySecret, securityToken); + + public IAcsClient GetAcsClient(string accessKeyId, string accessKeySecret, string regionId) + { + IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); + return new DefaultAcsClient(profile); + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs new file mode 100644 index 000000000..49f66c615 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +/// +/// For internal use, structure may change at any time +/// +public interface ICredentialProvider +{ + bool SupportSts { get; } + + TemporaryCredentialsResponse GetSecurityToken(); +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs new file mode 100644 index 000000000..78cc91091 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public interface IOssClientFactory +{ + IOss GetClient(string AccessKeyId, string AccessKeySecret, string? SecurityToken, string endpoint); + + IAcsClient GetAcsClient(string AccessKeyId, string AccessKeySecret, string regionId); +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs index 72ae7037d..26f9e138e 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs @@ -5,11 +5,17 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; internal class Const { - public const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.TemporaryCredentials"; + public const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.Storage.TemporaryCredentials"; - public const string DEFAULT_SECTION = "AliyunOss"; + public const string DEFAULT_SECTION = "Aliyun"; public const string INTERNAL_ENDPOINT_SUFFIX = "-internal.aliyuncs.com"; public const string PUBLIC_ENDPOINT_DOMAIN_SUFFIX = ".aliyuncs.com"; + + public const string ERROR_ENDPOINT_MESSAGE = "Unrecognized endpoint, failed to get RegionId"; + + public const int DEFAULT_DURATION_SECONDS = 3600; + + public const int DEFAULT_EARLY_EXPIRES = 10; } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs new file mode 100644 index 000000000..4315307f4 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs @@ -0,0 +1,18 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; + +internal class ObjectStorageExtensions +{ + internal static string GetEndpoint(string regionId, EndpointMode mode) + => regionId + (mode == EndpointMode.Public ? Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX : Const.INTERNAL_ENDPOINT_SUFFIX); + + internal static string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) + { + if (string.IsNullOrEmpty(parameter)) + throw new ArgumentException($"{parameterName} cannot be null and empty string"); + + return parameter; + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj index 4d2f5efb5..ba96a334c 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs new file mode 100644 index 000000000..a64a6b7f1 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs @@ -0,0 +1,75 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; + +public class AliyunOptions +{ + public string AccessKeyId { get; set; } + + public string AccessKeySecret { get; set; } + + public string RegionId { get; set; } + + private string _endpoint; + + public string Endpoint + { + get => _endpoint; + set => _endpoint = value?.Trim() ?? string.Empty; + } + + private long? _durationSeconds = null; + + /// + /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. + /// default: 3600 + /// unit: second + /// + public long? DurationSeconds + { + get => _durationSeconds; + set + { + if (value < 900 || value > 43200) + throw new ArgumentOutOfRangeException(nameof(DurationSeconds), $"{nameof(DurationSeconds)} must be in range of 900-43200"); + + _durationSeconds = value; + } + } + + private long? _earlyExpires = null; + + /// + /// Voucher expires early + /// default: 10 + /// unit: second + /// + public long? EarlyExpires + { + get => _earlyExpires; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(EarlyExpires), $"{nameof(EarlyExpires)} must be Greater than 0"); + + _earlyExpires = value; + } + } + + public long GetDurationSeconds() => DurationSeconds ?? Const.DEFAULT_DURATION_SECONDS; + + public long GetEarlyExpires() => EarlyExpires ?? Const.DEFAULT_EARLY_EXPIRES; + + internal static string GetRegionId(string endpoint) + { + endpoint = endpoint.Trim(); + if (endpoint.EndsWith(Const.INTERNAL_ENDPOINT_SUFFIX, StringComparison.OrdinalIgnoreCase)) + return endpoint.Remove(endpoint.Length - Const.INTERNAL_ENDPOINT_SUFFIX.Length); + + if (endpoint.EndsWith(Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX, StringComparison.OrdinalIgnoreCase)) + return endpoint.Remove(endpoint.Length - Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX.Length); + + throw new ArgumentException(Const.ERROR_ENDPOINT_MESSAGE); + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs new file mode 100644 index 000000000..897b1a22c --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; + +public class AliyunStorageConfigureOptions : AliyunOptions +{ + public AliyunStorageOptions Storage { get; set; } = new(); +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs index e34e11791..408b6abe6 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs @@ -3,37 +3,14 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; -public class AliyunStorageOptions +public class AliyunStorageOptions : AliyunOptions { - public string AccessKeyId { get; set; } + private string _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; - public string AccessKeySecret { get; set; } - - public string RegionId { get; set; } - - public string Endpoint { get; set; } - - public string RoleArn { get; set; } - - public string RoleSessionName { get; set; } - - private int _durationSeconds; - - /// - /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. - /// default: 3600 - /// unit: second - /// - public int DurationSeconds + public string TemporaryCredentialsCacheKey { - get => _durationSeconds; - set - { - if (value < 900 || value > 43200) - throw new ArgumentOutOfRangeException(nameof(DurationSeconds), $"{nameof(DurationSeconds)} must be in range of 900-43200"); - - _durationSeconds = value; - } + get => _temporaryCredentialsCacheKey; + set => _temporaryCredentialsCacheKey = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); } /// @@ -41,32 +18,9 @@ public int DurationSeconds /// public string Policy { get; set; } - private string _temporaryCredentialsCacheKey; - - public string TemporaryCredentialsCacheKey - { - get => _temporaryCredentialsCacheKey; - set => _temporaryCredentialsCacheKey = CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); - } - - private int _earlyExpires = 10; + public string RoleArn { get; set; } - /// - /// Voucher expires early - /// default: 10 - /// unit: second - /// - public int EarlyExpires - { - get => _earlyExpires; - set - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(EarlyExpires), $"{nameof(EarlyExpires)} must be Greater than 0"); - - _earlyExpires = value; - } - } + public string RoleSessionName { get; set; } /// /// The server address of the callback request @@ -105,34 +59,25 @@ public int EarlyExpires public AliyunStorageOptions() { - _durationSeconds = 3600; - _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; Quiet = true; + CallbackUrl = string.Empty; CallbackBody = "bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}"; EnableResumableUpload = true; + PartSize = null; BigObjectContentLength = 5 * (long)Math.Pow(1024, 3); } private AliyunStorageOptions(string accessKeyId, string accessKeySecret) : this() { - AccessKeyId = CheckNullOrEmptyAndReturnValue(accessKeyId, nameof(accessKeyId)); - AccessKeySecret = CheckNullOrEmptyAndReturnValue(accessKeySecret, nameof(accessKeySecret)); + AccessKeyId = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(accessKeyId, nameof(accessKeyId)); + AccessKeySecret = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(accessKeySecret, nameof(accessKeySecret)); } public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint) : this(accessKeyId, accessKeySecret) { - string regionId = GetRegionId(endpoint); - RegionId = CheckNullOrEmptyAndReturnValue(regionId, - () => throw new ArgumentException("Unrecognized endpoint, failed to get RegionId")); - Endpoint = CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); - } - - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, EndpointMode mode) - : this(accessKeyId, accessKeySecret) - { - RegionId = CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); - Endpoint = GetEndpoint(regionId, mode); + RegionId = GetRegionId(endpoint); + Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint, string roleArn, string roleSessionName) @@ -140,20 +85,19 @@ public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string e { } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, EndpointMode mode, string roleArn, - string roleSessionName) - : this(accessKeyId, accessKeySecret, regionId, GetEndpoint(regionId, mode), roleArn, roleSessionName) + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string endpoint) + : this(accessKeyId, accessKeySecret) { + RegionId = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); + Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string endpoint, string roleArn, string roleSessionName) - : this(accessKeyId, accessKeySecret) + : this(accessKeyId, accessKeySecret, regionId, endpoint) { - RegionId = CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); - Endpoint = endpoint; - RoleArn = CheckNullOrEmptyAndReturnValue(roleArn, nameof(roleArn)); - RoleSessionName = CheckNullOrEmptyAndReturnValue(roleSessionName, nameof(roleSessionName)); + RoleArn = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(roleArn, nameof(roleArn)); + RoleSessionName = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(roleSessionName, nameof(roleSessionName)); } public AliyunStorageOptions SetPolicy(string policy) @@ -168,7 +112,7 @@ public AliyunStorageOptions SetTemporaryCredentialsCacheKey(string temporaryCred return this; } - public AliyunStorageOptions SetDurationSeconds(int durationSeconds) + public AliyunStorageOptions SetDurationSeconds(long durationSeconds) { DurationSeconds = durationSeconds; return this; @@ -179,31 +123,4 @@ public AliyunStorageOptions SetEarlyExpires(int earlyExpires) EarlyExpires = earlyExpires; return this; } - - internal string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) - { - if (string.IsNullOrEmpty(parameter)) - throw new ArgumentException($"{parameterName} cannot be null and empty string"); - - return parameter; - } - - private string CheckNullOrEmptyAndReturnValue(string? parameter, Action error) - { - if (string.IsNullOrEmpty(parameter)) - error.Invoke(); - - return parameter!; - } - - private static string GetEndpoint(string regionId, EndpointMode mode) - => regionId + (mode == EndpointMode.Public ? Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX : Const.INTERNAL_ENDPOINT_SUFFIX); - - private static string GetRegionId(string endpoint) - { - if (endpoint.EndsWith(Const.INTERNAL_ENDPOINT_SUFFIX, StringComparison.OrdinalIgnoreCase)) - return endpoint.Remove(endpoint.Length - Const.INTERNAL_ENDPOINT_SUFFIX.Length); - - return endpoint.Remove(endpoint.Length - Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX.Length); - } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index c54c5f7e0..0df2cbc67 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -40,7 +40,7 @@ builder.Services.AddAliyunStorage(); 1. Add Alibaba Cloud Storage Service ````C# -builder.Services.AddAliyunStorage(new ALiYunStorageOptions("AccessKeyId", "AccessKeySecret", "regionId", "roleArn", "roleSessionName")); +builder.Services.AddAliyunStorage(new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "regionId", "roleArn", "roleSessionName")); ```` ### Usage 3: @@ -48,7 +48,7 @@ builder.Services.AddAliyunStorage(new ALiYunStorageOptions("AccessKeyId", "Acces 1. Add Alibaba Cloud Storage Service ````C# -builder.Services.AddAliyunStorage(() => new ALiYunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration ["Aliyun:RoleSessionName"])); +builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration ["Aliyun:RoleSessionName"])); ```` > The difference from usage 2 is that the configuration can take effect without restarting the project after the configuration update \ No newline at end of file diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index ec54dc310..c338877bb 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -9,17 +9,20 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun ``` 支持: + * GetSecurityToken 获取安全令牌 ### 用法1: 1. 配置appsettings.json + ``` C# { "AliyunOss": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", "RegionId": "Replace-With-Your-RegionId", + "Endpoint": "Replace-With-Your-Endpoint", "RoleArn": "Replace-With-Your-RoleArn", "RoleSessionName": "Replace-With-Your-RoleSessionName", "DurationSeconds": 3600,//选填、默认: 3600s @@ -41,7 +44,7 @@ builder.Services.AddAliyunStorage(); ```C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage(new ALiYunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); +builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); ``` ### 用法3: @@ -50,7 +53,7 @@ builder.Services.AddAliyunStorage(new ALiYunStorageOptions(configuration["Aliyun ```C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage(() => new ALiYunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); +builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); ``` > 与用法2的区别在于配置更新后无需重启项目即可生效 \ No newline at end of file diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index 422e69e76..cd6e1983e 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -19,21 +19,40 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic throw new ArgumentException(sectionName, nameof(sectionName)); services.AddAliyunStorageDepend(); - services.TryAddConfigure(sectionName); - services.TryAddSingleton(serviceProvider => - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - CheckAliYunStorageOptions(optionsMonitor.CurrentValue, $"Failed to get {nameof(IOptionsMonitor)}"); - return new Client(optionsMonitor.CurrentValue, GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider)); - }); + services.TryAddConfigure(sectionName); + services.TryAddSingleton(); + services.TryAddSingleton(serviceProvider => new DefaultCredentialProvider( + GetOssClientFactory(serviceProvider), + GetAliyunStorageConfigurationOption(serviceProvider), + GetMemoryCache(serviceProvider), + GetDefaultCredentialProviderLogger(serviceProvider))); + services.TryAddSingleton(serviceProvider => new Client( + GetCredentialProvider(serviceProvider), + GetAliyunStorageOption(serviceProvider), + GetClientLogger(serviceProvider))); return services; } + public static IServiceCollection AddAliyunStorage( + this IServiceCollection services, + string accessKeyId, + string accessKeySecret, + string regionId, + EndpointMode mode, + Action? actions =null) + { + string endpoint = ObjectStorageExtensions.GetEndpoint(regionId, mode); + var aliyunStorageOptions = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); + actions?.Invoke(aliyunStorageOptions); + return services.AddAliyunStorage(aliyunStorageOptions); + } + public static IServiceCollection AddAliyunStorage(this IServiceCollection services, AliyunStorageOptions options) { ArgumentNullException.ThrowIfNull(options, nameof(options)); - string message = $"{options.AccessKeyId}, {options.AccessKeySecret}, {options.RegionId}, {options.RoleArn}, {options.RoleSessionName} are required and cannot be empty"; + string message = + $"{options.AccessKeyId}, {options.AccessKeySecret}, {options.RegionId}, {options.RoleArn}, {options.RoleSessionName} are required and cannot be empty"; CheckAliYunStorageOptions(options, message); return services.AddAliyunStorage(() => options); @@ -44,8 +63,16 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic ArgumentNullException.ThrowIfNull(func, nameof(func)); services.AddAliyunStorageDepend(); - services.TryAddSingleton(serviceProvider - => new Client(func.Invoke(), GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider))); + services.TryAddSingleton(); + services.TryAddSingleton(serviceProvider => new DefaultCredentialProvider( + GetOssClientFactory(serviceProvider), + GetAliyunStorageConfigurationOption(serviceProvider), + GetMemoryCache(serviceProvider), + GetDefaultCredentialProviderLogger(serviceProvider))); + services.TryAddSingleton(serviceProvider => new Client( + GetCredentialProvider(serviceProvider), + func.Invoke(), + GetClientLogger(serviceProvider))); return services; } @@ -79,25 +106,34 @@ private static IServiceCollection TryAddConfigure( return services; } + private static IOssClientFactory GetOssClientFactory(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService(); + + private static ICredentialProvider GetCredentialProvider(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService(); + + private static IOptionsMonitor GetAliyunStorageConfigurationOption(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService>(); + + private static IOptionsMonitor GetAliyunStorageOption(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService>(); + private static IMemoryCache GetMemoryCache(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService(); private static ILogger? GetClientLogger(IServiceProvider serviceProvider) => serviceProvider.GetService>(); - private static void CheckAliYunStorageOptions(AliyunStorageOptions options, string? message = default) + private static ILogger? GetDefaultCredentialProviderLogger(IServiceProvider serviceProvider) + => serviceProvider.GetService>(); + + private static void CheckAliYunStorageOptions(AliyunStorageOptions options, string message) { ArgumentNullException.ThrowIfNull(options, nameof(options)); - if (options.AccessKeyId == null && - options.AccessKeySecret == null && - options.RegionId == null && - options.RoleArn == null && - options.RoleSessionName == null) + if (options.AccessKeyId == null && options.AccessKeySecret == null && options.RegionId == null) throw new ArgumentException(message); - options.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); - options.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); - options.CheckNullOrEmptyAndReturnValue(options.RegionId, nameof(options.RegionId)); - options.CheckNullOrEmptyAndReturnValue(options.RoleArn, nameof(options.RoleArn)); - options.CheckNullOrEmptyAndReturnValue(options.RoleSessionName, nameof(options.RoleSessionName)); + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.RegionId, nameof(options.RegionId)); } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs index 9c9a45abb..32d6f0ced 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs @@ -22,3 +22,4 @@ global using System.Net; global using System.Text; global using AliyunFormatType = Aliyun.Acs.Core.Http.FormatType; +global using System.Text.Json.Serialization; diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs new file mode 100644 index 000000000..d1efef2d9 --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs @@ -0,0 +1,21 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +[TestClass] +public class BaseTest +{ + protected AliyunStorageOptions _aLiYunStorageOptions; + protected const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; + + public BaseTest() + { + _aLiYunStorageOptions = new AliyunStorageOptions( + "AccessKeyId", + "AccessKeySecret", + HANG_ZHOUE_PUBLIC_ENDPOINT, + "RoleArn", + "RoleSessionName"); + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs deleted file mode 100644 index 5ecbaf333..000000000 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; - -public class CustomNullClient : Client -{ - public string Message = "You are not authorized to do this action. You should be authorized by RAM."; - - public CustomNullClient(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) : base(options, cache, logger) - { - } - - protected override TemporaryCredentialsResponse GetTemporaryCredentials( - string regionId, - string accessKeyId, - string accessKeySecret, - string roleArn, - string roleSessionName, - string policy, - long durationSeconds) => throw new Exception(Message); -} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs new file mode 100644 index 000000000..44aa86e03 --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs @@ -0,0 +1,74 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +public class CustomizeClient : Client +{ + public Mock? Oss; + + public CustomizeClient(ICredentialProvider credentialProvider, AliyunStorageOptions options, ILogger? logger) : base( + credentialProvider, options, logger) + { + } + + public override IOss GetClient() + { + if (Oss != null) + return Oss.Object; + + Oss = new(); + Oss.Setup(c => c.GetObject(It.IsAny(), It.IsAny())).Returns(GetOssObject()).Verifiable(); + Oss.Setup(c => c.GetObject(It.IsAny())).Returns(GetOssObject()).Verifiable(); + Oss.Setup(c => c.PutObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(GetPutObjectResult()).Verifiable(); + Oss.Setup(c => c.ResumableUploadObject(It.IsAny())).Returns(GetPutObjectResult()).Verifiable(); + Oss.Setup(c => c.DoesObjectExist(It.IsAny(), "1.jpg")).Returns(false).Verifiable(); + Oss.Setup(c => c.DoesObjectExist(It.IsAny(), "2.jpg")).Returns(true).Verifiable(); + Oss.Setup(c => c.DeleteObject(It.IsAny(), "1.jpg")).Returns(GetDeleteFail()).Verifiable(); + Oss.Setup(c => c.DeleteObject(It.IsAny(), "2.jpg")).Returns(GetDeleteSuccess()).Verifiable(); + Oss.Setup(c => c.DeleteObjects(It.IsAny())).Returns(GetDeleteObjectsResult()).Verifiable(); + return Oss.Object; + } + + private OssObject GetOssObject() + { + string objectName = string.Empty; + var constructor = typeof(OssObject).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, new[] { typeof(string) })!; + OssObject ossObject = (constructor.Invoke(new object[] { objectName }) as OssObject)!; + ossObject.ResponseStream = null; + return ossObject; + } + + private PutObjectResult GetPutObjectResult() + { + var constructor = typeof(PutObjectResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as PutObjectResult)!; + result.ResponseStream = new MemoryStream(Encoding.Default.GetBytes("test")); + return result; + } + + private DeleteObjectResult GetDeleteFail() + { + var constructor = typeof(DeleteObjectResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as DeleteObjectResult)!; + result.HttpStatusCode = HttpStatusCode.NotFound; + return result; + } + + private DeleteObjectResult GetDeleteSuccess() + { + var constructor = typeof(DeleteObjectResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as DeleteObjectResult)!; + result.HttpStatusCode = HttpStatusCode.OK; + return result; + } + + private DeleteObjectsResult GetDeleteObjectsResult() + { + var constructor = typeof(DeleteObjectsResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as DeleteObjectsResult)!; + result.HttpStatusCode = HttpStatusCode.OK; + return result; + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeCredentialProvider.cs similarity index 58% rename from test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomClient.cs rename to test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeCredentialProvider.cs index f4a4dc0aa..2bf0753c6 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomClient.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeCredentialProvider.cs @@ -3,28 +3,32 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; -public class CustomClient : Client +public class CustomizeCredentialProvider : DefaultCredentialProvider { - public readonly TemporaryCredentialsResponse TemporaryCredentials; + public readonly TemporaryCredentialsResponse TemporaryCredentials = new( + "accessKeyId", + "secretAccessKey", + "sessionToken", + DateTime.UtcNow.AddHours(-1)); - public CustomClient(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) : base(options, cache, logger) + public CustomizeCredentialProvider(IOssClientFactory ossClientFactory, AliyunStorageOptions options, IMemoryCache cache, + ILogger? logger) : base(ossClientFactory, options, cache, logger) { - TemporaryCredentials = new( - "accessKeyId", - "secretAccessKey", - "sessionToken", - DateTime.UtcNow.AddHours(-1)); } - protected override TemporaryCredentialsResponse GetTemporaryCredentials( + public CustomizeCredentialProvider(IOssClientFactory ossClientFactory, IOptionsMonitor options, IMemoryCache cache, + ILogger? logger) : base(ossClientFactory, options, cache, logger) + { + } + + public override TemporaryCredentialsResponse GetTemporaryCredentials( string regionId, string accessKeyId, string accessKeySecret, string roleArn, string roleSessionName, string policy, - long durationSeconds) - => TemporaryCredentials; + long durationSeconds) => TemporaryCredentials; public TemporaryCredentialsResponse TestGetTemporaryCredentials(string regionId, string accessKeyId, diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs new file mode 100644 index 000000000..ee7bd8657 --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs @@ -0,0 +1,26 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +public class CustomizeNullClient : DefaultCredentialProvider +{ + public string Message = "You are not authorized to do this action. You should be authorized by RAM."; + + public override TemporaryCredentialsResponse GetTemporaryCredentials( + string regionId, + string accessKeyId, + string accessKeySecret, + string roleArn, + string roleSessionName, + string policy, + long durationSeconds) => throw new Exception(Message); + + public CustomizeNullClient(IOssClientFactory ossClientFactory, AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) : base(ossClientFactory, options, cache, logger) + { + } + + public CustomizeNullClient(IOssClientFactory ossClientFactory, IOptionsMonitor options, IMemoryCache cache, ILogger? logger) : base(ossClientFactory, options, cache, logger) + { + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs index d0c628619..901519ee6 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) MASA Stack All rights reserved. +// Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; @@ -6,12 +6,17 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] public class TestALiYunStorageOptions { + private const string REGION_ID = "oss-cn-hangzhou"; + private const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; + private const string HANG_ZHOUE_INTERNAL_ENDPOINT = "oss-cn-hangzhou-internal.aliyuncs.com"; + private const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.Storage.TemporaryCredentials"; + [DataTestMethod] - [DataRow("", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName", "accessKeyId")] - [DataRow("AccessKeyId", "", "RegionId", "RoleArn", "RoleSessionName", "accessKeySecret")] + [DataRow("", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName", "accessKeyId")] + [DataRow("AccessKeyId", "", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName", "accessKeySecret")] [DataRow("AccessKeyId", "AccessKeySecret", "", "RoleArn", "RoleSessionName", "regionId")] - [DataRow("AccessKeyId", "AccessKeySecret", "RegionId", "", "RoleSessionName", "roleArn")] - [DataRow("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "", "roleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "", "RoleSessionName", "roleArn")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "", "roleSessionName")] public void TestErrorParameterThrowArgumentException( string accessKeyId, string accessKeySecret, @@ -28,7 +33,7 @@ public void TestErrorParameterThrowArgumentException( [TestMethod] public void TestDurationSecondsGreaterThan43200ReturnThrowArgumentOutOfRangeException() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); Assert.ThrowsException(() => options.SetDurationSeconds(43201), "DurationSeconds must be in range of 900-43200"); @@ -37,22 +42,24 @@ public void TestDurationSecondsGreaterThan43200ReturnThrowArgumentOutOfRangeExce [TestMethod] public void TestDurationSecondsLessThan900ReturnThrowArgumentOutOfRangeException() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); Assert.ThrowsException(() => options.SetDurationSeconds(899), "DurationSeconds must be in range of 900-43200"); } [DataTestMethod] - [DataRow("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, HANG_ZHOUE_INTERNAL_ENDPOINT, "RoleArn", "RoleSessionName")] public void TestSuccessParameterReturnInitializationSuccessful( string accessKeyId, string accessKeySecret, string regionId, + string endpoint, string roleArn, string roleSessionName) { - var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, roleArn, roleSessionName); + var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint, roleArn, roleSessionName); Assert.IsTrue(options.AccessKeyId == accessKeyId); Assert.IsTrue(options.AccessKeySecret == accessKeySecret); Assert.IsTrue(options.RegionId == regionId); @@ -68,7 +75,7 @@ public void TestSuccessParameterReturnInitializationSuccessful( [DataRow(2000)] public void TestDurationSecondsLessThan43200AndGreaterThan900ReturnTrue(int durationSeconds) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); options.SetDurationSeconds(durationSeconds); Assert.IsTrue(options.DurationSeconds == durationSeconds); } @@ -80,7 +87,7 @@ public void TestErrorTemporaryCredentialsCacheKeyReturnThrowArgumentException( string temporaryCredentialsCacheKey, string temporaryCredentialsCacheKeyName) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); Assert.ThrowsException(() => options.SetTemporaryCredentialsCacheKey(temporaryCredentialsCacheKey), $"{temporaryCredentialsCacheKeyName} cannot be empty"); @@ -90,7 +97,7 @@ public void TestErrorTemporaryCredentialsCacheKeyReturnThrowArgumentException( [DataRow("Aliyun.TemporaryCredentials")] public void TestNotNullTemporaryCredentialsCacheKeyReturnSuccess(string temporaryCredentialsCacheKey) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); options.SetTemporaryCredentialsCacheKey(temporaryCredentialsCacheKey); Assert.IsTrue(options.TemporaryCredentialsCacheKey == temporaryCredentialsCacheKey); } @@ -101,8 +108,103 @@ public void TestNotNullTemporaryCredentialsCacheKeyReturnSuccess(string temporar [DataRow("Policy")] public void TestSetPolicyReturnSuccess(string policy) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); options.SetPolicy(policy); Assert.IsTrue(options.Policy == policy); } + + [DataTestMethod] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT)] + public void TestNullRoleArnAndSessionNameReturnSessionNameIsNull( + string accessKeyId, + string accessKeySecret, + string endpoint) + { + var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); + Assert.IsTrue(options.RegionId == REGION_ID); + Assert.IsTrue(options.Endpoint == HANG_ZHOUE_PUBLIC_ENDPOINT); + Assert.IsTrue(options.GetDurationSeconds() == 3600); + Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); + Assert.IsTrue(options.Quiet); + Assert.IsNotNull(options.CallbackBody); + Assert.IsTrue(options.EnableResumableUpload); + Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); + Assert.IsNull(options.RoleArn); + Assert.IsNull(options.RoleSessionName); + } + + //[DataTestMethod] + //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Public, HANG_ZHOUE_PUBLIC_ENDPOINT)] + //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Internal, HANG_ZHOUE_INTERNAL_ENDPOINT)] + //public void TestRegionIdAndModeReturnEndpoint( + // string accessKeyId, + // string accessKeySecret, + // string regionId, + // EndpointMode mode, + // string endpoint) + //{ + // var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, mode); + // Assert.IsTrue(options.RegionId == REGION_ID); + // Assert.IsTrue(options.Endpoint == endpoint); + // Assert.IsTrue(options.DurationSeconds == 3600); + // Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); + // Assert.IsTrue(options.Quiet); + // Assert.IsNotNull(options.CallbackBody); + // Assert.IsTrue(options.EnableResumableUpload); + // Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); + // Assert.IsNull(options.RoleArn); + // Assert.IsNull(options.RoleSessionName); + //} + + //[DataTestMethod] + //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Public, HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName")] + //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Internal, HANG_ZHOUE_INTERNAL_ENDPOINT, "RoleArn", "RoleSessionName")] + //public void TestRegionIdAndModeReturnEndpoint( + // string accessKeyId, + // string accessKeySecret, + // string regionId, + // EndpointMode mode, + // string endpoint, + // string roleArn, + // string roleSessionName) + //{ + + // var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, mode, roleArn, roleSessionName); + // Assert.IsTrue(options.RegionId == REGION_ID); + // Assert.IsTrue(options.Endpoint == endpoint); + // Assert.IsTrue(options.DurationSeconds == 3600); + // Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); + // Assert.IsTrue(options.Quiet); + // Assert.IsNotNull(options.CallbackBody); + // Assert.IsTrue(options.EnableResumableUpload); + // Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); + // Assert.IsTrue(options.RoleArn == roleArn); + // Assert.IsTrue(options.RoleSessionName == roleSessionName); + //} + + [DataTestMethod] + [DataRow("AccessKeyId", "AccessKeySecret", "Endpoint")] + [DataRow("AccessKeyId", "AccessKeySecret", "Endpoint")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT + ".cn")] + public void TestErrorEndpointReturnThrowArgumentException( + string accessKeyId, + string accessKeySecret, + string endpoint) + { + Assert.ThrowsException(() => new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint), "Unrecognized endpoint, failed to get RegionId"); + } + + [TestMethod] + public void TestEarlyExpireLessThanZeroReturnThrowArgumentOutOfRangeException() + { + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); + Assert.ThrowsException(() => options.SetEarlyExpires(-1)); + } + + [TestMethod] + public void TestEarlyExpireEqual1Return1() + { + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); + Assert.IsTrue(options.SetEarlyExpires(1).GetEarlyExpires() == 1); + } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs index f2eb54c42..bbf7d44a6 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs @@ -4,139 +4,136 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] -public class TestClient +public class TestClient : BaseTest { - private AliyunStorageOptions _aLiYunStorageOptions; + private CustomizeClient _client; [TestInitialize] public void Initialize() { - _aLiYunStorageOptions = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + Mock credentialProvider = new(); + credentialProvider.Setup(provider => provider.SupportSts).Returns(false); + _client = new CustomizeClient(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); } [TestMethod] public void TestGetTokenAndNullLoggerReturnFalse() { - Mock memoryCache = new(); - var client = new Client(_aLiYunStorageOptions, memoryCache.Object, null); + Mock credentialProvider = new(); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, null); Assert.ThrowsException(() => client.GetToken(), "GetToken is not supported, please use GetSecurityToken"); } [TestMethod] public void TestGetTokenAndNotNullLoggerReturnFalse() { - Mock memoryCache = new(); - var client = new Client(_aLiYunStorageOptions, memoryCache.Object, NullLogger.Instance); + Mock credentialProvider = new(); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); Assert.ThrowsException(() => client.GetToken(), "GetToken is not supported, please use GetSecurityToken"); } [TestMethod] public void TestGetSecurityTokenByCacheReturnSuccess() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); + Mock credentialProvider = new(); TemporaryCredentialsResponse temporaryCredentials = new( "accessKeyId", "secretAccessKey", "sessionToken", DateTime.UtcNow.AddHours(-1)); - memoryCache.Set(_aLiYunStorageOptions.TemporaryCredentialsCacheKey, temporaryCredentials); - var client = new Client(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); + credentialProvider.Setup(provider => provider.GetSecurityToken()).Returns(temporaryCredentials); + credentialProvider.Setup(provider => provider.SupportSts).Returns(true); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); var responseBase = client.GetSecurityToken(); Assert.IsTrue(responseBase == temporaryCredentials); } [TestMethod] - public void TestGetSecurityTokenByCacheNotFoundReturnSuccess() + public void TestEmptyRoleArnGetSecurityTokenReturnThrowArgumentException() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - var securityToken = client.GetSecurityToken(); - - Assert.IsTrue(securityToken.Expiration == client.TemporaryCredentials.Expiration && - securityToken.AccessKeyId == client.TemporaryCredentials.AccessKeyId && - securityToken.AccessKeySecret == client.TemporaryCredentials.AccessKeySecret && - securityToken.SessionToken == client.TemporaryCredentials.SessionToken); - Assert.IsNotNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + Mock credentialProvider = new(); + credentialProvider.Setup(provider => provider.SupportSts).Returns(false); + _aLiYunStorageOptions = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); + Assert.ThrowsException(() => client.GetSecurityToken()); } [TestMethod] - public void TestGetSecurityTokenByCacheNotFoundAndGetTemporaryCredentialsIsNullReturnError() + public async Task TestGetObjectAsyncReturnGetObjecVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomNullClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - Assert.ThrowsException(() => client.GetSecurityToken(), client.Message); - Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + await _client.GetObjectAsync("bucketName", "objectName", stream => + { + Assert.IsTrue(stream == null); + }); + _client.Oss.Verify(oss => oss.GetObject(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] - public void TestSetTemporaryCredentialsAndExpirationLessThan10SecondsReturnSkip() + public async Task TestGetObjectAsyncByOffsetReturnGetObjecVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - client.TestExpirationTimeLessThan10Second(5); - Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + await _client.GetObjectAsync("bucketName", "objectName", 1, 2, stream => + { + Assert.IsTrue(stream == null); + }); + _client.Oss.Verify(oss => oss.GetObject(It.IsAny()), Times.Once); } - [DataTestMethod] - [DataRow(15)] - [DataRow(20)] - public void TestSetTemporaryCredentialsAndExpirationGreatherThanOrEqual10SecondsReturnSkip(int durationSeconds) + [TestMethod] + public async Task TestGetObjectAsyncByLengthLessThan0AndNotEqualMinus1ReturnThrowArgumentOutOfRangeException() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - client.TestExpirationTimeLessThan10Second(durationSeconds); - var res = memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey); - Assert.IsNotNull(res); + await Assert.ThrowsExceptionAsync(async () + => await _client.GetObjectAsync("bucketName", "objectName", 1, -2, null!)); } [TestMethod] - public void TestGetTemporaryCredentialsReturnNull() + public async Task TestPutObjectAsyncReturnPutObjectVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - Assert.ThrowsException(() => client.TestGetTemporaryCredentials( - "cn-shanghai", - "accessKeyId", - "accessKeySecret", - "roleArn", - "roleSessionName", - String.Empty, - 3600)); + string str = "JIm"; + await _client.PutObjectAsync("bucketName", "objectName", new MemoryStream(Encoding.Default.GetBytes(str))); + _client.Oss.Verify(oss => oss.PutObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); } [TestMethod] - public void TestGetTemporaryCredentialsAndNullLoggerReturnThrowException() + public async Task TestPutObjectAsyncReturnResumableUploadObjectVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, null); - Assert.ThrowsException(() => client.TestGetTemporaryCredentials( - "cn-shanghai", - "accessKeyId", - "accessKeySecret", - "roleArn", - "roleSessionName", - "policy", - 3600)); + _aLiYunStorageOptions.BigObjectContentLength = 2; + string str = "JIm"; + await _client.PutObjectAsync("bucketName", "objectName", new MemoryStream(Encoding.Default.GetBytes(str))); + _client.Oss.Verify(oss => oss.ResumableUploadObject(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestObjectExistsAsyncReturnNotFound() + { + Assert.IsFalse(await _client.ObjectExistsAsync("bucketName", "1.jpg")); + } + + [TestMethod] + public async Task TestObjectExistsAsyncReturnExist() + { + Assert.IsTrue(await _client.ObjectExistsAsync("bucketName", "2.jpg")); + } + + [TestMethod] + public async Task TestDeleteObjectAsyncReturnVerifytNever() + { + await _client.DeleteObjectAsync("bucketName", "1.jpg"); + _client.Oss.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestDeleteObjectAsyncReturnVerifytOnce() + { + await _client.DeleteObjectAsync("bucketName", "2.jpg"); + _client.Oss.Verify(oss => oss.DoesObjectExist(It.IsAny(), It.IsAny()), Times.Once); + _client.Oss.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestDeleteMultiObjectAsyncReturnVerifytOnce() + { + await _client.DeleteObjectAsync("bucketName", new[] { "2.jpg", "1.jog" }); + _client.Oss.Verify(oss => oss.DeleteObjects(It.IsAny()), Times.Once); } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs new file mode 100644 index 000000000..8088706ad --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs @@ -0,0 +1,124 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +[TestClass] +public class TestCredentialProvider : BaseTest +{ + [TestMethod] + public void TestGetSecurityTokenByCacheNotFoundReturnSuccess() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + var securityToken = client.GetSecurityToken(); + Assert.IsTrue(securityToken.Expiration == client.TemporaryCredentials.Expiration && + securityToken.AccessKeyId == client.TemporaryCredentials.AccessKeyId && + securityToken.AccessKeySecret == client.TemporaryCredentials.AccessKeySecret && + securityToken.SessionToken == client.TemporaryCredentials.SessionToken); + Assert.IsNotNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + } + + [TestMethod] + public void TestGetSecurityTokenByCacheNotFoundAndGetTemporaryCredentialsIsNullReturnError() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeNullClient(serviceProvider.GetRequiredService() + , _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + Assert.ThrowsException(() => client.GetSecurityToken(), client.Message); + Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + } + + [TestMethod] + public void TestSetTemporaryCredentialsAndExpirationLessThan10SecondsReturnSkip() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + client.TestExpirationTimeLessThan10Second(5); + Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + } + + [DataTestMethod] + [DataRow(15)] + [DataRow(20)] + public void TestSetTemporaryCredentialsAndExpirationGreatherThanOrEqual10SecondsReturnSkip(int durationSeconds) + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + client.TestExpirationTimeLessThan10Second(durationSeconds); + var res = memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey); + Assert.IsNotNull(res); + } + + [TestMethod] + public void TestGetTemporaryCredentialsReturnNull() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + Assert.ThrowsException(() => client.TestGetTemporaryCredentials( + "cn-shanghai", + "accessKeyId", + "accessKeySecret", + "roleArn", + "roleSessionName", + string.Empty, + 3600)); + } + + [TestMethod] + public void TestGetTemporaryCredentialsAndNullLoggerReturnThrowException() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + Assert.ThrowsException(() => client.TestGetTemporaryCredentials( + "cn-shanghai", + "accessKeyId", + "accessKeySecret", + "roleArn", + "roleSessionName", + "policy", + 3600)); + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs index bfb1fa1f0..b8db03dce 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs @@ -1,5 +1,3 @@ -using Masa.BuildingBlocks.Storage.ObjectStorage; - // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. @@ -8,21 +6,24 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] public class TestStorage { + private const string HANG_ZHOUE_REGIONID = "oss-cn-hangzhou"; + + private const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; + [TestMethod] - public void TestAddAliyunStorageReturnThrowArgumentException() + public void TestAddAliyunStorageAndNotAddConfigurationReturnClientIsNotNull() { - var services = new ServiceCollection(); + IServiceCollection services = new ServiceCollection(); services.AddAliyunStorage(); var serviceProvider = services.BuildServiceProvider(); - Assert.ThrowsException(() => serviceProvider.GetService(), - $"Failed to get {nameof(IOptionsMonitor)}"); + Assert.IsNotNull(serviceProvider.GetService()); } [TestMethod] public void TestAddAliyunStorageByEmptySectionReturnThrowArgumentException() { var services = new ServiceCollection(); - Assert.ThrowsException(() => services.AddAliyunStorage(String.Empty)); + Assert.ThrowsException(() => services.AddAliyunStorage(string.Empty)); } [TestMethod] @@ -48,17 +49,25 @@ public void TestAddAliyunStorageAndNullALiYunStorageOptionsReturnThrowArgumentNu } [TestMethod] - public void TestAddAliyunStorageByEmptyALiYunStorageOptionsReturnThrowArgumentNullException() + public void TestAddAliyunStorageByEmptyAccessKeyIdReturnThrowArgumentNullException() { - var services = new ServiceCollection(); - Assert.ThrowsException(() => services.AddAliyunStorage(new AliyunStorageOptions())); + Assert.ThrowsException(() => new AliyunStorageOptions(null!, null!, null!)); } [TestMethod] public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() { var services = new ServiceCollection(); - services.AddAliyunStorage(new AliyunStorageOptions("accessKeyId", "AccessKeySecret", "regionId", "roleArn", "roleSessionName")); + services.AddAliyunStorage(new AliyunStorageOptions("accessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "roleArn", "roleSessionName")); + var serviceProvider = services.BuildServiceProvider(); + Assert.IsNotNull(serviceProvider.GetService()); + } + + [TestMethod] + public void TestAddAliyunStorageByAccessKeyIdAndAccessKeySecretAndRegionIdReturnClientNotNull() + { + var services = new ServiceCollection(); + services.AddAliyunStorage("accessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public); var serviceProvider = services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); } @@ -67,7 +76,7 @@ public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() public void TestAddMultiAliyunStorageReturnClientCountIs1() { var services = new ServiceCollection(); - AliyunStorageOptions options = new AliyunStorageOptions("accessKeyId", "accessKeySecret", "regionId", "roleArn", "roleSessionName"); + AliyunStorageOptions options = new AliyunStorageOptions("accessKeyId", "accessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "roleArn", "roleSessionName"); services.AddAliyunStorage(options).AddAliyunStorage(options); var serviceProvider = services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs index 19081a874..a33b28211 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. See LICENSE.txt in the project root for license information. global using Aliyun.Acs.Core.Exceptions; +global using Aliyun.OSS; +global using Masa.BuildingBlocks.Storage.ObjectStorage; global using Masa.BuildingBlocks.Storage.ObjectStorage.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; global using Microsoft.Extensions.Caching.Memory; @@ -12,3 +14,6 @@ global using Microsoft.Extensions.Options; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; +global using System.Net; +global using System.Reflection; +global using System.Text; diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json index 68205675e..ecbeb7bf4 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json @@ -2,11 +2,14 @@ "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId", - "RoleArn": "Replace-With-Your-RoleArn", - "RoleSessionName": "Replace-With-Your-RoleSessionName", + "RegionId": "oss-cn-hangzhou", + "Endpoint": "oss-cn-hangzhou.aliyuncs.com", "DurationSeconds": 900, - "Policy": "", - "TemporaryCredentialsCacheKey": "Aliyun.TemporaryCredentials" + "Storage": { + "RoleArn": "Replace-With-Your-RoleArn", + "RoleSessionName": "Replace-With-Your-RoleSessionName", + "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials", + "Policy": "" + } } } \ No newline at end of file From 842e24a011d6f974e6e9e9cf9866079ed15e0c96 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Mon, 16 May 2022 13:24:56 +0800 Subject: [PATCH 04/12] test(Storage): Modify Parameter Value --- .../TestStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs index b8db03dce..099be24c9 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs @@ -67,7 +67,7 @@ public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() public void TestAddAliyunStorageByAccessKeyIdAndAccessKeySecretAndRegionIdReturnClientNotNull() { var services = new ServiceCollection(); - services.AddAliyunStorage("accessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public); + services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public); var serviceProvider = services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); } From 96e867c0667c63e40557cb5fc77fc32cbfc9de33 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Mon, 16 May 2022 13:25:54 +0800 Subject: [PATCH 05/12] docs(Storage.Aliyun): Modify Aliyun Storage Docs --- .../README.md | 33 +++++++++++++++---- .../README.zh-CN.md | 33 +++++++++++++++---- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index 0df2cbc67..8d20a46e0 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -9,22 +9,29 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun ```` support: -* GetSecurityToken to get the security token +* GetSecurityToken: Gets the security token(RoleArn, RoleSessionName are required) +* GetObjectAsync: Gets the stream of object data +* PutObjectAsync: Upload objects via Stream +* ObjectExistsAsync: Determine whether the object exists +* DeleteObjectAsync: Delete object ### Usage 1: 1. Configure appsettings.json ```` C# { - "AliyunOss": { + "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", "RegionId": "Replace-With-Your-RegionId", - "RoleArn": "Replace-With-Your-RoleArn", - "RoleSessionName": "Replace-With-Your-RoleSessionName", + "Endpoint": "Replace-With-Your-Endpoint", "DurationSeconds": 3600,//optional, default: 3600s - "Policy": "",//optional - "TemporaryCredentialsCacheKey": "Aliyun.TemporaryCredentials"//optional, default: Aliyun.TemporaryCredentials + "Storage": { + "RoleArn": "Replace-With-Your-RoleArn", + "RoleSessionName": "Replace-With-Your-RoleSessionName", + "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials",//optional, default: Aliyun.Storage.TemporaryCredentials + "Policy": ""//optional + } } } ```` @@ -51,4 +58,16 @@ builder.Services.AddAliyunStorage(new AliyunStorageOptions("AccessKeyId", "Acces builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration ["Aliyun:RoleSessionName"])); ```` -> The difference from usage 2 is that the configuration can take effect without restarting the project after the configuration update \ No newline at end of file +> The difference from usage 2 is that the configuration can take effect without restarting the project after the configuration update + +### Usage 4: + +1. Add Alibaba Cloud Storage Service + +````C# +var configuration = builder.Configuration; +builder.Services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public, options => +{ + options.CallbackUrl = "Replace-With-Your-CallbackUrl"; +}); +```` \ No newline at end of file diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index c338877bb..7403c6511 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -10,7 +10,11 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun 支持: -* GetSecurityToken 获取安全令牌 +* GetSecurityToken: 获取安全令牌 (需提供RoleArn、RoleSessionName) +* GetObjectAsync: 获取对象数据的流 +* PutObjectAsync: 通过Stream上传对象 +* ObjectExistsAsync: 判断对象是否存在 +* DeleteObjectAsync: 删除对象 ### 用法1: @@ -18,16 +22,18 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun ``` C# { - "AliyunOss": { + "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", "RegionId": "Replace-With-Your-RegionId", "Endpoint": "Replace-With-Your-Endpoint", - "RoleArn": "Replace-With-Your-RoleArn", - "RoleSessionName": "Replace-With-Your-RoleSessionName", "DurationSeconds": 3600,//选填、默认: 3600s - "Policy": "",//选填 - "TemporaryCredentialsCacheKey": "Aliyun.TemporaryCredentials"//选填、默认: Aliyun.TemporaryCredentials + "Storage": { + "RoleArn": "Replace-With-Your-RoleArn", + "RoleSessionName": "Replace-With-Your-RoleSessionName", + "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials",//选填、默认: Aliyun.Storage.TemporaryCredentials + "Policy": ""//选填 + } } } ``` @@ -56,4 +62,17 @@ var configuration = builder.Configuration; builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); ``` -> 与用法2的区别在于配置更新后无需重启项目即可生效 \ No newline at end of file +> 与用法2的区别在于配置更新后无需重启项目即可生效 + +### 用法4: + +1. 添加阿里云存储服务 + +```C# +var configuration = builder.Configuration; +builder.Services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public, options => +{ + options.CallbackUrl = "Replace-With-Your-CallbackUrl"; +}); +``` + From 048acacf4630c6c9770223439e8f40b7b2891556 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 17 May 2022 13:29:50 +0800 Subject: [PATCH 06/12] fix(Storage.Aliyun): Fix configuration errors --- .../ServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index cd6e1983e..8d0b06b30 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -39,7 +39,7 @@ public static IServiceCollection AddAliyunStorage( string accessKeySecret, string regionId, EndpointMode mode, - Action? actions =null) + Action? actions = null) { string endpoint = ObjectStorageExtensions.GetEndpoint(regionId, mode); var aliyunStorageOptions = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); @@ -66,7 +66,7 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic services.TryAddSingleton(); services.TryAddSingleton(serviceProvider => new DefaultCredentialProvider( GetOssClientFactory(serviceProvider), - GetAliyunStorageConfigurationOption(serviceProvider), + func.Invoke(), GetMemoryCache(serviceProvider), GetDefaultCredentialProviderLogger(serviceProvider))); services.TryAddSingleton(serviceProvider => new Client( From 1d713ddebf591faabade32b9a3766e9583b86ec7 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 17 May 2022 14:46:45 +0800 Subject: [PATCH 07/12] refactor(Storage): Adjust to get sts credentials --- .../DefaultCredentialProvider.cs | 24 ++++++++-------- .../Internal/ObjectStorageExtensions.cs | 3 -- .../Internal/Response/UploadObjectResponse.cs | 3 ++ .../Options/AliyunOptions.cs | 28 +++++-------------- .../Options/AliyunStorageOptions.cs | 22 ++++++++++----- .../Options/Enum/EndpointMode.cs | 10 ------- .../README.md | 4 +-- .../README.zh-CN.md | 4 +-- .../ServiceCollectionExtensions.cs | 13 ++++----- .../_Imports.cs | 2 -- 10 files changed, 47 insertions(+), 66 deletions(-) delete mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs index c89e23191..6066f838a 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs @@ -20,7 +20,9 @@ public DefaultCredentialProvider( { _ossClientFactory = ossClientFactory; _options = options; - SupportSts = !string.IsNullOrEmpty(options.RoleArn) && !string.IsNullOrEmpty(options.RoleSessionName); + SupportSts = !string.IsNullOrEmpty(options.RegionId) && + !string.IsNullOrEmpty(options.RoleArn) && + !string.IsNullOrEmpty(options.RoleSessionName); _cache = cache; _logger = logger; } @@ -52,7 +54,7 @@ public virtual TemporaryCredentialsResponse GetSecurityToken() if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) { temporaryCredentials = GetTemporaryCredentials( - _options.RegionId, + _options.RegionId!, _options.AccessKeyId, _options.AccessKeySecret, _options.RoleArn, @@ -84,14 +86,14 @@ public virtual TemporaryCredentialsResponse GetTemporaryCredentials( if (!string.IsNullOrEmpty(policy)) request.Policy = policy; var response = client.GetAcsResponse(request); - if (response.HttpResponse.isSuccess()) - { - return new TemporaryCredentialsResponse( - response.Credentials.AccessKeyId, - response.Credentials.AccessKeySecret, - response.Credentials.SecurityToken, - DateTime.Parse(response.Credentials.Expiration)); - } + // if (response.HttpResponse.isSuccess()) //todo: Get Sts response information is null, waiting for repair: https://github.com/aliyun/aliyun-openapi-net-sdk/pull/401 + // { + return new TemporaryCredentialsResponse( + response.Credentials.AccessKeyId, + response.Credentials.AccessKeySecret, + response.Credentials.SecurityToken, + DateTime.Parse(response.Credentials.Expiration)); + // } string responseContent = Encoding.Default.GetString(response.HttpResponse.Content); string message = @@ -115,7 +117,6 @@ private static AliyunStorageOptions GetAliyunStorageOptions(AliyunStorageConfigu aliyunStorageOptions.AccessKeyId = TryUpdate(options.Storage.AccessKeyId, options.AccessKeyId); aliyunStorageOptions.AccessKeySecret = TryUpdate(options.Storage.AccessKeySecret, options.AccessKeySecret); aliyunStorageOptions.RegionId = TryUpdate(options.Storage.RegionId, options.RegionId); - aliyunStorageOptions.Endpoint = TryUpdate(options.Storage.Endpoint, options.Endpoint); aliyunStorageOptions.DurationSeconds = options.Storage.DurationSeconds ?? options.DurationSeconds ?? options.GetDurationSeconds(); aliyunStorageOptions.EarlyExpires = options.Storage.EarlyExpires ?? options.EarlyExpires ?? options.GetEarlyExpires(); return aliyunStorageOptions; @@ -125,6 +126,7 @@ private static string TryUpdate(string source, string destination) { if (!string.IsNullOrWhiteSpace(source)) return source; + return destination; } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs index 4315307f4..3c2e261f4 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs @@ -5,9 +5,6 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; internal class ObjectStorageExtensions { - internal static string GetEndpoint(string regionId, EndpointMode mode) - => regionId + (mode == EndpointMode.Public ? Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX : Const.INTERNAL_ENDPOINT_SUFFIX); - internal static string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) { if (string.IsNullOrEmpty(parameter)) diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs index e629fa20f..2874a5e71 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs @@ -33,6 +33,9 @@ public UploadObjectResponse(PutObjectResult result) private string GetCallbackResponse(PutObjectResult putObjectResult) { using var stream = putObjectResult.ResponseStream; + if (stream == null) + return string.Empty; + var buffer = new byte[4 * 1024]; var bytesRead = stream.Read(buffer, 0, buffer.Length); string callbackResponse = Encoding.Default.GetString(buffer, 0, bytesRead); diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs index a64a6b7f1..fec51cc14 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs @@ -9,15 +9,13 @@ public class AliyunOptions public string AccessKeySecret { get; set; } - public string RegionId { get; set; } - - private string _endpoint; - - public string Endpoint - { - get => _endpoint; - set => _endpoint = value?.Trim() ?? string.Empty; - } + /// + /// sts region id + /// If the RegionId is missing, the temporary Sts credential cannot be obtained. + /// https://help.aliyun.com/document_detail/371859.html + /// https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb + /// + public string? RegionId { get; set; } private long? _durationSeconds = null; @@ -60,16 +58,4 @@ public long? EarlyExpires public long GetDurationSeconds() => DurationSeconds ?? Const.DEFAULT_DURATION_SECONDS; public long GetEarlyExpires() => EarlyExpires ?? Const.DEFAULT_EARLY_EXPIRES; - - internal static string GetRegionId(string endpoint) - { - endpoint = endpoint.Trim(); - if (endpoint.EndsWith(Const.INTERNAL_ENDPOINT_SUFFIX, StringComparison.OrdinalIgnoreCase)) - return endpoint.Remove(endpoint.Length - Const.INTERNAL_ENDPOINT_SUFFIX.Length); - - if (endpoint.EndsWith(Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX, StringComparison.OrdinalIgnoreCase)) - return endpoint.Remove(endpoint.Length - Const.PUBLIC_ENDPOINT_DOMAIN_SUFFIX.Length); - - throw new ArgumentException(Const.ERROR_ENDPOINT_MESSAGE); - } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs index 408b6abe6..1e1d7dcc5 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs @@ -5,12 +5,21 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; public class AliyunStorageOptions : AliyunOptions { + private string _endpoint; + + public string Endpoint + { + get => _endpoint; + set => _endpoint = value?.Trim() ?? string.Empty; + } + private string _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; public string TemporaryCredentialsCacheKey { get => _temporaryCredentialsCacheKey; - set => _temporaryCredentialsCacheKey = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); + set => _temporaryCredentialsCacheKey = + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); } /// @@ -76,23 +85,22 @@ private AliyunStorageOptions(string accessKeyId, string accessKeySecret) : this( public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint) : this(accessKeyId, accessKeySecret) { - RegionId = GetRegionId(endpoint); Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint, string roleArn, string roleSessionName) - : this(accessKeyId, accessKeySecret, GetRegionId(endpoint), endpoint, roleArn, roleSessionName) + : this(accessKeyId, accessKeySecret, null, endpoint, roleArn, roleSessionName) { } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string endpoint) - : this(accessKeyId, accessKeySecret) + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string? regionId, string endpoint) + : this(accessKeyId, accessKeySecret) { - RegionId = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); + RegionId = regionId; Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string endpoint, string roleArn, + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string? regionId, string endpoint, string roleArn, string roleSessionName) : this(accessKeyId, accessKeySecret, regionId, endpoint) { diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs deleted file mode 100644 index ec688d764..000000000 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/Enum/EndpointMode.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options.Enum; - -public enum EndpointMode -{ - Public = 1, - Internal, -} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index 8d20a46e0..b101d2ac8 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -23,10 +23,10 @@ support: "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId", - "Endpoint": "Replace-With-Your-Endpoint", + "RegionId": "Replace-With-Your-RegionId",//https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb "DurationSeconds": 3600,//optional, default: 3600s "Storage": { + "Endpoint": "Replace-With-Your-Endpoint",//https://www.alibabacloud.com/help/en/object-storage-service/latest/regions-and-endpoints#section-plb-2vy-5db "RoleArn": "Replace-With-Your-RoleArn", "RoleSessionName": "Replace-With-Your-RoleSessionName", "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials",//optional, default: Aliyun.Storage.TemporaryCredentials diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index 7403c6511..913328958 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -25,10 +25,10 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId", - "Endpoint": "Replace-With-Your-Endpoint", + "RegionId": "Replace-With-Your-RegionId",//https://help.aliyun.com/document_detail/371859.html "DurationSeconds": 3600,//选填、默认: 3600s "Storage": { + "Endpoint": "Replace-With-Your-Endpoint",//https://help.aliyun.com/document_detail/31837.html "RoleArn": "Replace-With-Your-RoleArn", "RoleSessionName": "Replace-With-Your-RoleSessionName", "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials",//选填、默认: Aliyun.Storage.TemporaryCredentials diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index 8d0b06b30..cbd5ed103 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -37,12 +37,11 @@ public static IServiceCollection AddAliyunStorage( this IServiceCollection services, string accessKeyId, string accessKeySecret, - string regionId, - EndpointMode mode, + string? regionId, + string endpoint, Action? actions = null) { - string endpoint = ObjectStorageExtensions.GetEndpoint(regionId, mode); - var aliyunStorageOptions = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); + var aliyunStorageOptions = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, endpoint); actions?.Invoke(aliyunStorageOptions); return services.AddAliyunStorage(aliyunStorageOptions); } @@ -51,8 +50,7 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic { ArgumentNullException.ThrowIfNull(options, nameof(options)); - string message = - $"{options.AccessKeyId}, {options.AccessKeySecret}, {options.RegionId}, {options.RoleArn}, {options.RoleSessionName} are required and cannot be empty"; + string message = $"{options.AccessKeyId}, {options.AccessKeySecret}, {options.RegionId}, {options.RoleArn}, {options.RoleSessionName} are required and cannot be empty"; CheckAliYunStorageOptions(options, message); return services.AddAliyunStorage(() => options); @@ -116,7 +114,7 @@ private static IOptionsMonitor GetAliyunStorageCo => serviceProvider.GetRequiredService>(); private static IOptionsMonitor GetAliyunStorageOption(IServiceProvider serviceProvider) - => serviceProvider.GetRequiredService>(); + => serviceProvider.GetRequiredService>(); private static IMemoryCache GetMemoryCache(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService(); @@ -134,6 +132,5 @@ private static void CheckAliYunStorageOptions(AliyunStorageOptions options, stri ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); - ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.RegionId, nameof(options.RegionId)); } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs index 32d6f0ced..2a8e3b172 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs @@ -12,7 +12,6 @@ global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; -global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options.Enum; global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; @@ -22,4 +21,3 @@ global using System.Net; global using System.Text; global using AliyunFormatType = Aliyun.Acs.Core.Http.FormatType; -global using System.Text.Json.Serialization; From 09dbb8972df24f37f24bcc96939056c81e55e2ae Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 17 May 2022 14:51:30 +0800 Subject: [PATCH 08/12] test(Storage): Modify unit tests --- .../TestALiYunStorageOptions.cs | 22 ++++--------------- .../TestClient.cs | 2 +- .../TestStorage.cs | 2 +- .../appsettings.json | 6 ++--- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs index 901519ee6..5128d2df9 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs @@ -6,7 +6,6 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] public class TestALiYunStorageOptions { - private const string REGION_ID = "oss-cn-hangzhou"; private const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; private const string HANG_ZHOUE_INTERNAL_ENDPOINT = "oss-cn-hangzhou-internal.aliyuncs.com"; private const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.Storage.TemporaryCredentials"; @@ -49,12 +48,11 @@ public void TestDurationSecondsLessThan900ReturnThrowArgumentOutOfRangeException } [DataTestMethod] - [DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName")] - [DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, HANG_ZHOUE_INTERNAL_ENDPOINT, "RoleArn", "RoleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_INTERNAL_ENDPOINT, "RoleArn", "RoleSessionName")] public void TestSuccessParameterReturnInitializationSuccessful( string accessKeyId, string accessKeySecret, - string regionId, string endpoint, string roleArn, string roleSessionName) @@ -62,7 +60,7 @@ public void TestSuccessParameterReturnInitializationSuccessful( var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint, roleArn, roleSessionName); Assert.IsTrue(options.AccessKeyId == accessKeyId); Assert.IsTrue(options.AccessKeySecret == accessKeySecret); - Assert.IsTrue(options.RegionId == regionId); + Assert.IsTrue(options.RegionId == null); Assert.IsTrue(options.RoleArn == roleArn); Assert.IsTrue(options.RoleSessionName == roleSessionName); } @@ -121,7 +119,7 @@ public void TestNullRoleArnAndSessionNameReturnSessionNameIsNull( string endpoint) { var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); - Assert.IsTrue(options.RegionId == REGION_ID); + Assert.IsTrue(options.RegionId == null); Assert.IsTrue(options.Endpoint == HANG_ZHOUE_PUBLIC_ENDPOINT); Assert.IsTrue(options.GetDurationSeconds() == 3600); Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); @@ -182,18 +180,6 @@ public void TestNullRoleArnAndSessionNameReturnSessionNameIsNull( // Assert.IsTrue(options.RoleSessionName == roleSessionName); //} - [DataTestMethod] - [DataRow("AccessKeyId", "AccessKeySecret", "Endpoint")] - [DataRow("AccessKeyId", "AccessKeySecret", "Endpoint")] - [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT + ".cn")] - public void TestErrorEndpointReturnThrowArgumentException( - string accessKeyId, - string accessKeySecret, - string endpoint) - { - Assert.ThrowsException(() => new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint), "Unrecognized endpoint, failed to get RegionId"); - } - [TestMethod] public void TestEarlyExpireLessThanZeroReturnThrowArgumentOutOfRangeException() { diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs index bbf7d44a6..8d2788b94 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs @@ -133,7 +133,7 @@ public async Task TestDeleteObjectAsyncReturnVerifytOnce() [TestMethod] public async Task TestDeleteMultiObjectAsyncReturnVerifytOnce() { - await _client.DeleteObjectAsync("bucketName", new[] { "2.jpg", "1.jog" }); + await _client.DeleteObjectAsync("bucketName", new[] { "2.jpg", "1.jpg" }); _client.Oss.Verify(oss => oss.DeleteObjects(It.IsAny()), Times.Once); } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs index 099be24c9..04aa3fcbe 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs @@ -67,7 +67,7 @@ public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() public void TestAddAliyunStorageByAccessKeyIdAndAccessKeySecretAndRegionIdReturnClientNotNull() { var services = new ServiceCollection(); - services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public); + services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID,HANG_ZHOUE_PUBLIC_ENDPOINT); var serviceProvider = services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json index ecbeb7bf4..971cabb60 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json @@ -2,14 +2,14 @@ "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "oss-cn-hangzhou", - "Endpoint": "oss-cn-hangzhou.aliyuncs.com", + "RegionId": "cn-hangzhou", "DurationSeconds": 900, "Storage": { "RoleArn": "Replace-With-Your-RoleArn", "RoleSessionName": "Replace-With-Your-RoleSessionName", "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials", - "Policy": "" + "Policy": "", + "Endpoint": "oss-cn-hangzhou.aliyuncs.com" } } } \ No newline at end of file From a0f1688806020dbc923fe7a89949138d27f60441 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 26 May 2022 10:11:32 +0800 Subject: [PATCH 09/12] chore: Handling empty warnings --- .../DefaultCredentialProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs index 6066f838a..1806a5708 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs @@ -114,15 +114,15 @@ public virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credent private static AliyunStorageOptions GetAliyunStorageOptions(AliyunStorageConfigureOptions options) { AliyunStorageOptions aliyunStorageOptions = options.Storage; - aliyunStorageOptions.AccessKeyId = TryUpdate(options.Storage.AccessKeyId, options.AccessKeyId); - aliyunStorageOptions.AccessKeySecret = TryUpdate(options.Storage.AccessKeySecret, options.AccessKeySecret); + aliyunStorageOptions.AccessKeyId = TryUpdate(options.Storage.AccessKeyId, options.AccessKeyId)!; + aliyunStorageOptions.AccessKeySecret = TryUpdate(options.Storage.AccessKeySecret, options.AccessKeySecret)!; aliyunStorageOptions.RegionId = TryUpdate(options.Storage.RegionId, options.RegionId); aliyunStorageOptions.DurationSeconds = options.Storage.DurationSeconds ?? options.DurationSeconds ?? options.GetDurationSeconds(); aliyunStorageOptions.EarlyExpires = options.Storage.EarlyExpires ?? options.EarlyExpires ?? options.GetEarlyExpires(); return aliyunStorageOptions; } - private static string TryUpdate(string source, string destination) + private static string? TryUpdate(string? source, string? destination) { if (!string.IsNullOrWhiteSpace(source)) return source; From 965d512a206e046edbfe33084c4c4fd2bfc75937 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 26 May 2022 15:14:21 +0800 Subject: [PATCH 10/12] chore: Adjust Aliyun Sts configuration --- .../DefaultCredentialProvider.cs | 30 +++---- .../Options/AliyunOptions.cs | 50 ----------- .../Options/AliyunStorageConfigureOptions.cs | 2 + .../Options/AliyunStorageOptions.cs | 42 +++++---- .../Options/AliyunStsOptions.cs | 62 +++++++++++++ .../README.md | 7 +- .../README.zh-CN.md | 5 ++ .../ServiceCollectionExtensions.cs | 23 +---- .../TestALiYunStorageOptions.cs | 89 ++++--------------- .../TestClient.cs | 16 ++-- .../TestStorage.cs | 9 -- .../appsettings.json | 6 +- 12 files changed, 144 insertions(+), 197 deletions(-) create mode 100644 src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs index 1806a5708..380f4c49d 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs @@ -20,7 +20,7 @@ public DefaultCredentialProvider( { _ossClientFactory = ossClientFactory; _options = options; - SupportSts = !string.IsNullOrEmpty(options.RegionId) && + SupportSts = !string.IsNullOrEmpty(options.Sts.RegionId) && !string.IsNullOrEmpty(options.RoleArn) && !string.IsNullOrEmpty(options.RoleSessionName); _cache = cache; @@ -54,13 +54,13 @@ public virtual TemporaryCredentialsResponse GetSecurityToken() if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) { temporaryCredentials = GetTemporaryCredentials( - _options.RegionId!, + _options.Sts.RegionId!, _options.AccessKeyId, _options.AccessKeySecret, _options.RoleArn, _options.RoleSessionName, _options.Policy, - _options.GetDurationSeconds()); + _options.Sts.GetDurationSeconds()); SetTemporaryCredentials(temporaryCredentials); } return temporaryCredentials!; @@ -95,19 +95,19 @@ public virtual TemporaryCredentialsResponse GetTemporaryCredentials( DateTime.Parse(response.Credentials.Expiration)); // } - string responseContent = Encoding.Default.GetString(response.HttpResponse.Content); - string message = - $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId}, Status: {response.HttpResponse.Status}, Message: {responseContent}"; - _logger?.LogWarning( - "Aliyun.Client: Failed to obtain temporary credentials, RequestId: {RequestId}, Status: {Status}, Message: {Message}", - response.RequestId, response.HttpResponse.Status, responseContent); - - throw new Exception(message); + // string responseContent = Encoding.Default.GetString(response.HttpResponse.Content); + // string message = + // $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId}, Status: {response.HttpResponse.Status}, Message: {responseContent}"; + // _logger?.LogWarning( + // "Aliyun.Client: Failed to obtain temporary credentials, RequestId: {RequestId}, Status: {Status}, Message: {Message}", + // response.RequestId, response.HttpResponse.Status, responseContent); + // + // throw new Exception(message); } public virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) { - var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - _options.GetEarlyExpires(); + var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - _options.Sts.GetEarlyExpires(); if (timespan >= 0) _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); } @@ -116,9 +116,9 @@ private static AliyunStorageOptions GetAliyunStorageOptions(AliyunStorageConfigu AliyunStorageOptions aliyunStorageOptions = options.Storage; aliyunStorageOptions.AccessKeyId = TryUpdate(options.Storage.AccessKeyId, options.AccessKeyId)!; aliyunStorageOptions.AccessKeySecret = TryUpdate(options.Storage.AccessKeySecret, options.AccessKeySecret)!; - aliyunStorageOptions.RegionId = TryUpdate(options.Storage.RegionId, options.RegionId); - aliyunStorageOptions.DurationSeconds = options.Storage.DurationSeconds ?? options.DurationSeconds ?? options.GetDurationSeconds(); - aliyunStorageOptions.EarlyExpires = options.Storage.EarlyExpires ?? options.EarlyExpires ?? options.GetEarlyExpires(); + aliyunStorageOptions.Sts.RegionId = TryUpdate(options.Storage.Sts.RegionId, options.Sts.RegionId); + aliyunStorageOptions.Sts.DurationSeconds = options.Storage.Sts.DurationSeconds ?? options.Sts.DurationSeconds ?? options.Sts.GetDurationSeconds(); + aliyunStorageOptions.Sts.EarlyExpires = options.Storage.Sts.EarlyExpires ?? options.Sts.EarlyExpires ?? options.Sts.GetEarlyExpires(); return aliyunStorageOptions; } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs index fec51cc14..bccda4777 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs @@ -8,54 +8,4 @@ public class AliyunOptions public string AccessKeyId { get; set; } public string AccessKeySecret { get; set; } - - /// - /// sts region id - /// If the RegionId is missing, the temporary Sts credential cannot be obtained. - /// https://help.aliyun.com/document_detail/371859.html - /// https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb - /// - public string? RegionId { get; set; } - - private long? _durationSeconds = null; - - /// - /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. - /// default: 3600 - /// unit: second - /// - public long? DurationSeconds - { - get => _durationSeconds; - set - { - if (value < 900 || value > 43200) - throw new ArgumentOutOfRangeException(nameof(DurationSeconds), $"{nameof(DurationSeconds)} must be in range of 900-43200"); - - _durationSeconds = value; - } - } - - private long? _earlyExpires = null; - - /// - /// Voucher expires early - /// default: 10 - /// unit: second - /// - public long? EarlyExpires - { - get => _earlyExpires; - set - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(EarlyExpires), $"{nameof(EarlyExpires)} must be Greater than 0"); - - _earlyExpires = value; - } - } - - public long GetDurationSeconds() => DurationSeconds ?? Const.DEFAULT_DURATION_SECONDS; - - public long GetEarlyExpires() => EarlyExpires ?? Const.DEFAULT_EARLY_EXPIRES; } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs index 897b1a22c..5a0893230 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs @@ -6,4 +6,6 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; public class AliyunStorageConfigureOptions : AliyunOptions { public AliyunStorageOptions Storage { get; set; } = new(); + + public AliyunStsOptions Sts { get; set; } = new(); } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs index 1e1d7dcc5..3f1ab8790 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs @@ -5,6 +5,8 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; public class AliyunStorageOptions : AliyunOptions { + public AliyunStsOptions Sts { get; set; } = new(); + private string _endpoint; public string Endpoint @@ -88,21 +90,35 @@ public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string e Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint, string roleArn, string roleSessionName) - : this(accessKeyId, accessKeySecret, null, endpoint, roleArn, roleSessionName) + public AliyunStorageOptions( + string accessKeyId, + string accessKeySecret, + string endpoint, + string roleArn, + string roleSessionName) + : this(accessKeyId, accessKeySecret, endpoint, roleArn, roleSessionName, null) { } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string? regionId, string endpoint) + public AliyunStorageOptions( + string accessKeyId, + string accessKeySecret, + string endpoint, + AliyunStsOptions? stsOptions) : this(accessKeyId, accessKeySecret) { - RegionId = regionId; + Sts = stsOptions ?? new(); Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string? regionId, string endpoint, string roleArn, - string roleSessionName) - : this(accessKeyId, accessKeySecret, regionId, endpoint) + public AliyunStorageOptions( + string accessKeyId, + string accessKeySecret, + string endpoint, + string roleArn, + string roleSessionName, + AliyunStsOptions? stsOptions) + : this(accessKeyId, accessKeySecret, endpoint, stsOptions) { RoleArn = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(roleArn, nameof(roleArn)); RoleSessionName = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(roleSessionName, nameof(roleSessionName)); @@ -119,16 +135,4 @@ public AliyunStorageOptions SetTemporaryCredentialsCacheKey(string temporaryCred TemporaryCredentialsCacheKey = temporaryCredentialsCacheKey; return this; } - - public AliyunStorageOptions SetDurationSeconds(long durationSeconds) - { - DurationSeconds = durationSeconds; - return this; - } - - public AliyunStorageOptions SetEarlyExpires(int earlyExpires) - { - EarlyExpires = earlyExpires; - return this; - } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs new file mode 100644 index 000000000..3cdff636e --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs @@ -0,0 +1,62 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; + +public class AliyunStsOptions +{ + /// + /// sts region id + /// If the RegionId is missing, the temporary Sts credential cannot be obtained. + /// https://help.aliyun.com/document_detail/371859.html + /// https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb + /// + public string? RegionId { get; set; } + + private long? _durationSeconds = null; + + /// + /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. + /// default: 3600 + /// unit: second + /// + public long? DurationSeconds + { + get => _durationSeconds; + set + { + if (value < 900 || value > 43200) + throw new ArgumentOutOfRangeException(nameof(DurationSeconds), $"{nameof(DurationSeconds)} must be in range of 900-43200"); + + _durationSeconds = value; + } + } + + private long? _earlyExpires = null; + + /// + /// Voucher expires early + /// default: 10 + /// unit: second + /// + public long? EarlyExpires + { + get => _earlyExpires; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(EarlyExpires), $"{nameof(EarlyExpires)} must be Greater than 0"); + + _earlyExpires = value; + } + } + + public AliyunStsOptions(string? regionId = null) + { + RegionId = regionId; + } + + public long GetDurationSeconds() => DurationSeconds ?? Const.DEFAULT_DURATION_SECONDS; + + public long GetEarlyExpires() => EarlyExpires ?? Const.DEFAULT_EARLY_EXPIRES; +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index b101d2ac8..55f695797 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -23,8 +23,11 @@ support: "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId",//https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb - "DurationSeconds": 3600,//optional, default: 3600s + "Sts": :{ + "RegionId": "Replace-With-Your-RegionId",//https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb + "DurationSeconds": 3600,//Temporary certificate validity period, default: 3600s + "EarlyExpires": 10//default: 10s + }, "Storage": { "Endpoint": "Replace-With-Your-Endpoint",//https://www.alibabacloud.com/help/en/object-storage-service/latest/regions-and-endpoints#section-plb-2vy-5db "RoleArn": "Replace-With-Your-RoleArn", diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index 913328958..316d8a4f8 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -25,6 +25,11 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", + "Sts": :{ + "RegionId":"Replace-With-Your-Sts-RegionId",//https://help.aliyun.com/document_detail/371859.html + "DurationSeconds":3600,//临时证书有效期, default: 3600秒 + "EarlyExpires":10//default: 10秒 + }, "RegionId": "Replace-With-Your-RegionId",//https://help.aliyun.com/document_detail/371859.html "DurationSeconds": 3600,//选填、默认: 3600s "Storage": { diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index cbd5ed103..1532d2372 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -33,25 +33,10 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic return services; } - public static IServiceCollection AddAliyunStorage( - this IServiceCollection services, - string accessKeyId, - string accessKeySecret, - string? regionId, - string endpoint, - Action? actions = null) - { - var aliyunStorageOptions = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, endpoint); - actions?.Invoke(aliyunStorageOptions); - return services.AddAliyunStorage(aliyunStorageOptions); - } - public static IServiceCollection AddAliyunStorage(this IServiceCollection services, AliyunStorageOptions options) { ArgumentNullException.ThrowIfNull(options, nameof(options)); - - string message = $"{options.AccessKeyId}, {options.AccessKeySecret}, {options.RegionId}, {options.RoleArn}, {options.RoleSessionName} are required and cannot be empty"; - CheckAliYunStorageOptions(options, message); + CheckAliYunStorageOptions(options); return services.AddAliyunStorage(() => options); } @@ -123,12 +108,12 @@ private static IOptionsMonitor GetAliyunStorageOption(ISer private static ILogger? GetDefaultCredentialProviderLogger(IServiceProvider serviceProvider) => serviceProvider.GetService>(); - private static void CheckAliYunStorageOptions(AliyunStorageOptions options, string message) + private static void CheckAliYunStorageOptions(AliyunStorageOptions options) { ArgumentNullException.ThrowIfNull(options, nameof(options)); - if (options.AccessKeyId == null && options.AccessKeySecret == null && options.RegionId == null) - throw new ArgumentException(message); + if (options.AccessKeyId == null && options.AccessKeySecret == null) + throw new ArgumentException($"{nameof(options.AccessKeyId)}, {nameof(options.AccessKeySecret)} are required and cannot be empty"); ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs index 5128d2df9..4861e61f6 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs @@ -32,18 +32,18 @@ public void TestErrorParameterThrowArgumentException( [TestMethod] public void TestDurationSecondsGreaterThan43200ReturnThrowArgumentOutOfRangeException() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); + var aliyunStsOptions = new AliyunStsOptions(); Assert.ThrowsException(() => - options.SetDurationSeconds(43201), + aliyunStsOptions.DurationSeconds = 43201, "DurationSeconds must be in range of 900-43200"); } [TestMethod] public void TestDurationSecondsLessThan900ReturnThrowArgumentOutOfRangeException() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); + var aliyunStsOptions = new AliyunStsOptions(); Assert.ThrowsException(() => - options.SetDurationSeconds(899), + aliyunStsOptions.DurationSeconds = 899, "DurationSeconds must be in range of 900-43200"); } @@ -60,24 +60,11 @@ public void TestSuccessParameterReturnInitializationSuccessful( var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint, roleArn, roleSessionName); Assert.IsTrue(options.AccessKeyId == accessKeyId); Assert.IsTrue(options.AccessKeySecret == accessKeySecret); - Assert.IsTrue(options.RegionId == null); + Assert.IsTrue(options.Sts.RegionId == null); Assert.IsTrue(options.RoleArn == roleArn); Assert.IsTrue(options.RoleSessionName == roleSessionName); } - [DataTestMethod] - [DataRow(900)] - [DataRow(901)] - [DataRow(43200)] - [DataRow(43199)] - [DataRow(2000)] - public void TestDurationSecondsLessThan43200AndGreaterThan900ReturnTrue(int durationSeconds) - { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); - options.SetDurationSeconds(durationSeconds); - Assert.IsTrue(options.DurationSeconds == durationSeconds); - } - [DataTestMethod] [DataRow("", "temporaryCredentialsCacheKey")] [DataRow(null, "temporaryCredentialsCacheKey")] @@ -119,9 +106,7 @@ public void TestNullRoleArnAndSessionNameReturnSessionNameIsNull( string endpoint) { var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); - Assert.IsTrue(options.RegionId == null); Assert.IsTrue(options.Endpoint == HANG_ZHOUE_PUBLIC_ENDPOINT); - Assert.IsTrue(options.GetDurationSeconds() == 3600); Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); Assert.IsTrue(options.Quiet); Assert.IsNotNull(options.CallbackBody); @@ -129,68 +114,26 @@ public void TestNullRoleArnAndSessionNameReturnSessionNameIsNull( Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); Assert.IsNull(options.RoleArn); Assert.IsNull(options.RoleSessionName); - } - - //[DataTestMethod] - //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Public, HANG_ZHOUE_PUBLIC_ENDPOINT)] - //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Internal, HANG_ZHOUE_INTERNAL_ENDPOINT)] - //public void TestRegionIdAndModeReturnEndpoint( - // string accessKeyId, - // string accessKeySecret, - // string regionId, - // EndpointMode mode, - // string endpoint) - //{ - // var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, mode); - // Assert.IsTrue(options.RegionId == REGION_ID); - // Assert.IsTrue(options.Endpoint == endpoint); - // Assert.IsTrue(options.DurationSeconds == 3600); - // Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); - // Assert.IsTrue(options.Quiet); - // Assert.IsNotNull(options.CallbackBody); - // Assert.IsTrue(options.EnableResumableUpload); - // Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); - // Assert.IsNull(options.RoleArn); - // Assert.IsNull(options.RoleSessionName); - //} - //[DataTestMethod] - //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Public, HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName")] - //[DataRow("AccessKeyId", "AccessKeySecret", REGION_ID, EndpointMode.Internal, HANG_ZHOUE_INTERNAL_ENDPOINT, "RoleArn", "RoleSessionName")] - //public void TestRegionIdAndModeReturnEndpoint( - // string accessKeyId, - // string accessKeySecret, - // string regionId, - // EndpointMode mode, - // string endpoint, - // string roleArn, - // string roleSessionName) - //{ - // var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, mode, roleArn, roleSessionName); - // Assert.IsTrue(options.RegionId == REGION_ID); - // Assert.IsTrue(options.Endpoint == endpoint); - // Assert.IsTrue(options.DurationSeconds == 3600); - // Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); - // Assert.IsTrue(options.Quiet); - // Assert.IsNotNull(options.CallbackBody); - // Assert.IsTrue(options.EnableResumableUpload); - // Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); - // Assert.IsTrue(options.RoleArn == roleArn); - // Assert.IsTrue(options.RoleSessionName == roleSessionName); - //} + } [TestMethod] - public void TestEarlyExpireLessThanZeroReturnThrowArgumentOutOfRangeException() + public void TestAliyunStsOptionsDefaultReturnDurationSecondsIs3600() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); - Assert.ThrowsException(() => options.SetEarlyExpires(-1)); + AliyunStsOptions stsOptions = new AliyunStsOptions(); + Assert.IsTrue(stsOptions.RegionId == null); + Assert.IsTrue(stsOptions.GetEarlyExpires() == 10); + Assert.IsTrue(stsOptions.GetDurationSeconds() == 3600); } [TestMethod] - public void TestEarlyExpireEqual1Return1() + public void TestEarlyExpireLessThanZeroReturnThrowArgumentOutOfRangeException() { var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); - Assert.IsTrue(options.SetEarlyExpires(1).GetEarlyExpires() == 1); + Assert.ThrowsException(() => options.Sts = new AliyunStsOptions() + { + EarlyExpires = -1 + }); } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs index 8d2788b94..f5d2c4bb9 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs @@ -65,7 +65,7 @@ await _client.GetObjectAsync("bucketName", "objectName", stream => { Assert.IsTrue(stream == null); }); - _client.Oss.Verify(oss => oss.GetObject(It.IsAny(), It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.GetObject(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -75,7 +75,7 @@ await _client.GetObjectAsync("bucketName", "objectName", 1, 2, stream => { Assert.IsTrue(stream == null); }); - _client.Oss.Verify(oss => oss.GetObject(It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.GetObject(It.IsAny()), Times.Once); } [TestMethod] @@ -90,7 +90,7 @@ public async Task TestPutObjectAsyncReturnPutObjectVerifytOnce() { string str = "JIm"; await _client.PutObjectAsync("bucketName", "objectName", new MemoryStream(Encoding.Default.GetBytes(str))); - _client.Oss.Verify(oss => oss.PutObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + _client.Oss!.Verify(oss => oss.PutObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -100,7 +100,7 @@ public async Task TestPutObjectAsyncReturnResumableUploadObjectVerifytOnce() _aLiYunStorageOptions.BigObjectContentLength = 2; string str = "JIm"; await _client.PutObjectAsync("bucketName", "objectName", new MemoryStream(Encoding.Default.GetBytes(str))); - _client.Oss.Verify(oss => oss.ResumableUploadObject(It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.ResumableUploadObject(It.IsAny()), Times.Once); } [TestMethod] @@ -119,21 +119,21 @@ public async Task TestObjectExistsAsyncReturnExist() public async Task TestDeleteObjectAsyncReturnVerifytNever() { await _client.DeleteObjectAsync("bucketName", "1.jpg"); - _client.Oss.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Never); + _client.Oss!.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Never); } [TestMethod] public async Task TestDeleteObjectAsyncReturnVerifytOnce() { await _client.DeleteObjectAsync("bucketName", "2.jpg"); - _client.Oss.Verify(oss => oss.DoesObjectExist(It.IsAny(), It.IsAny()), Times.Once); - _client.Oss.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.DoesObjectExist(It.IsAny(), It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] public async Task TestDeleteMultiObjectAsyncReturnVerifytOnce() { await _client.DeleteObjectAsync("bucketName", new[] { "2.jpg", "1.jpg" }); - _client.Oss.Verify(oss => oss.DeleteObjects(It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.DeleteObjects(It.IsAny()), Times.Once); } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs index 04aa3fcbe..d351a2c6e 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs @@ -63,15 +63,6 @@ public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() Assert.IsNotNull(serviceProvider.GetService()); } - [TestMethod] - public void TestAddAliyunStorageByAccessKeyIdAndAccessKeySecretAndRegionIdReturnClientNotNull() - { - var services = new ServiceCollection(); - services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID,HANG_ZHOUE_PUBLIC_ENDPOINT); - var serviceProvider = services.BuildServiceProvider(); - Assert.IsNotNull(serviceProvider.GetService()); - } - [TestMethod] public void TestAddMultiAliyunStorageReturnClientCountIs1() { diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json index 971cabb60..d383cce64 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json @@ -2,8 +2,10 @@ "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "cn-hangzhou", - "DurationSeconds": 900, + "Sts": { + "RegionId": "cn-hangzhou", + "DurationSeconds": 900 + }, "Storage": { "RoleArn": "Replace-With-Your-RoleArn", "RoleSessionName": "Replace-With-Your-RoleSessionName", From 7b31a1ea291417e2a2fe9c9cf715546fb92ba55b Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 26 May 2022 15:20:50 +0800 Subject: [PATCH 11/12] docs: Modifying Aliyun Storage Documents --- .../README.md | 24 ++++++++----------- .../README.zh-CN.md | 22 +++++++---------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index 55f695797..40ccc5d9d 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -50,27 +50,23 @@ builder.Services.AddAliyunStorage(); 1. Add Alibaba Cloud Storage Service ````C# -builder.Services.AddAliyunStorage(new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "regionId", "roleArn", "roleSessionName")); +var configuration = builder.Configuration; +builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) +{ + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]); +}); ```` ### Usage 3: 1. Add Alibaba Cloud Storage Service -````C# -builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration ["Aliyun:RoleSessionName"])); -```` - -> The difference from usage 2 is that the configuration can take effect without restarting the project after the configuration update - -### Usage 4: - -1. Add Alibaba Cloud Storage Service - ````C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public, options => +builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) { - options.CallbackUrl = "Replace-With-Your-CallbackUrl"; + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]) }); -```` \ No newline at end of file +```` + +> The difference from usage 2 is that the configuration can take effect without restarting the project after the configuration update \ No newline at end of file diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index 316d8a4f8..6bbe36935 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -55,7 +55,10 @@ builder.Services.AddAliyunStorage(); ```C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); +builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) +{ + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]); +}); ``` ### 用法3: @@ -64,20 +67,11 @@ builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun ```C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); -``` - -> 与用法2的区别在于配置更新后无需重启项目即可生效 - -### 用法4: - -1. 添加阿里云存储服务 - -```C# -var configuration = builder.Configuration; -builder.Services.AddAliyunStorage("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_REGIONID, Options.Enum.EndpointMode.Public, options => +builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) { - options.CallbackUrl = "Replace-With-Your-CallbackUrl"; + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]) }); ``` +> 与用法2的区别在于配置更新后无需重启项目即可生效 + From 8b19bc30360e867c8a34b1ed11c410dfd071ef93 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 26 May 2022 15:25:42 +0800 Subject: [PATCH 12/12] docs: Modify the Isolation document --- .../README.md | 26 +++++++++---------- .../README.zh-CN.md | 26 +++++++++---------- .../README.md | 26 +++++++++---------- .../README.zh-CN.md | 26 +++++++++---------- .../Masa.Contrib.Isolation.UoW.EF/README.md | 2 +- .../README.zh-CN.md | 2 +- 6 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md index 52063126d..360d329b6 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiEnvironment Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. Configure `appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "Environment": "development", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "Environment": "staging", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "Environment": "staging", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` * 1.1 When the current environment is development: database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md index 0486c0a4a..15bbfa5af 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiEnvironment Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. 配置`appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "Environment": "development", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "Environment": "staging", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "Environment": "staging", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index 6f94cac43..a53225f29 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiTenant Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. Configure `appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "TenantId": "00000000-0000-0000-0000-000000000002", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "TenantId": "00000000-0000-0000-0000-000000000003", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "TenantId": "00000000-0000-0000-0000-000000000003", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md index 1fe9c2b03..adbcf935b 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiTenant Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. 配置`appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "TenantId": "00000000-0000-0000-0000-000000000002", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "TenantId": "00000000-0000-0000-0000-000000000003", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "TenantId": "00000000-0000-0000-0000-000000000003", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md index b00debb19..5670d5b91 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -12,7 +12,7 @@ Install-Package Masa.Contrib.Isolation.MultiTenant // Multi-tenant isolation On- Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. Configure `appsettings.json` ``` appsettings.json { "ConnectionStrings": { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md index d6e79f64b..46c47cfd3 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -12,7 +12,7 @@ Install-Package Masa.Contrib.Isolation.MultiTenant // 多租户隔离 按需引 Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. 配置`appsettings.json` ``` appsettings.json { "ConnectionStrings": {