修改bug:对象释放后访问对象的错误

This commit is contained in:
Jackson.Bruce
2020-04-01 16:04:02 +08:00
parent 2515e9dc3d
commit 61c39e1d3f
16 changed files with 434 additions and 305 deletions

View 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);
}
}

View File

@@ -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();
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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 };
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>());

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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];
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View 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;
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View File

@@ -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();
}