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. spring生成EntityManagerFactory的三种方式

    spring生成EntityManagerFactory的三种方式 1.LocalEntityManagerFactoryBean只是简单环境中使用.它使用JPA PersistenceProvide ...

  2. keepalived 高可用lvs的dr模型(vip与dip不在同一网段)

    现在rs1和rs2上面安装httpd并准备测试页 [root@rs1 ~]# yum install httpd -y [root@rs1 ~]# echo "this is r1" ...

  3. Cnblog博客美化

    具体的使用教程文档在这里 BNDong/Cnblogs-Theme-SimpleMemory 简要的操作如下: 博客园 - 管理 - 设置 值得注意得是: 要想JS代码要申请才可以使用 博客侧边栏 可 ...

  4. 使用$.post方式来实现页面的局部刷新功能

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  5. 机器学习算法中的评价指标(准确率、召回率、F值、ROC、AUC等)

    参考链接:https://www.cnblogs.com/Zhi-Z/p/8728168.html 具体更详细的可以查阅周志华的西瓜书第二章,写的非常详细~ 一.机器学习性能评估指标 1.准确率(Ac ...

  6. Jenkins环境变量

    目录 一.环境变量 二.自定义环境变量 三.自定义全局变量 四.常用变量定义 五.常用环境变量 一.环境变量 环境变量可以被看作是pipeline与Jenkins交互的媒介.比如,可以在pipelin ...

  7. Python语法之变量

    一.变量简介 1.什么是变量 变量即变化的量,用于记录事物的某种状态,比如人的年龄.性别,游戏角色的等级.金钱. 2.如何使用变量 日常生活中: 姓名:Jason 年龄:18 爱好:音乐 程序中: u ...

  8. [BUUCTF]PWN——wustctf2020_closed

    wustctf2020_closed 附件 步骤: 例行检查,64位程序,开启了nx保护 本地试运行一下看看大概的情况 64位ida载入,首先是检索程序里的字符串,找到了后门 main函数里的关键函数 ...

  9. java 8 启动脚本优化 3

    #!/bin/bash #链接文件 source /etc/profile #java虚拟机启动参数 #通过http://xxfox.perfma.com/jvm/check来检查参数的合理性 #各参 ...

  10. 宕机导致分区丢失恢复方案testdisk

    一.执行此预案的动机 云主机数据盘分区丢失 二.执行此预案的条件 1.确定用户在报障时间点之前有过数据盘分区存在,而在报障时间点该分区消失 2.在执行我们的恢复操作之前,确保将分区所在数据盘进行备份 ...