Browse Source
- Add the interface for generating download links - Alibaba Cloud Object Storage adds configuration for the expiration time of pre-signed links - Tencent Cloud Object Storage adds configuration for the expiration time of pre-signed links - Add generation of download url feature - Add the setting item for the expiration time of the generated download url - Add the interface for parsing file ContentType - Integrate the MimeCheck module to parse the Content-Type of the filepull/1489/head
48 changed files with 737 additions and 108 deletions
@ -0,0 +1,18 @@ |
|||
using System.Net.Http; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection; |
|||
internal static class AliyunHttpClientFactoryServiceCollectionExtensions |
|||
{ |
|||
private const string HttpClientName = "__AliyunBlobStoringApiClient"; |
|||
public static IServiceCollection AddAliyunHttpClient(this IServiceCollection services) |
|||
{ |
|||
services.AddHttpClient(HttpClientName); |
|||
|
|||
return services; |
|||
} |
|||
|
|||
public static HttpClient CreateAliyunHttpClient(this IHttpClientFactory httpClientFactory) |
|||
{ |
|||
return httpClientFactory.CreateClient(HttpClientName); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement.Dtos; |
|||
|
|||
public class BlobDownloadByKeyInput |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
[Required] |
|||
public string Key { get; set; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public class AbpBlobManagementContentTypeResolveOptions |
|||
{ |
|||
[NotNull] |
|||
public List<IBlobContentTypeResolveContributor> BlobContentTypeResolvers { get; } |
|||
|
|||
public AbpBlobManagementContentTypeResolveOptions() |
|||
{ |
|||
BlobContentTypeResolvers = new List<IBlobContentTypeResolveContributor>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public class BlobContentTypeResolveContext |
|||
{ |
|||
public IServiceProvider ServiceProvider { get; } |
|||
public string BlobName { get; } |
|||
public Stream Stream { get; } |
|||
public bool Handled { get; private set; } |
|||
public string? ContentType { get; private set; } |
|||
public BlobContentTypeResolveContext( |
|||
IServiceProvider serviceProvider, |
|||
string blobName, |
|||
Stream stream) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
BlobName = blobName; |
|||
Stream = stream; |
|||
} |
|||
|
|||
public void Success(string contentType) |
|||
{ |
|||
ContentType = contentType; |
|||
Handled = true; |
|||
} |
|||
|
|||
public bool HasResolvedContentType() |
|||
{ |
|||
return Handled && !ContentType.IsNullOrWhiteSpace(); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public interface IBlobContentTypeResolveContributor |
|||
{ |
|||
string Name { get; } |
|||
int Priority { get; } |
|||
Task ResolveAsync(BlobContentTypeResolveContext context); |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public class BlobContentTypeResolver : IBlobContentTypeResolver, ITransientDependency |
|||
{ |
|||
public ILogger<BlobContentTypeResolver> Logger { get; set; } |
|||
|
|||
protected IServiceScopeFactory ServiceScopeFactory { get; } |
|||
protected AbpBlobManagementContentTypeResolveOptions Options { get; } |
|||
|
|||
public BlobContentTypeResolver( |
|||
IOptions<AbpBlobManagementContentTypeResolveOptions> options, |
|||
IServiceScopeFactory serviceScopeFactory) |
|||
{ |
|||
ServiceScopeFactory = serviceScopeFactory; |
|||
Options = options.Value; |
|||
|
|||
Logger = NullLogger<BlobContentTypeResolver>.Instance; |
|||
} |
|||
|
|||
public async virtual Task<string> ResolveContentTypeAsync(string blobName, Stream blobStream) |
|||
{ |
|||
string? result = null; |
|||
|
|||
Logger.LogDebug("Starting resolving content type..."); |
|||
using (var serviceScope = ServiceScopeFactory.CreateScope()) |
|||
{ |
|||
var context = new BlobContentTypeResolveContext(serviceScope.ServiceProvider, blobName, blobStream); |
|||
|
|||
foreach (var blobContentTypeResolver in Options.BlobContentTypeResolvers.OrderByDescending(x => x.Priority)) |
|||
{ |
|||
var originalPosition = context.Stream.CanSeek ? context.Stream.Position : -1; |
|||
|
|||
Logger.LogDebug("Trying to resolve content type '{Name}'...", blobContentTypeResolver.Name); |
|||
|
|||
await blobContentTypeResolver.ResolveAsync(context); |
|||
|
|||
if (originalPosition >= 0 && context.Stream.CanSeek) |
|||
{ |
|||
context.Stream.Position = originalPosition; |
|||
} |
|||
|
|||
if (context.HasResolvedContentType()) |
|||
{ |
|||
result = context.ContentType; |
|||
|
|||
Logger.LogDebug("Content type resolved by '{Name}' as '{ContentType}'.", blobContentTypeResolver.Name, result); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (result.IsNullOrWhiteSpace()) |
|||
{ |
|||
Logger.LogWarning("No provider can resolve content type. Using the default \"application/octet-stream\"."); |
|||
|
|||
return "application/octet-stream"; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public class BlobDownloadKeyCacheItem |
|||
{ |
|||
public string Url { get; set; } |
|||
public Guid BlobId { get; set; } |
|||
public BlobDownloadKeyCacheItem() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public BlobDownloadKeyCacheItem(Guid blobId, string url) |
|||
{ |
|||
BlobId = blobId; |
|||
Url = url; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(Guid blobId, string blobName) |
|||
{ |
|||
return $"{blobId:N}{blobName.RemovePreFix("/")}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public interface IBlobContentTypeResolver |
|||
{ |
|||
Task<string> ResolveContentTypeAsync(string blobName, Stream blobStream); |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using Microsoft.AspNetCore.StaticFiles; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement; |
|||
|
|||
public class FileExtensionBlobContentTypeResolveContributor : IBlobContentTypeResolveContributor |
|||
{ |
|||
public string Name => "FileExtension"; |
|||
public int Priority => 0; |
|||
|
|||
public virtual Task ResolveAsync(BlobContentTypeResolveContext context) |
|||
{ |
|||
var contentTypeProvider = context.ServiceProvider.GetService<IContentTypeProvider>(); |
|||
if (contentTypeProvider != null && |
|||
contentTypeProvider.TryGetContentType(context.BlobName, out var contentType) && |
|||
!contentType.IsNullOrWhiteSpace()) |
|||
{ |
|||
context.Success(contentType); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
@ -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,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,25 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.BlobManagement.MimeCheck</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.BlobManagement.MimeCheck</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<Nullable>enable</Nullable> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="MimeCheck" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.BlobManagement.Domain\LINGYUN.Abp.BlobManagement.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,15 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement.MimeCheck; |
|||
|
|||
[DependsOn(typeof(AbpBlobManagementDomainModule))] |
|||
public class AbpBlobManagementMimeCheckModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBlobManagementContentTypeResolveOptions>(options => |
|||
{ |
|||
options.BlobContentTypeResolvers.Add(new MimeCheckBlobContentTypeResolveContributor()); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using MimeCheck.Detection; |
|||
using MimeCheck.Validation; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.BlobManagement.MimeCheck; |
|||
|
|||
public class MimeCheckBlobContentTypeResolveContributor : IBlobContentTypeResolveContributor |
|||
{ |
|||
public string Name => "MimeCheck"; |
|||
public int Priority => 200; |
|||
|
|||
public async virtual Task ResolveAsync(BlobContentTypeResolveContext context) |
|||
{ |
|||
try |
|||
{ |
|||
var streamResult = await MimeValidator.DetectAsync(context.Stream); |
|||
if (!streamResult.MimeType.IsNullOrWhiteSpace() && |
|||
!string.Equals(streamResult.MimeType, DetectionResult.Unknown.MimeType)) |
|||
{ |
|||
context.Success(streamResult.MimeType); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
context.ServiceProvider |
|||
.GetService<ILogger<MimeCheckBlobContentTypeResolveContributor>>() |
|||
?.LogWarning("Failed to decetion the blob mime type: {message}!", ex.Message); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue