82 changed files with 3600 additions and 33 deletions
@ -0,0 +1,13 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\common\LINGYUN.Abp.BlobStoring.Aliyun\LINGYUN.Abp.BlobStoring.Aliyun.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.FileManagement.Domain\LINGYUN.Abp.FileManagement.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,17 @@ |
|||
using LINGYUN.Abp.BlobStoring.Aliyun; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.Aliyun |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpBlobStoringAliyunModule), |
|||
typeof(AbpFileManagementDomainModule))] |
|||
public class AbpFileManagementAliyunModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddTransient<IOssContainerFactory, AliyunOssContainerFactory>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,369 @@ |
|||
using Aliyun.OSS; |
|||
using LINGYUN.Abp.BlobStoring.Aliyun; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.Aliyun |
|||
{ |
|||
/// <summary>
|
|||
/// Oss容器的阿里云实现
|
|||
/// </summary>
|
|||
internal class AliyunOssContainer : IOssContainer |
|||
{ |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IOssClientFactory OssClientFactory { get; } |
|||
public AliyunOssContainer( |
|||
ICurrentTenant currentTenant, |
|||
IOssClientFactory ossClientFactory) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
OssClientFactory = ossClientFactory; |
|||
} |
|||
public virtual async Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) |
|||
{ |
|||
var ossClient = await CreateClientAsync(); |
|||
|
|||
var path = GetBasePath(request.Path); |
|||
var aliyunRequest = new DeleteObjectsRequest(request.Bucket, request.Objects.Select(x => x += path).ToList()); |
|||
|
|||
ossClient.DeleteObjects(aliyunRequest); |
|||
} |
|||
|
|||
public virtual async Task<OssContainer> CreateAsync(string name) |
|||
{ |
|||
var ossClient = await CreateClientAsync(); |
|||
|
|||
if (BucketExists(ossClient, name)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ContainerAlreadyExists); |
|||
} |
|||
|
|||
var bucket = ossClient.CreateBucket(name); |
|||
|
|||
return new OssContainer( |
|||
bucket.Name, |
|||
bucket.CreationDate, |
|||
0L, |
|||
bucket.CreationDate, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "Id", bucket.Owner?.Id }, |
|||
{ "DisplayName", bucket.Owner?.DisplayName } |
|||
}); |
|||
} |
|||
|
|||
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 (ObjectExists(ossClient, request.Bucket, objectName)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.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)) |
|||
{ |
|||
ossClient.CreateBucket(request.Bucket); |
|||
} |
|||
|
|||
var aliyunObjectRequest = new PutObjectRequest(request.Bucket, objectName, request.Content) |
|||
{ |
|||
Metadata = new ObjectMetadata() |
|||
}; |
|||
if (request.ExpirationTime.HasValue) |
|||
{ |
|||
aliyunObjectRequest.Metadata.ExpirationTime = DateTime.Now.Add(request.ExpirationTime.Value); |
|||
} |
|||
|
|||
var aliyunObject = ossClient.PutObject(aliyunObjectRequest); |
|||
|
|||
var ossObject = new OssObject( |
|||
!objectPath.IsNullOrWhiteSpace() |
|||
? objectName.Replace(objectPath, "") |
|||
: objectName, |
|||
objectPath, |
|||
DateTime.Now, |
|||
aliyunObject.ContentLength, |
|||
DateTime.Now, |
|||
aliyunObject.ResponseMetadata, |
|||
objectName.EndsWith("/") // 名称结尾是 / 符号的则为目录:https://help.aliyun.com/document_detail/31910.html
|
|||
) |
|||
{ |
|||
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) |
|||
{ |
|||
var ossClient = await CreateClientAsync(); |
|||
|
|||
if (BucketExists(ossClient, name)) |
|||
{ |
|||
ossClient.DeleteBucket(name); |
|||
} |
|||
} |
|||
|
|||
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 objectListing = ossClient.ListObjects(request.Bucket, objectName); |
|||
if (objectListing.CommonPrefixes.Any() || |
|||
objectListing.ObjectSummaries.Any()) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ObjectDeleteWithNotEmpty); |
|||
// throw new ObjectDeleteWithNotEmptyException("00201", $"Can't not delete oss object {request.Object}, because it is not empty!");
|
|||
} |
|||
ossClient.DeleteObject(request.Bucket, objectName); |
|||
} |
|||
} |
|||
|
|||
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: FileManagementErrorCodes.ContainerNotFound); |
|||
// throw new ContainerNotFoundException($"Can't not found container {name} in aliyun blob storing");
|
|||
} |
|||
var bucket = ossClient.GetBucketInfo(name); |
|||
|
|||
return new OssContainer( |
|||
bucket.Bucket.Name, |
|||
bucket.Bucket.CreationDate, |
|||
0L, |
|||
bucket.Bucket.CreationDate, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "Id", bucket.Bucket.Owner?.Id }, |
|||
{ "DisplayName", bucket.Bucket.Owner?.DisplayName } |
|||
}); |
|||
} |
|||
|
|||
public virtual async Task<OssObject> GetObjectAsync(GetOssObjectRequest request) |
|||
{ |
|||
var ossClient = await CreateClientAsync(); |
|||
if (!BucketExists(ossClient, request.Bucket)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.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: FileManagementErrorCodes.ObjectNotFound); |
|||
// throw new ContainerNotFoundException($"Can't not found object {objectName} in container {request.Bucket} with aliyun blob storing");
|
|||
} |
|||
|
|||
var aliyunOssObjectRequest = new GetObjectRequest(request.Bucket, objectName, request.Process); |
|||
var aliyunOssObject = ossClient.GetObject(aliyunOssObjectRequest); |
|||
var ossObject = new OssObject( |
|||
!objectPath.IsNullOrWhiteSpace() |
|||
? aliyunOssObject.Key.Replace(objectPath, "") |
|||
: aliyunOssObject.Key, |
|||
request.Path, |
|||
aliyunOssObject.Metadata.LastModified, |
|||
aliyunOssObject.Metadata.ContentLength, |
|||
aliyunOssObject.Metadata.LastModified, |
|||
aliyunOssObject.Metadata.UserMetadata, |
|||
aliyunOssObject.Key.EndsWith("/")) |
|||
{ |
|||
FullName = aliyunOssObject.Key |
|||
}; |
|||
|
|||
if (aliyunOssObject.IsSetResponseStream()) |
|||
{ |
|||
var memoryStream = new MemoryStream(); |
|||
await aliyunOssObject.Content.CopyToAsync(memoryStream); |
|||
memoryStream.Seek(0, SeekOrigin.Begin); |
|||
ossObject.SetContent(memoryStream); |
|||
} |
|||
|
|||
return ossObject; |
|||
} |
|||
|
|||
public virtual async Task<GetOssContainersResponse> GetListAsync(GetOssContainersRequest request) |
|||
{ |
|||
var ossClient = await CreateClientAsync(); |
|||
|
|||
var aliyunRequest = new ListBucketsRequest |
|||
{ |
|||
Marker = request.Marker, |
|||
Prefix = request.Prefix, |
|||
MaxKeys = request.MaxKeys |
|||
}; |
|||
var bucketsResponse = ossClient.ListBuckets(aliyunRequest); |
|||
|
|||
return new GetOssContainersResponse( |
|||
bucketsResponse.Prefix, |
|||
bucketsResponse.Marker, |
|||
bucketsResponse.NextMaker, |
|||
bucketsResponse.MaxKeys ?? 0, |
|||
bucketsResponse.Buckets |
|||
.Select(x => new OssContainer( |
|||
x.Name, |
|||
x.CreationDate, |
|||
0L, |
|||
x.CreationDate, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "Id", x.Owner?.Id }, |
|||
{ "DisplayName", x.Owner?.DisplayName } |
|||
})) |
|||
.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; |
|||
var aliyunRequest = new ListObjectsRequest(request.BucketName) |
|||
{ |
|||
Marker = !marker.IsNullOrWhiteSpace() ? objectPath + marker : marker, |
|||
Prefix = objectPath, |
|||
MaxKeys = request.MaxKeys, |
|||
EncodingType = request.EncodingType, |
|||
Delimiter = request.Delimiter |
|||
}; |
|||
var objectsResponse = ossClient.ListObjects(aliyunRequest); |
|||
|
|||
var ossObjects = objectsResponse.ObjectSummaries |
|||
.Where(x => !x.Key.Equals(objectsResponse.Prefix))// 过滤当前的目录返回值
|
|||
.Select(x => new OssObject( |
|||
!objectPath.IsNullOrWhiteSpace() && !x.Key.Equals(objectPath) |
|||
? x.Key.Replace(objectPath, "") |
|||
: x.Key, // 去除目录名称
|
|||
request.Prefix, |
|||
x.LastModified, |
|||
x.Size, |
|||
x.LastModified, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "Id", x.Owner?.Id }, |
|||
{ "DisplayName", x.Owner?.DisplayName } |
|||
}, |
|||
x.Key.EndsWith("/")) |
|||
{ |
|||
FullName = x.Key |
|||
}) |
|||
.ToList(); |
|||
// 当 Delimiter 为 / 时, objectsResponse.CommonPrefixes 可用于代表层级目录
|
|||
if (objectsResponse.CommonPrefixes.Any()) |
|||
{ |
|||
ossObjects.InsertRange(0, |
|||
objectsResponse.CommonPrefixes |
|||
.Select(x => new OssObject( |
|||
x.Replace(objectPath, ""), |
|||
request.Prefix, |
|||
null, |
|||
0L, |
|||
null, |
|||
null, |
|||
true))); |
|||
} |
|||
// 排序
|
|||
// TODO: 是否需要客户端来排序
|
|||
ossObjects.Sort(new OssObjectComparer()); |
|||
|
|||
return new GetOssObjectsResponse( |
|||
objectsResponse.BucketName, |
|||
request.Prefix, |
|||
marker, |
|||
!objectPath.IsNullOrWhiteSpace() && !objectsResponse.NextMarker.IsNullOrWhiteSpace() |
|||
? objectsResponse.NextMarker.Replace(objectPath, "") |
|||
: objectsResponse.NextMarker, |
|||
objectsResponse.Delimiter, |
|||
objectsResponse.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(IOss client, string bucketName) |
|||
{ |
|||
return client.DoesBucketExist(bucketName); |
|||
} |
|||
|
|||
protected virtual bool ObjectExists(IOss client, string bucketName, string objectName) |
|||
{ |
|||
return client.DoesObjectExist(bucketName, objectName); |
|||
} |
|||
|
|||
protected virtual async Task<IOss> CreateClientAsync() |
|||
{ |
|||
return await OssClientFactory.CreateAsync<AbpFileManagementContainer>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using LINGYUN.Abp.BlobStoring.Aliyun; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.Aliyun |
|||
{ |
|||
public class AliyunOssContainerFactory : IOssContainerFactory |
|||
{ |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IOssClientFactory OssClientFactory { get; } |
|||
|
|||
public AliyunOssContainerFactory( |
|||
ICurrentTenant currentTenant, |
|||
IOssClientFactory ossClientFactory) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
OssClientFactory = ossClientFactory; |
|||
} |
|||
|
|||
public IOssContainer Create() |
|||
{ |
|||
return new AliyunOssContainer( |
|||
CurrentTenant, |
|||
OssClientFactory); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
# LINGYUN.Abp.FileManagement.Aliyun |
|||
|
|||
阿里云oss容器接口 |
|||
|
|||
## 配置使用 |
|||
|
|||
模块按需引用 |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpFileManagementAliyunModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
// other |
|||
} |
|||
``` |
|||
@ -0,0 +1,15 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class BulkDeleteOssObjectInput |
|||
{ |
|||
[Required] |
|||
public string Bucket { get; set; } |
|||
|
|||
public string Path { get; set; } |
|||
|
|||
[Required] |
|||
public string[] Objects { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class CreateOssObjectInput |
|||
{ |
|||
public string Bucket { get; set; } |
|||
public string Path { get; set; } |
|||
public string Object { get; set; } |
|||
public Stream Content { get; set; } |
|||
public TimeSpan? ExpirationTime { get; set; } |
|||
|
|||
public void SetContent(Stream content) |
|||
{ |
|||
Content = content; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssContainersInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Prefix { get; set; } |
|||
public string Marker { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssObjectInput |
|||
{ |
|||
[Required] |
|||
public string Bucket { get; set; } |
|||
|
|||
public string Path { get; set; } |
|||
|
|||
[Required] |
|||
public string Object { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssObjectsInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Bucket { get; set; } |
|||
public string Prefix { get; set; } |
|||
public string Delimiter { get; set; } |
|||
public string Marker { get; set; } |
|||
public string EncodingType { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetStaticFileInput |
|||
{ |
|||
[Required] |
|||
public string Name { get; set; } |
|||
|
|||
public string Path { get; set; } |
|||
|
|||
public string Bucket { get; set; } |
|||
|
|||
public string Process { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public interface IOssContainerAppService: IApplicationService |
|||
{ |
|||
Task<OssContainerDto> CreateAsync(string name); |
|||
|
|||
Task<OssContainerDto> GetAsync(string name); |
|||
|
|||
Task DeleteAsync(string name); |
|||
|
|||
Task<OssContainersResultDto> GetListAsync(GetOssContainersInput input); |
|||
|
|||
Task<OssObjectsResultDto> GetObjectListAsync(GetOssObjectsInput input); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public interface IOssObjectAppService : IApplicationService |
|||
{ |
|||
Task<OssObjectDto> CreateAsync(CreateOssObjectInput input); |
|||
|
|||
Task<OssObjectDto> GetAsync(GetOssObjectInput input); |
|||
|
|||
Task DeleteAsync(GetOssObjectInput input); |
|||
|
|||
Task BulkDeleteAsync(BulkDeleteOssObjectInput input); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public interface IStaticFilesAppService: IApplicationService |
|||
{ |
|||
Task<Stream> GetAsync(GetStaticFileInput input); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssContainerDto |
|||
{ |
|||
public string Name { get; set; } |
|||
public long Size { get; set; } |
|||
public DateTime CreationDate { get; set; } |
|||
public DateTime? LastModifiedDate { get; set; } |
|||
public IDictionary<string, string> Metadata { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssContainersResultDto |
|||
{ |
|||
public string Prefix { get; set; } |
|||
public string Marker { get; set; } |
|||
public string NextMarker { get; set; } |
|||
public int MaxKeys { get; set; } |
|||
public List<OssContainerDto> Containers { get; set; } = new List<OssContainerDto>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssObjectDto |
|||
{ |
|||
public bool IsFolder { get; set; } |
|||
public string Path { get; set; } |
|||
public string Name { get; set; } |
|||
public long Size { get; set; } |
|||
public DateTime? CreationDate { get; set; } |
|||
public DateTime? LastModifiedDate { get; set; } |
|||
public IDictionary<string, string> Metadata { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssObjectsResultDto |
|||
{ |
|||
public string Bucket { get; set; } |
|||
public string Prefix { get; set; } |
|||
public string Delimiter { get; set; } |
|||
public string Marker { get; set; } |
|||
public string NextMarker { get; set; } |
|||
public int MaxKeys { get; set; } |
|||
public List<OssObjectDto> Objects { get; set; } = new List<OssObjectDto>(); |
|||
} |
|||
} |
|||
@ -1,12 +1,23 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Modularity; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAutoMapperModule), |
|||
typeof(AbpFileManagementDomainModule), |
|||
typeof(AbpFileManagementApplicationContractsModule))] |
|||
public class AbpFileManagementApplicationModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<AbpFileManagementApplicationModule>(); |
|||
|
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddProfile<FileManagementApplicationAutoMapperProfile>(validate: true); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,17 @@ |
|||
using AutoMapper; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class FileManagementApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public FileManagementApplicationAutoMapperProfile() |
|||
{ |
|||
CreateMap<OssContainer, OssContainerDto>(); |
|||
CreateMap<OssObject, OssObjectDto>() |
|||
.ForMember(dto => dto.Path, map => map.MapFrom(src => src.Prefix)); |
|||
|
|||
CreateMap<GetOssContainersResponse, OssContainersResultDto>(); |
|||
CreateMap<GetOssObjectsResponse, OssObjectsResultDto>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssContainerAppService : FileManagementApplicationServiceBase, IOssContainerAppService |
|||
{ |
|||
protected IOssContainerFactory OssContainerFactory { get; } |
|||
|
|||
public OssContainerAppService( |
|||
IOssContainerFactory ossContainerFactory) |
|||
{ |
|||
OssContainerFactory = ossContainerFactory; |
|||
} |
|||
|
|||
public virtual async Task<OssContainerDto> CreateAsync(string name) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
var container = await oss.CreateAsync(name); |
|||
|
|||
return ObjectMapper.Map<OssContainer, OssContainerDto>(container); |
|||
} |
|||
|
|||
public virtual async Task DeleteAsync(string name) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
|
|||
await oss.DeleteAsync(name); |
|||
} |
|||
|
|||
public virtual async Task<OssContainerDto> GetAsync(string name) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
var container = await oss.GetAsync(name); |
|||
|
|||
return ObjectMapper.Map<OssContainer, OssContainerDto>(container); |
|||
} |
|||
|
|||
public virtual async Task<OssContainersResultDto> GetListAsync(GetOssContainersInput input) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
|
|||
var containerResponse = await oss.GetListAsync( |
|||
input.Prefix, input.Marker, input.MaxResultCount); |
|||
|
|||
return ObjectMapper.Map<GetOssContainersResponse, OssContainersResultDto>(containerResponse); |
|||
} |
|||
|
|||
public virtual async Task<OssObjectsResultDto> GetObjectListAsync(GetOssObjectsInput input) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
|
|||
var ossObjectResponse = await oss.GetObjectsAsync( |
|||
input.Bucket, input.Prefix, input.Marker, |
|||
input.Delimiter, input.EncodingType, |
|||
input.MaxResultCount); |
|||
|
|||
return ObjectMapper.Map<GetOssObjectsResponse, OssObjectsResultDto>(ossObjectResponse); |
|||
} |
|||
|
|||
protected virtual IOssContainer CreateOssContainer() |
|||
{ |
|||
return OssContainerFactory.Create(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
using LINGYUN.Abp.FileManagement.Settings; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.IO; |
|||
using Volo.Abp.Settings; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssObjectAppService : FileManagementApplicationServiceBase, IOssObjectAppService |
|||
{ |
|||
protected IOssContainerFactory OssContainerFactory { get; } |
|||
|
|||
public OssObjectAppService( |
|||
IOssContainerFactory ossContainerFactory) |
|||
{ |
|||
OssContainerFactory = ossContainerFactory; |
|||
} |
|||
|
|||
public virtual async Task<OssObjectDto> CreateAsync(CreateOssObjectInput input) |
|||
{ |
|||
if (!input.Content.IsNullOrEmpty()) |
|||
{ |
|||
// 检查文件大小
|
|||
var fileSizeLimited = await SettingProvider |
|||
.GetAsync( |
|||
AbpFileManagementSettingNames.FileLimitLength, |
|||
AbpFileManagementSettingNames.DefaultFileLimitLength); |
|||
if (fileSizeLimited * 1024 * 1024 < input.Content.Length) |
|||
{ |
|||
ThrowValidationException(L["UploadFileSizeBeyondLimit", fileSizeLimited], nameof(input.Content)); |
|||
} |
|||
|
|||
// 文件扩展名
|
|||
var fileExtensionName = FileHelper.GetExtension(input.Object); |
|||
var fileAllowExtension = await SettingProvider.GetOrNullAsync(AbpFileManagementSettingNames.AllowFileExtensions); |
|||
// 检查文件扩展名
|
|||
if (!fileAllowExtension.Split(',') |
|||
.Any(fe => fe.Equals(fileExtensionName, StringComparison.CurrentCultureIgnoreCase))) |
|||
{ |
|||
ThrowValidationException(L["NotAllowedFileExtensionName", fileExtensionName], "FileName"); |
|||
} |
|||
} |
|||
|
|||
var oss = CreateOssContainer(); |
|||
|
|||
var createOssObjectRequest = new CreateOssObjectRequest( |
|||
input.Bucket, |
|||
input.Object, |
|||
input.Content, |
|||
input.Path, |
|||
input.ExpirationTime); |
|||
var ossObject = await oss.CreateObjectAsync(createOssObjectRequest); |
|||
|
|||
return ObjectMapper.Map<OssObject, OssObjectDto>(ossObject); |
|||
} |
|||
|
|||
public virtual async Task BulkDeleteAsync(BulkDeleteOssObjectInput input) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
|
|||
await oss.BulkDeleteObjectsAsync(input.Bucket, input.Objects, input.Path); |
|||
} |
|||
|
|||
public virtual async Task DeleteAsync(GetOssObjectInput input) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
|
|||
await oss.DeleteObjectAsync(input.Bucket, input.Object, input.Path); |
|||
} |
|||
|
|||
public virtual async Task<OssObjectDto> GetAsync(GetOssObjectInput input) |
|||
{ |
|||
var oss = CreateOssContainer(); |
|||
|
|||
var ossObject = await oss.GetObjectAsync(input.Bucket, input.Object, input.Path); |
|||
|
|||
return ObjectMapper.Map<OssObject, OssObjectDto>(ossObject); |
|||
} |
|||
|
|||
protected virtual IOssContainer CreateOssContainer() |
|||
{ |
|||
return OssContainerFactory.Create(); |
|||
} |
|||
|
|||
private static void ThrowValidationException(string message, string memberName) |
|||
{ |
|||
throw new AbpValidationException(message, |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult(message, new[] {memberName}) |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using System.Web; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class StaticFilesAppService : FileManagementApplicationServiceBase, IStaticFilesAppService |
|||
{ |
|||
protected IOssContainerFactory OssContainerFactory { get; } |
|||
|
|||
public StaticFilesAppService( |
|||
IOssContainerFactory ossContainerFactory) |
|||
{ |
|||
OssContainerFactory = ossContainerFactory; |
|||
} |
|||
|
|||
public virtual async Task<Stream> GetAsync(GetStaticFileInput input) |
|||
{ |
|||
var ossObjectRequest = new GetOssObjectRequest( |
|||
HttpUtility.UrlDecode(input.Bucket), // 需要处理特殊字符
|
|||
HttpUtility.UrlDecode(input.Name), |
|||
HttpUtility.UrlDecode(input.Path), |
|||
HttpUtility.UrlDecode(input.Process)); |
|||
|
|||
var ossContainer = OssContainerFactory.Create(); |
|||
var ossObject = await ossContainer.GetObjectAsync(ossObjectRequest); |
|||
|
|||
return ossObject.Content; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public static class FileManagementErrorCodes |
|||
{ |
|||
public const string Namespace = "Abp.FileManagement"; |
|||
|
|||
public const string ContainerDeleteWithNotEmpty = Namespace + ":010001"; |
|||
public const string ContainerAlreadyExists = Namespace + ":010402"; |
|||
public const string ContainerNotFound = Namespace + ":010404"; |
|||
|
|||
public const string ObjectDeleteWithNotEmpty = Namespace + ":020001"; |
|||
public const string ObjectAlreadyExists = Namespace + ":020402"; |
|||
public const string ObjectNotFound = Namespace + ":020404"; |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
namespace System.IO |
|||
{ |
|||
public static class StreamExtensions |
|||
{ |
|||
public static bool IsNullOrEmpty( |
|||
this Stream stream) |
|||
{ |
|||
return stream == null || |
|||
Equals(stream, Stream.Null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class BulkDeleteObjectRequest |
|||
{ |
|||
public string Bucket { get; } |
|||
public string Path { get; } |
|||
public ICollection<string> Objects { get; } |
|||
|
|||
public BulkDeleteObjectRequest( |
|||
[NotNull] string bucket, |
|||
ICollection<string> objects, |
|||
string path = "") |
|||
{ |
|||
Check.NotNullOrWhiteSpace(bucket, nameof(bucket)); |
|||
Check.NotNullOrEmpty(objects, nameof(objects)); |
|||
|
|||
Bucket = bucket; |
|||
Objects = objects; |
|||
Path = path; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using JetBrains.Annotations; |
|||
using System; |
|||
using System.IO; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class CreateOssObjectRequest |
|||
{ |
|||
public string Bucket { get; } |
|||
public string Path { get; } |
|||
public string Object { get; } |
|||
public Stream Content { get; private set; } |
|||
public TimeSpan? ExpirationTime { get; } |
|||
public CreateOssObjectRequest( |
|||
[NotNull] string bucket, |
|||
[NotNull] string @object, |
|||
[CanBeNull] Stream content, |
|||
[CanBeNull] string path = null, |
|||
[CanBeNull] TimeSpan? expirationTime = null) |
|||
{ |
|||
Bucket = Check.NotNullOrWhiteSpace(bucket, nameof(bucket)); |
|||
Object = Check.NotNullOrWhiteSpace(@object, nameof(@object)); |
|||
|
|||
Path = path; |
|||
Content = content; |
|||
ExpirationTime = expirationTime; |
|||
} |
|||
|
|||
public void SetContent(Stream content) |
|||
{ |
|||
Content = content; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssContainersRequest |
|||
{ |
|||
public string Prefix { get; } |
|||
public string Marker { get; } |
|||
public int? MaxKeys { get; } |
|||
public GetOssContainersRequest( |
|||
string prefix = null, |
|||
string marker = null, |
|||
int? maxKeys = 10) |
|||
{ |
|||
Prefix = prefix; |
|||
Marker = marker; |
|||
MaxKeys = maxKeys; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssContainersResponse |
|||
{ |
|||
public string Prefix { get; } |
|||
public string Marker { get; } |
|||
public string NextMarker { get; } |
|||
public int MaxKeys { get; } |
|||
public List<OssContainer> Containers { get; } |
|||
|
|||
public GetOssContainersResponse( |
|||
string prefix, |
|||
string marker, |
|||
string nextMarker, |
|||
int maxKeys, |
|||
List<OssContainer> containers) |
|||
{ |
|||
Prefix = prefix; |
|||
Marker = marker; |
|||
NextMarker = nextMarker; |
|||
MaxKeys = maxKeys; |
|||
|
|||
Containers = containers; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssObjectRequest |
|||
{ |
|||
public string Bucket { get; } |
|||
public string Path { get; } |
|||
public string Object { get; } |
|||
/// <summary>
|
|||
/// 需要处理文件的参数
|
|||
/// </summary>
|
|||
public string Process { get; } |
|||
|
|||
public GetOssObjectRequest( |
|||
[NotNull] string bucket, |
|||
[NotNull] string @object, |
|||
[CanBeNull] string path = "", |
|||
[CanBeNull] string process = "") |
|||
{ |
|||
Check.NotNullOrWhiteSpace(bucket, nameof(bucket)); |
|||
Check.NotNullOrWhiteSpace(@object, nameof(@object)); |
|||
|
|||
Bucket = bucket; |
|||
Object = @object; |
|||
Path = path; |
|||
Process = process; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssObjectsRequest |
|||
{ |
|||
public string BucketName { get; } |
|||
public string Prefix { get; } |
|||
public string Delimiter { get; } |
|||
public string Marker { get; } |
|||
public string EncodingType { get; } |
|||
public int? MaxKeys { get; } |
|||
public GetOssObjectsRequest( |
|||
[NotNull] string bucketName, |
|||
string prefix = null, |
|||
string marker = null, |
|||
string delimiter = null, |
|||
string encodingType = null, |
|||
int maxKeys = 10) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(bucketName, nameof(bucketName)); |
|||
|
|||
BucketName = bucketName; |
|||
Prefix = prefix; |
|||
Marker = marker; |
|||
Delimiter = delimiter; |
|||
EncodingType = encodingType; |
|||
MaxKeys = maxKeys; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class GetOssObjectsResponse |
|||
{ |
|||
public string Bucket { get; } |
|||
public string Prefix { get; } |
|||
public string Delimiter { get; } |
|||
public string Marker { get; } |
|||
public string NextMarker { get; } |
|||
public int MaxKeys { get; } |
|||
public List<OssObject> Objects { get; } |
|||
public GetOssObjectsResponse( |
|||
string bucket, |
|||
string prefix, |
|||
string marker, |
|||
string nextMarker, |
|||
string delimiter, |
|||
int maxKeys, |
|||
List<OssObject> ossObjects) |
|||
{ |
|||
Bucket = bucket; |
|||
Prefix = prefix; |
|||
Marker = marker; |
|||
NextMarker = nextMarker; |
|||
Delimiter = delimiter; |
|||
MaxKeys = maxKeys; |
|||
|
|||
Objects = ossObjects; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
/// <summary>
|
|||
/// Oss容器
|
|||
/// </summary>
|
|||
public interface IOssContainer |
|||
{ |
|||
/// <summary>
|
|||
/// 创建容器
|
|||
/// </summary>
|
|||
/// <param name="name"></param>
|
|||
/// <returns></returns>
|
|||
Task<OssContainer> CreateAsync(string name); |
|||
/// <summary>
|
|||
/// 创建Oss对象
|
|||
/// </summary>
|
|||
/// <param name="request"></param>
|
|||
/// <returns></returns>
|
|||
Task<OssObject> CreateObjectAsync(CreateOssObjectRequest request); |
|||
/// <summary>
|
|||
/// 获取容器信息
|
|||
/// </summary>
|
|||
/// <param name="name"></param>
|
|||
/// <returns></returns>
|
|||
Task<OssContainer> GetAsync(string name); |
|||
/// <summary>
|
|||
/// 获取Oss对象信息
|
|||
/// </summary>
|
|||
/// <param name="request"></param>
|
|||
/// <returns></returns>
|
|||
Task<OssObject> GetObjectAsync(GetOssObjectRequest request); |
|||
/// <summary>
|
|||
/// 删除容器
|
|||
/// </summary>
|
|||
/// <param name="name"></param>
|
|||
/// <returns></returns>
|
|||
Task DeleteAsync(string name); |
|||
/// <summary>
|
|||
/// 删除Oss对象
|
|||
/// </summary>
|
|||
/// <param name="request"></param>
|
|||
/// <returns></returns>
|
|||
Task DeleteObjectAsync(GetOssObjectRequest request); |
|||
/// <summary>
|
|||
/// 批量删除Oss对象
|
|||
/// </summary>
|
|||
/// <param name="request"></param>
|
|||
/// <returns></returns>
|
|||
Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request); |
|||
/// <summary>
|
|||
/// 容器是否存在
|
|||
/// </summary>
|
|||
/// <param name="name"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> ExistsAsync(string name); |
|||
/// <summary>
|
|||
/// 获取容器列表
|
|||
/// </summary>
|
|||
/// <param name="request"></param>
|
|||
/// <returns></returns>
|
|||
Task<GetOssContainersResponse> GetListAsync(GetOssContainersRequest request); |
|||
/// <summary>
|
|||
/// 获取对象列表
|
|||
/// </summary>
|
|||
/// <param name="request"></param>
|
|||
/// <returns></returns>
|
|||
Task<GetOssObjectsResponse> GetObjectsAsync(GetOssObjectsRequest request); |
|||
// Task<List<OssObject>> GetObjectsAsync(string name, string prefix = null, string marker = null, string delimiter = null, int maxResultCount = 10);
|
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public static class IOssContainerExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// 如果不存在容器则创建
|
|||
/// </summary>
|
|||
/// <param name="ossContainer"></param>
|
|||
/// <param name="name"></param>
|
|||
/// <returns>返回容器信息</returns>
|
|||
public static async Task<OssContainer> CreateIfNotExistsAsync( |
|||
this IOssContainer ossContainer, |
|||
string name) |
|||
{ |
|||
if (! await ossContainer.ExistsAsync(name)) |
|||
{ |
|||
await ossContainer.CreateAsync(name); |
|||
} |
|||
|
|||
return await ossContainer.GetAsync(name); |
|||
} |
|||
|
|||
public static async Task DeleteObjectAsync( |
|||
this IOssContainer ossContainer, |
|||
string bucket, |
|||
string @object, |
|||
string path = "") |
|||
{ |
|||
await ossContainer.DeleteObjectAsync( |
|||
new GetOssObjectRequest(bucket, @object, path)); |
|||
} |
|||
|
|||
public static async Task BulkDeleteObjectsAsync( |
|||
this IOssContainer ossContainer, |
|||
string bucketName, |
|||
ICollection<string> objectNames, |
|||
string path = "") |
|||
{ |
|||
await ossContainer.BulkDeleteObjectsAsync( |
|||
new BulkDeleteObjectRequest(bucketName, objectNames, path)); |
|||
} |
|||
|
|||
public static async Task<GetOssContainersResponse> GetListAsync( |
|||
this IOssContainer ossContainer, |
|||
string prefix = null, |
|||
string marker = null, |
|||
int maxResultCount = 10) |
|||
{ |
|||
return await ossContainer.GetListAsync( |
|||
new GetOssContainersRequest(prefix, marker, maxResultCount)); |
|||
} |
|||
|
|||
public static async Task<OssObject> GetObjectAsync( |
|||
this IOssContainer ossContainer, |
|||
string bucket, |
|||
string @object, |
|||
string path = "") |
|||
{ |
|||
return await ossContainer.GetObjectAsync( |
|||
new GetOssObjectRequest(bucket, @object, path)); |
|||
} |
|||
|
|||
public static async Task<GetOssObjectsResponse> GetObjectsAsync( |
|||
this IOssContainer ossContainer, |
|||
string name, |
|||
string prefix = null, |
|||
string marker = null, |
|||
string delimiter = null, |
|||
string encodingType = null, |
|||
int maxResultCount = 10) |
|||
{ |
|||
return await ossContainer.GetObjectsAsync( |
|||
new GetOssObjectsRequest(name, prefix, marker, delimiter, encodingType, maxResultCount)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
/// <summary>
|
|||
/// Oss容器构建工厂
|
|||
/// </summary>
|
|||
public interface IOssContainerFactory |
|||
{ |
|||
IOssContainer Create(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Oss容器构建工厂
|
|||
/// </summary>
|
|||
public interface IOssContainerFactory<TConfiguration> |
|||
{ |
|||
IOssContainer Create(TConfiguration configuration); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
/// <summary>
|
|||
/// 描述了一个容器的状态信息
|
|||
/// </summary>
|
|||
public class OssContainer |
|||
{ |
|||
public string Name { get; } |
|||
public long Size { get; } |
|||
public DateTime CreationDate { get; } |
|||
public DateTime? LastModifiedDate { get; } |
|||
public IDictionary<string, string> Metadata { get; } |
|||
|
|||
public OssContainer( |
|||
string name, |
|||
DateTime creationDate, |
|||
long size = 0, |
|||
DateTime? lastModifiedDate = null, |
|||
IDictionary<string, string> metadata = null) |
|||
{ |
|||
Name = name; |
|||
CreationDate = creationDate; |
|||
LastModifiedDate = lastModifiedDate; |
|||
Size = size; |
|||
Metadata = metadata ?? new Dictionary<string, string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
/// <summary>
|
|||
/// 描述了一个对象的状态信息
|
|||
/// </summary>
|
|||
public class OssObject |
|||
{ |
|||
private Stream _content; |
|||
|
|||
public bool IsFolder { get; } |
|||
public string Name { get; } |
|||
public string FullName { get; set; } |
|||
public string Prefix { get; } |
|||
public long Size { get; } |
|||
public Stream Content => _content; |
|||
public DateTime? CreationDate { get; } |
|||
public DateTime? LastModifiedDate { get; } |
|||
public IDictionary<string, string> Metadata { get; } |
|||
public OssObject( |
|||
string name, |
|||
string prefix, |
|||
DateTime? creationDate = null, |
|||
long size = 0, |
|||
DateTime? lastModifiedDate = null, |
|||
IDictionary<string, string> metadata = null, |
|||
bool isFolder = false) |
|||
{ |
|||
Name = name; |
|||
Prefix = prefix; |
|||
CreationDate = creationDate; |
|||
LastModifiedDate = lastModifiedDate; |
|||
Size = size; |
|||
IsFolder = isFolder; |
|||
Metadata = metadata ?? new Dictionary<string, string>(); |
|||
} |
|||
|
|||
public void SetContent(Stream stream) |
|||
{ |
|||
_content = stream; |
|||
if (!_content.IsNullOrEmpty()) |
|||
{ |
|||
_content.Seek(0, SeekOrigin.Begin); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class OssObjectComparer : IComparer<OssObject> |
|||
{ |
|||
public virtual int Compare(OssObject x, OssObject y) |
|||
{ |
|||
if (x.IsFolder && y.IsFolder) |
|||
{ |
|||
return x.Name.CompareTo(y.Name); |
|||
} |
|||
|
|||
if (x.IsFolder && !y.IsFolder) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
if (!x.IsFolder && y.IsFolder) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
return x.Name.CompareTo(y.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.FileManagement.FileSystem\LINGYUN.Abp.FileManagement.FileSystem.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,16 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem.ImageSharp |
|||
{ |
|||
[DependsOn(typeof(AbpFileManagementFileSystemModule))] |
|||
public class AbpFileManagementFileSystemImageSharpModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<FileSystemOssOptions>(options => |
|||
{ |
|||
options.AddProcesser(new ImageSharpProcesserContributor()); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Drawing.Processing; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem.ImageSharp |
|||
{ |
|||
public class ImageSharpProcesserContributor : IFileSystemOssObjectProcesserContributor |
|||
{ |
|||
protected static readonly string[] ImageTypes = new string[] |
|||
{ |
|||
"6677",// bmp
|
|||
"7173",// gif
|
|||
"13780",// png
|
|||
"255216"// jpg
|
|||
}; |
|||
|
|||
public virtual async Task ProcessAsync(FileSystemOssObjectContext context) |
|||
{ |
|||
var bytes = await context.OssObject.Content.GetAllBytesAsync(); |
|||
|
|||
if (IsImage(bytes)) |
|||
{ |
|||
var args = context.Process.Split(','); |
|||
if (DrawGraphics(bytes, args, out Stream content)) |
|||
{ |
|||
context.SetContent(content); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual bool DrawGraphics(byte[] fileBytes, string[] args, out Stream content) |
|||
{ |
|||
using var image = Image.Load(fileBytes, out IImageFormat format); |
|||
|
|||
// 大小
|
|||
var width = GetInt32Prarm(args, "w_"); |
|||
var height = GetInt32Prarm(args, "h_"); |
|||
if (!width.IsNullOrWhiteSpace() && |
|||
!height.IsNullOrWhiteSpace()) |
|||
{ |
|||
image.Mutate(x => x.Resize(int.Parse(width), int.Parse(height))); |
|||
} |
|||
|
|||
// 水印
|
|||
//var txt = GetString(args, "t_");
|
|||
//if (!txt.IsNullOrWhiteSpace())
|
|||
//{
|
|||
// FontCollection fonts = new FontCollection();
|
|||
// FontFamily fontfamily = fonts.Install("本地字体.TTF");
|
|||
// var font = new Font(fontfamily, 20, FontStyle.Bold);
|
|||
// var size = TextMeasurer.Measure(txt, new RendererOptions(font));
|
|||
|
|||
// image.Mutate(x => x.DrawText(txt, font, Color.WhiteSmoke,
|
|||
// new PointF(image.Width - size.Width - 3, image.Height - size.Height - 3)));
|
|||
//}
|
|||
|
|||
// TODO: 其他处理参数及现有的优化
|
|||
|
|||
var imageStream = new MemoryStream(); |
|||
var encoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(format); |
|||
image.Save(imageStream, encoder); |
|||
imageStream.Seek(0, SeekOrigin.Begin); |
|||
|
|||
content = imageStream; |
|||
return true; |
|||
} |
|||
|
|||
private static bool IsImage(byte[] fileBytes) |
|||
{ |
|||
if (fileBytes.IsNullOrEmpty()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
string fileclass = ""; |
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
fileclass += fileBytes[i].ToString(); |
|||
} |
|||
|
|||
return ImageTypes.Any(type => type.Equals(fileclass)); |
|||
} |
|||
|
|||
private static string GetString(string[] args, string key) |
|||
{ |
|||
if (!args.Any()) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return args |
|||
.Where(arg => arg.StartsWith(key)) |
|||
.Select(arg => arg.Substring(key.Length)) |
|||
.FirstOrDefault(); |
|||
} |
|||
|
|||
private static string GetInt32Prarm(string[] args, string key) |
|||
{ |
|||
if (!args.Any()) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return args |
|||
.Where(arg => arg.StartsWith(key)) |
|||
.Select(arg => arg.Substring(key.Length)) |
|||
.FirstOrDefault(arg => int.TryParse(arg, out _)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
# LINGYUN.Abp.FileManagement.FileSystem.ImageSharp |
|||
|
|||
本地文件系统oss对象ImageSharp图形处理接口 |
|||
|
|||
## 配置使用 |
|||
|
|||
模块按需引用 |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpFileManagementFileSystemImageSharpModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
// other |
|||
} |
|||
``` |
|||
@ -0,0 +1,17 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.1</TargetFramework> |
|||
<LangVersion>8.0</LangVersion> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.BlobStoring.FileSystem" Version="4.2.1" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.FileManagement.Domain\LINGYUN.Abp.FileManagement.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,17 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.BlobStoring.FileSystem; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpBlobStoringFileSystemModule), |
|||
typeof(AbpFileManagementDomainModule))] |
|||
public class AbpFileManagementFileSystemModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddTransient<IOssContainerFactory, FileSystemOssContainerFactory>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,512 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BlobStoring; |
|||
using Volo.Abp.BlobStoring.FileSystem; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.IO; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
/// <summary>
|
|||
/// Oss容器的本地文件系统实现
|
|||
/// </summary>
|
|||
internal class FileSystemOssContainer : IOssContainer |
|||
{ |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IHostEnvironment Environment { get; } |
|||
protected IBlobFilePathCalculator FilePathCalculator { get; } |
|||
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
protected FileSystemOssOptions Options { get; } |
|||
|
|||
public FileSystemOssContainer( |
|||
ICurrentTenant currentTenant, |
|||
IHostEnvironment environment, |
|||
IServiceProvider serviceProvider, |
|||
IBlobFilePathCalculator blobFilePathCalculator, |
|||
IBlobContainerConfigurationProvider configurationProvider, |
|||
IOptions<FileSystemOssOptions> options) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
Environment = environment; |
|||
ServiceProvider = serviceProvider; |
|||
FilePathCalculator = blobFilePathCalculator; |
|||
ConfigurationProvider = configurationProvider; |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public virtual Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) |
|||
{ |
|||
var filesPath = request.Objects.Select(x => CalculateFilePath(request.Bucket, x)); |
|||
|
|||
foreach (var file in filesPath) |
|||
{ |
|||
if (File.Exists(file)) |
|||
{ |
|||
File.Delete(file); |
|||
} |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public virtual Task<OssContainer> CreateAsync(string name) |
|||
{ |
|||
var filePath = CalculateFilePath(name); |
|||
if (!Directory.Exists(filePath)) |
|||
{ |
|||
Directory.CreateDirectory(filePath); |
|||
} |
|||
|
|||
var directoryInfo = new DirectoryInfo(filePath); |
|||
var container = new OssContainer( |
|||
directoryInfo.Name, |
|||
directoryInfo.CreationTime, |
|||
0L, |
|||
directoryInfo.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}); |
|||
|
|||
return Task.FromResult(container); |
|||
} |
|||
|
|||
public virtual async Task<OssObject> CreateObjectAsync(CreateOssObjectRequest request) |
|||
{ |
|||
var objectPath = !request.Path.IsNullOrWhiteSpace() |
|||
? request.Path.EnsureEndsWith('/') |
|||
: ""; |
|||
var objectName = objectPath.IsNullOrWhiteSpace() |
|||
? request.Object |
|||
: objectPath + request.Object; |
|||
|
|||
var filePath = CalculateFilePath(request.Bucket, objectName); |
|||
if (!request.Content.IsNullOrEmpty()) |
|||
{ |
|||
if (File.Exists(filePath)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ObjectAlreadyExists); |
|||
// throw new OssObjectAlreadyExistsException($"Can't not put object {objectName} in container {request.Bucket}, Because a file with the same name already exists in the directory!");
|
|||
} |
|||
|
|||
DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath)); |
|||
|
|||
using (var fileStream = File.Open(filePath, FileMode.CreateNew, FileAccess.Write)) |
|||
{ |
|||
await request.Content.CopyToAsync(fileStream); |
|||
|
|||
await fileStream.FlushAsync(); |
|||
} |
|||
var fileInfo = new FileInfo(filePath); |
|||
var ossObject = new OssObject( |
|||
fileInfo.Name, |
|||
objectPath, |
|||
fileInfo.CreationTime, |
|||
fileInfo.Length, |
|||
fileInfo.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "IsReadOnly", fileInfo.IsReadOnly.ToString() }, |
|||
{ "LastAccessTime", fileInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}) |
|||
{ |
|||
FullName = fileInfo.FullName.Replace(Environment.ContentRootPath, "") |
|||
}; |
|||
ossObject.SetContent(request.Content); |
|||
|
|||
return ossObject; |
|||
} |
|||
else |
|||
{ |
|||
if (Directory.Exists(filePath)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ObjectAlreadyExists); |
|||
// throw new OssObjectAlreadyExistsException($"Can't not put object {objectName} in container {request.Bucket}, Because a file with the same name already exists in the directory!");
|
|||
} |
|||
Directory.CreateDirectory(filePath); |
|||
var directoryInfo = new DirectoryInfo(filePath); |
|||
|
|||
var ossObject = new OssObject( |
|||
directoryInfo.Name.EnsureEndsWith('/'), |
|||
objectPath, |
|||
directoryInfo.CreationTime, |
|||
0L, |
|||
directoryInfo.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}, |
|||
true) |
|||
{ |
|||
FullName = directoryInfo.FullName.Replace(Environment.ContentRootPath, "") |
|||
}; |
|||
ossObject.SetContent(request.Content); |
|||
|
|||
return ossObject; |
|||
} |
|||
} |
|||
|
|||
public virtual Task DeleteAsync(string name) |
|||
{ |
|||
var filePath = CalculateFilePath(name); |
|||
if (!Directory.Exists(filePath)) |
|||
{ |
|||
// 非空目录无法删除
|
|||
if (Directory.GetFiles(filePath).Length > 0) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ContainerDeleteWithNotEmpty); |
|||
// throw new ContainerDeleteWithNotEmptyException("00101", $"Can't not delete container {name}, because it is not empty!");
|
|||
} |
|||
Directory.Delete(filePath); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public virtual Task DeleteObjectAsync(GetOssObjectRequest request) |
|||
{ |
|||
var objectName = request.Path.IsNullOrWhiteSpace() |
|||
? request.Object |
|||
: request.Path.EnsureEndsWith('/') + request.Object; |
|||
var filePath = CalculateFilePath(request.Bucket, objectName); |
|||
if (!File.Exists(filePath)) |
|||
{ |
|||
File.Delete(filePath); |
|||
} |
|||
else if (Directory.Exists(filePath)) |
|||
{ |
|||
if (Directory.GetFiles(filePath).Length > 0) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ObjectDeleteWithNotEmpty); |
|||
} |
|||
Directory.Delete(filePath); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public virtual Task<bool> ExistsAsync(string name) |
|||
{ |
|||
var filePath = CalculateFilePath(name); |
|||
|
|||
return Task.FromResult(Directory.Exists(filePath)); |
|||
} |
|||
|
|||
public virtual Task<OssContainer> GetAsync(string name) |
|||
{ |
|||
var filePath = CalculateFilePath(name); |
|||
if (!Directory.Exists(filePath)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ContainerNotFound); |
|||
// throw new ContainerNotFoundException($"Can't not found container {name} in file system");
|
|||
} |
|||
|
|||
var directoryInfo = new DirectoryInfo(filePath); |
|||
var container = new OssContainer( |
|||
directoryInfo.Name, |
|||
directoryInfo.CreationTime, |
|||
0L, |
|||
directoryInfo.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}); |
|||
|
|||
return Task.FromResult(container); |
|||
} |
|||
|
|||
public virtual async Task<OssObject> GetObjectAsync(GetOssObjectRequest request) |
|||
{ |
|||
var objectPath = !request.Path.IsNullOrWhiteSpace() |
|||
? request.Path.EnsureEndsWith('/') |
|||
: ""; |
|||
var objectName = objectPath.IsNullOrWhiteSpace() |
|||
? request.Object |
|||
: objectPath + request.Object; |
|||
|
|||
var filePath = CalculateFilePath(request.Bucket, objectName); |
|||
if (!File.Exists(filePath)) |
|||
{ |
|||
if (!Directory.Exists(filePath)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ObjectNotFound); |
|||
// throw new ContainerNotFoundException($"Can't not found object {objectName} in container {request.Bucket} with file system");
|
|||
} |
|||
var directoryInfo = new DirectoryInfo(filePath); |
|||
var ossObject = new OssObject( |
|||
directoryInfo.Name.EnsureEndsWith('/'), |
|||
objectPath, |
|||
directoryInfo.CreationTime, |
|||
0L, |
|||
directoryInfo.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}, |
|||
true) |
|||
{ |
|||
FullName = directoryInfo.FullName.Replace(Environment.ContentRootPath, "") |
|||
}; |
|||
return ossObject; |
|||
} |
|||
else |
|||
{ |
|||
var fileInfo = new FileInfo(filePath); |
|||
var ossObject = new OssObject( |
|||
fileInfo.Name, |
|||
objectPath, |
|||
fileInfo.CreationTime, |
|||
fileInfo.Length, |
|||
fileInfo.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "IsReadOnly", fileInfo.IsReadOnly.ToString() }, |
|||
{ "LastAccessTime", fileInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}) |
|||
{ |
|||
FullName = fileInfo.FullName.Replace(Environment.ContentRootPath, "") |
|||
}; |
|||
using (var fileStream = File.OpenRead(filePath)) |
|||
{ |
|||
var memoryStream = new MemoryStream(); |
|||
await fileStream.CopyToAsync(memoryStream); |
|||
ossObject.SetContent(memoryStream); |
|||
|
|||
if (!request.Process.IsNullOrWhiteSpace()) |
|||
{ |
|||
using var serviceScope = ServiceProvider.CreateScope(); |
|||
var context = new FileSystemOssObjectContext(request.Process, ossObject, serviceScope.ServiceProvider); |
|||
foreach (var processer in Options.Processers) |
|||
{ |
|||
await processer.ProcessAsync(context); |
|||
|
|||
if (context.Handled) |
|||
{ |
|||
ossObject.SetContent(context.Content); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return ossObject; |
|||
} |
|||
} |
|||
|
|||
public virtual Task<GetOssContainersResponse> GetListAsync(GetOssContainersRequest request) |
|||
{ |
|||
// 不传递Bucket 检索根目录的Bucket
|
|||
var filePath = CalculateFilePath(null); |
|||
|
|||
// 获取根目录
|
|||
var directories = Directory.GetDirectories(filePath, request.Prefix ?? "*.*"); |
|||
|
|||
// 排序目录
|
|||
Array.Sort(directories, delegate (string x, string y) |
|||
{ |
|||
return x.CompareTo(y); |
|||
}); |
|||
|
|||
var spiltDirectories = directories; |
|||
// 计算标记的位置进行截断
|
|||
if (!request.Marker.IsNullOrWhiteSpace()) |
|||
{ |
|||
var markIndex = directories.FindIndex(x => x.EndsWith(request.Marker)); |
|||
if (markIndex < 0) |
|||
{ |
|||
directories = new string[0]; |
|||
} |
|||
else |
|||
{ |
|||
var markDirectories = new string[directories.Length - markIndex]; |
|||
Array.Copy(directories, markIndex, markDirectories, 0, markDirectories.Length); |
|||
directories = markDirectories; |
|||
} |
|||
} |
|||
// 需要截断最大的容器集合
|
|||
if (request.MaxKeys.HasValue) |
|||
{ |
|||
spiltDirectories = directories.Take(request.MaxKeys ?? directories.Length).ToArray(); |
|||
} |
|||
var nextDirectory = spiltDirectories.Length < directories.Length ? directories[spiltDirectories.Length] : ""; |
|||
if (!nextDirectory.IsNullOrWhiteSpace()) |
|||
{ |
|||
// 下一个标记的目录名称
|
|||
|
|||
nextDirectory = new DirectoryInfo(nextDirectory).Name; |
|||
} |
|||
// 容器对应的目录信息集合
|
|||
var directoryInfos = spiltDirectories.Select(x => new DirectoryInfo(x)); |
|||
// 返回Oss容器描述集合
|
|||
var response = new GetOssContainersResponse( |
|||
request.Prefix, |
|||
request.Marker, |
|||
nextDirectory, |
|||
directories.Length, |
|||
directoryInfos.Select(x => new OssContainer( |
|||
x.Name, |
|||
x.CreationTime, |
|||
0L, |
|||
x.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "LastAccessTime", x.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
})) |
|||
.ToList()); |
|||
|
|||
return Task.FromResult(response); |
|||
} |
|||
|
|||
public virtual Task<GetOssObjectsResponse> GetObjectsAsync(GetOssObjectsRequest request) |
|||
{ |
|||
// 先定位检索的目录
|
|||
var filePath = CalculateFilePath(request.BucketName, request.Prefix); |
|||
if (!Directory.Exists(filePath)) |
|||
{ |
|||
throw new BusinessException(code: FileManagementErrorCodes.ContainerNotFound); |
|||
// throw new ContainerNotFoundException($"Can't not found container {request.BucketName} in file system");
|
|||
} |
|||
// 目录也属于Oss对象,需要抽象的文件系统集合来存储
|
|||
var fileSystemNames = Directory.GetFileSystemEntries(filePath); |
|||
int maxFilesCount = fileSystemNames.Length; |
|||
|
|||
// 排序所有文件与目录
|
|||
Array.Sort(fileSystemNames, delegate (string x, string y) |
|||
{ |
|||
// 检索的是文件系统名称
|
|||
// 需要判断是否为文件夹进行升序排序
|
|||
// 参考 OssObjectComparer
|
|||
|
|||
var xFolder = Directory.Exists(x); |
|||
var yFolder = Directory.Exists(y); |
|||
|
|||
if (xFolder && yFolder) |
|||
{ |
|||
return x.CompareTo(y); |
|||
} |
|||
|
|||
if (xFolder && !yFolder) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
if (!xFolder && yFolder) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
return x.CompareTo(y); |
|||
}); |
|||
|
|||
// 需要计算从哪个位置截断
|
|||
int markIndex = 0; |
|||
if (!request.Marker.IsNullOrWhiteSpace()) |
|||
{ |
|||
markIndex = fileSystemNames.FindIndex(x => x.EndsWith(request.Marker)); |
|||
if (markIndex < 0) |
|||
{ |
|||
markIndex = 0; |
|||
} |
|||
} |
|||
|
|||
// 需要截断Oss对象列表
|
|||
var copyFileSystemNames = fileSystemNames; |
|||
if (markIndex > 0) |
|||
{ |
|||
copyFileSystemNames = fileSystemNames[(markIndex+1)..]; |
|||
} |
|||
// 截取指定数量的Oss对象
|
|||
int maxResultCount = request.MaxKeys ?? 10; |
|||
// Oss对象信息集合
|
|||
var fileSystems = copyFileSystemNames |
|||
.Take(maxResultCount) |
|||
.Select<string, FileSystemInfo>(file => |
|||
{ |
|||
if (File.Exists(file)) |
|||
{ |
|||
return new FileInfo(file); |
|||
} |
|||
return new DirectoryInfo(file); |
|||
}) |
|||
.ToArray(); |
|||
|
|||
// 计算下一页起始标记文件/目录名称
|
|||
var nextMarkerIndex = fileSystemNames.FindIndex(x => x.EndsWith(fileSystems[fileSystems.Length - 1].Name)); |
|||
string nextMarker = ""; |
|||
if (nextMarkerIndex >=0 && nextMarkerIndex + 1 < fileSystemNames.Length) |
|||
{ |
|||
nextMarker = fileSystemNames[nextMarkerIndex + 1]; |
|||
nextMarker = File.Exists(nextMarker) |
|||
? new FileInfo(nextMarker).Name |
|||
: new DirectoryInfo(nextMarker).Name.EnsureEndsWith('/'); |
|||
} |
|||
// 返回Oss对象描述集合
|
|||
var response = new GetOssObjectsResponse( |
|||
request.BucketName, |
|||
request.Prefix, |
|||
request.Marker, |
|||
nextMarker, |
|||
"/", // 文件系统目录分隔符
|
|||
fileSystemNames.Length, |
|||
fileSystems.Select(x => new OssObject( |
|||
(x is DirectoryInfo) ? x.Name.EnsureEndsWith('/') : x.Name, |
|||
request.Prefix, |
|||
x.CreationTime, |
|||
(x as FileInfo)?.Length ?? 0L, |
|||
x.LastWriteTime, |
|||
new Dictionary<string, string> |
|||
{ |
|||
{ "LastAccessTime", x.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } |
|||
}, |
|||
x is DirectoryInfo) |
|||
{ |
|||
FullName = x.FullName.Replace(Environment.ContentRootPath, "") |
|||
}) |
|||
.ToList()); |
|||
|
|||
return Task.FromResult(response); |
|||
} |
|||
|
|||
protected virtual FileSystemBlobProviderConfiguration GetFileSystemConfiguration() |
|||
{ |
|||
var configuration = ConfigurationProvider.Get<AbpFileManagementContainer>(); |
|||
var fileSystemConfiguration = configuration.GetFileSystemConfiguration(); |
|||
return fileSystemConfiguration; |
|||
} |
|||
|
|||
protected virtual string CalculateFilePath(string bucketName, string blobName = "") |
|||
{ |
|||
var fileSystemConfiguration = GetFileSystemConfiguration(); |
|||
var blobPath = fileSystemConfiguration.BasePath; |
|||
|
|||
if (CurrentTenant.Id == null) |
|||
{ |
|||
blobPath = Path.Combine(blobPath, "host"); |
|||
} |
|||
else |
|||
{ |
|||
blobPath = Path.Combine(blobPath, "tenants", CurrentTenant.Id.Value.ToString("D")); |
|||
} |
|||
|
|||
if (fileSystemConfiguration.AppendContainerNameToBasePath && |
|||
!bucketName.IsNullOrWhiteSpace()) |
|||
{ |
|||
blobPath = Path.Combine(blobPath, bucketName); |
|||
} |
|||
if (!blobName.IsNullOrWhiteSpace()) |
|||
{ |
|||
blobPath = Path.Combine(blobPath, blobName); |
|||
} |
|||
|
|||
return blobPath; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using Volo.Abp.BlobStoring; |
|||
using Volo.Abp.BlobStoring.FileSystem; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public class FileSystemOssContainerFactory : IOssContainerFactory |
|||
{ |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IHostEnvironment Environment { get; } |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
protected IBlobFilePathCalculator FilePathCalculator { get; } |
|||
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } |
|||
protected IOptions<FileSystemOssOptions> Options { get; } |
|||
|
|||
public FileSystemOssContainerFactory( |
|||
ICurrentTenant currentTenant, |
|||
IHostEnvironment environment, |
|||
IServiceProvider serviceProvider, |
|||
IBlobFilePathCalculator blobFilePathCalculator, |
|||
IBlobContainerConfigurationProvider configurationProvider, |
|||
IOptions<FileSystemOssOptions> options) |
|||
{ |
|||
Environment = environment; |
|||
CurrentTenant = currentTenant; |
|||
ServiceProvider = serviceProvider; |
|||
FilePathCalculator = blobFilePathCalculator; |
|||
ConfigurationProvider = configurationProvider; |
|||
Options = options; |
|||
} |
|||
|
|||
public IOssContainer Create() |
|||
{ |
|||
return new FileSystemOssContainer( |
|||
CurrentTenant, |
|||
Environment, |
|||
ServiceProvider, |
|||
FilePathCalculator, |
|||
ConfigurationProvider, |
|||
Options); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.IO; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public class FileSystemOssObjectContext : IServiceProviderAccessor |
|||
{ |
|||
public string Process { get; } |
|||
public OssObject OssObject { get; } |
|||
public bool Handled { get; private set; } |
|||
public Stream Content { get; private set; } |
|||
public IServiceProvider ServiceProvider { get; } |
|||
|
|||
public FileSystemOssObjectContext( |
|||
string process, |
|||
OssObject ossObject, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
Process = process; |
|||
OssObject = ossObject; |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
|
|||
public void SetContent(Stream content) |
|||
{ |
|||
Content = content; |
|||
Content.Seek(0, SeekOrigin.Begin); |
|||
Handled = true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public class FileSystemOssOptions |
|||
{ |
|||
[NotNull] |
|||
public List<IFileSystemOssObjectProcesserContributor> Processers { get; } |
|||
|
|||
public FileSystemOssOptions() |
|||
{ |
|||
Processers = new List<IFileSystemOssObjectProcesserContributor> |
|||
{ |
|||
new NoneFileSystemOssObjectProcesser() |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public static class FileSystemOssOptionsExtensions |
|||
{ |
|||
public static void AddProcesser<TProcesserContributor>( |
|||
this FileSystemOssOptions options, |
|||
TProcesserContributor contributor) |
|||
where TProcesserContributor : IFileSystemOssObjectProcesserContributor |
|||
{ |
|||
options.Processers.InsertBefore((x) => x is NoneFileSystemOssObjectProcesser, contributor); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Threading; |
|||
using Volo.Abp.BlobStoring; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public class FileSystemOssProviderArgs : BlobProviderArgs |
|||
{ |
|||
public FileSystemOssProviderArgs( |
|||
string containerName, |
|||
BlobContainerConfiguration configuration, |
|||
string blobName, |
|||
CancellationToken cancellationToken = default) |
|||
: base(containerName, configuration, blobName, cancellationToken) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public interface IFileSystemOssObjectProcesserContributor |
|||
{ |
|||
Task ProcessAsync(FileSystemOssObjectContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement.FileSystem |
|||
{ |
|||
public class NoneFileSystemOssObjectProcesser : IFileSystemOssObjectProcesserContributor |
|||
{ |
|||
public Task ProcessAsync(FileSystemOssObjectContext context) |
|||
{ |
|||
context.SetContent(context.OssObject.Content); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
# LINGYUN.Abp.FileManagement.FileSystem |
|||
|
|||
本地文件系统oss容器接口 |
|||
|
|||
## 配置使用 |
|||
|
|||
模块按需引用 |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpFileManagementFileSystemModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
// other |
|||
} |
|||
``` |
|||
@ -0,0 +1,56 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
[RemoteService(Name = "AbpFileManagement")] |
|||
[Area("file-management")] |
|||
[Route("api/file-management/containes")] |
|||
public class OssContainerController : AbpController, IOssContainerAppService |
|||
{ |
|||
protected IOssContainerAppService OssContainerAppService { get; } |
|||
|
|||
public OssContainerController( |
|||
IOssContainerAppService ossContainerAppService) |
|||
{ |
|||
OssContainerAppService = ossContainerAppService; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("{name}")] |
|||
public virtual async Task<OssContainerDto> CreateAsync(string name) |
|||
{ |
|||
return await OssContainerAppService.CreateAsync(name); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{name}")] |
|||
public virtual async Task DeleteAsync(string name) |
|||
{ |
|||
await OssContainerAppService.DeleteAsync(name); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{name}")] |
|||
public virtual async Task<OssContainerDto> GetAsync(string name) |
|||
{ |
|||
return await OssContainerAppService.GetAsync(name); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual async Task<OssContainersResultDto> GetListAsync(GetOssContainersInput input) |
|||
{ |
|||
return await OssContainerAppService.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("objects")] |
|||
public virtual async Task<OssObjectsResultDto> GetObjectListAsync(GetOssObjectsInput input) |
|||
{ |
|||
return await OssContainerAppService.GetObjectListAsync(input); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
[RemoteService(Name = "AbpFileManagement")] |
|||
[Area("file-management")] |
|||
[Route("api/file-management/objects")] |
|||
public class OssObjectController : AbpController, IOssObjectAppService |
|||
{ |
|||
protected IOssObjectAppService OssObjectAppService { get; } |
|||
|
|||
public OssObjectController( |
|||
IOssObjectAppService ossObjectAppService) |
|||
{ |
|||
OssObjectAppService = ossObjectAppService; |
|||
} |
|||
|
|||
[HttpPost] |
|||
public virtual async Task<OssObjectDto> CreateAsync(CreateOssObjectInput input) |
|||
{ |
|||
return await OssObjectAppService.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("upload")] |
|||
public virtual async Task<OssObjectDto> UploadAsync([FromForm] UploadOssObjectInput input) |
|||
{ |
|||
var createOssObjectInput = new CreateOssObjectInput |
|||
{ |
|||
Bucket = input.Bucket, |
|||
Path = input.Path, |
|||
Object = input.Name |
|||
}; |
|||
if (input.File != null) |
|||
{ |
|||
//if (input.File.Length <= 0)
|
|||
//{
|
|||
// ThrowValidationException(L["FileNotBeNullOrEmpty"], "File");
|
|||
//}
|
|||
createOssObjectInput.Object = input.File.FileName; |
|||
createOssObjectInput.Content = input.File.OpenReadStream(); |
|||
} |
|||
|
|||
return await OssObjectAppService.CreateAsync(createOssObjectInput); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("bulk-delete")] |
|||
public virtual async Task BulkDeleteAsync(BulkDeleteOssObjectInput input) |
|||
{ |
|||
await OssObjectAppService.BulkDeleteAsync(input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
public virtual async Task DeleteAsync(GetOssObjectInput input) |
|||
{ |
|||
await OssObjectAppService.DeleteAsync(input); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual async Task<OssObjectDto> GetAsync(GetOssObjectInput input) |
|||
{ |
|||
return await OssObjectAppService.GetAsync(input); |
|||
} |
|||
|
|||
private static void ThrowValidationException(string message, string memberName) |
|||
{ |
|||
throw new AbpValidationException(message, |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult(message, new[] {memberName}) |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Http; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
[RemoteService(Name = "AbpFileManagement")] |
|||
[Area("file-management")] |
|||
[Route("api/files/static")] |
|||
public class StaticFilesController : AbpController |
|||
{ |
|||
private readonly IOssObjectAppService _ossObjectAppService; |
|||
private readonly IStaticFilesAppService _staticFilesAppServic; |
|||
|
|||
public StaticFilesController( |
|||
IOssObjectAppService ossObjectAppService, |
|||
IStaticFilesAppService staticFilesAppServic) |
|||
{ |
|||
_ossObjectAppService = ossObjectAppService; |
|||
_staticFilesAppServic = staticFilesAppServic; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("{bucket}")] |
|||
[Route("{bucket}/{path}")] |
|||
public virtual async Task<OssObjectDto> UploadAsync(string bucket, string path, [FromForm] IFormFile file) |
|||
{ |
|||
if (file == null || file.Length <= 0) |
|||
{ |
|||
ThrowValidationException(L["FileNotBeNullOrEmpty"], "File"); |
|||
} |
|||
|
|||
var createOssObjectInput = new CreateOssObjectInput |
|||
{ |
|||
Bucket = bucket, |
|||
Path = path, |
|||
Object = file.FileName, |
|||
Content = file.OpenReadStream() |
|||
}; |
|||
|
|||
return await _ossObjectAppService.CreateAsync(createOssObjectInput); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{bucket}/{name}")] |
|||
[Route("{bucket}/{name}/{process}")] |
|||
[Route("{bucket}/p/{path}/{name}")] |
|||
[Route("{bucket}/p/{path}/{name}/{process}")] |
|||
public virtual async Task<IActionResult> GetAsync(string bucket, string path, string name, string process) |
|||
{ |
|||
var input = new GetStaticFileInput |
|||
{ |
|||
Bucket = bucket, |
|||
Name = name, |
|||
Path = path, |
|||
Process = process |
|||
}; |
|||
var fileStream = await _staticFilesAppServic.GetAsync(input); |
|||
|
|||
if (fileStream.IsNullOrEmpty()) |
|||
{ |
|||
return NotFound(); |
|||
} |
|||
|
|||
return File( |
|||
fileStream, |
|||
MimeTypes.GetByExtension(Path.GetExtension(input.Name)) |
|||
); |
|||
} |
|||
|
|||
private static void ThrowValidationException(string message, string memberName) |
|||
{ |
|||
throw new AbpValidationException(message, |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult(message, new[] {memberName}) |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
|
|||
namespace LINGYUN.Abp.FileManagement |
|||
{ |
|||
public class UploadOssObjectInput |
|||
{ |
|||
public string Bucket { get; set; } |
|||
public string Path { get; set; } |
|||
public string Name { get; set; } |
|||
public IFormFile File { get; set; } |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,40 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.AspNetCore.MultiTenancy; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Text.Formatting; |
|||
|
|||
namespace LINGYUN.Platform |
|||
{ |
|||
public class DomainTenantResolveContributor : HttpTenantResolveContributorBase |
|||
{ |
|||
public const string ContributorName = "Domain"; |
|||
|
|||
public override string Name => ContributorName; |
|||
|
|||
private static readonly string[] ProtocolPrefixes = { "http://", "https://" }; |
|||
|
|||
private readonly string _domainFormat; |
|||
|
|||
public DomainTenantResolveContributor(string domainFormat) |
|||
{ |
|||
_domainFormat = domainFormat.RemovePreFix(ProtocolPrefixes); |
|||
} |
|||
|
|||
protected override Task<string> GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) |
|||
{ |
|||
if (!httpContext.Request.Host.HasValue) |
|||
{ |
|||
return Task.FromResult<string>(null); |
|||
} |
|||
|
|||
var hostName = httpContext.Request.Host.Value.RemovePreFix(ProtocolPrefixes); |
|||
var extractResult = FormattedStringValueExtracter.Extract(hostName, _domainFormat, ignoreCase: true); |
|||
|
|||
context.Handled = true; |
|||
|
|||
return Task.FromResult(extractResult.IsMatch ? extractResult.Matches[0].Value : null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.AspNetCore.MultiTenancy; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Platform |
|||
{ |
|||
public class HeaderTenantResolveContributor : HttpTenantResolveContributorBase |
|||
{ |
|||
public const string ContributorName = "Header"; |
|||
|
|||
public override string Name => ContributorName; |
|||
|
|||
protected override Task<string> GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) |
|||
{ |
|||
if (httpContext.Request.Headers.IsNullOrEmpty()) |
|||
{ |
|||
return Task.FromResult((string)null); |
|||
} |
|||
|
|||
var tenantIdKey = context.GetAbpAspNetCoreMultiTenancyOptions().TenantKey; |
|||
|
|||
var tenantIdHeader = httpContext.Request.Headers[tenantIdKey]; |
|||
if (tenantIdHeader == string.Empty || tenantIdHeader.Count < 1) |
|||
{ |
|||
return Task.FromResult((string)null); |
|||
} |
|||
|
|||
if (tenantIdHeader.Count > 1) |
|||
{ |
|||
Log(context, $"HTTP request includes more than one {tenantIdKey} header value. First one will be used. All of them: {tenantIdHeader.JoinAsString(", ")}"); |
|||
} |
|||
|
|||
return Task.FromResult(tenantIdHeader.First()); |
|||
} |
|||
|
|||
protected virtual void Log(ITenantResolveContext context, string text) |
|||
{ |
|||
context |
|||
.ServiceProvider |
|||
.GetRequiredService<ILogger<HeaderTenantResolveContributor>>() |
|||
.LogWarning(text); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Platform |
|||
{ |
|||
[Dependency(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient, ReplaceServices = true)] |
|||
[ExposeServices(typeof(ITenantConfigurationProvider))] |
|||
public class TenantConfigurationProvider : ITenantConfigurationProvider |
|||
{ |
|||
protected virtual ITenantResolver TenantResolver { get; } |
|||
protected virtual ITenantStore TenantStore { get; } |
|||
protected virtual ITenantResolveResultAccessor TenantResolveResultAccessor { get; } |
|||
|
|||
public TenantConfigurationProvider( |
|||
ITenantResolver tenantResolver, |
|||
ITenantStore tenantStore, |
|||
ITenantResolveResultAccessor tenantResolveResultAccessor) |
|||
{ |
|||
TenantResolver = tenantResolver; |
|||
TenantStore = tenantStore; |
|||
TenantResolveResultAccessor = tenantResolveResultAccessor; |
|||
} |
|||
|
|||
public virtual async Task<TenantConfiguration> GetAsync(bool saveResolveResult = false) |
|||
{ |
|||
var resolveResult = await TenantResolver.ResolveTenantIdOrNameAsync(); |
|||
|
|||
if (saveResolveResult) |
|||
{ |
|||
TenantResolveResultAccessor.Result = resolveResult; |
|||
} |
|||
|
|||
TenantConfiguration tenant = null; |
|||
if (resolveResult.TenantIdOrName != null) |
|||
{ |
|||
tenant = await FindTenantAsync(resolveResult.TenantIdOrName); |
|||
|
|||
if (tenant == null) |
|||
{ |
|||
throw new BusinessException( |
|||
code: "Volo.AbpIo.MultiTenancy:010001", |
|||
message: "Tenant not found!", |
|||
details: "There is no tenant with the tenant id or name: " + resolveResult.TenantIdOrName |
|||
); |
|||
} |
|||
} |
|||
|
|||
return tenant; |
|||
} |
|||
|
|||
protected virtual async Task<TenantConfiguration> FindTenantAsync(string tenantIdOrName) |
|||
{ |
|||
if (Guid.TryParse(tenantIdOrName, out var parsedTenantId)) |
|||
{ |
|||
return await TenantStore.FindAsync(parsedTenantId); |
|||
} |
|||
else |
|||
{ |
|||
return await TenantStore.FindAsync(tenantIdOrName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Platform |
|||
{ |
|||
[Dependency(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient, ReplaceServices = true)] |
|||
[ExposeServices(typeof(ITenantResolver))] |
|||
public class TenantResolver : ITenantResolver |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
private readonly AbpTenantResolveOptions _options; |
|||
|
|||
public TenantResolver(IOptions<AbpTenantResolveOptions> options, IServiceProvider serviceProvider) |
|||
{ |
|||
_serviceProvider = serviceProvider; |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public virtual async Task<TenantResolveResult> ResolveTenantIdOrNameAsync() |
|||
{ |
|||
var result = new TenantResolveResult(); |
|||
|
|||
using (var serviceScope = _serviceProvider.CreateScope()) |
|||
{ |
|||
var context = new TenantResolveContext(serviceScope.ServiceProvider); |
|||
|
|||
foreach (var tenantResolver in _options.TenantResolvers) |
|||
{ |
|||
await tenantResolver.ResolveAsync(context); |
|||
|
|||
result.AppliedResolvers.Add(tenantResolver.Name); |
|||
|
|||
if (context.Handled || context.TenantIdOrName != null) |
|||
{ |
|||
result.TenantIdOrName = context.TenantIdOrName; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
import ApiService from './serviceBase' |
|||
import { PagedAndSortedResultRequestDto } from './types' |
|||
import { urlStringify } from '@/utils/index' |
|||
|
|||
const serviceUrl = process.env.VUE_APP_BASE_API |
|||
const containerUrl = '/api/file-management/containes' |
|||
const objectUrl = '/api/file-management/objects' |
|||
export const objectUploadUrl = objectUrl + '/upload' |
|||
export const staticUrl = '/api/files/static/' |
|||
|
|||
export default class OssManager { |
|||
public static createBucket(name: string) { |
|||
const _url = containerUrl + '/' + name |
|||
return ApiService.Post<OssContainer>(_url, null, serviceUrl) |
|||
} |
|||
|
|||
public static getBucket(name: string) { |
|||
const _url = containerUrl + '/' + name |
|||
return ApiService.Get<OssContainer>(_url, serviceUrl) |
|||
} |
|||
|
|||
public static getBuckets(input: GetOssContainerRequest) { |
|||
const _url = containerUrl + '?' + urlStringify(input) |
|||
return ApiService.Get<OssContainerResultList>(_url, serviceUrl) |
|||
} |
|||
|
|||
public static deleteBucket(name: string) { |
|||
const _url = containerUrl + '/' + name |
|||
return ApiService.Delete(_url, serviceUrl) |
|||
} |
|||
|
|||
public static createFolder(bucket: string, name: string, path: string = "") { |
|||
const input = { |
|||
bucket: bucket, |
|||
object: name, |
|||
path: path |
|||
} |
|||
return ApiService.Post<OssObject>(objectUrl, input, serviceUrl) |
|||
} |
|||
|
|||
public static getObjects(input: GetOssObjectRequest) { |
|||
const _url = containerUrl + '/objects?' + urlStringify(input) |
|||
return ApiService.Get<OssObjectResultList>(_url, serviceUrl) |
|||
} |
|||
|
|||
public static getObject(bucket: string, object: string, path: string = "") { |
|||
let _url = objectUrl + '?bucket=' + bucket + '&object=' + object |
|||
if (path) { |
|||
_url += '&path=' + path |
|||
} |
|||
return ApiService.Get<OssObject>(_url, serviceUrl) |
|||
} |
|||
|
|||
public static getObjectData(bucket: string, object: string, path: string = "") { |
|||
let _url = staticUrl + bucket + '/' |
|||
if (path) { |
|||
// 某些情况下要对 / 编码
|
|||
_url += '/p/' + path.replace('/', '%2F') |
|||
if (_url.endsWith('%2F')) { |
|||
_url = _url.substring(0, _url.length - 3) + '/' |
|||
} |
|||
} |
|||
_url += object |
|||
return ApiService.HttpRequest<Blob>({ |
|||
url: _url, |
|||
baseURL: serviceUrl, |
|||
method: 'GET', |
|||
responseType: 'blob' |
|||
}) |
|||
} |
|||
|
|||
public static deleteObject(bucket: string, object: string, path: string = "") { |
|||
let _url = objectUrl + '?bucket=' + bucket + '&object=' + object |
|||
if (path) { |
|||
_url += '&path=' + path |
|||
} |
|||
return ApiService.Delete(_url, serviceUrl) |
|||
} |
|||
} |
|||
|
|||
export class GetOssContainerRequest extends PagedAndSortedResultRequestDto { |
|||
prefix?: string |
|||
marker?: string |
|||
} |
|||
|
|||
export class GetOssObjectRequest extends PagedAndSortedResultRequestDto { |
|||
bucket!: string |
|||
prefix?: string |
|||
delimiter?: string = '/' |
|||
marker?: string |
|||
encodingType?: string |
|||
maxResultCount = 100 |
|||
} |
|||
|
|||
export class OssContainer { |
|||
name!: string |
|||
size!: number |
|||
creationDate?: Date |
|||
lastModifiedDate?: Date |
|||
metadata?: {[key: string]: string} |
|||
} |
|||
|
|||
export class OssContainerResultList { |
|||
prefix?: string |
|||
marker?: string |
|||
nextMarker?: string |
|||
maxKeys?: number |
|||
containers!: OssContainer[] |
|||
} |
|||
|
|||
export class OssObject { |
|||
name!: string |
|||
path?: string |
|||
size!: number |
|||
isFolder!: boolean |
|||
creationDate?: Date |
|||
lastModifiedDate?: Date |
|||
metadata?: {[key: string]: string} |
|||
} |
|||
|
|||
export class OssObjectResultList { |
|||
bucket!: string |
|||
prefix?: string |
|||
delimiter?: string |
|||
marker?: string |
|||
nextMarker?: string |
|||
maxKeys?: number |
|||
objects!: OssObject[] |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
import { DirectiveOptions } from 'vue' |
|||
|
|||
export const elTableLazyLoad: DirectiveOptions = { |
|||
inserted(el, binding) { |
|||
const selectWrap = el.querySelector('.el-table__body-wrapper') |
|||
if (selectWrap) { |
|||
selectWrap.addEventListener('scroll', function() { |
|||
console.log('onscroll directive') |
|||
let sign = 0 |
|||
const scrollDistance = selectWrap.scrollHeight - selectWrap.scrollTop - selectWrap.clientHeight |
|||
if (scrollDistance <= sign) { |
|||
binding.value() |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +1,5 @@ |
|||
export * from './clipboard' |
|||
export * from './el-draggable-dialog' |
|||
export * from './el-table-lazy-load' |
|||
export * from './permission' |
|||
export * from './waves' |
|||
|
|||
@ -0,0 +1,162 @@ |
|||
<template> |
|||
<el-dialog |
|||
width="800px" |
|||
title="Oss对象" |
|||
:visible="showDialog" |
|||
custom-class="modal-form" |
|||
@close="onFormClosed" |
|||
> |
|||
<div class="app-container"> |
|||
<el-form |
|||
ref="formOssObject" |
|||
label-width="130px" |
|||
> |
|||
<el-form-item label-width="0px"> |
|||
<el-carousel |
|||
:interval="5000" |
|||
arrow="always" |
|||
class="oss-preview-div" |
|||
> |
|||
<el-carousel-item> |
|||
<el-image |
|||
:src="ossPreviewUrl" |
|||
fit="contain" |
|||
class="oss-preview-img" |
|||
> |
|||
<div |
|||
slot="error" |
|||
class="image-slot" |
|||
> |
|||
<el-alert |
|||
title="当前格式不支持预览" |
|||
type="warning" |
|||
center |
|||
show-icon |
|||
/> |
|||
</div> |
|||
</el-image> |
|||
</el-carousel-item> |
|||
</el-carousel> |
|||
</el-form-item> |
|||
<el-form-item |
|||
label="名称" |
|||
> |
|||
<el-input |
|||
:value="oss.name" |
|||
readonly |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
label="路径" |
|||
> |
|||
<el-input |
|||
:value="oss.path" |
|||
readonly |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
label="大小" |
|||
> |
|||
<el-input |
|||
:value="oss.size" |
|||
readonly |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
label="创建时间" |
|||
> |
|||
<el-input |
|||
:value="oss.creationDate | dateTimeFilter" |
|||
readonly |
|||
/> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Component, Prop, Watch, Vue } from 'vue-property-decorator' |
|||
import { dateFormat } from '@/utils/index' |
|||
import OssManagerApi, { OssObject } from '@/api/oss-manager' |
|||
|
|||
// 暂时支持预览的文件 |
|||
const supportFileTypes = ['jpg', 'png', 'gif', 'bmp', 'jpeg'] |
|||
|
|||
@Component({ |
|||
name: 'OssObjectProfile', |
|||
filters: { |
|||
dateTimeFilter(datetime: string) { |
|||
if (datetime) { |
|||
const date = new Date(datetime) |
|||
return dateFormat(date, 'YYYY-mm-dd HH:MM:SS') |
|||
} |
|||
return '' |
|||
} |
|||
} |
|||
}) |
|||
export default class OssObjectProfile extends Vue { |
|||
@Prop({ default: '' }) |
|||
private name!: string |
|||
|
|||
@Prop({ default: '' }) |
|||
private bucket!: string |
|||
|
|||
@Prop({ default: '' }) |
|||
private path?: string |
|||
|
|||
@Prop({ default: false }) |
|||
private showDialog!: string |
|||
|
|||
private notSupport = false |
|||
private ossPreviewUrl = '' |
|||
private oss = new OssObject() |
|||
|
|||
@Watch('showDialog', { immediate: true }) |
|||
private onShowDialogChanged() { |
|||
this.handleGetOssObject() |
|||
} |
|||
|
|||
private handleGetOssObject() { |
|||
if (this.name && this.showDialog) { |
|||
this.ossPreviewUrl = '' |
|||
OssManagerApi |
|||
.getObject(this.bucket, this.name, this.path) |
|||
.then(res => { |
|||
this.oss = res |
|||
this.handleGetOssObjectBase64() |
|||
}) |
|||
} |
|||
} |
|||
|
|||
private handleGetOssObjectBase64() { |
|||
if (this.oss.name && |
|||
supportFileTypes.some(x => this.oss.name.toLowerCase().endsWith(x))) { // 仅下载支持预览的文件 |
|||
OssManagerApi |
|||
.getObjectData(this.bucket, this.oss.name, this.oss.path) |
|||
.then(res => { |
|||
const reader = new FileReader() |
|||
reader.onload = (e) => { |
|||
if (e.target?.result) { |
|||
this.ossPreviewUrl = e.target.result.toString() |
|||
} |
|||
} |
|||
reader.readAsDataURL(res) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
private onFormClosed() { |
|||
this.ossPreviewUrl = '' |
|||
this.oss = new OssObject() |
|||
this.$emit('closed') |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.oss-preview-img { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,476 @@ |
|||
<template> |
|||
<div class="app-container"> |
|||
<div class="filter-container"> |
|||
<div class="fs-filter"> |
|||
<el-select |
|||
v-model="bucket" |
|||
class="fs-bucket" |
|||
@change="onBucketChanged" |
|||
> |
|||
<el-option |
|||
v-for="b in buckets" |
|||
:key="b.name" |
|||
:label="b.name" |
|||
:value="b.name" |
|||
/> |
|||
</el-select> |
|||
<el-breadcrumb |
|||
ref="fileSystemBreadCrumb" |
|||
separator-class="el-icon-arrow-right" |
|||
class="fs-breadcrumb" |
|||
> |
|||
<el-breadcrumb-item |
|||
v-for="(fileRoot, index) in fileSystemRoot" |
|||
:key="index" |
|||
class="file-system-breadcrumb" |
|||
@click.native="(event) => onBreadCrumbClick(event, index)" |
|||
> |
|||
{{ fileRoot }} |
|||
</el-breadcrumb-item> |
|||
</el-breadcrumb> |
|||
</div> |
|||
</div> |
|||
|
|||
<el-table |
|||
id="ossObjectTable" |
|||
ref="ossObjectTable" |
|||
v-loading="objectLoading" |
|||
v-el-table-lazy-load="handleGetObjects" |
|||
row-key="name" |
|||
:data="objects" |
|||
border |
|||
fit |
|||
highlight-current-row |
|||
style="width: 100%;" |
|||
height="800" |
|||
@row-click="onRowClick" |
|||
@row-dblclick="onRowDoubleClick" |
|||
@contextmenu.native="onContextMenu" |
|||
> |
|||
<el-table-column |
|||
type="selection" |
|||
width="50" |
|||
align="center" |
|||
/> |
|||
<el-table-column |
|||
:label="$t('fileSystem.name')" |
|||
prop="name" |
|||
sortable |
|||
width="350" |
|||
align="left" |
|||
> |
|||
<template slot-scope="{row}"> |
|||
<svg-icon |
|||
v-if="row.isFolder" |
|||
name="folder" |
|||
class="folder-icon" |
|||
/> |
|||
<svg-icon |
|||
v-else |
|||
name="file" |
|||
class="file-icon" |
|||
/> |
|||
<span>{{ row.name }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
:label="$t('fileSystem.creationTime')" |
|||
prop="creationDate" |
|||
sortable |
|||
width="200" |
|||
align="center" |
|||
> |
|||
<template slot-scope="{row}"> |
|||
<span>{{ row.creationDate | dateTimeFilter }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
:label="$t('fileSystem.lastModificationTime')" |
|||
prop="lastModifiedDate" |
|||
sortable |
|||
width="200" |
|||
align="center" |
|||
> |
|||
<template slot-scope="{row}"> |
|||
<span>{{ row.lastModifiedDate | dateTimeFilter }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
:label="$t('fileSystem.type')" |
|||
prop="type" |
|||
sortable |
|||
width="150" |
|||
align="center" |
|||
> |
|||
<template slot-scope="{row}"> |
|||
<span>{{ row.isFolder ? $t('fileSystem.folder') : '标准存储' }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
:label="$t('fileSystem.size')" |
|||
prop="size" |
|||
sortable |
|||
width="200" |
|||
align="center" |
|||
> |
|||
<template slot-scope="{row}"> |
|||
<span>{{ row.size | fileSystemSizeFilter }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
:label="$t('global.operaActions')" |
|||
align="center" |
|||
width="200" |
|||
min-width="200" |
|||
> |
|||
<template slot-scope="{row}"> |
|||
<el-button |
|||
v-permission="['AbpFileManagement.FileSystem.FileManager']" |
|||
size="mini" |
|||
type="success" |
|||
icon="el-icon-tickets" |
|||
@click="handleShowOssObject(row)" |
|||
/> |
|||
<el-button |
|||
v-permission="[row.isFolder ? 'AbpFileManagement.FileSystem.Delete' : 'AbpFileManagement.FileSystem.FileManager.Delete']" |
|||
size="mini" |
|||
type="danger" |
|||
icon="el-icon-delete" |
|||
@click="handleDeleteOssObject(row)" |
|||
/> |
|||
<el-button |
|||
v-permission="['AbpFileManagement.FileSystem.FileManager.Download']" |
|||
:disabled="row.isFolder" |
|||
size="mini" |
|||
type="info" |
|||
icon="el-icon-download" |
|||
@click="handleDownloadOssObject(row)" |
|||
/> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<OssObjectProfile |
|||
:show-dialog="showOssObject" |
|||
:bucket="bucket" |
|||
:name="selectionOssObject.name" |
|||
:path="selectionOssObject.path" |
|||
@closed="showOssObject=false" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Vue, Component } from 'vue-property-decorator' |
|||
import OssManagerApi, { |
|||
GetOssContainerRequest, |
|||
GetOssObjectRequest, |
|||
OssContainer, |
|||
OssObject |
|||
} from '@/api/oss-manager' |
|||
import { dateFormat } from '@/utils/index' |
|||
import { checkPermission } from '@/utils/permission' |
|||
import OssObjectProfile from './components/OssObjectProfile.vue' |
|||
|
|||
const kbUnit = 1 * 1024 |
|||
const mbUnit = kbUnit * 1024 |
|||
const gbUnit = mbUnit * 1024 |
|||
const $contextmenu = Vue.prototype.$contextmenu |
|||
|
|||
@Component({ |
|||
name: 'OssManagement', |
|||
components: { |
|||
OssObjectProfile |
|||
}, |
|||
methods: { |
|||
checkPermission |
|||
}, |
|||
filters: { |
|||
dateTimeFilter(datetime: string) { |
|||
if (!datetime) { |
|||
return '' |
|||
} |
|||
const date = new Date(datetime) |
|||
return dateFormat(date, 'YYYY-mm-dd HH:MM') |
|||
}, |
|||
fileSystemSizeFilter(size: number) { |
|||
if (size > gbUnit) { |
|||
let gb = Math.round(size / gbUnit) |
|||
if (gb < 1) { |
|||
gb = 1 |
|||
} |
|||
return gb + ' GB' |
|||
} |
|||
if (size > mbUnit) { |
|||
let mb = Math.round(size / mbUnit) |
|||
if (mb < 1) { |
|||
mb = 1 |
|||
} |
|||
return mb + ' MB' |
|||
} |
|||
let kb = Math.round(size / kbUnit) |
|||
if (kb < 1) { |
|||
kb = 1 |
|||
} |
|||
return kb + ' KB' |
|||
} |
|||
} |
|||
}) |
|||
export default class OssManagement extends Vue { |
|||
private bucket = '' |
|||
private ossObjectEnd = false |
|||
private objectLoading = false |
|||
private showOssObject = false |
|||
private selectionOssObject = new OssObject() |
|||
private buckets = new Array<OssContainer>() |
|||
private objects = new Array<OssObject>() |
|||
private fileSystemRoot = new Array<string>() |
|||
|
|||
private getObjectRequest = new GetOssObjectRequest() |
|||
private getBucketRequest = new GetOssContainerRequest() |
|||
|
|||
mounted() { |
|||
this.fileSystemRoot.push(this.$t('fileSystem.root').toString()) |
|||
this.handleGetBuckets() |
|||
} |
|||
|
|||
private navigationToFilePath() { |
|||
const fileSystemPathArray = this.fileSystemRoot.slice(1) |
|||
const fileSystemPath = fileSystemPathArray.join('') |
|||
this.getObjectRequest.prefix = fileSystemPath |
|||
this.handleClearObjects() |
|||
this.handleGetObjects() |
|||
} |
|||
|
|||
private handleGoToLastFolder() { |
|||
if (this.fileSystemRoot.length > 1) { |
|||
this.fileSystemRoot.splice(this.fileSystemRoot.length - 1) |
|||
this.navigationToFilePath() |
|||
} |
|||
} |
|||
|
|||
private onRowClick(row: any) { |
|||
const table = this.$refs.ossObjectTable as any |
|||
table.toggleRowSelection(row) |
|||
} |
|||
|
|||
private onRowDoubleClick(row: OssObject) { |
|||
if (row.isFolder) { |
|||
// if (row.name.length > 1 && row.name.endsWith('/')) { |
|||
// this.fileSystemRoot.push(row.name.substring(0, row.name.length - 1)) |
|||
// } else { |
|||
// this.fileSystemRoot.push(row.name) |
|||
// } |
|||
this.fileSystemRoot.push(row.name) |
|||
this.navigationToFilePath() |
|||
} |
|||
} |
|||
|
|||
private onBreadCrumbClick(event: any, index: number) { |
|||
// 如果点击的索引为最后一个,不做响应 |
|||
if ((index + 1) < this.fileSystemRoot.length) { |
|||
this.fileSystemRoot.splice(index + 1) |
|||
this.navigationToFilePath() |
|||
} else { |
|||
this.handleClearObjects() |
|||
this.handleGetObjects() |
|||
} |
|||
} |
|||
|
|||
private onBucketChanged(bucket: string) { |
|||
this.bucket = bucket |
|||
this.handleClearObjects() |
|||
this.handleGetObjects() |
|||
} |
|||
|
|||
private handleGetBuckets() { |
|||
if (this.getBucketRequest.skipCount < this.getBucketRequest.maxResultCount) { |
|||
this.handleClearObjects() |
|||
this.bucket = '' |
|||
OssManagerApi |
|||
.getBuckets(this.getBucketRequest) |
|||
.then(result => { |
|||
this.buckets = result.containers |
|||
if (result.containers.length === 0) { |
|||
// 控制已在最后一页 |
|||
this.getBucketRequest.skipCount = this.getBucketRequest.maxResultCount |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
private handleGetObjects() { |
|||
if (this.bucket && !this.ossObjectEnd) { |
|||
this.objectLoading = true |
|||
this.getObjectRequest.bucket = this.bucket |
|||
OssManagerApi |
|||
.getObjects(this.getObjectRequest) |
|||
.then(result => { |
|||
this.objects = this.objects.concat(result.objects) |
|||
this.getObjectRequest.prefix = result.prefix ?? '' |
|||
this.getObjectRequest.delimiter = result.delimiter ?? '' |
|||
this.getObjectRequest.marker = result.nextMarker ?? '' |
|||
if (!result.nextMarker || result.objects.length === 0) { |
|||
this.ossObjectEnd = true |
|||
} |
|||
}) |
|||
.finally(() => { |
|||
this.objectLoading = false |
|||
}) |
|||
} |
|||
} |
|||
|
|||
private handleDeleteOssObject(oss: OssObject) { |
|||
this.$confirm(this.l('global.whetherDeleteData', { name: oss.name }), |
|||
this.l('global.questingDeleteByMessage', |
|||
{ message: oss.isFolder ? this.l('fileSystem.folder') : this.l('fileSystem.file') }), { |
|||
callback: (action) => { |
|||
if (action === 'confirm') { |
|||
OssManagerApi |
|||
.deleteObject(this.bucket, oss.name, oss.path) |
|||
.then(() => { |
|||
this.$notify.success(this.l('global.dataHasBeenDeleted', { name: oss.name })) |
|||
this.handleGetObjects() |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
private handleShowOssObject(oss: OssObject) { |
|||
this.selectionOssObject = oss |
|||
this.showOssObject = true |
|||
} |
|||
|
|||
private handleDownloadOssObject(oss: OssObject) { |
|||
console.log(oss) |
|||
} |
|||
|
|||
private handleFileSystemCommand(command: any) { |
|||
console.log(command) |
|||
} |
|||
|
|||
private handleClearObjects() { |
|||
this.objects.length = 0 |
|||
this.ossObjectEnd = false |
|||
this.objectLoading = false |
|||
} |
|||
|
|||
private onContextMenu(event: any) { |
|||
event.preventDefault() |
|||
$contextmenu({ |
|||
items: [ |
|||
{ |
|||
label: this.$t('fileSystem.addFolder'), |
|||
disabled: !checkPermission(['AbpFileManagement.FileSystem.Create']), |
|||
onClick: () => { |
|||
let parent = '' |
|||
// 在根目录下 |
|||
if (this.fileSystemRoot.length > 1) { |
|||
parent = this.fileSystemRoot.slice(1).join('') |
|||
} |
|||
this.$prompt(this.$t('global.pleaseInputBy', { key: this.$t('fileSystem.name') }).toString(), |
|||
this.$t('fileSystem.addFolder').toString(), { |
|||
showInput: true, |
|||
inputValidator: (val) => { |
|||
return !(!val || val.length === 0) |
|||
}, |
|||
inputErrorMessage: this.$t('fileSystem.folderNameIsRequired').toString(), |
|||
inputPlaceholder: this.$t('global.pleaseInputBy', { key: this.$t('fileSystem.name') }).toString() |
|||
}).then((val: any) => { |
|||
const name = val.value + '/' |
|||
OssManagerApi |
|||
.createFolder(this.bucket, name.replace('//', '/'), parent) |
|||
.then(res => { |
|||
this.$message.success(this.$t('fileSystem.folderCreateSuccess', { name: res.name }).toString()) |
|||
this.handleClearObjects() |
|||
this.handleGetObjects() |
|||
}) |
|||
}).catch(_ => _) |
|||
}, |
|||
divided: true |
|||
}, |
|||
{ |
|||
label: this.$t('fileSystem.upload'), |
|||
disabled: !checkPermission(['AbpFileManagement.FileSystem.FileManager.Create']), |
|||
onClick: () => { |
|||
let path = '' |
|||
if (this.fileSystemRoot.length > 1) { |
|||
path = this.fileSystemRoot.slice(1).join('/') |
|||
} |
|||
}, |
|||
divided: true |
|||
}, |
|||
{ |
|||
label: this.$t('fileSystem.bacthDownload'), |
|||
disabled: !checkPermission(['AbpFileManagement.FileSystem.FileManager.Download']), |
|||
onClick: () => { |
|||
const table = this.$refs.ossObjectTable as any |
|||
} |
|||
}, |
|||
{ |
|||
label: this.$t('fileSystem.bacthDelete'), |
|||
disabled: true, // !checkPermission(['AbpFileManagement.FileSystem.FileManager.Delete']), |
|||
onClick: () => { |
|||
// 未公布批量删除接口 |
|||
// const table = this.$refs.ossObjectTable as any |
|||
// 过滤类型为文件的选中项 |
|||
// const selectFiles = table.selection.filter((x: any) => x.type === 1) |
|||
} |
|||
} |
|||
], |
|||
event, |
|||
customClass: 'context-menu', |
|||
zIndex: 3, |
|||
minWidth: 150 |
|||
}) |
|||
return false |
|||
} |
|||
|
|||
private l(name: string, values?: any[] | { [key: string]: any }) { |
|||
return this.$t(name, values).toString() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.el-table .folder-row { |
|||
cursor: pointer; |
|||
background-color:rgb(245, 235, 226); |
|||
} |
|||
.el-table .file-row { |
|||
cursor: pointer; |
|||
background-color: rgb(210, 219, 235); |
|||
} |
|||
.fs-filter { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: stretch; |
|||
.fs-bucket { |
|||
width: 300px; |
|||
} |
|||
.fs-breadcrumb { |
|||
padding-left: 10px; |
|||
padding-top: 10px; |
|||
} |
|||
} |
|||
.file-system-breadcrumb .el-breadcrumb__inner { |
|||
color: rgb(34, 34, 173); |
|||
cursor: pointer; |
|||
} |
|||
.file-icon { |
|||
margin-left: 0px; |
|||
margin-right: 5px; |
|||
margin-top: 0px; |
|||
margin-bottom: 0px; |
|||
color: rgb(55, 189, 189); |
|||
} |
|||
.folder-icon { |
|||
margin-left: 0px; |
|||
margin-right: 5px; |
|||
margin-top: 0px; |
|||
margin-bottom: 0px; |
|||
color: rgb(235, 130, 33); |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue