前段时间Gitee图床突然"暴雷",导致许多写作者笔记、博客、网站等图片全部无法访问,一时间哀怨四起,大家也纷纷意识到图片备份的重要性,博主也在上面许多作者的行列中,花费了一天时间恢复博客、以及个人网站的图片,同时迁移到腾讯OSS上,也开始着手搭建个人的文件系统平台。
在日常业务开发中,文件存储无处不在,小到图片存储访问,大到svg、zip、视频、音频等文件上传下载,如何将这些文件进行存储,并提供访问呢?此时就离不开文件系统了。
本篇文章内容:详细介绍如何在海量文件系统中选择合适的文件系统、完成自己的文件系统搭建、并将使用程序管理文件的上传下载功能封装成具体轮子。
阅读说明: 如果只是了解如何搭建和使用程序实现文件上传、下载,可直接通过目录跳转到对应章节,无需阅读完全文。
在引入文件系统之前,先来认识下相关的概念,可以更好地帮助了解后续的文件系统。在计算机世界中,数据可以简单分为:结构化数据、半结构化数据、非结构化数据三大类。
指的是能够根据预定义的模型结构化或者预定义的方式组织的数据。 简单来说,能够将信息使用某种统一的结构(如数字、文字、符号等)加以表示,该种数据可以被称为结构化数据。
通常结构化数据可以通过关系型数据库进行存储和管理,具体表现为二维形式,以行为单位,每行可以表示一个实体的信息,每行的数据属性都是相同的。
示例图片:
与结构化数据相反,该类型的数据没有一定预定义好的数据模型或者没有一个预定义的方式来组织的数据,如音频、视频、图片、邮件等。该种类型的数据没有一个固定的结构,格式可能是多种多样的。
示例图片:
介于结构化(如关系型数据库中的数据)和非结构数据(音频、视频、图片等)之间的一种数据,**它可以是自描述的(即结构可以是自定义,格式并不固定,如相同的键值下存储的数据可能是数值、字符、列表等,结构和内容是混在一起的,无明显区分)**,常见的如JSON、XML,HTML文档等。
示例图片:
在上文谈到了数据类型的划分,通常结构性数据都可以通过关系型数据进行存储和管理,而如果想管理非结构性数据,则需要使用到文件系统。
文件系统又可以分为:一般(本地)文件系统和分布式文件系统,具体特点如下:
根据存储需求,我们还可以将文件系统简单划分为“第三方文件系统”和“己方文件系统”。
“第三方文件系统”:即可以通过购买第三方厂商已经搭建好的文件系统,实现非结构类型数据存储,常见的有阿里云的OSS、腾讯云的OSS等,这种类型特点就是简单,能够购买即可使用,缺点就是数据需要存储在第三方,对于一些敏感数据可能会存在风险。
“己方文件系统”:即可以通过开源或者第三方组件,在自己服务器中搭建属于自己的文件系统,常见用于搭建文件系统的框架有:Seaweedfs、FastDfs、MinIO、Ceph、HDFS、TFS等。 这类型的特点就是数据能够存储在自己的服务器,能够最大程度控制安全问题,许多国企项目有这种强制要求,缺点就是搭建过程比直接购买第三方的服务更复杂,需要成本更高,且需要自己维护。
两种类型对比:
简单地对文件系统类型进行了分析后,下面就开始对搭建“己方文件系统”时技术选型因素做一个详细介绍,以便最大程度选择适合自己的文件系统组件。
在搭建文件系统时,主要考虑了以下几个纬度指标进行技术选型:
一款Apache基金会开源项目,基于go语言开发的高度可拓展开源的分布式存储系统、它支持基于Restful API风格进行增、删、查操作,非常适用于处理小文件。 设计之初是为了对Facebook的一篇论文的实现,用于优化内部图片的存储和获取,后来在此基础上进行不同迭代,从而得出seaweedfs产品。
官方描述,它具有以下特点:
说明:图源网络(侵联删)
综上分析,可以发现Seaweedfs具有的特点能够完美契合我们在技术选型时考虑的因素,因此,使用Seaweedfs框架搭建文件系统是相对合适的,下面就开始实战篇章-完成对Seaweedfs的搭建和使用。
在开始搭建之前,先简单介绍下Seaweedfs中不同模块的具体作用:
说明:Seaweedfs是基于Go语言开发,它支持通过weed进行源码编译安装(需有go环境,该方式主要是对seaweedfs有自定义拓展的时候使用),同时它也提供已经编译好的二级制包,可以直接解压使用(无需依赖Go环境,演示使用该种方式)
本次演示的Seaweedfs版本为:0.99,目的是方便后续使用JAVA程序集成Seaweedfs,因为使用Seaweedfs较高的版本,第三方提供的Seaweedfs客户端会有一些问题。
一、上传压缩包并解压
二、启动Master和Volume服务
说明:因为本篇文章篇幅较长,所以不在此处详细介绍启动参数的具体含义,有需要的可以参考官方网站或者下面这篇博客,都已经描述得非常清楚。
三、浏览器访问可视化界面
说明:因为文字篇幅原因,演示只放部分核心代码,全部源码已经开发到【轮子之王】开源项目中,欢迎大家到下面地址访问获取(如有帮助,可给star哦)。
一、引入客户端依赖和配置
// 依赖
<dependency>
<groupId>org.lokra.seaweedfs</groupId>
<artifactId>seaweedfs-client</artifactId>
<version>0.7.3.RELEASE</version>
</dependency>
// seaweedfs文件服务器信息
seaweedfs:
host: 127.0.0.1
port: 9333
二、封装操作系统工具类
/**
* @description: 上传单个文件到文件服务器
* @param: file
* @return: 文件的fid + 文件的请全访问地址
* @author: it
*/
public String uploadFile(MultipartFile file) throws Exception {
FileSource fileSource = getFileSource();
FileTemplate fileTemplate = new FileTemplate(fileSource.getConnection());
// 上传文件
FileHandleStatus handleStatus = fileTemplate.saveFileByStream(file.getOriginalFilename(), file.getInputStream(), contentType);
// 获取上传文件的访问地址
String fileUrl = fileTemplate.getFileUrl(handleStatus.getFileId());
// 关闭当前连接
fileSource.shutdown();
return handleStatus.getFileId() + StrUtil.DASHED + fileUrl;
}
/**
* @description: 根据文件ID删除文件
* @author: it
*/
public void deleteFileByFid(String fileId) throws Exception {
FileSource fileSource = getFileSource();
FileTemplate fileTemplate = new FileTemplate(fileSource.getConnection());
fileTemplate.deleteFile(fileId);
fileSource.shutdown();
}
/**
* @description: 根据文件下载文件
* @param: fid
* @param: response
* @param: fileName
* @author: it
*/
public void downloadFileByFid(HttpServletResponse response, HttpServletRequest request, String fid, String fileName) throws Exception {
FileSource fileSource = getFileSource();
FileTemplate fileTemplate = new FileTemplate(fileSource.getConnection());
StreamResponse fileStream = fileTemplate.getFileStream(fid);
// 设置响应头
response.setContentType(CommonConstant.CONTENT_TYPE);
response.setCharacterEncoding(CommonConstant.UTF_8);
String encodeFileName = buildingFileNameAdapterBrowser(request, fileName);
response.setHeader(CommonConstant.CONTENT_DISPOSITION, CommonConstant.ATTACHMENT_FILENAME + encodeFileName);
// 读取并写入到响应输出
InputStream inputStream = fileStream.getInputStream();
byte[] fileByte = new byte[inputStream.available()];
inputStream.read(fileByte);
response.getOutputStream().write(fileByte);
response.getOutputStream().flush();
fileSource.shutdown();
}
三、测试结果
说明:每次文件成功上传后都会给对应的文件生成唯一的fid,对文件的删、查都通过该fid实现。
1、上传文件
2、下载文件
3、删除文件
至此,从文件系统的选型、搭建到程序集成都已完成,但该文件系统与第三方OSS服务还有一个小的区别,就是无法使用类似PicGo的工具将它做成对应的图床,这个有需要的话后续再研究做个开源插件吧。
"冰冻三尺非一日之寒",从了解一门技术到真正使用它,需要用非常多的时间去认识熟悉。
文章中所有源码已开源到轮子之王项目,有需要者可通过下方链接跳转: