49 changed files with 2191 additions and 42 deletions
@ -0,0 +1,137 @@ |
|||||
|
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\MicroService\CRM\Vue\abp-next-admin\aspnet-core codebase based on best match to current usage at 2021-12-27 |
||||
|
# You can modify the rules from these initially generated values to suit your own policies |
||||
|
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference |
||||
|
[*.cs] |
||||
|
|
||||
|
|
||||
|
#Core editorconfig formatting - indentation |
||||
|
|
||||
|
#use soft tabs (spaces) for indentation |
||||
|
indent_style = space |
||||
|
|
||||
|
#Formatting - indentation options |
||||
|
|
||||
|
#indent switch case contents. |
||||
|
csharp_indent_case_contents = true |
||||
|
#indent switch labels |
||||
|
csharp_indent_switch_labels = true |
||||
|
|
||||
|
#Formatting - new line options |
||||
|
|
||||
|
#place catch statements on a new line |
||||
|
csharp_new_line_before_catch = true |
||||
|
#place else statements on a new line |
||||
|
csharp_new_line_before_else = true |
||||
|
#require members of anonymous types to be on separate lines |
||||
|
csharp_new_line_before_members_in_anonymous_types = true |
||||
|
#require members of object initializers to be on the same line |
||||
|
csharp_new_line_before_members_in_object_initializers = false |
||||
|
#require braces to be on a new line for anonymous_types, control_blocks, types, lambdas, object_collection_array_initializers, methods, and anonymous_methods (also known as "Allman" style) |
||||
|
csharp_new_line_before_open_brace = anonymous_types, control_blocks, types, lambdas, object_collection_array_initializers, methods, anonymous_methods |
||||
|
|
||||
|
#Formatting - organize using options |
||||
|
|
||||
|
#do not place System.* using directives before other using directives |
||||
|
dotnet_sort_system_directives_first = false |
||||
|
|
||||
|
#Formatting - spacing options |
||||
|
|
||||
|
#require NO space between a cast and the value |
||||
|
csharp_space_after_cast = false |
||||
|
#require a space before the colon for bases or interfaces in a type declaration |
||||
|
csharp_space_after_colon_in_inheritance_clause = true |
||||
|
#require a space after a keyword in a control flow statement such as a for loop |
||||
|
csharp_space_after_keywords_in_control_flow_statements = true |
||||
|
#require a space before the colon for bases or interfaces in a type declaration |
||||
|
csharp_space_before_colon_in_inheritance_clause = true |
||||
|
#remove space within empty argument list parentheses |
||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false |
||||
|
#remove space between method call name and opening parenthesis |
||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false |
||||
|
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call |
||||
|
csharp_space_between_method_call_parameter_list_parentheses = false |
||||
|
#remove space within empty parameter list parentheses for a method declaration |
||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false |
||||
|
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. |
||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false |
||||
|
|
||||
|
#Formatting - wrapping options |
||||
|
|
||||
|
#leave code block on single line |
||||
|
csharp_preserve_single_line_blocks = true |
||||
|
#leave statements and member declarations on the same line |
||||
|
csharp_preserve_single_line_statements = true |
||||
|
|
||||
|
#Style - Code block preferences |
||||
|
|
||||
|
#prefer curly braces even for one line of code |
||||
|
csharp_prefer_braces = true:suggestion |
||||
|
|
||||
|
#Style - expression bodied member options |
||||
|
|
||||
|
#prefer block bodies for constructors |
||||
|
csharp_style_expression_bodied_constructors = false:suggestion |
||||
|
#prefer block bodies for methods |
||||
|
csharp_style_expression_bodied_methods = false:suggestion |
||||
|
#prefer expression-bodied members for properties |
||||
|
csharp_style_expression_bodied_properties = true:suggestion |
||||
|
|
||||
|
#Style - expression level options |
||||
|
|
||||
|
#prefer out variables to be declared inline in the argument list of a method call when possible |
||||
|
csharp_style_inlined_variable_declaration = true:suggestion |
||||
|
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them |
||||
|
dotnet_style_predefined_type_for_member_access = true:suggestion |
||||
|
|
||||
|
#Style - Expression-level preferences |
||||
|
|
||||
|
#prefer default over default(T) |
||||
|
csharp_prefer_simple_default_expression = true:suggestion |
||||
|
#prefer objects to be initialized using object initializers when possible |
||||
|
dotnet_style_object_initializer = true:suggestion |
||||
|
#prefer inferred anonymous type member names |
||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion |
||||
|
|
||||
|
#Style - implicit and explicit types |
||||
|
|
||||
|
#prefer var over explicit type in all cases, unless overridden by another code style rule |
||||
|
csharp_style_var_elsewhere = true:suggestion |
||||
|
#prefer var is used to declare variables with built-in system types such as int |
||||
|
csharp_style_var_for_built_in_types = true:suggestion |
||||
|
#prefer var when the type is already mentioned on the right-hand side of a declaration expression |
||||
|
csharp_style_var_when_type_is_apparent = true:suggestion |
||||
|
|
||||
|
#Style - language keyword and framework type options |
||||
|
|
||||
|
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them |
||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion |
||||
|
|
||||
|
#Style - Miscellaneous preferences |
||||
|
|
||||
|
#prefer anonymous functions over local functions |
||||
|
csharp_style_pattern_local_over_anonymous_function = false:suggestion |
||||
|
|
||||
|
#Style - modifier options |
||||
|
|
||||
|
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. |
||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion |
||||
|
|
||||
|
#Style - Modifier preferences |
||||
|
|
||||
|
#when this rule is set to a list of modifiers, prefer the specified ordering. |
||||
|
csharp_preferred_modifier_order = public,private,protected,internal,async,virtual,readonly,static,override,abstract:suggestion |
||||
|
|
||||
|
#Style - Pattern matching |
||||
|
|
||||
|
#prefer pattern matching instead of is expression with type casts |
||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion |
||||
|
|
||||
|
#Style - qualification options |
||||
|
|
||||
|
#prefer fields not to be prefaced with this. or Me. in Visual Basic |
||||
|
dotnet_style_qualification_for_field = false:suggestion |
||||
|
#prefer methods not to be prefaced with this. or Me. in Visual Basic |
||||
|
dotnet_style_qualification_for_method = false:suggestion |
||||
|
#prefer properties not to be prefaced with this. or Me. in Visual Basic |
||||
|
dotnet_style_qualification_for_property = false:suggestion |
||||
|
csharp_style_namespace_declarations=file_scoped:silent |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<Description>腾讯云Oss对象存储Abp集成</Description> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.BlobStoring" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.*" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Tencent\LINGYUN.Abp.Tencent.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using LINGYUN.Abp.Tencent; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringModule), |
||||
|
typeof(AbpTencentCloudModule))] |
||||
|
public class AbpBlobStoringTencentCloudModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using COSXML; |
||||
|
using COSXML.Auth; |
||||
|
using LINGYUN.Abp.Tencent; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
|
||||
|
public class CosClientFactory : AbstractTencentCloudClientFactory<CosXml, TencentBlobProviderConfiguration>, |
||||
|
ICosClientFactory, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } |
||||
|
|
||||
|
public CosClientFactory( |
||||
|
IMemoryCache clientCache, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ISettingProvider settingProvider, |
||||
|
IBlobContainerConfigurationProvider configurationProvider) |
||||
|
: base(clientCache, currentTenant, settingProvider) |
||||
|
{ |
||||
|
ConfigurationProvider = configurationProvider; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<CosXml> CreateAsync<TContainer>() |
||||
|
{ |
||||
|
var configuration = ConfigurationProvider.Get<TContainer>(); |
||||
|
|
||||
|
return await CreateAsync(configuration.GetTencentConfiguration()); |
||||
|
} |
||||
|
|
||||
|
protected override CosXml CreateClient(TencentBlobProviderConfiguration configuration, TencentCloudClientCacheItem cloudCache) |
||||
|
{ |
||||
|
var configBuilder = new CosXmlConfig.Builder(); |
||||
|
configBuilder |
||||
|
.SetAppid(configuration.AppId) |
||||
|
.SetRegion(configuration.Region); |
||||
|
|
||||
|
var cred = new DefaultQCloudCredentialProvider( |
||||
|
cloudCache.SecretId, |
||||
|
cloudCache.SecretKey, |
||||
|
cloudCache.DurationSecond); |
||||
|
|
||||
|
// TODO: 推荐全局单个对象,需要解决缓存过期事件
|
||||
|
return new CosXmlServer(configBuilder.Build(), cred); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent |
||||
|
{ |
||||
|
public class DefaultTencentBlobNameCalculator : ITencentBlobNameCalculator, ITransientDependency |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
|
||||
|
public DefaultTencentBlobNameCalculator( |
||||
|
ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
public string Calculate(BlobProviderArgs args) |
||||
|
{ |
||||
|
return CurrentTenant.Id == null |
||||
|
? $"host/{args.BlobName}" |
||||
|
: $"tenants/{CurrentTenant.Id.Value:D}/{args.BlobName}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using COSXML; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
|
||||
|
public interface ICosClientFactory |
||||
|
{ |
||||
|
Task<CosXml> CreateAsync<TContainer>(); |
||||
|
|
||||
|
Task<CosXml> CreateAsync(TencentBlobProviderConfiguration configuration); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent |
||||
|
{ |
||||
|
public interface ITencentBlobNameCalculator |
||||
|
{ |
||||
|
string Calculate(BlobProviderArgs args); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent |
||||
|
{ |
||||
|
public static class TencentBlobContainerConfigurationExtensions |
||||
|
{ |
||||
|
public static TencentBlobProviderConfiguration GetTencentConfiguration( |
||||
|
this BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
return new TencentBlobProviderConfiguration(containerConfiguration); |
||||
|
} |
||||
|
|
||||
|
public static BlobContainerConfiguration UseTencentCloud( |
||||
|
this BlobContainerConfiguration containerConfiguration, |
||||
|
Action<TencentBlobProviderConfiguration> aliyunConfigureAction) |
||||
|
{ |
||||
|
containerConfiguration.ProviderType = typeof(TencentCloudBlobProvider); |
||||
|
|
||||
|
aliyunConfigureAction(new TencentBlobProviderConfiguration(containerConfiguration)); |
||||
|
|
||||
|
return containerConfiguration; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using System.Text.RegularExpressions; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent |
||||
|
{ |
||||
|
public class TencentBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 腾讯云对象命名规范
|
||||
|
/// https://cloud.tencent.com/document/product/436/13324
|
||||
|
/// </summary>
|
||||
|
/// <param name="blobName"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public virtual string NormalizeBlobName(string blobName) |
||||
|
{ |
||||
|
// 不允许以正斜线/或者反斜线\开头。
|
||||
|
blobName = Regex.Replace(blobName, "^/", string.Empty); |
||||
|
blobName = blobName.StartsWith("\\") ? blobName.Substring(1) : blobName; |
||||
|
|
||||
|
// 对象键中不支持 ASCII 控制字符中的
|
||||
|
// 字符上(↑),字符下(↓),字符右(→),字符左(←),
|
||||
|
// 分别对应 CAN(24),EM(25),SUB(26),ESC(27)。
|
||||
|
blobName = blobName.Replace("↑", ""); |
||||
|
blobName = blobName.Replace("↓", ""); |
||||
|
blobName = blobName.Replace("←", ""); |
||||
|
blobName = blobName.Replace("→", ""); |
||||
|
|
||||
|
// TODO: 要求还真多...其他暂时不写了
|
||||
|
|
||||
|
return blobName; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 腾讯云BucketName命名规范
|
||||
|
/// https://cloud.tencent.com/document/product/436/13312
|
||||
|
/// </summary>
|
||||
|
/// <param name="containerName"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public virtual string NormalizeContainerName(string containerName) |
||||
|
{ |
||||
|
// 仅支持小写英文字母和数字,即[a-z,0-9]、中划线“-”及其组合。
|
||||
|
containerName = containerName.ToLower(); |
||||
|
containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); |
||||
|
|
||||
|
// 不能以短划线(-)开头
|
||||
|
containerName = Regex.Replace(containerName, "^-", string.Empty); |
||||
|
// 不能以短划线(-)结尾
|
||||
|
containerName = Regex.Replace(containerName, "-$", string.Empty); |
||||
|
|
||||
|
// 存储桶名称的最大允许字符受到 地域简称 和 APPID 的字符数影响,组成的完整请求域名字符数总计最多60个字符。
|
||||
|
// 例如请求域名123456789012345678901-1250000000.cos.ap-beijing.myqcloud.com总和为60个字符。
|
||||
|
if (containerName.Length > 60) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, 60); |
||||
|
} |
||||
|
|
||||
|
return containerName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent |
||||
|
{ |
||||
|
public class TencentBlobProviderConfiguration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// AppId
|
||||
|
/// </summary>
|
||||
|
public string AppId { |
||||
|
get => _containerConfiguration.GetConfiguration<string>(TencentBlobProviderConfigurationNames.AppId); |
||||
|
set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.AppId, Check.NotNullOrWhiteSpace(value, nameof(value))); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 区域
|
||||
|
/// </summary>
|
||||
|
public string Region { |
||||
|
get => _containerConfiguration.GetConfiguration<string>(TencentBlobProviderConfigurationNames.Region); |
||||
|
set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.Region, value); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 命名空间
|
||||
|
/// </summary>
|
||||
|
public string BucketName |
||||
|
{ |
||||
|
get => _containerConfiguration.GetConfiguration<string>(TencentBlobProviderConfigurationNames.BucketName); |
||||
|
set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.BucketName, Check.NotNullOrWhiteSpace(value, nameof(value))); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 命名空间不存在是否创建
|
||||
|
/// </summary>
|
||||
|
public bool CreateBucketIfNotExists |
||||
|
{ |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(TencentBlobProviderConfigurationNames.CreateBucketIfNotExists, false); |
||||
|
set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.CreateBucketIfNotExists, value); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 创建命名空间时防盗链列表
|
||||
|
/// </summary>
|
||||
|
public List<string> CreateBucketReferer { |
||||
|
get => _containerConfiguration.GetConfiguration<List<string>>(TencentBlobProviderConfigurationNames.CreateBucketReferer); |
||||
|
set { |
||||
|
if (value == null) |
||||
|
{ |
||||
|
_containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.CreateBucketReferer, new List<string>()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.CreateBucketReferer, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly BlobContainerConfiguration _containerConfiguration; |
||||
|
|
||||
|
public TencentBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
_containerConfiguration = containerConfiguration; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent |
||||
|
{ |
||||
|
public static class TencentBlobProviderConfigurationNames |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// AppId
|
||||
|
/// </summary>
|
||||
|
public const string AppId = "Tencent:OSS:AppId"; |
||||
|
/// <summary>
|
||||
|
/// 区域
|
||||
|
/// </summary>
|
||||
|
public const string Region = "Tencent:OSS:Region"; |
||||
|
/// <summary>
|
||||
|
/// 命名空间
|
||||
|
/// </summary>
|
||||
|
public const string BucketName = "Tencent:OSS:BucketName"; |
||||
|
/// <summary>
|
||||
|
/// 命名空间不存在是否创建
|
||||
|
/// </summary>
|
||||
|
public const string CreateBucketIfNotExists = "Tencent:OSS:CreateBucketIfNotExists"; |
||||
|
/// <summary>
|
||||
|
/// 创建命名空间时防盗链列表
|
||||
|
/// </summary>
|
||||
|
public const string CreateBucketReferer = "Tencent:OSS:CreateBucketReferer"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,165 @@ |
|||||
|
using COSXML; |
||||
|
using COSXML.Common; |
||||
|
using COSXML.Model.Bucket; |
||||
|
using COSXML.Model.Object; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
|
||||
|
public class TencentCloudBlobProvider : BlobProviderBase, ITransientDependency |
||||
|
{ |
||||
|
protected ICosClientFactory CosClientFactory { get; } |
||||
|
protected ITencentBlobNameCalculator TencentBlobNameCalculator { get; } |
||||
|
|
||||
|
public TencentCloudBlobProvider( |
||||
|
ICosClientFactory cosClientFactory, |
||||
|
ITencentBlobNameCalculator tencentBlobNameCalculator) |
||||
|
{ |
||||
|
CosClientFactory = cosClientFactory; |
||||
|
TencentBlobNameCalculator = tencentBlobNameCalculator; |
||||
|
} |
||||
|
|
||||
|
public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args) |
||||
|
{ |
||||
|
var ossClient = await GetOssClientAsync(args); |
||||
|
var blobName = TencentBlobNameCalculator.Calculate(args); |
||||
|
|
||||
|
if (await BlobExistsAsync(ossClient, args, blobName)) |
||||
|
{ |
||||
|
var request = new DeleteObjectRequest(GetBucketName(args), blobName); |
||||
|
return ossClient.DeleteObject(request).IsSuccessful(); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args) |
||||
|
{ |
||||
|
var ossClient = await GetOssClientAsync(args); |
||||
|
var blobName = TencentBlobNameCalculator.Calculate(args); |
||||
|
|
||||
|
return await BlobExistsAsync(ossClient, args, blobName); |
||||
|
} |
||||
|
|
||||
|
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args) |
||||
|
{ |
||||
|
var ossClient = await GetOssClientAsync(args); |
||||
|
var blobName = TencentBlobNameCalculator.Calculate(args); |
||||
|
|
||||
|
if (!await BlobExistsAsync(ossClient, args, blobName)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// TODO: 未经验证
|
||||
|
|
||||
|
var request = new GetObjectBytesRequest(GetBucketName(args), blobName); |
||||
|
var ossObject = ossClient.GetObject(request); |
||||
|
var memoryStream = new MemoryStream(); |
||||
|
await memoryStream.WriteAsync(ossObject.content, 0, ossObject.content.Length); |
||||
|
memoryStream.Seek(0, SeekOrigin.Begin); |
||||
|
return memoryStream; |
||||
|
} |
||||
|
|
||||
|
public override async Task SaveAsync(BlobProviderSaveArgs args) |
||||
|
{ |
||||
|
var ossClient = await GetOssClientAsync(args); |
||||
|
var blobName = TencentBlobNameCalculator.Calculate(args); |
||||
|
var configuration = args.Configuration.GetTencentConfiguration(); |
||||
|
|
||||
|
// 先检查Bucket
|
||||
|
if (configuration.CreateBucketIfNotExists) |
||||
|
{ |
||||
|
await CreateBucketIfNotExists(ossClient, args, configuration.CreateBucketReferer); |
||||
|
} |
||||
|
|
||||
|
var bucketName = GetBucketName(args); |
||||
|
|
||||
|
// 是否已存在
|
||||
|
if (await BlobExistsAsync(ossClient, args, blobName)) |
||||
|
{ |
||||
|
// 是否覆盖
|
||||
|
if (!args.OverrideExisting) |
||||
|
{ |
||||
|
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the bucketName '{GetBucketName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 删除原文件
|
||||
|
var deleteRequest = new DeleteObjectRequest(bucketName, blobName); |
||||
|
ossClient.DeleteObject(deleteRequest); |
||||
|
} |
||||
|
} |
||||
|
// 保存
|
||||
|
var putRequest = new PutObjectRequest(bucketName, blobName, args.BlobStream); |
||||
|
ossClient.PutObject(putRequest); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<CosXml> GetOssClientAsync(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetTencentConfiguration(); |
||||
|
var ossClient = await CosClientFactory.CreateAsync(configuration); |
||||
|
return ossClient; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task CreateBucketIfNotExists(CosXml cos, BlobProviderArgs args, IList<string> refererList = null) |
||||
|
{ |
||||
|
if (!await BucketExistsAsync(cos, args)) |
||||
|
{ |
||||
|
var bucketName = GetBucketName(args); |
||||
|
|
||||
|
var request = new PutBucketRequest(bucketName); |
||||
|
// TODO: good! 这很Java
|
||||
|
request.SetCosACL(CosACL.PublicReadWrite); |
||||
|
|
||||
|
cos.PutBucket(request); |
||||
|
|
||||
|
if (refererList != null && refererList.Count > 0) |
||||
|
{ |
||||
|
var srq = new PutBucketRefererRequest(bucketName); |
||||
|
var refererConfig = new COSXML.Model.Tag.RefererConfiguration(); |
||||
|
foreach (var domain in refererList) |
||||
|
{ |
||||
|
refererConfig.domainList.AddDomain(domain); |
||||
|
} |
||||
|
srq.SetRefererConfiguration(refererConfig); |
||||
|
|
||||
|
cos.PutBucketReferer(srq); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<bool> BlobExistsAsync(CosXml cos, BlobProviderArgs args, string blobName) |
||||
|
{ |
||||
|
var bucketExists = await BucketExistsAsync(cos, args); |
||||
|
if (bucketExists) |
||||
|
{ |
||||
|
var request = new DoesObjectExistRequest(GetBucketName(args), blobName); |
||||
|
var objectExists = cos.DoesObjectExist(request); |
||||
|
|
||||
|
return objectExists; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private Task<bool> BucketExistsAsync(CosXml cos, BlobProviderArgs args) |
||||
|
{ |
||||
|
var request = new DoesBucketExistRequest(GetBucketName(args)); |
||||
|
var bucketExists = cos.DoesBucketExist(request); |
||||
|
|
||||
|
return Task.FromResult(bucketExists); |
||||
|
} |
||||
|
|
||||
|
private static string GetBucketName(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetTencentConfiguration(); |
||||
|
return configuration.BucketName.IsNullOrWhiteSpace() |
||||
|
? args.ContainerName |
||||
|
: configuration.BucketName; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\Sms\Tencent\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Sms\Tencent\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Sms" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Tencent\LINGYUN.Abp.Tencent.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,30 @@ |
|||||
|
using LINGYUN.Abp.Tencent; |
||||
|
using LINGYUN.Abp.Tencent.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Sms; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Sms.Tencent |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpSmsModule), |
||||
|
typeof(AbpTencentCloudModule))] |
||||
|
public class AbpSmsTencentModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpSmsTencentModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<TencentCloudResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/Sms/Tencent/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"DisplayName:TenantCloud.SmsSetting": "Sms Setting", |
||||
|
"DisplayName:AppId": "App Id", |
||||
|
"Description:AppId": "AppId applied on Tencent Cloud SMS control platform.", |
||||
|
"DisplayName:DefaultSignName": "Default Sign Name", |
||||
|
"Description:DefaultSignName": "Default signname if no signname is specified in the SMS message body", |
||||
|
"DisplayName:DefaultTemplateId": "Default Template Id", |
||||
|
"Description:DefaultTemplateId": "Default template id if the Template Id number is not specified in the SMS message body." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"DisplayName:TenantCloud.SmsSetting": "短信设置", |
||||
|
"DisplayName:AppId": "应用Id", |
||||
|
"Description:AppId": "在腾讯云短信控制平台申请的应用标识", |
||||
|
"DisplayName:DefaultSignName": "默认签名", |
||||
|
"Description:DefaultSignName": "当短信消息体未指定签名时的默认签名", |
||||
|
"DisplayName:DefaultTemplateId": "默认模板", |
||||
|
"Description:DefaultTemplateId": "当短信消息体未指定模板号时的默认模板标识" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using LINGYUN.Abp.Tencent.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Sms.Tencent.Settings |
||||
|
{ |
||||
|
public static class TencentCloudSmsSettingNames |
||||
|
{ |
||||
|
public const string Prefix = TencentCloudSettingNames.Prefix + ".Sms"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 短信 SdkAppId
|
||||
|
/// 在 短信控制台 添加应用后生成的实际 SdkAppId,示例如1400006666。
|
||||
|
/// </summary>
|
||||
|
public const string AppId = Prefix + ".Domain"; |
||||
|
/// <summary>
|
||||
|
/// 短信签名内容
|
||||
|
/// </summary>
|
||||
|
public const string DefaultSignName = Prefix + ".DefaultSignName"; |
||||
|
/// <summary>
|
||||
|
/// 默认短信模板 ID
|
||||
|
/// </summary>
|
||||
|
public const string DefaultTemplateId = Prefix + ".DefaultTemplateId"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using LINGYUN.Abp.Tencent.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Sms.Tencent.Settings |
||||
|
{ |
||||
|
public class TencentCloudSmsSettingProvider : SettingDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(ISettingDefinitionContext context) |
||||
|
{ |
||||
|
context.Add(CreateAliyunSettings()); |
||||
|
} |
||||
|
|
||||
|
private SettingDefinition[] CreateAliyunSettings() |
||||
|
{ |
||||
|
return new SettingDefinition[] |
||||
|
{ |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSmsSettingNames.AppId, |
||||
|
displayName: L("DisplayName:AppId"), |
||||
|
description: L("Description:AppId"), |
||||
|
isVisibleToClients: false, |
||||
|
isEncrypted: true |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSmsSettingNames.DefaultSignName, |
||||
|
displayName: L("DisplayName:DefaultSignName"), |
||||
|
description: L("Description:DefaultSignName"), |
||||
|
isVisibleToClients: false, |
||||
|
isEncrypted: true |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSmsSettingNames.DefaultTemplateId, |
||||
|
displayName: L("DisplayName:DefaultTemplateId"), |
||||
|
description: L("Description:DefaultTemplateId"), |
||||
|
isVisibleToClients: false, |
||||
|
isEncrypted: true |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private ILocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<TencentCloudResource>(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
using LINGYUN.Abp.Sms.Tencent.Settings; |
||||
|
using LINGYUN.Abp.Tencent; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using TencentCloud.Sms.V20210111; |
||||
|
using TencentCloud.Sms.V20210111.Models; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Settings; |
||||
|
using Volo.Abp.Sms; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Sms.Tencent |
||||
|
{ |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class TencentCloudSmsSender : ISmsSender, ITransientDependency |
||||
|
{ |
||||
|
public ILogger<TencentCloudSmsSender> Logger { protected get; set; } |
||||
|
|
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected ISettingProvider SettingProvider { get; } |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
protected TencentCloudClientFactory<SmsClient> TencentCloudClientFactory { get; } |
||||
|
public TencentCloudSmsSender( |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
ISettingProvider settingProvider, |
||||
|
IServiceProvider serviceProvider, |
||||
|
TencentCloudClientFactory<SmsClient> tencentCloudClientFactory) |
||||
|
{ |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
SettingProvider = settingProvider; |
||||
|
ServiceProvider = serviceProvider; |
||||
|
TencentCloudClientFactory = tencentCloudClientFactory; |
||||
|
|
||||
|
Logger = NullLogger<TencentCloudSmsSender>.Instance; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SendAsync(SmsMessage smsMessage) |
||||
|
{ |
||||
|
var appId = await SettingProvider.GetOrNullAsync(TencentCloudSmsSettingNames.AppId); |
||||
|
|
||||
|
Check.NotNullOrWhiteSpace(appId, TencentCloudSmsSettingNames.AppId); |
||||
|
|
||||
|
// 统一使用 TemplateCode作为模板参数, 解决不一样的sms提供商参数差异
|
||||
|
if (!smsMessage.Properties.TryGetValue("TemplateCode", out var templateId)) |
||||
|
{ |
||||
|
templateId = await SettingProvider.GetOrNullAsync(TencentCloudSmsSettingNames.DefaultTemplateId); |
||||
|
} |
||||
|
|
||||
|
if (!smsMessage.Properties.TryGetValue("SignName", out var signName)) |
||||
|
{ |
||||
|
signName = await SettingProvider.GetOrNullAsync(TencentCloudSmsSettingNames.DefaultSignName); |
||||
|
} |
||||
|
|
||||
|
if (!smsMessage.Properties.TryGetValue("TemplateParam", out var templateParam)) |
||||
|
{ |
||||
|
templateParam = await SettingProvider.GetOrNullAsync(TencentCloudSmsSettingNames.DefaultSignName); |
||||
|
} |
||||
|
|
||||
|
var smsClient = await TencentCloudClientFactory.CreateAsync(); |
||||
|
|
||||
|
var request = new SendSmsRequest |
||||
|
{ |
||||
|
SmsSdkAppId = appId, |
||||
|
SignName = signName?.ToString(), |
||||
|
TemplateId = templateId?.ToString(), |
||||
|
PhoneNumberSet = smsMessage.PhoneNumber.Split(';'), |
||||
|
TemplateParamSet = templateParam?.ToString()?.Split(';'), |
||||
|
}; |
||||
|
|
||||
|
var response = await smsClient.SendSms(request); |
||||
|
|
||||
|
var warningMessage = response.SendStatusSet |
||||
|
.Where(x => !"ok".Equals(x.Code, StringComparison.InvariantCultureIgnoreCase)) |
||||
|
// 记录流水号, 手机号, 错误代码, 错误信息
|
||||
|
.Select(x => $"SerialNo: {x.SerialNo}, PhoneNumber: {x.PhoneNumber}, Status: {x.Code}, Error: {x.Message}"); |
||||
|
// 所有短信发送失败, 抛出错误
|
||||
|
// 只要一条发送成功,即视为成功
|
||||
|
if (!response.SendStatusSet.Any(x => "ok".Equals(x.Code, StringComparison.InvariantCultureIgnoreCase))) |
||||
|
{ |
||||
|
var errorMessage = warningMessage.JoinAsString(Environment.NewLine); |
||||
|
|
||||
|
throw new AbpException($"Send tencent cloud sms error: {errorMessage}"); |
||||
|
} |
||||
|
|
||||
|
if (warningMessage.Any()) |
||||
|
{ |
||||
|
Logger.LogWarning("Some failed send sms messages, error info:"); |
||||
|
Logger.LogWarning(warningMessage.JoinAsString(Environment.NewLine)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using LINGYUN.Abp.Sms.Tencent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Sms |
||||
|
{ |
||||
|
public static class TencentSmsSenderExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 扩展短信接口
|
||||
|
/// </summary>
|
||||
|
/// <param name="smsSender"></param>
|
||||
|
/// <param name="templateCode">短信模板号</param>
|
||||
|
/// <param name="phoneNumber">发送手机号</param>
|
||||
|
/// <param name="templateParams">短信模板参数</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static async Task SendAsync( |
||||
|
this ISmsSender smsSender, |
||||
|
string templateCode, |
||||
|
string phoneNumber, |
||||
|
IDictionary<string, object> templateParams = null) |
||||
|
{ |
||||
|
var smsMessage = new SmsMessage(phoneNumber, nameof(TencentCloudSmsSender)); |
||||
|
smsMessage.Properties.Add("TemplateCode", templateCode); |
||||
|
if(templateParams != null) |
||||
|
{ |
||||
|
smsMessage.Properties.Add("TemplateParam", templateParams); |
||||
|
} |
||||
|
await smsSender.SendAsync(smsMessage); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 扩展短信接口
|
||||
|
/// </summary>
|
||||
|
/// <param name="smsSender"></param>
|
||||
|
/// <param name="signName">短信签名</param>
|
||||
|
/// <param name="templateCode">短信模板号</param>
|
||||
|
/// <param name="phoneNumber">发送手机号</param>
|
||||
|
/// <param name="templateParams">短信模板参数</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static async Task SendAsync( |
||||
|
this ISmsSender smsSender, |
||||
|
string signName, |
||||
|
string templateCode, |
||||
|
string phoneNumber, |
||||
|
IDictionary<string, object> templateParams = null) |
||||
|
{ |
||||
|
var smsMessage = new SmsMessage(phoneNumber, nameof(TencentCloudSmsSender)); |
||||
|
smsMessage.Properties.Add("SignName", signName); |
||||
|
smsMessage.Properties.Add("TemplateCode", templateCode); |
||||
|
if (templateParams != null) |
||||
|
{ |
||||
|
smsMessage.Properties.Add("TemplateParam", templateParams); |
||||
|
} |
||||
|
await smsSender.SendAsync(smsMessage); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Aliyun\SettingManagement\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.SettingManagement.Domain" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\settings\LINGYUN.Abp.SettingManagement.Application.Contracts\LINGYUN.Abp.SettingManagement.Application.Contracts.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Sms.Tencent\LINGYUN.Abp.Sms.Tencent.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Tencent\LINGYUN.Abp.Tencent.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,16 @@ |
|||||
|
using LINGYUN.Abp.SettingManagement; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.SettingManagement; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent.SettingManagement; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpTencentCloudModule), |
||||
|
typeof(AbpSettingManagementApplicationContractsModule), |
||||
|
typeof(AbpSettingManagementDomainModule), |
||||
|
typeof(AbpAspNetCoreMvcModule))] |
||||
|
public class AbpTencentCloudSettingManagementModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
using LINGYUN.Abp.SettingManagement; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent.SettingManagement; |
||||
|
|
||||
|
public interface ITenantCloudSettingAppService : IReadonlySettingAppService |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,168 @@ |
|||||
|
using LINGYUN.Abp.SettingManagement; |
||||
|
using LINGYUN.Abp.Sms.Tencent.Settings; |
||||
|
using LINGYUN.Abp.Tencent.Localization; |
||||
|
using LINGYUN.Abp.Tencent.Settings; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.SettingManagement; |
||||
|
using Volo.Abp.Settings; |
||||
|
using ValueType = LINGYUN.Abp.SettingManagement.ValueType; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent.SettingManagement; |
||||
|
|
||||
|
public class TenantCloudSettingAppService : ApplicationService, ITenantCloudSettingAppService |
||||
|
{ |
||||
|
protected ISettingManager SettingManager { get; } |
||||
|
protected IPermissionChecker PermissionChecker { get; } |
||||
|
protected ISettingDefinitionManager SettingDefinitionManager { get; } |
||||
|
protected ITencentCloudClientFactory TencentCloudClientFactory { get; } |
||||
|
|
||||
|
public TenantCloudSettingAppService( |
||||
|
ISettingManager settingManager, |
||||
|
IPermissionChecker permissionChecker, |
||||
|
ISettingDefinitionManager settingDefinitionManager, |
||||
|
ITencentCloudClientFactory tencentCloudClientFactory) |
||||
|
{ |
||||
|
SettingManager = settingManager; |
||||
|
PermissionChecker = permissionChecker; |
||||
|
SettingDefinitionManager = settingDefinitionManager; |
||||
|
TencentCloudClientFactory = tencentCloudClientFactory; |
||||
|
|
||||
|
LocalizationResource = typeof(TencentCloudResource); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<SettingGroupResult> GetAllForCurrentTenantAsync() |
||||
|
{ |
||||
|
return await GetAllForProviderAsync(TenantSettingValueProvider.ProviderName, CurrentTenant.GetId().ToString()); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<SettingGroupResult> GetAllForGlobalAsync() |
||||
|
{ |
||||
|
return await GetAllForProviderAsync(GlobalSettingValueProvider.ProviderName, null); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<SettingGroupResult> GetAllForProviderAsync(string providerName, string providerKey) |
||||
|
{ |
||||
|
var settingGroups = new SettingGroupResult(); |
||||
|
|
||||
|
// 无权限返回空结果,直接报错的话,网关聚合会抛出异常
|
||||
|
if (await PermissionChecker.IsGrantedAsync(TenantCloudSettingPermissionNames.Settings)) |
||||
|
{ |
||||
|
var settingGroup = new SettingGroupDto(L["DisplayName:TenantCloud"], L["DisplayName:TenantCloud"]); |
||||
|
|
||||
|
#region 基本设置
|
||||
|
|
||||
|
var basicSetting = settingGroup.AddSetting(L["DisplayName:TenantCloud.BasicSetting"], L["Description:TenantCloud.BasicSetting"]); |
||||
|
|
||||
|
basicSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSettingNames.EndPoint), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSettingNames.EndPoint, providerName, providerKey), |
||||
|
ValueType.Option, |
||||
|
providerName) |
||||
|
.AddOptions(GetAvailableRegionOptions()); |
||||
|
basicSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSettingNames.SecretId), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSettingNames.SecretId, providerName, providerKey), |
||||
|
ValueType.String, |
||||
|
providerName); |
||||
|
basicSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSettingNames.SecretKey), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSettingNames.SecretKey, providerName, providerKey), |
||||
|
ValueType.String, |
||||
|
providerName); |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region 连接信息
|
||||
|
|
||||
|
var connectionSetting = settingGroup.AddSetting( |
||||
|
L["DisplayName:TenantCloud.ConnectionSetting"], L["Description:TenantCloud.ConnectionSetting"]); |
||||
|
|
||||
|
connectionSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSettingNames.Connection.HttpMethod), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSettingNames.Connection.HttpMethod, providerName, providerKey), |
||||
|
ValueType.Option, |
||||
|
providerName) |
||||
|
.AddOption("POST", "POST") |
||||
|
.AddOption("GET", "GET"); |
||||
|
connectionSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSettingNames.Connection.Timeout), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSettingNames.Connection.Timeout, providerName, providerKey), |
||||
|
ValueType.Number, |
||||
|
providerName); |
||||
|
connectionSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSettingNames.Connection.WebProxy), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSettingNames.Connection.WebProxy, providerName, providerKey), |
||||
|
ValueType.Number, |
||||
|
providerName); |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region 短信设置
|
||||
|
|
||||
|
var smsSetting = settingGroup.AddSetting( |
||||
|
L["DisplayName:TenantCloud.SmsSetting"], L["Description:TenantCloud.SmsSetting"]); |
||||
|
|
||||
|
smsSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSmsSettingNames.AppId), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSmsSettingNames.AppId, providerName, providerKey), |
||||
|
ValueType.String, |
||||
|
providerName); |
||||
|
smsSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSmsSettingNames.DefaultTemplateId), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSmsSettingNames.DefaultTemplateId, providerName, providerKey), |
||||
|
ValueType.String, |
||||
|
providerName); |
||||
|
smsSetting.AddDetail( |
||||
|
SettingDefinitionManager.Get(TencentCloudSmsSettingNames.DefaultSignName), |
||||
|
StringLocalizerFactory, |
||||
|
await SettingManager.GetOrNullAsync(TencentCloudSmsSettingNames.DefaultSignName, providerName, providerKey), |
||||
|
ValueType.String, |
||||
|
providerName); |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
settingGroups.AddGroup(settingGroup); |
||||
|
} |
||||
|
|
||||
|
return settingGroups; |
||||
|
} |
||||
|
|
||||
|
protected virtual IEnumerable<OptionDto> GetAvailableRegionOptions() |
||||
|
{ |
||||
|
return new OptionDto[] |
||||
|
{ |
||||
|
new OptionDto(L["Region:Beijing"], "ap-beijing"), |
||||
|
new OptionDto(L["Region:Chengdu"], "ap-chengdu"), |
||||
|
new OptionDto(L["Region:Chongqing"], "ap-chongqing"), |
||||
|
new OptionDto(L["Region:Guangzhou"], "ap-guangzhou"), |
||||
|
new OptionDto(L["Region:Hongkong"], "ap-hongkong"), |
||||
|
new OptionDto(L["Region:Nanjing"], "ap-nanjing"), |
||||
|
new OptionDto(L["Region:Shanghai"], "ap-shanghai"), |
||||
|
new OptionDto(L["Region:ShanghaiFsi"], "ap-shanghai-fsi"), |
||||
|
new OptionDto(L["Region:ShenzhenFsi"], "ap-shenzhen-fsi"), |
||||
|
new OptionDto(L["Region:Bangkok"], "ap-bangkok"), |
||||
|
new OptionDto(L["Region:Jakarta"], "ap-jakarta"), |
||||
|
new OptionDto(L["Region:Mumbai"], "ap-mumbai"), |
||||
|
new OptionDto(L["Region:Seoul"], "ap-seoul"), |
||||
|
new OptionDto(L["Region:Singapore"], "ap-singapore"), |
||||
|
new OptionDto(L["Region:Tokyo"], "ap-tokyo"), |
||||
|
new OptionDto(L["Region:Frankfurt"], "eu-frankfurt"), |
||||
|
new OptionDto(L["Region:Moscow"], "eu-moscow"), |
||||
|
new OptionDto(L["Region:Virginia"], "na-ashburn"), |
||||
|
new OptionDto(L["Region:SiliconValley"], "na-siliconvalley"), |
||||
|
new OptionDto(L["Region:Toronto"], "na-toronto"), |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using LINGYUN.Abp.Tencent.Localization; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent.SettingManagement; |
||||
|
|
||||
|
public class TenantCloudSettingPermissionDefinitionProvider : PermissionDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IPermissionDefinitionContext context) |
||||
|
{ |
||||
|
var wechatGroup = context.AddGroup( |
||||
|
TenantCloudSettingPermissionNames.GroupName, |
||||
|
L("Permission:TencentCloud")); |
||||
|
|
||||
|
wechatGroup.AddPermission( |
||||
|
TenantCloudSettingPermissionNames.Settings, L("Permission:TencentCloud.Settings")); |
||||
|
} |
||||
|
|
||||
|
protected LocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<TencentCloudResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace LINGYUN.Abp.Tencent.SettingManagement |
||||
|
{ |
||||
|
public class TenantCloudSettingPermissionNames |
||||
|
{ |
||||
|
public const string GroupName = "Abp.Tencent"; |
||||
|
|
||||
|
public const string Settings = GroupName + ".Settings"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using TencentCloud.Common; |
||||
|
using TencentCloud.Common.Profile; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent; |
||||
|
|
||||
|
public class AbpTencentCloudOptions |
||||
|
{ |
||||
|
public IDictionary<Type, Func<Credential, string, ClientProfile, AbstractClient>> ClientProxies { get; } |
||||
|
|
||||
|
public AbpTencentCloudOptions() |
||||
|
{ |
||||
|
ClientProxies = new Dictionary<Type, Func<Credential, string, ClientProfile, AbstractClient>>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,124 @@ |
|||||
|
using LINGYUN.Abp.Tencent.Settings; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent; |
||||
|
|
||||
|
public abstract class AbstractTencentCloudClientFactory<TClient> |
||||
|
{ |
||||
|
protected IMemoryCache ClientCache { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected ISettingProvider SettingProvider { get; } |
||||
|
|
||||
|
public AbstractTencentCloudClientFactory( |
||||
|
IMemoryCache clientCache, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ISettingProvider settingProvider) |
||||
|
{ |
||||
|
ClientCache = clientCache; |
||||
|
CurrentTenant = currentTenant; |
||||
|
SettingProvider = settingProvider; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TClient> CreateAsync() |
||||
|
{ |
||||
|
var clientCacheItem = await GetClientCacheItemAsync(); |
||||
|
|
||||
|
return CreateClient(clientCacheItem); |
||||
|
} |
||||
|
|
||||
|
protected abstract TClient CreateClient(TencentCloudClientCacheItem cloudCache); |
||||
|
|
||||
|
protected virtual async Task<TencentCloudClientCacheItem> GetClientCacheItemAsync() |
||||
|
{ |
||||
|
return await ClientCache.GetOrCreateAsync( |
||||
|
TencentCloudClientCacheItem.CalculateCacheKey(CurrentTenant), |
||||
|
async (_) => |
||||
|
{ |
||||
|
var secretId = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.SecretId); |
||||
|
var secretKey = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.SecretKey); |
||||
|
var endpoint = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.EndPoint); |
||||
|
|
||||
|
Check.NotNullOrWhiteSpace(secretId, TencentCloudSettingNames.SecretId); |
||||
|
Check.NotNullOrWhiteSpace(secretKey, TencentCloudSettingNames.SecretKey); |
||||
|
|
||||
|
|
||||
|
var method = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.Connection.HttpMethod); |
||||
|
var webProxy = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.Connection.WebProxy); |
||||
|
var timeout = await SettingProvider.GetAsync(TencentCloudSettingNames.Connection.Timeout, 60); |
||||
|
|
||||
|
return new TencentCloudClientCacheItem |
||||
|
{ |
||||
|
SecretId = secretId, |
||||
|
SecretKey = secretKey, |
||||
|
// 连接区域
|
||||
|
EndPoint = endpoint, |
||||
|
HttpMethod = method, |
||||
|
WebProxy = webProxy, |
||||
|
Timeout = timeout, |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public abstract class AbstractTencentCloudClientFactory<TClient, TConfiguration> |
||||
|
{ |
||||
|
protected IMemoryCache ClientCache { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected ISettingProvider SettingProvider { get; } |
||||
|
|
||||
|
public AbstractTencentCloudClientFactory( |
||||
|
IMemoryCache clientCache, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ISettingProvider settingProvider) |
||||
|
{ |
||||
|
ClientCache = clientCache; |
||||
|
CurrentTenant = currentTenant; |
||||
|
SettingProvider = settingProvider; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TClient> CreateAsync(TConfiguration configuration) |
||||
|
{ |
||||
|
var clientCacheItem = await GetClientCacheItemAsync(); |
||||
|
|
||||
|
return CreateClient(configuration, clientCacheItem); |
||||
|
} |
||||
|
|
||||
|
protected abstract TClient CreateClient(TConfiguration configuration, TencentCloudClientCacheItem cloudCache); |
||||
|
|
||||
|
protected virtual async Task<TencentCloudClientCacheItem> GetClientCacheItemAsync() |
||||
|
{ |
||||
|
return await ClientCache.GetOrCreateAsync( |
||||
|
TencentCloudClientCacheItem.CalculateCacheKey(CurrentTenant), |
||||
|
async (_) => |
||||
|
{ |
||||
|
var secretId = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.SecretId); |
||||
|
var secretKey = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.SecretKey); |
||||
|
var endpoint = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.EndPoint); |
||||
|
var durationSecond = await SettingProvider.GetAsync(TencentCloudSettingNames.DurationSecond, 3600); |
||||
|
|
||||
|
Check.NotNullOrWhiteSpace(secretId, TencentCloudSettingNames.SecretId); |
||||
|
Check.NotNullOrWhiteSpace(secretKey, TencentCloudSettingNames.SecretKey); |
||||
|
|
||||
|
|
||||
|
var method = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.Connection.HttpMethod); |
||||
|
var webProxy = await SettingProvider.GetOrNullAsync(TencentCloudSettingNames.Connection.WebProxy); |
||||
|
var timeout = await SettingProvider.GetAsync(TencentCloudSettingNames.Connection.Timeout, 60); |
||||
|
|
||||
|
return new TencentCloudClientCacheItem |
||||
|
{ |
||||
|
SecretId = secretId, |
||||
|
SecretKey = secretKey, |
||||
|
// 连接区域
|
||||
|
EndPoint = endpoint, |
||||
|
DurationSecond = durationSecond, |
||||
|
HttpMethod = method, |
||||
|
WebProxy = webProxy, |
||||
|
Timeout = timeout, |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -1,5 +1,42 @@ |
|||||
{ |
{ |
||||
"culture": "en", |
"culture": "en", |
||||
"texts": { |
"texts": { |
||||
|
"DisplayName:TenantCloud": "Tenant Cloud", |
||||
|
"DisplayName:TenantCloud.BasicSetting": "Basic Setting", |
||||
|
"Description:TenantCloud.BasicSetting": "Tencent cloud basic information configuration", |
||||
|
"DisplayName:TenantCloud.ConnectionSetting": "Connection Setting", |
||||
|
"Description:TenantCloud.ConnectionSetting": "Tencent Cloud SDK connection configuration", |
||||
|
"DisplayName:EndPoint": "EndPoint", |
||||
|
"Description:EndPoint": "Represents a region where a resource resides, and each region contains one or more available regions.", |
||||
|
"DisplayName:SecretId": "Secret Id", |
||||
|
"Description:SecretId": "The Secret Id can be obtained only from Tencent Cloud Management Console.", |
||||
|
"DisplayName:SecretKey": "Secret Key", |
||||
|
"Description:SecretKey": "The Secret Key can be obtained only from Tencent Cloud Management Console.", |
||||
|
"DisplayName:HttpMethod": "Http Method", |
||||
|
"Description:HttpMethod": "The SDK uses the POST method by default, but if you must use the GET method, you can set it here. The GET method cannot handle some large requests.", |
||||
|
"DisplayName:Timeout": "Timeout", |
||||
|
"Description:Timeout": "Connection timeout, in seconds (default: 60 seconds)", |
||||
|
"DisplayName:WebProxy": "Web Proxy", |
||||
|
"Description:WebProxy": "Proxy server, set when there are proxy servers in your environment.", |
||||
|
"Region:Beijing": "North China (Beijing)", |
||||
|
"Region:Chengdu": "Southwest China (Chengdu)", |
||||
|
"Region:Chongqing": "Southwest China (Chongqing)", |
||||
|
"Region:Guangzhou": "South China (Guangzhou)", |
||||
|
"Region:Hongkong": "Hong Kong/Macao/Taiwan (Hong Kong, China)", |
||||
|
"Region:Nanjing": "East China (Nanjing)", |
||||
|
"Region:Shanghai": "East China (Shanghai)", |
||||
|
"Region:ShanghaiFsi": "East China (Shanghai financial)", |
||||
|
"Region:ShenzhenFsi": "South China (Shenzhen financial)", |
||||
|
"Region:Bangkok": "Southeast Asia (Bangkok)", |
||||
|
"Region:Jakarta": "Southeast Asia (Jakarta)", |
||||
|
"Region:Mumbai": "South Asia (Mumbai)", |
||||
|
"Region:Seoul": "Northeast Asia (Seoul)", |
||||
|
"Region:Singapore": "Southeast Asia (Singapore)", |
||||
|
"Region:Tokyo": "Northeast Asia (Tokyo)", |
||||
|
"Region:Frankfurt": "Europe (Frankfurt)", |
||||
|
"Region:Moscow": "Europe (Moscow)", |
||||
|
"Region:Virginia": "Eastern US (Virginia)", |
||||
|
"Region:SiliconValley": "Western US (Silicon Valley)", |
||||
|
"Region:Toronto": "North America (Toronto)" |
||||
} |
} |
||||
} |
} |
||||
@ -1,5 +1,42 @@ |
|||||
{ |
{ |
||||
"culture": "zh-Hans", |
"culture": "zh-Hans", |
||||
"texts": { |
"texts": { |
||||
|
"DisplayName:TenantCloud": "腾讯云服务", |
||||
|
"DisplayName:TenantCloud.BasicSetting": "基础配置", |
||||
|
"Description:TenantCloud.BasicSetting": "腾讯云基础信息配置", |
||||
|
"DisplayName:TenantCloud.ConnectionSetting": "连接配置", |
||||
|
"Description:TenantCloud.ConnectionSetting": "腾讯云SDK连接相关配置", |
||||
|
"DisplayName:EndPoint": "资源地域", |
||||
|
"Description:EndPoint": "表示资源所在的地域,每个地域包含一个或多个可用区。", |
||||
|
"DisplayName:SecretId": "密钥Id", |
||||
|
"Description:SecretId": "密钥Id,只能从腾讯云管理控制台获取。", |
||||
|
"DisplayName:SecretKey": "密钥Key", |
||||
|
"Description:SecretKey": "密钥Key,只能从腾讯云管理控制台获取。", |
||||
|
"DisplayName:HttpMethod": "请求类型", |
||||
|
"Description:HttpMethod": "SDK默认使用POST方法,如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求。", |
||||
|
"DisplayName:Timeout": "超时时间", |
||||
|
"Description:Timeout": "请求连接超时时间,单位为秒(默认60秒)", |
||||
|
"DisplayName:WebProxy": "代理服务器", |
||||
|
"Description:WebProxy": "代理服务器,当你的环境下有代理服务器时设定", |
||||
|
"Region:Beijing": "华北地区(北京)", |
||||
|
"Region:Chengdu": "西南地区(成都)", |
||||
|
"Region:Chongqing": "西南地区(重庆)", |
||||
|
"Region:Guangzhou": "华南地区(广州)", |
||||
|
"Region:Hongkong": "港澳台地区(中国香港)", |
||||
|
"Region:Nanjing": "华东地区(南京)", |
||||
|
"Region:Shanghai": "华东地区(上海)", |
||||
|
"Region:ShanghaiFsi": "华东地区(上海金融)", |
||||
|
"Region:ShenzhenFsi": "华南地区(深圳金融)", |
||||
|
"Region:Bangkok": "亚太东南(曼谷)", |
||||
|
"Region:Jakarta": "亚太东南(雅加达)", |
||||
|
"Region:Mumbai": "亚太南部(孟买)", |
||||
|
"Region:Seoul": "亚太东北(首尔)", |
||||
|
"Region:Singapore": "亚太东南(新加坡)", |
||||
|
"Region:Tokyo": "亚太东北(东京)", |
||||
|
"Region:Frankfurt": "欧洲地区(法兰克福)", |
||||
|
"Region:Moscow": "欧洲地区(莫斯科)", |
||||
|
"Region:Virginia": "美国东部(弗吉尼亚)", |
||||
|
"Region:SiliconValley": "美国西部(硅谷)", |
||||
|
"Region:Toronto": "北美地区(多伦多)" |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent.Localization |
||||
|
{ |
||||
|
[LocalizationResourceName("TencentCloud")] |
||||
|
public class TencentCloudResource |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -1,9 +0,0 @@ |
|||||
using Volo.Abp.Localization; |
|
||||
|
|
||||
namespace LINYUN.Abp.Tencent.Localization |
|
||||
{ |
|
||||
[LocalizationResourceName("Tencent")] |
|
||||
public class TencentResource |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,94 @@ |
|||||
|
using LINGYUN.Abp.Tencent.Localization; |
||||
|
using LINGYUN.Abp.Tencent.Settings; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINYUN.Abp.Tencent.Settings; |
||||
|
|
||||
|
public class TencentCloudSettingDefinitionProvider : SettingDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(ISettingDefinitionContext context) |
||||
|
{ |
||||
|
context.Add(CreateTencentCloudSettings()); |
||||
|
} |
||||
|
|
||||
|
private SettingDefinition[] CreateTencentCloudSettings() |
||||
|
{ |
||||
|
return new SettingDefinition[] |
||||
|
{ |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSettingNames.EndPoint, |
||||
|
// 腾讯云默认使用广州区域
|
||||
|
defaultValue: "ap-guangzhou", |
||||
|
displayName: L("DisplayName:EndPoint"), |
||||
|
description: L("Description:EndPoint"), |
||||
|
isVisibleToClients: false |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSettingNames.SecretId, |
||||
|
displayName: L("DisplayName:SecretId"), |
||||
|
description: L("Description:SecretId"), |
||||
|
isVisibleToClients: false, |
||||
|
isEncrypted: true |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSettingNames.SecretKey, |
||||
|
displayName: L("DisplayName:SecretKey"), |
||||
|
description: L("Description:SecretKey"), |
||||
|
isVisibleToClients: false, |
||||
|
isEncrypted: true |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSettingNames.Connection.HttpMethod, |
||||
|
// 默认 post
|
||||
|
defaultValue: "POST", |
||||
|
displayName: L("DisplayName:HttpMethod"), |
||||
|
description: L("Description:HttpMethod"), |
||||
|
isVisibleToClients: false |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSettingNames.Connection.Timeout, |
||||
|
// 默认 60秒
|
||||
|
defaultValue: "60", |
||||
|
displayName: L("DisplayName:Timeout"), |
||||
|
description: L("Description:Timeout"), |
||||
|
isVisibleToClients: false |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
TencentCloudSettingNames.Connection.WebProxy, |
||||
|
displayName: L("DisplayName:WebProxy"), |
||||
|
description: L("Description:WebProxy"), |
||||
|
isVisibleToClients: false |
||||
|
) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private ILocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<TencentCloudResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
namespace LINGYUN.Abp.Tencent.Settings; |
||||
|
|
||||
|
public static class TencentCloudSettingNames |
||||
|
{ |
||||
|
public const string Prefix = "Abp.TencentCloud"; |
||||
|
/// <summary>
|
||||
|
/// SecretId
|
||||
|
/// </summary>
|
||||
|
public const string SecretId = Prefix + ".SecretId"; |
||||
|
/// <summary>
|
||||
|
/// SecretKey
|
||||
|
/// </summary>
|
||||
|
public const string SecretKey = Prefix + ".SecretKey"; |
||||
|
/// <summary>
|
||||
|
/// 连接地域
|
||||
|
/// </summary>
|
||||
|
public const string EndPoint = Prefix + ".EndPoint"; |
||||
|
/// <summary>
|
||||
|
/// 会话持续时间
|
||||
|
/// </summary>
|
||||
|
public const string DurationSecond = Prefix + ".DurationSecond"; |
||||
|
/// <summary>
|
||||
|
/// 连接设置
|
||||
|
/// </summary>
|
||||
|
public class Connection |
||||
|
{ |
||||
|
public const string Prefix = TencentCloudSettingNames.Prefix + ".Connection"; |
||||
|
/// <summary>
|
||||
|
/// 代理服务器
|
||||
|
/// </summary>
|
||||
|
public const string WebProxy = Prefix + ".WebProxy"; |
||||
|
/// <summary>
|
||||
|
/// 连接地域
|
||||
|
/// 按照腾讯云文档,对于金融区服务需要指定域名
|
||||
|
/// </summary>
|
||||
|
public const string EndPoint = Prefix + ".EndPoint"; |
||||
|
/// <summary>
|
||||
|
/// Http方法,按照腾讯云文档,不同的方法对于请求大小有限制
|
||||
|
/// 默认:POST
|
||||
|
/// </summary>
|
||||
|
public const string HttpMethod = Prefix + ".HttpMethod"; |
||||
|
/// <summary>
|
||||
|
/// 超时时间
|
||||
|
/// </summary>
|
||||
|
public const string Timeout = Prefix + ".Timeout"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent; |
||||
|
|
||||
|
public class TencentCloudClientCacheItem |
||||
|
{ |
||||
|
public const string CacheKeyFormat = "pn:{0},n:tenant-cloud"; |
||||
|
public string SecretId { get; set; } |
||||
|
public string SecretKey { get; set; } |
||||
|
public string EndPoint { get; set; } |
||||
|
public string WebProxy { get; set; } |
||||
|
public string ApiEndPoint { get; set; } |
||||
|
public string HttpMethod { get; set; } |
||||
|
public int Timeout { get; set; } |
||||
|
public int DurationSecond { get; set; } |
||||
|
|
||||
|
public static string CalculateCacheKey(ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
return string.Format(CacheKeyFormat, currentTenant.IsAvailable ? currentTenant.GetId().ToString() : "global"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using System.Linq; |
||||
|
using TencentCloud.Common; |
||||
|
using TencentCloud.Common.Profile; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent; |
||||
|
|
||||
|
public class TencentCloudClientFactory<TClient> : AbstractTencentCloudClientFactory<TClient> |
||||
|
{ |
||||
|
public TencentCloudClientFactory( |
||||
|
IMemoryCache clientCache, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ISettingProvider settingProvider) |
||||
|
: base(clientCache, currentTenant, settingProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override TClient CreateClient(TencentCloudClientCacheItem cloudCache) |
||||
|
{ |
||||
|
var clientCtr = typeof(TClient) |
||||
|
.GetConstructors() |
||||
|
.Where(x => x.GetParameters().Length == 3) |
||||
|
.FirstOrDefault(); |
||||
|
if (clientCtr != null) |
||||
|
{ |
||||
|
var cred = new Credential |
||||
|
{ |
||||
|
SecretId = cloudCache.SecretId, |
||||
|
SecretKey = cloudCache.SecretKey, |
||||
|
}; |
||||
|
|
||||
|
var httpProfile = new HttpProfile |
||||
|
{ |
||||
|
ReqMethod = cloudCache.HttpMethod ?? "POST", |
||||
|
Timeout = cloudCache.Timeout, |
||||
|
// 不同的api需要的区域不同, 有的
|
||||
|
Endpoint = cloudCache.ApiEndPoint, |
||||
|
WebProxy = cloudCache.WebProxy, |
||||
|
}; |
||||
|
var clientProfile = new ClientProfile |
||||
|
{ |
||||
|
HttpProfile = httpProfile, |
||||
|
}; |
||||
|
|
||||
|
// 通过反射创建客户端实例
|
||||
|
// TODO: 如果影响到性能需要调整到通过Options手动创建实例
|
||||
|
return (TClient)clientCtr.Invoke(new object[] { cred, cloudCache.EndPoint, clientProfile }); |
||||
|
} |
||||
|
|
||||
|
throw new AbpException($"Failed to specify initialization Type for client {typeof(TClient).FullName}. Client instance could not be created"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using LINGYUN.Abp.Wrapper; |
||||
|
using Microsoft.AspNetCore.Cors.Infrastructure; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Cors; |
||||
|
|
||||
|
public static class AbpCorsPolicyBuilderExtensions |
||||
|
{ |
||||
|
public static CorsPolicyBuilder WithAbpWrapExposedHeaders(this CorsPolicyBuilder corsPolicyBuilder) |
||||
|
{ |
||||
|
return corsPolicyBuilder |
||||
|
.WithExposedHeaders( |
||||
|
AbpHttpWrapConsts.AbpWrapResult, |
||||
|
AbpHttpWrapConsts.AbpDontWrapResult); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,16 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\cloud-tencent\LINGYUN.Abp.BlobStoring.Tencent\LINGYUN.Abp.BlobStoring.Tencent.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.OssManagement.Domain\LINGYUN.Abp.OssManagement.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,17 @@ |
|||||
|
using LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement.Tencent |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringTencentCloudModule), |
||||
|
typeof(AbpOssManagementDomainModule))] |
||||
|
public class AbpOssManagementTencentModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTransient<IOssContainerFactory, TencentOssContainerFactory>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,403 @@ |
|||||
|
using COSXML; |
||||
|
using COSXML.Model.Bucket; |
||||
|
using COSXML.Model.Object; |
||||
|
using COSXML.Model.Service; |
||||
|
using LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement.Tencent |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Oss容器的阿里云实现
|
||||
|
/// </summary>
|
||||
|
internal class TencentOssContainer : IOssContainer |
||||
|
{ |
||||
|
protected IClock Clock { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected ICosClientFactory CosClientFactory { get; } |
||||
|
public TencentOssContainer( |
||||
|
IClock clock, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ICosClientFactory cosClientFactory) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
CurrentTenant = currentTenant; |
||||
|
CosClientFactory = cosClientFactory; |
||||
|
} |
||||
|
public virtual async Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
var path = GetBasePath(request.Path); |
||||
|
var deleteRequest = new DeleteMultiObjectRequest(request.Bucket); |
||||
|
deleteRequest.SetObjectKeys(request.Objects.Select(x => x += path).ToList()); |
||||
|
|
||||
|
ossClient.DeleteMultiObjects(deleteRequest); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<OssContainer> CreateAsync(string name) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
if (BucketExists(ossClient, name)) |
||||
|
{ |
||||
|
throw new BusinessException(code: OssManagementErrorCodes.ContainerAlreadyExists); |
||||
|
} |
||||
|
|
||||
|
var putBucketRequest = new PutBucketRequest(name); |
||||
|
var bucketResult = ossClient.PutBucket(putBucketRequest); |
||||
|
|
||||
|
return new OssContainer( |
||||
|
bucketResult.Key, |
||||
|
Clock.Now, |
||||
|
0L, |
||||
|
Clock.Now, |
||||
|
new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "Id", bucketResult.Key }, |
||||
|
{ "DisplayName", bucketResult.Key } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<OssObject> CreateObjectAsync(CreateOssObjectRequest request) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
var objectPath = GetBasePath(request.Path); |
||||
|
|
||||
|
var objectName = objectPath.IsNullOrWhiteSpace() |
||||
|
? request.Object |
||||
|
: objectPath + request.Object; |
||||
|
|
||||
|
if (!request.Overwrite && ObjectExists(ossClient, request.Bucket, objectName)) |
||||
|
{ |
||||
|
throw new BusinessException(code: OssManagementErrorCodes.ObjectAlreadyExists); |
||||
|
} |
||||
|
|
||||
|
// 当一个对象名称是以 / 结尾时,不论该对象是否存有数据,都以目录的形式存在
|
||||
|
// 详情见:https://help.aliyun.com/document_detail/31910.html
|
||||
|
if (objectName.EndsWith("/") && |
||||
|
request.Content.IsNullOrEmpty()) |
||||
|
{ |
||||
|
var emptyStream = new MemoryStream(); |
||||
|
var emptyData = System.Text.Encoding.UTF8.GetBytes(""); |
||||
|
await emptyStream.WriteAsync(emptyData, 0, emptyData.Length); |
||||
|
request.SetContent(emptyStream); |
||||
|
} |
||||
|
|
||||
|
// 没有bucket则创建
|
||||
|
if (!BucketExists(ossClient, request.Bucket)) |
||||
|
{ |
||||
|
var putBucketRequest = new PutBucketRequest(request.Bucket); |
||||
|
ossClient.PutBucket(putBucketRequest); |
||||
|
} |
||||
|
|
||||
|
var contentLength = request.Content.Length; |
||||
|
var putObjectRequest = new PutObjectRequest(request.Bucket, objectName, request.Content); |
||||
|
|
||||
|
var objectResult = ossClient.PutObject(putObjectRequest); |
||||
|
|
||||
|
if (objectResult.IsSuccessful() && request.ExpirationTime.HasValue) |
||||
|
{ |
||||
|
var putBuckerLifeRequest = new PutBucketLifecycleRequest(request.Bucket); |
||||
|
|
||||
|
var rule = new COSXML.Model.Tag.LifecycleConfiguration.Rule(); |
||||
|
rule.id = "lfiecycleConfigureId"; |
||||
|
rule.status = "Enabled"; //Enabled,Disabled
|
||||
|
|
||||
|
rule.filter = new COSXML.Model.Tag.LifecycleConfiguration.Filter(); |
||||
|
// TODO: 需要测试
|
||||
|
rule.filter.prefix = objectName; |
||||
|
|
||||
|
putBuckerLifeRequest.SetRule(rule); |
||||
|
|
||||
|
ossClient.PutBucketLifecycle(putBuckerLifeRequest); |
||||
|
} |
||||
|
|
||||
|
var ossObject = new OssObject( |
||||
|
!objectPath.IsNullOrWhiteSpace() |
||||
|
? objectName.Replace(objectPath, "") |
||||
|
: objectName, |
||||
|
objectPath, |
||||
|
objectResult.eTag, |
||||
|
DateTime.Now, |
||||
|
contentLength, |
||||
|
DateTime.Now, |
||||
|
new Dictionary<string, string>(), |
||||
|
objectName.EndsWith("/") // 名称结尾是 / 符号的则为目录:https://cloud.tencent.com/document/product/436/13324
|
||||
|
) |
||||
|
{ |
||||
|
FullName = objectName |
||||
|
}; |
||||
|
|
||||
|
if (!Equals(request.Content, Stream.Null)) |
||||
|
{ |
||||
|
request.Content.Seek(0, SeekOrigin.Begin); |
||||
|
ossObject.SetContent(request.Content); |
||||
|
} |
||||
|
|
||||
|
return ossObject; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task DeleteAsync(string name) |
||||
|
{ |
||||
|
// 阿里云oss在控制台设置即可,无需改变
|
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
if (BucketExists(ossClient, name)) |
||||
|
{ |
||||
|
var deleteBucketRequest = new DeleteBucketRequest(name); |
||||
|
ossClient.DeleteBucket(deleteBucketRequest); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task DeleteObjectAsync(GetOssObjectRequest request) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
var objectPath = GetBasePath(request.Path); |
||||
|
|
||||
|
var objectName = objectPath.IsNullOrWhiteSpace() |
||||
|
? request.Object |
||||
|
: objectPath + request.Object; |
||||
|
|
||||
|
if (BucketExists(ossClient, request.Bucket) && |
||||
|
ObjectExists(ossClient, request.Bucket, objectName)) |
||||
|
{ |
||||
|
var getBucketRequest = new GetBucketRequest(request.Bucket); |
||||
|
|
||||
|
var getBucketResult = ossClient.GetBucket(getBucketRequest); |
||||
|
if (getBucketResult.listBucket.commonPrefixesList.Any() || |
||||
|
getBucketResult.listBucket.contentsList.Any()) |
||||
|
{ |
||||
|
throw new BusinessException(code: OssManagementErrorCodes.ObjectDeleteWithNotEmpty); |
||||
|
} |
||||
|
var deleteObjectRequest = new DeleteObjectRequest(request.Bucket, objectName); |
||||
|
ossClient.DeleteObject(deleteObjectRequest); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> ExistsAsync(string name) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
return BucketExists(ossClient, name); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<OssContainer> GetAsync(string name) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
if (!BucketExists(ossClient, name)) |
||||
|
{ |
||||
|
throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); |
||||
|
// throw new ContainerNotFoundException($"Can't not found container {name} in aliyun blob storing");
|
||||
|
} |
||||
|
var getBucketRequest = new GetBucketRequest(name); |
||||
|
var bucket = ossClient.GetBucket(getBucketRequest); |
||||
|
|
||||
|
return new OssContainer( |
||||
|
bucket.Key, |
||||
|
new DateTime(1970, 1, 1, 0, 0, 0), // TODO: 从header获取? 需要测试
|
||||
|
0L, |
||||
|
null, |
||||
|
new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "Id", bucket.Key }, |
||||
|
{ "DisplayName", bucket.Key } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<OssObject> GetObjectAsync(GetOssObjectRequest request) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
if (!BucketExists(ossClient, request.Bucket)) |
||||
|
{ |
||||
|
throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); |
||||
|
// throw new ContainerNotFoundException($"Can't not found container {request.Bucket} in aliyun blob storing");
|
||||
|
} |
||||
|
|
||||
|
var objectPath = GetBasePath(request.Path); |
||||
|
var objectName = objectPath.IsNullOrWhiteSpace() |
||||
|
? request.Object |
||||
|
: objectPath + request.Object; |
||||
|
|
||||
|
if (!ObjectExists(ossClient, request.Bucket, objectName)) |
||||
|
{ |
||||
|
throw new BusinessException(code: OssManagementErrorCodes.ObjectNotFound); |
||||
|
// throw new ContainerNotFoundException($"Can't not found object {objectName} in container {request.Bucket} with aliyun blob storing");
|
||||
|
} |
||||
|
|
||||
|
var getObjectRequest = new GetObjectBytesRequest(request.Bucket, objectName); |
||||
|
if (!request.Process.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
getObjectRequest.SetQueryParameter(request.Process, null); |
||||
|
} |
||||
|
var objectResult = ossClient.GetObject(getObjectRequest); |
||||
|
var ossObject = new OssObject( |
||||
|
!objectPath.IsNullOrWhiteSpace() |
||||
|
? objectResult.Key.Replace(objectPath, "") |
||||
|
: objectResult.Key, |
||||
|
request.Path, |
||||
|
objectResult.eTag, |
||||
|
null, |
||||
|
objectResult.content.Length, |
||||
|
null, |
||||
|
new Dictionary<string, string>(), |
||||
|
objectResult.Key.EndsWith("/")) |
||||
|
{ |
||||
|
FullName = objectResult.Key |
||||
|
}; |
||||
|
|
||||
|
if (objectResult.content.Length > 0) |
||||
|
{ |
||||
|
var memoryStream = new MemoryStream(); |
||||
|
await memoryStream.WriteAsync(objectResult.content, 0, objectResult.content.Length); |
||||
|
memoryStream.Seek(0, SeekOrigin.Begin); |
||||
|
ossObject.SetContent(memoryStream); |
||||
|
} |
||||
|
|
||||
|
return ossObject; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<GetOssContainersResponse> GetListAsync(GetOssContainersRequest request) |
||||
|
{ |
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
// TODO: 腾讯云直接返回所有列表?
|
||||
|
var getBucketRequest = new GetServiceRequest(); |
||||
|
var bucket = ossClient.GetService(getBucketRequest); |
||||
|
|
||||
|
return new GetOssContainersResponse( |
||||
|
request.Prefix, |
||||
|
request.Marker, |
||||
|
null, |
||||
|
bucket.listAllMyBuckets.buckets.Count, |
||||
|
bucket.listAllMyBuckets.buckets |
||||
|
.Select(x => new OssContainer( |
||||
|
x.name, |
||||
|
DateTime.TryParse(x.createDate, out var time) ? time : new DateTime(1970, 1, 1), |
||||
|
0L, |
||||
|
null, |
||||
|
new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "Id", x.name }, |
||||
|
{ "DisplayName", x.name } |
||||
|
})) |
||||
|
.ToList()); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<GetOssObjectsResponse> GetObjectsAsync(GetOssObjectsRequest request) |
||||
|
{ |
||||
|
|
||||
|
var ossClient = await CreateClientAsync(); |
||||
|
|
||||
|
var objectPath = GetBasePath(request.Prefix); |
||||
|
var marker = !objectPath.IsNullOrWhiteSpace() && !request.Marker.IsNullOrWhiteSpace() |
||||
|
? request.Marker.Replace(objectPath, "") |
||||
|
: request.Marker; |
||||
|
|
||||
|
// TODO: 阿里云的分页差异需要前端来弥补,传递Marker, 按照Oss控制台的逻辑,直接把MaxKeys设置较大值就行了
|
||||
|
|
||||
|
var getBucketRequest = new GetBucketRequest(request.BucketName); |
||||
|
getBucketRequest.SetMarker(!marker.IsNullOrWhiteSpace() ? objectPath + marker : marker); |
||||
|
getBucketRequest.SetMaxKeys(request.MaxKeys?.ToString() ?? "10"); |
||||
|
getBucketRequest.SetPrefix(objectPath); |
||||
|
getBucketRequest.SetDelimiter(request.Delimiter); |
||||
|
getBucketRequest.SetEncodingType(request.EncodingType); |
||||
|
|
||||
|
var getBucketResult = ossClient.GetBucket(getBucketRequest); |
||||
|
|
||||
|
var ossObjects = getBucketResult.listBucket.contentsList |
||||
|
.Where(x => !x.key.Equals(objectPath))// 过滤当前的目录返回值
|
||||
|
.Select(x => new OssObject( |
||||
|
!objectPath.IsNullOrWhiteSpace() && !x.key.Equals(objectPath) |
||||
|
? x.key.Replace(objectPath, "") |
||||
|
: x.key, // 去除目录名称
|
||||
|
request.Prefix, |
||||
|
x.eTag, |
||||
|
DateTime.TryParse(x.lastModified, out var ctime) ? ctime : null, |
||||
|
x.size, |
||||
|
DateTime.TryParse(x.lastModified, out var mtime) ? mtime : null, |
||||
|
new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "Id", x.key }, |
||||
|
{ "DisplayName", x.key } |
||||
|
}, |
||||
|
x.key.EndsWith("/")) |
||||
|
{ |
||||
|
FullName = x.key |
||||
|
}) |
||||
|
.ToList(); |
||||
|
// 当 Delimiter 为 / 时, objectsResponse.CommonPrefixes 可用于代表层级目录
|
||||
|
if (getBucketResult.listBucket.commonPrefixesList.Any()) |
||||
|
{ |
||||
|
ossObjects.InsertRange(0, |
||||
|
getBucketResult.listBucket.commonPrefixesList |
||||
|
.Select(x => new OssObject( |
||||
|
x.prefix.Replace(objectPath, ""), |
||||
|
request.Prefix, |
||||
|
"", |
||||
|
null, |
||||
|
0L, |
||||
|
null, |
||||
|
null, |
||||
|
true))); |
||||
|
} |
||||
|
// 排序
|
||||
|
// TODO: 是否需要客户端来排序
|
||||
|
ossObjects.Sort(new OssObjectComparer()); |
||||
|
|
||||
|
return new GetOssObjectsResponse( |
||||
|
getBucketResult.Key, |
||||
|
request.Prefix, |
||||
|
marker, |
||||
|
!objectPath.IsNullOrWhiteSpace() && !getBucketResult.listBucket.nextMarker.IsNullOrWhiteSpace() |
||||
|
? getBucketResult.listBucket.nextMarker.Replace(objectPath, "") |
||||
|
: getBucketResult.listBucket.nextMarker, |
||||
|
getBucketResult.listBucket.delimiter, |
||||
|
getBucketResult.listBucket.maxKeys, |
||||
|
ossObjects); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetBasePath(string path) |
||||
|
{ |
||||
|
string objectPath = ""; |
||||
|
if (CurrentTenant.Id == null) |
||||
|
{ |
||||
|
objectPath += "host/"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
objectPath += "tenants/" + CurrentTenant.Id.Value.ToString("D"); |
||||
|
} |
||||
|
|
||||
|
objectPath += path ?? ""; |
||||
|
|
||||
|
return objectPath.EnsureEndsWith('/'); |
||||
|
} |
||||
|
|
||||
|
protected virtual bool BucketExists(CosXml cos, string bucketName) |
||||
|
{ |
||||
|
var request = new DoesBucketExistRequest(bucketName); |
||||
|
return cos.DoesBucketExist(request); |
||||
|
} |
||||
|
|
||||
|
protected virtual bool ObjectExists(CosXml cos, string bucketName, string objectName) |
||||
|
{ |
||||
|
var request = new DoesObjectExistRequest(bucketName, objectName); |
||||
|
return cos.DoesObjectExist(request); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<CosXml> CreateClientAsync() |
||||
|
{ |
||||
|
return await CosClientFactory.CreateAsync<AbpOssManagementContainer>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement.Tencent |
||||
|
{ |
||||
|
public class TencentOssContainerFactory : IOssContainerFactory |
||||
|
{ |
||||
|
protected IClock Clock { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected ICosClientFactory CosClientFactory { get; } |
||||
|
|
||||
|
public TencentOssContainerFactory( |
||||
|
IClock clock, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ICosClientFactory cosClientFactory) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
CurrentTenant = currentTenant; |
||||
|
CosClientFactory = cosClientFactory; |
||||
|
} |
||||
|
|
||||
|
public IOssContainer Create() |
||||
|
{ |
||||
|
return new TencentOssContainer( |
||||
|
Clock, |
||||
|
CurrentTenant, |
||||
|
CosClientFactory); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
# LINGYUN.Abp.OssManagement.Tencent |
||||
|
|
||||
|
腾讯云oss容器接口 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
模块按需引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpOssManagementTencentCloudModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
Loading…
Reference in new issue