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. CSS3新增特性\HTML标签类型

    RGBA:透明度      作用: 设置透明度(R G B A)   opacity:不透明度     文字也会被设置不透明度   圆角      border-radius:圆角{左上角,右上角.. ...

  2. linux下把一个用户从某个组中删除,而不删除用户

    查看当前用户/登录用户 基本语法 whoami / who am I 用户组 介绍 类似于角色,系统可以对有共性的多个用户进行统一的管理. 新增组 语法 groupadd 组名 案例演示 添加test ...

  3. spring整合mybatis和springmvc的配置概图

  4. spring基于注解的声明式事务控制

    package com.hope.service.impl;import com.hope.dao.IAccountDao;import com.hope.domain.Account;import ...

  5. JavaEE复习三

    Http协议是基于请求/响应模式.无状态的协议:所有请求时相互独立的.无连续的:服务器无法记住与识别用户. 对于简单的页面浏览或信息获取,http协议可以完全胜任:对于需要提供客户端和服务器端交互的网 ...

  6. 【Vulfocus解题系列】手把手教你使用Vulfocus公开靶场对Apache Log4j2远程命令执行漏洞复现

    前言 关于这个漏洞,啥都不用说了,直接发车. 工具下载 JNDIExploit:https://github.com/0x727/JNDIExploit 复现过程 启动靶场环境 直接用vulfocus ...

  7. 【kafka学习笔记】合理安排broker、partition、consumer数量

    broker的数量最好大于等于partition数量 一个partition最好对应一个硬盘,这样能最大限度发挥顺序写的优势. broker如果免得是多个partition,需要随机分发,顺序IO会退 ...

  8. CF699A Launch of Collider 题解

    Content 有 \(n\) 辆车在一条数轴上,第 \(i\) 辆车在 \(a_i\) 上,每辆车要么向左,要么向右.开始,它们以 \(1\) 个单位每秒的速度在行驶.问你第一次撞车发生在第几秒的时 ...

  9. java 编程基础 Class对象 反射:代理模式和静态代理

    生活中的代理 类(对象)代理模式 代理模式是面向对象编程中比较常见的设计模式. 1. 用户只关心接口功能,而不在乎谁提供了功能.上图中接口是 Subject 2. 接口真正实现者是上图的 RealSu ...

  10. Unhandled Exception: FormatException: Unexpected character

    错误原因 json格式不正确,检查:是否加了注释.最后一个是否加了逗号... 正确格式 { "name": "shellon", "age" ...