14 changed files with 530 additions and 241 deletions
@ -0,0 +1,22 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Dynamic |
||||
|
{ |
||||
|
public class AbpLocalizationDynamicOptions |
||||
|
{ |
||||
|
internal LocalizationDictionary LocalizationDictionary { get; } |
||||
|
|
||||
|
public AbpLocalizationDynamicOptions() |
||||
|
{ |
||||
|
LocalizationDictionary = new LocalizationDictionary(); |
||||
|
} |
||||
|
|
||||
|
internal void AddOrUpdate(string resourceName, Dictionary<string, ILocalizationDictionary> dictionares) |
||||
|
{ |
||||
|
var _dictionares = LocalizationDictionary |
||||
|
.GetOrAdd(resourceName, () => new Dictionary<string, ILocalizationDictionary>()); |
||||
|
_dictionares.AddIfNotContains(dictionares); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Dynamic |
||||
|
{ |
||||
|
public class DynamicLocalizationInitializeService : IHostedService |
||||
|
{ |
||||
|
protected ILocalizationStore Store { get; } |
||||
|
protected AbpLocalizationOptions LocalizationOptions { get; } |
||||
|
protected AbpLocalizationDynamicOptions DynamicOptions { get; } |
||||
|
|
||||
|
public DynamicLocalizationInitializeService( |
||||
|
ILocalizationStore store, |
||||
|
IOptions<AbpLocalizationOptions> localizationOptions, |
||||
|
IOptions<AbpLocalizationDynamicOptions> dynamicOptions) |
||||
|
{ |
||||
|
Store = store; |
||||
|
DynamicOptions = dynamicOptions.Value; |
||||
|
LocalizationOptions = localizationOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
foreach (var resource in LocalizationOptions.Resources) |
||||
|
{ |
||||
|
foreach (var contributor in resource.Value.Contributors) |
||||
|
{ |
||||
|
if (contributor.GetType().IsAssignableFrom(typeof(DynamicLocalizationResourceContributor))) |
||||
|
{ |
||||
|
var resourceLocalizationDic = await Store.GetLocalizationDictionaryAsync(resource.Value.ResourceName); |
||||
|
DynamicOptions.AddOrUpdate(resource.Value.ResourceName, resourceLocalizationDic); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task StopAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,14 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Localization.Dynamic |
|
||||
{ |
|
||||
internal interface ILocalizationDispatcher |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 发布变更事件
|
|
||||
/// </summary>
|
|
||||
/// <param name="data"></param>
|
|
||||
/// <returns></returns>
|
|
||||
Task DispatchAsync(LocalizedStringCacheResetEventData data); |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Localization.Dynamic |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 本地化资源订阅
|
|
||||
/// </summary>
|
|
||||
internal interface ILocalizationSubscriber |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 订阅变更事件
|
|
||||
/// </summary>
|
|
||||
/// <param name="func"></param>
|
|
||||
void Subscribe(Func<LocalizedStringCacheResetEventData, Task> func); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,10 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Dynamic |
||||
|
{ |
||||
|
public class LocalizationDictionary : Dictionary<string, Dictionary<string, ILocalizationDictionary>> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -1,33 +0,0 @@ |
|||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Localization.Dynamic |
|
||||
{ |
|
||||
[ExposeServices( |
|
||||
typeof(ILocalizationSubscriber), |
|
||||
typeof(ILocalizationDispatcher), |
|
||||
typeof(LocalizationSubscriber))] |
|
||||
internal class LocalizationSubscriber : ILocalizationSubscriber, ILocalizationDispatcher, ISingletonDependency |
|
||||
{ |
|
||||
private Func<LocalizedStringCacheResetEventData, Task> _handler; |
|
||||
|
|
||||
public LocalizationSubscriber() |
|
||||
{ |
|
||||
_handler = (d) => |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public void Subscribe(Func<LocalizedStringCacheResetEventData, Task> func) |
|
||||
{ |
|
||||
_handler += func; |
|
||||
} |
|
||||
|
|
||||
public virtual async Task DispatchAsync(LocalizedStringCacheResetEventData data) |
|
||||
{ |
|
||||
await _handler(data); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,96 @@ |
|||||
|
using LINGYUN.Abp.OssManagement.Localization; |
||||
|
using LINGYUN.Abp.OssManagement.Settings; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.IO; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement |
||||
|
{ |
||||
|
public class FileValidater : IFileValidater, ISingletonDependency |
||||
|
{ |
||||
|
private readonly IMemoryCache _cache; |
||||
|
private readonly ISettingProvider _settingProvider; |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
private readonly IStringLocalizer _stringLocalizer; |
||||
|
|
||||
|
public FileValidater( |
||||
|
IMemoryCache cache, |
||||
|
ISettingProvider settingProvider, |
||||
|
IServiceProvider serviceProvider, |
||||
|
IStringLocalizer<AbpOssManagementResource> stringLocalizer) |
||||
|
{ |
||||
|
_cache = cache; |
||||
|
_settingProvider = settingProvider; |
||||
|
_serviceProvider = serviceProvider; |
||||
|
_stringLocalizer = stringLocalizer; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ValidationAsync(UploadFile input) |
||||
|
{ |
||||
|
var validation = await GetByCacheItemAsync(); |
||||
|
if (validation.SizeLimit * 1024 * 1024 < input.TotalSize) |
||||
|
{ |
||||
|
throw new UserFriendlyException(_stringLocalizer["UploadFileSizeBeyondLimit", validation.SizeLimit]); |
||||
|
} |
||||
|
var fileExtensionName = FileHelper.GetExtension(input.FileName); |
||||
|
if (!validation.AllowedExtensions |
||||
|
.Any(fe => fe.Equals(fileExtensionName, StringComparison.CurrentCultureIgnoreCase))) |
||||
|
{ |
||||
|
throw new UserFriendlyException(_stringLocalizer["NotAllowedFileExtensionName", fileExtensionName]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<FileValidation> GetByCacheItemAsync() |
||||
|
{ |
||||
|
var fileValidation = _cache.Get<FileValidation>(FileValidation.CacheKey); |
||||
|
if (fileValidation == null) |
||||
|
{ |
||||
|
fileValidation = await GetBySettingAsync(); |
||||
|
_cache.Set(FileValidation.CacheKey, |
||||
|
fileValidation, |
||||
|
new MemoryCacheEntryOptions |
||||
|
{ |
||||
|
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2) |
||||
|
}); |
||||
|
} |
||||
|
return fileValidation; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<FileValidation> GetBySettingAsync() |
||||
|
{ |
||||
|
var fileSizeLimited = await _settingProvider |
||||
|
.GetAsync( |
||||
|
AbpOssManagementSettingNames.FileLimitLength, |
||||
|
AbpOssManagementSettingNames.DefaultFileLimitLength); |
||||
|
var fileAllowExtension = await _settingProvider |
||||
|
.GetOrDefaultAsync(AbpOssManagementSettingNames.AllowFileExtensions, _serviceProvider); |
||||
|
|
||||
|
return new FileValidation(fileSizeLimited, fileAllowExtension.Split(',')); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class FileValidation |
||||
|
{ |
||||
|
public const string CacheKey = "Abp.OssManagement.FileValidation"; |
||||
|
public long SizeLimit { get; set; } |
||||
|
public string[] AllowedExtensions { get; set; } |
||||
|
public FileValidation() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public FileValidation( |
||||
|
long sizeLimit, |
||||
|
string[] allowedExtensions) |
||||
|
{ |
||||
|
SizeLimit = sizeLimit; |
||||
|
AllowedExtensions = allowedExtensions; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
using LINGYUN.Abp.Features.LimitValidation; |
||||
|
using LINGYUN.Abp.OssManagement.Features; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 所有登录用户公开访问文件服务接口
|
||||
|
/// bucket限制在users
|
||||
|
/// path限制在用户id
|
||||
|
/// </summary>
|
||||
|
[Authorize] |
||||
|
// 不对外公开,仅通过控制器调用
|
||||
|
[RemoteService(IsEnabled = false, IsMetadataEnabled = false)] |
||||
|
public class PublicFileAppService : OssManagementApplicationServiceBase, IPublicFileAppService |
||||
|
{ |
||||
|
private readonly IFileValidater _fileValidater; |
||||
|
private readonly IOssContainerFactory _ossContainerFactory; |
||||
|
|
||||
|
public PublicFileAppService( |
||||
|
IFileValidater fileValidater, |
||||
|
IOssContainerFactory ossContainerFactory) |
||||
|
{ |
||||
|
_fileValidater = fileValidater; |
||||
|
_ossContainerFactory = ossContainerFactory; |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(AbpOssManagementFeatureNames.OssObject.UploadFile)] |
||||
|
[RequiresLimitFeature( |
||||
|
AbpOssManagementFeatureNames.OssObject.UploadLimit, |
||||
|
AbpOssManagementFeatureNames.OssObject.UploadInterval, |
||||
|
LimitPolicy.Month)] |
||||
|
public virtual async Task<OssObjectDto> UploadAsync(UploadPublicFileInput input) |
||||
|
{ |
||||
|
await _fileValidater.ValidationAsync(new UploadFile |
||||
|
{ |
||||
|
TotalSize = input.Content.Length, |
||||
|
FileName = input.Object |
||||
|
}); |
||||
|
|
||||
|
var oss = _ossContainerFactory.Create(); |
||||
|
|
||||
|
var createOssObjectRequest = new CreateOssObjectRequest( |
||||
|
"users", |
||||
|
HttpUtility.UrlDecode(input.Object), |
||||
|
input.Content, |
||||
|
GetCurrentUserPath(HttpUtility.UrlDecode(input.Path))) |
||||
|
{ |
||||
|
Overwrite = input.Overwrite |
||||
|
}; |
||||
|
|
||||
|
var ossObject = await oss.CreateObjectAsync(createOssObjectRequest); |
||||
|
|
||||
|
return ObjectMapper.Map<OssObject, OssObjectDto>(ossObject); |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(AbpOssManagementFeatureNames.OssObject.DownloadFile)] |
||||
|
[RequiresLimitFeature( |
||||
|
AbpOssManagementFeatureNames.OssObject.DownloadLimit, |
||||
|
AbpOssManagementFeatureNames.OssObject.DownloadInterval, |
||||
|
LimitPolicy.Month)] |
||||
|
public virtual async Task<Stream> GetAsync(GetPublicFileInput input) |
||||
|
{ |
||||
|
var ossObjectRequest = new GetOssObjectRequest( |
||||
|
"users", // 需要处理特殊字符
|
||||
|
HttpUtility.UrlDecode(input.Name), |
||||
|
GetCurrentUserPath(HttpUtility.UrlDecode(input.Path)), |
||||
|
HttpUtility.UrlDecode(input.Process)); |
||||
|
|
||||
|
var ossContainer = _ossContainerFactory.Create(); |
||||
|
var ossObject = await ossContainer.GetObjectAsync(ossObjectRequest); |
||||
|
|
||||
|
return ossObject.Content; |
||||
|
} |
||||
|
|
||||
|
private string GetCurrentUserPath(string path) |
||||
|
{ |
||||
|
path = path.StartsWith("/") ? path.Substring(1) : path; |
||||
|
var userId = CurrentUser.GetId().ToString(); |
||||
|
return path.StartsWith(userId) ? path : $"{userId}/{path}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using LINGYUN.Abp.OssManagement.Localization; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Validation.StringValues; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement.Features |
||||
|
{ |
||||
|
public class AbpOssManagementFeatureDefinitionProvider : FeatureDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IFeatureDefinitionContext context) |
||||
|
{ |
||||
|
var featureGroup = context.AddGroup( |
||||
|
name: AbpOssManagementFeatureNames.GroupName, |
||||
|
displayName: L("Features:OssManagement")); |
||||
|
|
||||
|
var ossFeature = featureGroup.AddFeature( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.Default, |
||||
|
defaultValue: true.ToString(), |
||||
|
displayName: L("Features:DisplayName:OssObject"), |
||||
|
description: L("Features:Description:OssObject"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
|
||||
|
ossFeature.CreateChild( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.DownloadFile, |
||||
|
defaultValue: false.ToString(), |
||||
|
displayName: L("Features:DisplayName:DownloadFile"), |
||||
|
description: L("Features:Description:DownloadFile"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
ossFeature.CreateChild( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.DownloadLimit, |
||||
|
defaultValue: "1000", |
||||
|
displayName: L("Features:DisplayName:DownloadLimit"), |
||||
|
description: L("Features:Description:DownloadLimit"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(0, 100_0000))); // 上限100万次调用
|
||||
|
ossFeature.CreateChild( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.DownloadInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:DisplayName:DownloadInterval"), |
||||
|
description: L("Features:Description:DownloadInterval"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 12))); // 上限12月
|
||||
|
|
||||
|
ossFeature.CreateChild( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.UploadFile, |
||||
|
defaultValue: true.ToString(), |
||||
|
displayName: L("Features:DisplayName:UploadFile"), |
||||
|
description: L("Features:Description:UploadFile"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
ossFeature.CreateChild( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.UploadLimit, |
||||
|
defaultValue: "1000", |
||||
|
displayName: L("Features:DisplayName:UploadLimit"), |
||||
|
description: L("Features:Description:UploadLimit"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(0, 100_0000))); // 上限100万次调用
|
||||
|
ossFeature.CreateChild( |
||||
|
name: AbpOssManagementFeatureNames.OssObject.UploadInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:DisplayName:UploadInterval"), |
||||
|
description: L("Features:Description:UploadInterval"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 12))); // 上限12月
|
||||
|
|
||||
|
// TODO: 此功能需要控制器协同,暂时不实现
|
||||
|
//fileSystemFeature.CreateChild(
|
||||
|
// name: AbpOssManagementFeatureNames.OssObject.MaxUploadFileCount,
|
||||
|
// defaultValue: 1.ToString(),
|
||||
|
// displayName: L("Features:DisplayName:MaxUploadFileCount"),
|
||||
|
// description: L("Features:Description:MaxUploadFileCount"),
|
||||
|
// valueType: new FreeTextStringValueType(new NumericValueValidator(1, 10)));
|
||||
|
} |
||||
|
|
||||
|
protected ILocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<AbpOssManagementResource>(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
namespace LINGYUN.Abp.OssManagement.Features |
||||
|
{ |
||||
|
public class AbpOssManagementFeatureNames |
||||
|
{ |
||||
|
public const string GroupName = "AbpOssManagement"; |
||||
|
|
||||
|
|
||||
|
public class OssObject |
||||
|
{ |
||||
|
public const string Default = GroupName + ".OssObject"; |
||||
|
/// <summary>
|
||||
|
/// 下载文件功能
|
||||
|
/// </summary>
|
||||
|
public const string DownloadFile = Default + ".DownloadFile"; |
||||
|
/// <summary>
|
||||
|
/// 下载文件功能限制次数
|
||||
|
/// </summary>
|
||||
|
public const string DownloadLimit = Default + ".DownloadLimit"; |
||||
|
/// <summary>
|
||||
|
/// 下载文件功能限制次数周期
|
||||
|
/// </summary>
|
||||
|
public const string DownloadInterval = Default + ".DownloadInterval"; |
||||
|
/// <summary>
|
||||
|
/// 上传文件功能
|
||||
|
/// </summary>
|
||||
|
public const string UploadFile = Default + ".UploadFile"; |
||||
|
/// <summary>
|
||||
|
/// 上传文件功能限制次数
|
||||
|
/// </summary>
|
||||
|
public const string UploadLimit = Default + ".UploadLimit"; |
||||
|
/// <summary>
|
||||
|
/// 上传文件功能限制次数周期
|
||||
|
/// </summary>
|
||||
|
public const string UploadInterval = Default + ".UploadInterval"; |
||||
|
/// <summary>
|
||||
|
/// 最大上传文件
|
||||
|
/// </summary>
|
||||
|
public const string MaxUploadFileCount = Default + ".MaxUploadFileCount"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue