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

FileClientOSS

注意:MultipartFormDataContent 处理文件上传,如果段都传文件没问题,如果带有参数情况,可能写法就有点坑了,其他参数的描述不要直接加到 xxxContent中,需要单独加头文件描述,我也是Fiddler抓取了PostMan上传的描述解析到,应该比较靠谱

5、通过引用客户端类库来编写自己的文件上传帮助类,之类授权地址以及文件地址确定的情况下可以封装好,这里指定客户端的信息以及文件夹信息,这里我指定了OSSTest

 public class CustomOSSHelper
{
private readonly UosoFileOSS uosoFileOSS;
private const string oauthurl = "http://localhost:3000";
private const string ossserverurl = "http://localhost:3005";
private const string clientid = "fileclient";
private const string clientsecret = "fileclient";
private const string cbucketName = "OSSTest";
public CustomOSSHelper()
{
uosoFileOSS = new UosoFileOSS(oauthurl, clientid, clientsecret, ossserverurl);
} public HttpResponseMessage Upload(Stream stream, string bucketName, string fileName)
{
var result = uosoFileOSS.UosoPutObject(stream, bucketName, fileName);
return result;
}
public HttpResponseMessage Upload(Stream stream, string fileName)
{
var result = uosoFileOSS.UosoPutObject(stream, cbucketName, fileName);
return result;
}
public HttpResponseMessage UploadKey(Stream stream, string oldfileName,string fileName)
{
var result = uosoFileOSS.UosoPutObject(stream, cbucketName, oldfileName,fileName);
return result;
} public string GetFileUrl(string bucketName, string fileName)
{ var req = new UosoGeneratePresignedUriRequest(ossserverurl, bucketName, fileName, SignHttpMethod.Get)
{
Expiration = DateTime.Now.AddHours(1)
};
return uosoFileOSS.UosoGeneratePresignedUri(req).AbsoluteUri;
}
public string GetFileUrl(string fileName)
{ var req = new UosoGeneratePresignedUriRequest(ossserverurl, cbucketName, fileName, SignHttpMethod.Get)
{
Expiration = DateTime.Now.AddHours(1)
};
return uosoFileOSS.UosoGeneratePresignedUri(req).AbsoluteUri;
}
}

CustomOSSHelper

6、接下来我们构建一个文件服务管理的界面来添加管理文件夹、文件信息,添加一个私用的OSSTest文件夹

7、接下来我们通过api来测试下,如果客户端指定一个没有的文件夹 会提示 {"status":1,"message":"你不具有文件夹权限","filename":""},这里必须上传自己管理的已有的文件夹,直接来正确的吧

提示上传成功了,得到文件名称,文件名称可以业务端自定义可以服务端生成

通过下面的文件管理可以看到文件的一些信息

8、通过文件名称来获取访问地址,这里是为了方便客户端获取到具有访问权限的地址的路径

http://localhost:3005/files/5ed3c5ab-e891-4a9d-b425-464cea4829f5.jpg?Expires=637307912138084906&OSSAccessKeyId=fileclient&Signature=01sqQMWse7NwSXQVEjEtloOaApRGEyWQzpWiV6GVgt8%3d

获取到指定生成地址的路径,下面把地址拷贝到浏览器中看下能不能正确访问且验证下自己的权限验证是否有效

下面我们来改一改路径将fileclient改成了fileclient1,然后访问,同时这个地址是带有过期时间的,过期后也不会有访问权限,这里不贴图了

接下来我们来验证下文件夹的权限,在这之前文件设置的 私有,设置共有后,该文件夹下所有文件都可以访问,再用之前的fileclient1修改的地址访问,可以看到文件可以访问,后面的参数地址就会进入校验,所以http://localhost:3005/files/5ed3c5ab-e891-4a9d-b425-464cea4829f5.jpg 也是可以直接访问的

10、接下来我们来看下文件服务的文件也是按照我们预期生成的,访问路径也是ok的

总结:此文件服务虽不能文件同步,但是可以根据业务不同分离文件,同时此文件服务可以部署多个地址来减小服务器压力,这个文件服务模仿了AliyunOSS 文件管理来做的,也是为了公司的客户不想使用第三方的文件服务编写的

.NetCore下构建自己的文件服务管理(UosoOSS)的更多相关文章

  1. .NetCore下构建自己的服务配置中心-手动造轮子

    本人主要利用IdentityServer4以及SignalR来实现,IdentityServer4作为认证,SignalR来交互配置,这里一些代码可能就是部分提出来,主要介绍实现原理及方法 实现配置中 ...

  2. Linux文件服务管理之vsftpd

    简介 vsftpd是 "very secure FTP deamon"的缩写,是一个完全免费,开源的ftp服务器软件. 特点 小巧轻快,安全易用,支持虚拟用户.支持带宽限制等功能. ...

  3. Homebrew下安装的软件自启动服务管理工具:Launchrocket

    帮助管理Homebrew安装的服务的软件,比如使用Homebrew安装的Mysql.Redis.MongoDB,传统方式需要使用命令行的命令,而使用LaunchRocket则可以在图形界面中进行管理. ...

  4. 在VMware下进行的使用ssh服务管理远程主机

    基于密钥的安全验证--sshd服务的配置文件解析(两台linux) 首先你有两台虚拟机  并且能够ping通(该实验的目的是通过客户端访问服务端) 打开终端进入到这个界面 看一下服务  如果有这三个服 ...

  5. Linux文件服务管理之nfs

    NFS(Network File System)即网络文件系统, 是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源. 在NFS的应用中,本地NFS的客户端应 ...

  6. Linux文件服务管理之Samba

    Linux文件服务器的搭建            Samba      vsftpd      nfs       Samba服务                     作用:共享目录        ...

  7. Linux软件服务管理

    学习该课程之前先学习linux的软件安装管理 1.linux的运行级别有下面几种类型 在后面的服务启动管理之中会被使用到 [root@weiyuan httpd-2.4.20]# runlevel N ...

  8. CentOS 7下MySQL5.7.23的服务配置参数测试

    CentOS 7默认安装MySQL5.7.23,服务管理发生了变化,从sysvinit(service mysql start)变化为systemd(systemctl start mysqld.se ...

  9. .netcore下的微服务、容器、运维、自动化发布

    原文:.netcore下的微服务.容器.运维.自动化发布 微服务 1.1     基本概念 1.1.1       什么是微服务? 微服务架构是SOA思想某一种具体实现.是一种将单应用程序作为一套小型 ...

随机推荐

  1. 修改页面.JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %><%@tag ...

  2. JDK的简介,卸载和安装过程

    JDK的简介,卸载和安装过程 JDK JRE JVM JDK:Java Development Kit JRE:Java Runtime Environment JVM:Java Virtual Ma ...

  3. Mybatis环境搭建及测试

    1.新建java project,导入相应jar包 本次使用到的mybatis-3.2.7版本 mybatis需要jar包:mybatis-3.2.7.jar.lib文件下的依赖jar mysql驱动 ...

  4. 8-1yum私有云仓库

    针对centos8的BaseOS.AppStream源 yum -y install httpd systemctl enable --now httpd mkdir -pv /var/www/htm ...

  5. IPtables 之“四表五链”

    目录 架构图 IP tables 简介 包过滤防火墙 Iptables如何过滤 "四表" "五链" Iptables流程 架构图 公司架构模式(酒店迎宾比喻) ...

  6. FilesCodingConvert--批量文件编码格式转换工具

    FilesCodingConvert–批量文件编码格式转换工具 简介 最近开始学习使用Android Studio,因为它的方便易用,我打算以后就不在使用ADT的方式编写Android项目了.当从Ec ...

  7. 【LeetCode】146. LRU Cache 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典+双向链表 日期 题目地址:https://le ...

  8. 【LeetCode】137. Single Number II 解题报告(Python)

    [LeetCode]137. Single Number II 解题报告(Python) 标签: LeetCode 题目地址:https://leetcode.com/problems/single- ...

  9. 【LeetCode】494. Target Sum 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetc ...

  10. python学习第四天:python基础(字符编码和乱码到底咋回事儿)

    字符编码 这得从字符编码开始说起: 字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题.因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理. 最早的计算机在设计时采 ...