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 |
namespace LINGYUN.Abp.FileManagement |
||||
{ |
{ |
||||
[DependsOn( |
[DependsOn( |
||||
|
typeof(AbpAutoMapperModule), |
||||
typeof(AbpFileManagementDomainModule), |
typeof(AbpFileManagementDomainModule), |
||||
typeof(AbpFileManagementApplicationContractsModule))] |
typeof(AbpFileManagementApplicationContractsModule))] |
||||
public class AbpFileManagementApplicationModule : AbpModule |
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 './clipboard' |
||||
export * from './el-draggable-dialog' |
export * from './el-draggable-dialog' |
||||
|
export * from './el-table-lazy-load' |
||||
export * from './permission' |
export * from './permission' |
||||
export * from './waves' |
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