Web开发系统文件默认存储在wwwroot目录下面,现在越来越多的系统服务化了,UI也更加多元化,当然文件可以用第三方的文件服务,但是这里准备文件分离出来构建自己的文件服务配合数据库表来实现(UosoOSS)
思路:
1、构建自己的文件夹及文件管理(包括私有权限)这里需要结合IdentityServer4的客户端模式中的一些客户端ID设计到文件管理中去
2、实现一个类似 AliyunOSS的文件管理及客户端类库,帮助实现上传,以及文件地址的生成
3、处理文件的访问权限
首先来实现服务部分,构建自己的API文件服务
配置文件的根目录以及文件访问的根路径
public class LYMFileServerOptions { /// <summary> /// 文件存储路径 /// </summary> public string PhyicalFilePath { get; set; } /// <summary> /// 文件访问的路径 必须以"/"开始 /// </summary> public string RequestPath { get; set; } }
public static IServiceCollection AddUosoFile(this IServiceCollection services, Action<LYMFileServerOptions> serverOptions) {
//LYMFileServerOptions 配置处理
//相关服务处理
}
接下来就是处理中间件了,这里需要用到UseFileServer,处理相关的路径就可以了
var fileProvider = new PhysicalFileProvider(ppath); var fileServerOptions = new FileServerOptions(); fileServerOptions.DefaultFilesOptions.DefaultFileNames = new[] { "" }; fileServerOptions.FileProvider = fileProvider; fileServerOptions.RequestPath = options.RequestPath; applicationBuilder.UseFileServer(fileServerOptions);
1、接下来我们还需要构建自己的文件上次及文件访问限制设置
applicationBuilder.UseEndpoints(endpoint => { endpoint.MapPost("/upload", async context => { var clientId = context.User.Claims.Where(c => c.Type == "client_id").FirstOrDefault()?.Value; var fileRepository = context.RequestServices.GetService<IFileRepository>(); #region FormData上传 var bucketname = context.Request.Form["bucketname"]; if (context.Request.Form.Files == null || context.Request.Form.Files.Count == 0) { context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = 1, message = "上传文件不存在", filename = "" })); return; } try { #region 验证文件夹 //验证文件夹相关逻辑....#endregion var file = context.Request.Form.Files[0]; var filename = context.Request.Form["filename"]; //可以没传服务端可以生成,为了客户端自定义的文件名称using (var stream = file.OpenReadStream()) { const int FILE_WRITE_SIZE = 84975; var basepath = PlatformServices.Default.Application.ApplicationBasePath; var ppath = Path.Combine(basepath, options.PhyicalFilePath, bucketname); if (!Directory.Exists(ppath)) { Directory.CreateDirectory(ppath); } using (FileStream fileStream = new FileStream(Path.Combine(ppath, filename), FileMode.Create, FileAccess.Write, FileShare.Write, FILE_WRITE_SIZE, true)) { byte[] byteArr = new byte[FILE_WRITE_SIZE]; int readCount = 0; while ((readCount = await stream.ReadAsync(byteArr, 0, byteArr.Length)) > 0) { await fileStream.WriteAsync(byteArr, 0, readCount); } } }; //添加文件数据 ...... context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = 0, message = "上传成功", filename = filename })); } catch (Exception ex) { context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = 1, message = "上传失败", filename = "" })); } #endregion }).RequireAuthorization(); });
2、获取访问文件的地址 ,根据自己的规则来获取,这里可以结合IdentityServer4的token来处理,但是有一点不好每次都需IdentityServer4去校验,所以这里还是自定义规则 把前面生成到地址栏通过某种算法校验
applicationBuilder.UseEndpoints(endpoint => { endpoint.MapPost("/getfileurl", async context => { //根据自己的规则来获取,这里可以结合IdentityServer4的token来处理,但是有一点不好每次都需identityServer去校验,所以这里还是自定义规则 把前面生成到地址栏通过某种算法校验 context.Response.ContentType = "application/json"; await context.Response.WriteAsync(url); }).RequireAuthorization(); });
3、匹配文件访问的路由了
applicationBuilder.UseEndpoints(endpoint => { //{**slug} var builder = endpoint.MapGet(options.RequestPath + "/{filename}.{ext?}", async context => { object filename = string.Empty; context.Request.RouteValues.TryGetValue("filename", out filename); object ext = string.Empty; context.Request.RouteValues.TryGetValue("ext", out ext); //处理文件夹权限以及文件访问权限算法验签业务逻辑
// 成功 await context.Response.SendFileAsync(fileurl);
//文件不存在或者 没有权限 可以返回404 401
}); });
4、服务端的配置基本结束了,接下来我们来处理客户端库的处理,包括文件上传、访问地址获取,文件夹的管理等等,这里需要注意文件上传带参数的坑
public class UosoFileOSS { private string authurl; private string ossserverurl; private string clientid; private string clientsecret; public UosoFileOSS(string _authurl, string _clientid, string _clientsecret, string _ossserverurl) { this.authurl = _authurl; this.clientid = _clientid; this.clientsecret = _clientsecret; this.ossserverurl = _ossserverurl; } /// <summary> /// 文件信息 /// </summary> /// <param name="stream">文件流</param> /// <param name="bucketName"></param> /// <param name="fileName"></param> public async Task<HttpResponseMessage> UosoPutObjectAsync(Stream stream, string bucketName, string fileOldName, string fileName = "") { HttpMessageHandler httpMessageHandler = new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate }; using (var client = new HttpClient(httpMessageHandler)) { var requestoptions = new ClientCredentialsTokenRequest { Address = this.authurl + "/connect/token", ClientId = this.clientid, ClientSecret = this.clientsecret, Scope = "fileapi" }; var request =await client.RequestClientCredentialsTokenAsync(requestoptions); if (request.IsError) { throw new Exception(request.Error); } var content = new MultipartFormDataContent(); var fileContent = new StreamContent(stream); fileContent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"" + fileOldName + "\""); content.Add(fileContent); var stringContent = new StringContent(bucketName); stringContent.Headers.Add("Content-Disposition", "form-data; name=\"bucketname\""); content.Add(stringContent); var stringContent1 = new StringContent(fileName); stringContent1.Headers.Add("Content-Disposition", "form-data; name=\"filename\""); content.Add(stringContent1); client.SetBearerToken(request.AccessToken); var respose =await client.PostAsync(this.ossserverurl + "/upload", content); return respose; } } public async Task<Uri> UosoGeneratePresignedUriAsync(UosoGeneratePresignedUriRequest req) { HttpMessageHandler httpMessageHandler = new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate }; using (var client = new HttpClient(httpMessageHandler)) { var requestoptions = new ClientCredentialsTokenRequest { Address = this.authurl + "/connect/token", ClientId = this.clientid, ClientSecret = this.clientsecret, Scope = "fileapi" }; var request = await client.RequestClientCredentialsTokenAsync(requestoptions); if (request.IsError) { throw new Exception(request.Error); } var dic = new Dictionary<string, string> { { "Expires",req.Expiration.Ticks.ToString()}, { "Key",req.key} }; var content = new FormUrlEncodedContent(dic); client.SetBearerToken(request.AccessToken); var respose =await client.PostAsync(this.ossserverurl + "/getfileurl", content); var result =await respose.Content.ReadAsStringAsync(); return new Uri(result); } } /// <summary> /// 文件信息 /// </summary> /// <param name="stream">文件流</param> /// <param name="bucketName"></param> /// <param name="fileName"></param> public HttpResponseMessage UosoPutObject(Stream stream, string bucketName, string fileOldName, string fileName = "") { HttpMessageHandler httpMessageHandler = new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate }; using (var client = new HttpClient(httpMessageHandler)) { var requestoptions = new ClientCredentialsTokenRequest { Address = this.authurl + "/connect/token", ClientId = this.clientid, ClientSecret = this.clientsecret, Scope = "fileapi" }; var request = client.RequestClientCredentialsTokenAsync(requestoptions).Result; if (request.IsError) { throw new Exception(request.Error); } var content = new MultipartFormDataContent(); var fileContent = new StreamContent(stream); fileContent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"" + fileOldName + "\""); content.Add(fileContent); var stringContent = new StringContent(bucketName); stringContent.Headers.Add("Content-Disposition", "form-data; name=\"bucketname\""); content.Add(stringContent); var stringContent1 = new StringContent(fileName); stringContent1.Headers.Add("Content-Disposition", "form-data; name=\"filename\""); content.Add(stringContent1); client.SetBearerToken(request.AccessToken); var respose = client.PostAsync(this.ossserverurl + "/upload", content).Result; return respose; } } public Uri UosoGeneratePresignedUri(UosoGeneratePresignedUriRequest req) { HttpMessageHandler httpMessageHandler = new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate }; using (var client = new HttpClient(httpMessageHandler)) { var requestoptions = new ClientCredentialsTokenRequest { Address = this.authurl + "/connect/token", ClientId = this.clientid, ClientSecret = this.clientsecret, Scope = "fileapi" }; var request = client.RequestClientCredentialsTokenAsync(requestoptions).Result; if (request.IsError) { throw new Exception(request.Error); } var dic = new Dictionary<string, string> { { "Expires",req.Expiration.Ticks.ToString()}, { "Key",req.key} }; var content = new FormUrlEncodedContent(dic); client.SetBearerToken(request.AccessToken); var respose = client.PostAsync(this.ossserverurl + "/getfileurl", content).Result; var result = respose.Content.ReadAsStringAsync().Result; return new Uri(result); } } }