修改bug:对象释放后访问对象的错误
This commit is contained in:
13
FileService/Abstractions/IBlobUploader.cs
Normal file
13
FileService/Abstractions/IBlobUploader.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ufangx.FileServices.Models;
|
||||
|
||||
namespace Ufangx.FileServices.Abstractions
|
||||
{
|
||||
public interface IBlobUploader
|
||||
{
|
||||
Task<object> Handle(Blob blob, string schemeName = null);
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,11 @@ namespace Ufangx.FileServices.Abstractions
|
||||
///
|
||||
/// </summary>
|
||||
string DefaultSchemeName { get; }
|
||||
/// <summary>
|
||||
/// 生成文件名称
|
||||
/// </summary>
|
||||
/// <param name="originName"></param>
|
||||
/// <param name="directory"></param>
|
||||
/// <returns></returns>
|
||||
Task<string> GenerateFileName(string originName, string schemeName, string directory = null);
|
||||
FileValidateResult Validate(string schemeName, string fileName,long fileSize);
|
||||
IFileHandler GetHandler(string schemeName);
|
||||
string GetStoreDirectory(string schemeName);
|
||||
IFileService GetFileService();
|
||||
IResumableService GetResumableService();
|
||||
FileServiceScheme GetScheme(string schemeName);
|
||||
IEnumerable<FileServiceScheme> GetSchemes();
|
||||
|
||||
FileNameRuleOptions GetNameRuleOptions();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
16
FileService/Abstractions/IResumableCreator.cs
Normal file
16
FileService/Abstractions/IResumableCreator.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ufangx.FileServices.Models;
|
||||
|
||||
namespace Ufangx.FileServices.Abstractions
|
||||
{
|
||||
public interface IResumableCreator
|
||||
{
|
||||
Task<IResumableInfo> Create(string fileName, long fileSize, string fileType, long blobCount, int blobSize, string schemeName = null, string dir = null, string name = null);
|
||||
Task<IResumableInfo> Get(string key);
|
||||
FileValidateResult Validate(string fileName, long fileSize, string schemeName);
|
||||
|
||||
}
|
||||
}
|
||||
17
FileService/Abstractions/IUploader.cs
Normal file
17
FileService/Abstractions/IUploader.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ufangx.FileServices.Models;
|
||||
|
||||
namespace Ufangx.FileServices.Abstractions
|
||||
{
|
||||
public interface IUploader
|
||||
{
|
||||
FileValidateResult Validate(IFormFile file, string schemeName=null);
|
||||
FileValidateResult Validate(IFormFileCollection files, string schemeName=null);
|
||||
Task<object> Handle(IFormFileCollection files, string schemeName = null);
|
||||
Task<object> Handle(IFormFile file,string schemeName=null, string dir=null,string name=null);
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,18 @@ using System.Text;
|
||||
using Ufangx.FileServices;
|
||||
using Ufangx.FileServices.Abstractions;
|
||||
using Ufangx.FileServices.Models;
|
||||
using Ufangx.FileServices.Services;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class FileServiceBuilderServiceCollectionExtensions
|
||||
{
|
||||
public static IFileServiceBuilder AddFileServiceBuilder(this IServiceCollection services, Action<FileServiceOptions> configureBuilder = null) {
|
||||
services.AddTransient<IFileServiceProvider, FileServiceProvider>();
|
||||
public static IFileServiceBuilder AddFileServices(this IServiceCollection services, Action<FileServiceOptions> configureBuilder = null) {
|
||||
services.Configure(configureBuilder);
|
||||
services.AddSingleton<IFileServiceProvider, FileServiceProvider>();
|
||||
services.AddTransient<IUploader, Uploader>();
|
||||
services.AddTransient<IResumableCreator, ResumableCreator>();
|
||||
services.AddTransient<IBlobUploader, BlobUploader>();
|
||||
return new FileServiceBuilder() { Services = services };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,20 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Ufangx.FileServices
|
||||
{
|
||||
public class FileServiceProvider : IFileServiceProvider,IDisposable
|
||||
public class FileServiceProvider : IFileServiceProvider
|
||||
{
|
||||
private readonly FileServiceOptions options;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IServiceScope serviceScope;
|
||||
|
||||
public FileServiceProvider(IOptions<FileServiceOptions> options, IServiceProvider serviceProvider)
|
||||
public FileServiceProvider(IOptions<FileServiceOptions> options)
|
||||
{
|
||||
this.options = options.Value;
|
||||
this.serviceScope = serviceProvider.CreateScope();
|
||||
this.serviceProvider = serviceScope.ServiceProvider;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AuthenticationSchemes => options.AuthenticationSchemes;
|
||||
|
||||
public string DefaultSchemeName => options.DefaultScheme;
|
||||
|
||||
FileServiceScheme GetScheme(string name) {
|
||||
public FileServiceScheme GetScheme(string name) {
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("message", nameof(name));
|
||||
@@ -40,100 +36,12 @@ namespace Ufangx.FileServices
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
public IFileHandler GetHandler(string schemeName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(schemeName)) return null;
|
||||
var scheme = GetScheme(schemeName);
|
||||
if (scheme.HandlerType == null) return null;
|
||||
if (!typeof(IFileHandler).IsAssignableFrom(scheme.HandlerType)) {
|
||||
throw new Exception($"类型“{scheme.HandlerType.FullName}”没有实现“{typeof(IFileHandler).FullName}”接口");
|
||||
}
|
||||
return serviceProvider.GetService(scheme.HandlerType) as IFileHandler;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public string GetStoreDirectory(string schemeName)
|
||||
=> string.IsNullOrWhiteSpace(schemeName) ? string.Empty : GetScheme(schemeName).StoreDirectory;
|
||||
|
||||
public async Task<string> GenerateFileName(string originName, string schemeName, string directory = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(originName))
|
||||
{
|
||||
throw new ArgumentException("message", nameof(originName));
|
||||
}
|
||||
|
||||
FileNameRule nameRule = options?.RuleOptions?.Rule ?? FileNameRule.Ascending;
|
||||
if (nameRule == FileNameRule.Custom && options?.RuleOptions?.Custom==null) {
|
||||
nameRule = FileNameRule.Ascending;
|
||||
}
|
||||
if (directory == null) { directory = string.Empty; }
|
||||
directory = Path.Combine(GetStoreDirectory(schemeName), directory);
|
||||
string fileName;
|
||||
switch (nameRule)
|
||||
{
|
||||
case FileNameRule.Ascending:
|
||||
fileName = Path.Combine(directory, originName);
|
||||
int index = 0;
|
||||
var fileService = GetFileService();
|
||||
while (await fileService.Exists(fileName))
|
||||
{
|
||||
fileName = Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(originName)}({++index}){Path.GetExtension(originName)}");
|
||||
}
|
||||
break;
|
||||
case FileNameRule.Date:
|
||||
fileName = Path.Combine(directory, string.Format(options?.RuleOptions?.Format ?? "{0:yyyyMMddHHmmss}", DateTime.Now) + Path.GetExtension(originName));
|
||||
break;
|
||||
case FileNameRule.Custom:
|
||||
fileName = Path.Combine(directory, options.RuleOptions.Custom(originName));
|
||||
break;
|
||||
default:
|
||||
fileName = Path.Combine(directory, originName);
|
||||
break;
|
||||
}
|
||||
return fileName.Replace('\\', '/');
|
||||
|
||||
}
|
||||
|
||||
public FileValidateResult Validate(string schemeName, string fileName,long fileSize)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(schemeName)) {
|
||||
return FileValidateResult.Successfully;
|
||||
}
|
||||
var scheme = GetScheme(schemeName);
|
||||
if (scheme.LimitedSize.HasValue && scheme.LimitedSize.Value < fileSize) {
|
||||
return FileValidateResult.Limited;
|
||||
}
|
||||
|
||||
string ext = Path.GetExtension(fileName);
|
||||
return
|
||||
scheme.SupportExtensions==null
|
||||
|| scheme.SupportExtensions.Count()==0
|
||||
|| scheme.SupportExtensions.Any(e => string.Equals(ext, e, StringComparison.OrdinalIgnoreCase))
|
||||
? FileValidateResult.Successfully : FileValidateResult.Invalid;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public IFileService GetFileService()
|
||||
{
|
||||
return serviceProvider.GetService<IFileService>();
|
||||
}
|
||||
|
||||
public IResumableService GetResumableService()
|
||||
{
|
||||
return serviceProvider.GetService<IResumableService>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
serviceScope.Dispose();
|
||||
}
|
||||
|
||||
public FileNameRuleOptions GetNameRuleOptions() => options.RuleOptions;
|
||||
public IEnumerable<FileServiceScheme> GetSchemes()
|
||||
{
|
||||
return options.Schemes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@ namespace Microsoft.AspNetCore.Builder
|
||||
app.Map(pathMatch, pathRoute => {
|
||||
pathRoute.Map("/uploader", route =>
|
||||
{
|
||||
route.MapWhen(ctx => string.Equals("GET", ctx.Request.Method, StringComparison.OrdinalIgnoreCase), a => a.UseMiddleware<ResumableInfoUploaderMiddleware>())
|
||||
route
|
||||
.MapWhen(ctx => string.Equals("GET", ctx.Request.Method, StringComparison.OrdinalIgnoreCase), a => a.UseMiddleware<ResumableInfoUploaderMiddleware>())
|
||||
.MapWhen(ctx => string.Equals("DELETE", ctx.Request.Method, StringComparison.OrdinalIgnoreCase), a => a.UseMiddleware<ResumableInfoDeleteUploaderMiddleware>())
|
||||
.MapWhen(ctx =>
|
||||
string.Equals("POST", ctx.Request.Method, StringComparison.OrdinalIgnoreCase) && ctx.Request.Form.Files.Count== 1 && !string.IsNullOrWhiteSpace(ctx.Request.Form["key"]) && !string.IsNullOrWhiteSpace(ctx.Request.Form["blobIndex"]),
|
||||
string.Equals("POST", ctx.Request.Method, StringComparison.OrdinalIgnoreCase) && ctx.Request.Form.Files.Count == 1 && !string.IsNullOrWhiteSpace(ctx.Request.Form["key"]) && !string.IsNullOrWhiteSpace(ctx.Request.Form["blobIndex"]),
|
||||
a => a.UseMiddleware<ResumableServiceUploaderMiddleware>())
|
||||
.MapWhen(ctx => string.Equals("POST", ctx.Request.Method, StringComparison.OrdinalIgnoreCase) && ctx.Request.Form.Files.Count > 0,
|
||||
a => a.UseMiddleware<FileServiceUploaderMiddleware>());
|
||||
|
||||
@@ -14,61 +14,33 @@ namespace Ufangx.FileServices.Middlewares
|
||||
{
|
||||
public class FileServiceUploaderMiddleware: UploaderMiddleware
|
||||
{
|
||||
private IFileService service;
|
||||
|
||||
public IFileService Service
|
||||
=> service??(service=serviceProvider.GetFileService());
|
||||
|
||||
public FileServiceUploaderMiddleware(RequestDelegate next,
|
||||
IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
public FileServiceUploaderMiddleware(RequestDelegate next, IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
{
|
||||
}
|
||||
protected override string GetRequestParams(string key)
|
||||
{
|
||||
return Context.Request.Form[key];
|
||||
}
|
||||
async Task SingleHandler(IFormFile file) {
|
||||
if (await ValidateResultHandler(Validate(file.FileName, file.Length)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string path = await GetStoreFileName(file.FileName);
|
||||
if (await Service.Save(path, file.OpenReadStream()))
|
||||
{
|
||||
await WriteJsonAsync(await SchemeHandler(path, file.ContentType, file.Length));
|
||||
return;
|
||||
}
|
||||
await Error(StringLocalizer["保存文件失败"]);
|
||||
|
||||
}
|
||||
async Task MultiHandler(IFormFileCollection files) {
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (await ValidateResultHandler(Validate(file.FileName, file.Length)))
|
||||
protected async override Task Handler(HttpContext context) {
|
||||
string _scheme = context.Request.Headers["scheme"];
|
||||
string dir = context.Request.Form["dir"];
|
||||
string fileName = context.Request.Form["Name"];
|
||||
var uploader = context.RequestServices.GetRequiredService<IUploader>();
|
||||
if (context.Request.Form.Files.Count > 1)
|
||||
{
|
||||
if (await ValidateResultHandler(uploader.Validate(context.Request.Form.Files, _scheme),context))
|
||||
{
|
||||
//如果有文件验证失败则返回
|
||||
return;
|
||||
}
|
||||
}
|
||||
List<object> results = new List<object>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
string path = await GetStoreFileName(file.FileName);
|
||||
if (await Service.Save(path, file.OpenReadStream()))
|
||||
{
|
||||
results.Add(await SchemeHandler(path, file.ContentType, file.Length));
|
||||
}
|
||||
}
|
||||
await WriteJsonAsync(results);
|
||||
}
|
||||
protected async override Task Handler(HttpContext context) {
|
||||
if (context.Request.Form.Files.Count > 1)
|
||||
{
|
||||
await MultiHandler(context.Request.Form.Files);
|
||||
await WriteJsonAsync(context, await uploader.Handle(context.Request.Form.Files, _scheme));
|
||||
return;
|
||||
|
||||
}
|
||||
else if(context.Request.Form.Files.Count==1) {
|
||||
await SingleHandler(context.Request.Form.Files[0]);
|
||||
else if (context.Request.Form.Files.Count == 1)
|
||||
{
|
||||
if (await ValidateResultHandler(uploader.Validate(context.Request.Form.Files[0], _scheme), context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
await WriteJsonAsync(context, await uploader.Handle(context.Request.Form.Files[0], _scheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,14 @@ namespace Ufangx.FileServices.Middlewares
|
||||
{
|
||||
public class ResumableInfoDeleteUploaderMiddleware : UploaderMiddleware
|
||||
{
|
||||
private IResumableService service;
|
||||
|
||||
public IResumableService Service => service??(service=serviceProvider.GetResumableService());
|
||||
public ResumableInfoDeleteUploaderMiddleware(RequestDelegate next,
|
||||
IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
public ResumableInfoDeleteUploaderMiddleware(RequestDelegate next, IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
{
|
||||
|
||||
}
|
||||
protected override async Task Handler(HttpContext context) {
|
||||
await WriteJsonAsync(await Service.DeleteBlobs(GetRequestParams("key")));
|
||||
}
|
||||
protected override string GetRequestParams(string key)
|
||||
protected override async Task Handler(HttpContext context)
|
||||
{
|
||||
return Context.Request.Form[key];
|
||||
string key = context.Request.Form["key"];
|
||||
var service= context.RequestServices.GetRequiredService<IResumableService>();
|
||||
await WriteJsonAsync(context, await service.DeleteBlobs(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,53 +14,54 @@ namespace Ufangx.FileServices.Middlewares
|
||||
{
|
||||
public class ResumableInfoUploaderMiddleware : UploaderMiddleware
|
||||
{
|
||||
private IResumableInfoService service;
|
||||
|
||||
public IResumableInfoService Service => service ?? (service = Context.RequestServices.GetRequiredService<IResumableInfoService>());
|
||||
|
||||
public ResumableInfoUploaderMiddleware(RequestDelegate next,
|
||||
IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
public ResumableInfoUploaderMiddleware(RequestDelegate next, IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
{
|
||||
}
|
||||
protected override async Task Handler(HttpContext context) {
|
||||
string key = GetRequestParams("key");
|
||||
protected override async Task Handler(HttpContext context)
|
||||
{
|
||||
string key = context.Request.Query["key"];
|
||||
IResumableInfo info = null;
|
||||
IResumableCreator resumableCreator = context.RequestServices.GetRequiredService<IResumableCreator>();
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
if (long.TryParse(GetRequestParams("fileSize"), out long fileSize)
|
||||
&& int.TryParse(GetRequestParams("blobSize"), out int blobSize)
|
||||
&& long.TryParse(GetRequestParams("blobCount"), out long blobCount))
|
||||
if (long.TryParse(context.Request.Query["fileSize"], out long fileSize)
|
||||
&& int.TryParse(context.Request.Query["blobSize"], out int blobSize)
|
||||
&& long.TryParse(context.Request.Query["blobCount"], out long blobCount))
|
||||
{
|
||||
string fileName = GetRequestParams("fileName");
|
||||
if (string.IsNullOrWhiteSpace(fileName)) {
|
||||
await Error(StringLocalizer["参数文件名称(fileName)是必须的"]);
|
||||
string fileName = context.Request.Query["fileName"];
|
||||
string _scheme = context.Request.Headers["scheme"];
|
||||
string fileType= context.Request.Query["fileType"];
|
||||
string dir = context.Request.Query["dir"];
|
||||
string name = context.Request.Query["name"];
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
await Error(context, "参数文件名称(fileName)是必须的");
|
||||
return;
|
||||
}
|
||||
if (await ValidateResultHandler(Validate(fileName, fileSize))) {
|
||||
if (await ValidateResultHandler(resumableCreator.Validate(fileName,fileSize,_scheme),context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
info = await Service.Create(await GetStoreFileName(fileName),
|
||||
fileName,
|
||||
info = await resumableCreator.Create(fileName,
|
||||
fileSize,
|
||||
GetRequestParams("fileType"),
|
||||
fileType,
|
||||
blobCount,
|
||||
blobSize);
|
||||
blobSize,
|
||||
_scheme,
|
||||
dir,
|
||||
name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info = await Service.Get(key);
|
||||
{
|
||||
info = await resumableCreator.Get(key);
|
||||
}
|
||||
await WriteJsonAsync(info == null ? null : new
|
||||
await WriteJsonAsync(context, info == null ? null : new
|
||||
{
|
||||
key = info.Key,
|
||||
index = info.BlobIndex,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected override string GetRequestParams(string key)
|
||||
{
|
||||
return Context.Request.Query[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,41 +16,21 @@ namespace Ufangx.FileServices.Middlewares
|
||||
{
|
||||
public class ResumableServiceUploaderMiddleware : UploaderMiddleware
|
||||
{
|
||||
private IResumableService _service;
|
||||
private readonly ILogger<ResumableServiceUploaderMiddleware> logger;
|
||||
|
||||
public IResumableService Service => _service ?? (_service = serviceProvider.GetResumableService());
|
||||
|
||||
public ResumableServiceUploaderMiddleware(RequestDelegate next,
|
||||
ILogger<ResumableServiceUploaderMiddleware> logger,
|
||||
IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
public ResumableServiceUploaderMiddleware(RequestDelegate next, IFileServiceProvider serviceProvider) : base(next, serviceProvider)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
protected override async Task Handler(HttpContext context) {
|
||||
var blobIndex = long.Parse(GetRequestParams("blobIndex"));
|
||||
string resumableKey = GetRequestParams("key");
|
||||
bool finished = false;
|
||||
await Service.SaveBlob(new Blob() { BlobIndex = blobIndex, ResumableKey = resumableKey, Data = context.Request.Form.Files[0].OpenReadStream() },
|
||||
async (info,success) =>
|
||||
{
|
||||
finished = true;
|
||||
if (success)
|
||||
{
|
||||
await WriteJsonAsync(await SchemeHandler(info.StoreName, info.FileType, info.FileSize));
|
||||
}
|
||||
else {
|
||||
await Error(StringLocalizer["保存文件失败"]);
|
||||
}
|
||||
});
|
||||
if (!finished)
|
||||
protected override async Task Handler(HttpContext context)
|
||||
{
|
||||
var blobIndex = long.Parse(context.Request.Form["blobIndex"]);
|
||||
string resumableKey = context.Request.Form["key"];
|
||||
string _scheme = context.Request.Headers["scheme"];
|
||||
var uploader = context.RequestServices.GetRequiredService<IBlobUploader>();
|
||||
await WriteJsonAsync(context, await uploader.Handle(new Blob()
|
||||
{
|
||||
await WriteJsonAsync(true);
|
||||
}
|
||||
}
|
||||
protected override string GetRequestParams(string key)
|
||||
{
|
||||
return Context.Request.Form[key];
|
||||
}
|
||||
BlobIndex = blobIndex,
|
||||
ResumableKey = resumableKey,
|
||||
Data = context.Request.Form.Files[0].OpenReadStream()
|
||||
}, _scheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,41 +19,24 @@ namespace Ufangx.FileServices.Middlewares
|
||||
public abstract class UploaderMiddleware
|
||||
{
|
||||
protected readonly RequestDelegate next;
|
||||
protected readonly IFileServiceProvider serviceProvider;
|
||||
|
||||
private IFileServiceProvider serviceProvider;
|
||||
|
||||
public UploaderMiddleware(RequestDelegate next,IFileServiceProvider serviceProvider)
|
||||
|
||||
public UploaderMiddleware(RequestDelegate next, IFileServiceProvider serviceProvider)
|
||||
{
|
||||
this.next = next;
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
protected HttpContext Context { get; private set; }
|
||||
private IStringLocalizer stringLocalizer;
|
||||
protected IStringLocalizer StringLocalizer
|
||||
=> stringLocalizer??(stringLocalizer=Context.RequestServices.GetService<IStringLocalizer>()??new DefaultStringLocalizer());
|
||||
|
||||
|
||||
protected abstract string GetRequestParams(string key);
|
||||
protected async Task<string> GetStoreFileName(string originName) {
|
||||
|
||||
string dir = GetRequestParams("dir");
|
||||
string fileName = GetRequestParams("Name");
|
||||
string scheme = GetScheme();
|
||||
//如果客户端指定了
|
||||
if (!string.IsNullOrWhiteSpace(fileName))
|
||||
|
||||
async Task<bool> Authenticate(HttpContext context)
|
||||
{
|
||||
if (serviceProvider.AuthenticationSchemes?.Count() > 0)
|
||||
{
|
||||
return Path.Combine(serviceProvider.GetStoreDirectory(scheme), dir, fileName).Replace('\\', '/');
|
||||
}
|
||||
//否则生成文件名称
|
||||
return await serviceProvider.GenerateFileName(originName, scheme, dir);
|
||||
|
||||
}
|
||||
async Task<bool> Authenticate(HttpContext context) {
|
||||
if (serviceProvider.AuthenticationSchemes?.Count() > 0) {
|
||||
foreach ( var scheme in serviceProvider.AuthenticationSchemes)
|
||||
foreach (var scheme in serviceProvider.AuthenticationSchemes)
|
||||
{
|
||||
var result = await context.AuthenticateAsync(scheme);
|
||||
if (result.Succeeded) {
|
||||
if (result.Succeeded)
|
||||
{
|
||||
context.User = result.Principal;
|
||||
return true;
|
||||
}
|
||||
@@ -61,65 +44,48 @@ namespace Ufangx.FileServices.Middlewares
|
||||
return false;
|
||||
}
|
||||
return context.User.Identity.IsAuthenticated;
|
||||
//return true;
|
||||
}
|
||||
string _scheme;
|
||||
protected string GetScheme() {
|
||||
if (_scheme == null)
|
||||
{
|
||||
_scheme =Context.Request.Headers["scheme"];
|
||||
if (string.IsNullOrWhiteSpace(_scheme))
|
||||
{
|
||||
_scheme = serviceProvider.DefaultSchemeName ?? string.Empty;
|
||||
}
|
||||
}
|
||||
return _scheme;
|
||||
}
|
||||
protected async Task<object> SchemeHandler(string path,string fileType,long fileSize) {
|
||||
var handler = serviceProvider.GetHandler(GetScheme());
|
||||
if (handler == null) return path;
|
||||
return await handler.Handler(path, fileType, fileSize);
|
||||
|
||||
}
|
||||
protected FileValidateResult Validate(string fileName,long fileSize) {
|
||||
return serviceProvider.Validate(GetScheme(), fileName, fileSize);
|
||||
}
|
||||
protected abstract Task Handler(HttpContext context);
|
||||
protected async Task<bool> ValidateResultHandler(FileValidateResult fileValidateResult)
|
||||
protected async Task<bool> ValidateResultHandler(FileValidateResult fileValidateResult,HttpContext context)
|
||||
{
|
||||
if (fileValidateResult == FileValidateResult.Successfully) return false;
|
||||
await Error(StringLocalizer[fileValidateResult == FileValidateResult.Invalid ? "不支持的文件类型" : "文件大小超过了最大限制"]);
|
||||
return true;
|
||||
await Error(context,fileValidateResult == FileValidateResult.Invalid ? "不支持的文件类型" : "文件大小超过了最大限制");
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
protected Task Error(object error, int code = 500) {
|
||||
Context.Response.StatusCode = code;
|
||||
return WriteJsonAsync(error);
|
||||
protected Task Error(HttpContext context, object error, int code = 500)
|
||||
{
|
||||
context.Response.StatusCode = code;
|
||||
return WriteJsonAsync(context,error);
|
||||
}
|
||||
protected Task Error(string message, int code = 500)
|
||||
=> Error((object)message, code);
|
||||
protected Task WriteAsync(string content) => WriteJsonAsync(content);
|
||||
protected Task WriteJsonAsync(object obj)
|
||||
=> Context.Response.WriteAsync(JsonConvert.SerializeObject(obj,
|
||||
Formatting.Indented,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
}), Encoding.UTF8);
|
||||
protected async Task Error(HttpContext context, string message, int code = 500)
|
||||
{
|
||||
var localizer = context.RequestServices.GetService<IStringLocalizer>();
|
||||
string localMessage = localizer != null ? localizer[message] : message;
|
||||
await Error(context, (object)localMessage, code);
|
||||
}
|
||||
|
||||
protected Task WriteAsync(HttpContext context, string content) => WriteJsonAsync(context,content);
|
||||
protected async Task WriteJsonAsync(HttpContext context, object obj)
|
||||
{
|
||||
|
||||
context.Response.ContentType = "application/json; charset=UTF-8";
|
||||
await context.Response.WriteAsync(JsonConvert.SerializeObject(obj,
|
||||
Formatting.Indented,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
}), Encoding.UTF8);
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
Context = context;
|
||||
context.Response.ContentType = "application/json; charset=UTF-8";
|
||||
if (await Authenticate(context)) {
|
||||
if (await Authenticate(context))
|
||||
{
|
||||
await Handler(context);
|
||||
return;
|
||||
}
|
||||
context.Response.StatusCode = 401;
|
||||
await WriteJsonAsync(StringLocalizer["身份认证失败!"].Value);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
await Error(context, "身份认证失败!", 401);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
42
FileService/Services/BlobUploader.cs
Normal file
42
FileService/Services/BlobUploader.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ufangx.FileServices.Abstractions;
|
||||
using Ufangx.FileServices.Models;
|
||||
|
||||
namespace Ufangx.FileServices.Services
|
||||
{
|
||||
public class BlobUploader :Uploader, IBlobUploader
|
||||
{
|
||||
private readonly IResumableService service;
|
||||
|
||||
public BlobUploader(IResumableService fileService, IFileServiceProvider serviceProvider, IHttpContextAccessor contextAccessor) : base(fileService, serviceProvider, contextAccessor)
|
||||
{
|
||||
this.service = fileService;
|
||||
}
|
||||
|
||||
public async Task<object> Handle(Blob blob,string schemeName=null)
|
||||
{
|
||||
bool finished = false;
|
||||
object result =null;
|
||||
var ok= await service.SaveBlob(blob,
|
||||
async (info, success) =>
|
||||
{
|
||||
finished = true;
|
||||
if (success)
|
||||
{
|
||||
result =await SchemeHandler(GetScheme(schemeName), info.StoreName, info.FileType, info.FileSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
});
|
||||
return finished ? result : ok;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
68
FileService/Services/ResumableCreator.cs
Normal file
68
FileService/Services/ResumableCreator.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ufangx.FileServices.Abstractions;
|
||||
using Ufangx.FileServices.Models;
|
||||
|
||||
namespace Ufangx.FileServices.Services
|
||||
{
|
||||
public class ResumableCreator : Uploader, IResumableCreator
|
||||
{
|
||||
private readonly IResumableInfoService resumableInfoService;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="resumableInfoService"></param>
|
||||
/// <param name="fileService"></param>
|
||||
/// <param name="serviceProvider"></param>
|
||||
/// <param name="contextAccessor"></param>
|
||||
public ResumableCreator(IResumableInfoService resumableInfoService, IFileService fileService, IFileServiceProvider serviceProvider, IHttpContextAccessor contextAccessor)
|
||||
: base(fileService, serviceProvider, contextAccessor)
|
||||
{
|
||||
this.resumableInfoService = resumableInfoService;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="fileSize"></param>
|
||||
/// <param name="fileType"></param>
|
||||
/// <param name="blobCount"></param>
|
||||
/// <param name="blobSize"></param>
|
||||
/// <param name="schemeName"></param>
|
||||
/// <param name="dir"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IResumableInfo> Create(string fileName, long fileSize, string fileType, long blobCount, int blobSize, string schemeName = null,string dir=null,string name=null)
|
||||
{
|
||||
return await resumableInfoService.Create(await GetStoreFileName(GetScheme(schemeName), fileName, dir, name),
|
||||
fileName,
|
||||
fileSize,
|
||||
fileType,
|
||||
blobCount,
|
||||
blobSize);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IResumableInfo> Get(string key)
|
||||
{
|
||||
return await resumableInfoService.Get(key);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="fileSize"></param>
|
||||
/// <param name="schemeName"></param>
|
||||
/// <returns></returns>
|
||||
public FileValidateResult Validate(string fileName, long fileSize, string schemeName)
|
||||
{
|
||||
return Validate(fileName, fileSize, GetScheme(schemeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
155
FileService/Services/Uploader.cs
Normal file
155
FileService/Services/Uploader.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ufangx.FileServices.Abstractions;
|
||||
using Ufangx.FileServices.Models;
|
||||
|
||||
namespace Ufangx.FileServices.Services
|
||||
{
|
||||
public class Uploader : IUploader
|
||||
{
|
||||
private readonly IFileService fileService;
|
||||
private readonly IFileServiceProvider serviceProvider;
|
||||
private readonly IHttpContextAccessor contextAccessor;
|
||||
|
||||
public Uploader(IFileService fileService,IFileServiceProvider serviceProvider,IHttpContextAccessor contextAccessor)
|
||||
{
|
||||
this.fileService = fileService;
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.contextAccessor = contextAccessor;
|
||||
}
|
||||
|
||||
protected IFileService FileService => fileService;
|
||||
|
||||
protected IFileServiceProvider ServiceProvider => serviceProvider;
|
||||
|
||||
protected IHttpContextAccessor ContextAccessor => contextAccessor;
|
||||
protected async Task<string> GenerateFileName(FileServiceScheme scheme, string originName, string directory = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(originName))
|
||||
{
|
||||
throw new ArgumentException("message", nameof(originName));
|
||||
}
|
||||
var fileNameRuleOptions = serviceProvider.GetNameRuleOptions();
|
||||
FileNameRule nameRule = fileNameRuleOptions?.Rule ?? FileNameRule.Ascending;
|
||||
if (nameRule == FileNameRule.Custom && fileNameRuleOptions?.Custom == null)
|
||||
{
|
||||
nameRule = FileNameRule.Ascending;
|
||||
}
|
||||
if (directory == null) { directory = string.Empty; }
|
||||
directory = Path.Combine(scheme?.StoreDirectory ?? string.Empty, directory);
|
||||
string fileName;
|
||||
switch (nameRule)
|
||||
{
|
||||
case FileNameRule.Ascending:
|
||||
fileName = Path.Combine(directory, originName);
|
||||
int index = 0;
|
||||
while (await fileService.Exists(fileName))
|
||||
{
|
||||
fileName = Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(originName)}({++index}){Path.GetExtension(originName)}");
|
||||
}
|
||||
break;
|
||||
case FileNameRule.Date:
|
||||
fileName = Path.Combine(directory, string.Format(fileNameRuleOptions?.Format ?? "{0:yyyyMMddHHmmss}", DateTime.Now) + Path.GetExtension(originName));
|
||||
break;
|
||||
case FileNameRule.Custom:
|
||||
fileName = Path.Combine(directory, fileNameRuleOptions.Custom(originName));
|
||||
break;
|
||||
default:
|
||||
fileName = Path.Combine(directory, originName);
|
||||
break;
|
||||
}
|
||||
return fileName.Replace('\\', '/');
|
||||
|
||||
}
|
||||
protected async Task<string> GetStoreFileName(FileServiceScheme scheme, string originName, string dir, string fileName)
|
||||
{
|
||||
//如果客户端指定了
|
||||
if (!string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
return Path.Combine(scheme?.StoreDirectory ?? string.Empty, dir, fileName).Replace('\\', '/');
|
||||
}
|
||||
//否则生成文件名称
|
||||
return await GenerateFileName(scheme, originName, dir);
|
||||
}
|
||||
public async Task<object> Handle(IFormFileCollection files, string schemeName = null)
|
||||
{
|
||||
List<object> results = new List<object>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
var result = await Handle(file, schemeName);
|
||||
if (result != null){ results.Add(result); }
|
||||
}
|
||||
return results;
|
||||
}
|
||||
protected async Task<object> SchemeHandler(FileServiceScheme scheme, string path, string fileType, long fileSize)
|
||||
{
|
||||
if (scheme?.HandlerType == null) return path;
|
||||
if (!typeof(IFileHandler).IsAssignableFrom(scheme.HandlerType))
|
||||
{
|
||||
throw new Exception($"类型“{scheme.HandlerType.FullName}”没有实现“{typeof(IFileHandler).FullName}”接口");
|
||||
}
|
||||
var handler = contextAccessor.HttpContext.RequestServices.GetService(scheme.HandlerType) as IFileHandler;
|
||||
if (handler == null) return path;
|
||||
return await handler.Handler(path, fileType, fileSize);
|
||||
|
||||
}
|
||||
async Task<object> Handle(IFormFile file, FileServiceScheme scheme, string dir, string name)
|
||||
{
|
||||
string path = await GetStoreFileName(scheme, file.FileName, dir, name);
|
||||
if (await fileService.Save(path, file.OpenReadStream()))
|
||||
{
|
||||
return await SchemeHandler(scheme, path, file.ContentType, file.Length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected FileServiceScheme GetScheme(string schemeName) {
|
||||
if (string.IsNullOrWhiteSpace(schemeName))
|
||||
{
|
||||
schemeName = serviceProvider.DefaultSchemeName;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(schemeName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return serviceProvider.GetScheme(schemeName);
|
||||
}
|
||||
public Task<object> Handle(IFormFile file, string schemeName = null, string dir = null, string name = null)
|
||||
=> Handle(file, GetScheme(schemeName), dir, name);
|
||||
protected FileValidateResult Validate(string fileName, long fileSize, FileServiceScheme scheme) {
|
||||
if (scheme == null) return FileValidateResult.Successfully;
|
||||
if (scheme.LimitedSize.HasValue && scheme.LimitedSize.Value < fileSize)
|
||||
{
|
||||
return FileValidateResult.Limited;
|
||||
}
|
||||
string ext = Path.GetExtension(fileName);
|
||||
return
|
||||
scheme.SupportExtensions == null
|
||||
|| scheme.SupportExtensions.Count() == 0
|
||||
|| scheme.SupportExtensions.Any(e => string.Equals(ext, e, StringComparison.OrdinalIgnoreCase))
|
||||
? FileValidateResult.Successfully : FileValidateResult.Invalid;
|
||||
}
|
||||
|
||||
FileValidateResult Validate(IFormFile file, FileServiceScheme scheme)
|
||||
=> Validate(file.FileName, file.Length, scheme);
|
||||
|
||||
public FileValidateResult Validate(IFormFile file, string schemeName = null)
|
||||
=> Validate(file, GetScheme(schemeName));
|
||||
public FileValidateResult Validate(IFormFileCollection files, string schemeName = null)
|
||||
{
|
||||
var scheme = GetScheme(schemeName);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var result = Validate(file, scheme);
|
||||
if (result != FileValidateResult.Successfully) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return FileValidateResult.Successfully;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace TestWeb
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddFileServiceBuilder(opt=>opt.DefaultScheme="")
|
||||
services.AddFileServices(opt=>opt.DefaultScheme="")
|
||||
.AddLocalServices(o => o.StorageRootDir = hostEnvironment.ContentRootPath);
|
||||
services.AddControllersWithViews();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user