问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

一个简单易用的文件上传方案

发布网友 发布时间:2024-09-26 17:22

我来回答

1个回答

热心网友 时间:2024-10-03 01:19

现在 OSS 服务算是一个基础服务了,很多云服务厂商都有提供这样的服务,价格也不贵,松哥自己的 www.javaboy.org 用的就是类似的服务。

不过对于中小公司来说,除了购买 OSS 服务之外,也可以自己搭建专业的文件服务器,自己搭建专门的文件服务器的话,曾经比较专业的做法是 FastDFS,松哥之前也专门为之录过视频发在 B 站上,感兴趣的小伙伴可以自行查看。不过 FastDFS 搭建比较麻烦,非常容易出错,所以对各位小伙伴来说多多少少有一点门槛。

松哥在之前的文章录制的一些项目视频中,如果涉及到文件上传,基本上都是保存在项目本地,这种方式比较省事,但是安全性不高。

所以,今天给大伙介绍一个较好的玩意 MinIO,看看这个工具带给我们什么惊喜。

1. MinIO 简介

MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务,它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 KB 到最大 5T 不等。

MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

简单来说,可以使用 MinIO 来搭建一个对象存储服务,而且 MinIO 的 Java 客户端和亚马逊的 S3 云存储服务客户端接口兼容,换句话说,你会往 MinIO 上存数据,就会往 S3 上存数据。

MinIO 的特点:

兼容 Amazon S3:可以使用 MinIO SDK,MinIO Client,AWS SDK 和 AWS CLI 访问 MinIO 服务器。

较强的数据保护能力:MinIO 使用 Minio Erasure Code 来防止硬件故障。

高度可用:MinIO 服务器可以容忍分布式设置中高达(N/2)-1 节点故障。

支持 Lambda 计算。

具有加密和防篡改功能:MinIO 为加密数据提供了机密性,完整性和真实性保证,而且性能开销微乎其微。使用 AES-256-GCM,ChaCha20-Poly1305 和 AES-CBC 支持服务器端和客户端加密。

可对接后端存储:除了 MinIO 自己的文件系统,还支持 DAS、 JBODs、NAS、Google 云存储和 Azure Blob 存储。

2. MinIO 安装

不废话了,赶紧装一个体验一把吧。

为了省事,咱们就直接用 docker 来安装吧,如果你对 docker 还不熟悉,公众号后台回复 docker 获取松哥的 docker 教程。

我们执行如下命令,安装 MinIO:

docker?run?-p?9000:9000?-p?9001:9001?-d?minio/minio?server?/data?--console-address?":9000"?--address?":9001"

这个启动命令中配置了两个端口:console-address 是后台管理的网页端口;address 则是 API 通信端口。以上面的启动脚本为例,项目启动成功后,网页上的访问端口是 9000,如果我们通过 Java 代码上传文件,通信端口则是 9001。

项目启动成功后,浏览器地址栏输入 http://127.0.0.1:9000/login 即可访问到 MinIO 的后端页面:

默认的登录用户名和密码均为 minioadmin。

登录成功之后,我们首先创建一个 bucket,将来我们上传的文件都处于 bucket 之中,如下:

创建成功之后,我们还需要设置一下桶的读取权限,确保文件将来上传成功之后可以读取到,点击左上角的设置按钮进行设置,如下:

设置完成后,接下来我们就可以往这个桶中上传资源了,如下图:

上传完成后,就可以看到刚刚上传的文件了:

上传成功后,点击文件,然后点击右边的 Share 按钮会弹出来文件的访问链接,由于我们已经设置了文件可读,因此可以不用管这里的链接有效期了,直接通过路径的前面部分就可以访问到刚刚上传的图片了,如下:

现在文件就可上传可访问了。是不是比 FastDFS 容易多了!

不过前面这种安装方式其实有点小问题,因为我们没有为 docker 容器设置数据卷,所以如果你把 docker 容器不小心删除了,那么数据也就没了!

所以我们要设置数据卷。

修正后的 docker 脚本如下:

docker?run?-p?9000:9000?-p?9001:9001?-d?--name?minio?-v?/Users/sang/minio/data:/data?-v?/Users/sang/minio/config:/root/.minio?-e?"MINIO_ROOT_USER=javaboy"?-e?"MINIO_ROOT_PASSWORD=123@45678"?minio/minio?server?/data?--console-address?":9000"?--address?":9001"

主要是加了数据卷映射功能,将 MinIO 的数据和配置文件映射到宿主机上,这样将来即使容器删除了,数据也都还在。

注意上面也自定义了登录用户名和密码。

按照上面的命令,重新创建容器之后,我们也创建一个桶并上传文件,上传成功之后,我们就可以在本地对应的文件夹看到我们上传的文件,如下:

3. 整合 Spring Boot

接下来我们再来看看在 Spring Boot 中如何玩 MinIO。

首先我们创建一个 Spring Boot 项目,引入 Web 依赖,如下:

项目创建成功之后,我们再来手动添加一下 MinIO 的依赖,如下:

<dependency>????<groupId>io.minio</groupId>????<artifactId>minio</artifactId>????<version>8.2.1</version></dependency>

这里我尝试用了最新的版本,但是似乎有一些 BUG,我也没有深究,就换了 8.2.1 这个版本,这个版本是 OK 的。

接下来我们来配置一下 application.yaml,配置一下文件上传所需要的基本信息:

minio:??endpoint:?http://localhost:9001??accessKey:?javaboy??secretKey:?123@45678??nginxHost:?http://local.javaboy.org:9001

这里四个属性:

endpoint:这是 MinIO 的 API 通信地址。

accessKey 和 secretKey 是通信的用户名和密码,这跟网页上登录时候的用户名密码一致。

nginxHost:这个配置用来生成上传文件的访问路径。对于这个路径,有的小伙伴可能会有疑问,nginxHost 不就是 endpoint 吗?为什么还要单独配置?因为对于文件服务器而言,我们上传文件是通过 MinIO,但是访问的时候不一定通过 MinIO,我们可能会自己搭建一个 Nginx 服务器,通过 Nginx 服务器来访问上传后的资源,大家知道 Nginx 非常擅长于做这个事情,效率非常高。所以这里的 nginxHost 其实是指 Nginx 的访问路径。

接下来我们提供一个 MinioProperties 来接收这里的四个属性,如下:

@ConfigurationProperties(prefix?=?"minio")public?class?MinioProperties?{????/**?????*?连接地址?????*/????private?String?endpoint;????/**?????*?用户名?????*/????private?String?accessKey;????/**?????*?密码?????*/????private?String?secretKey;????/**?????*?域名?????*/????private?String?nginxHost;????public?String?getEndpoint()?{????????return?endpoint;????}????public?void?setEndpoint(String?endpoint)?{????????this.endpoint?=?endpoint;????}????public?String?getAccessKey()?{????????return?accessKey;????}????public?void?setAccessKey(String?accessKey)?{????????this.accessKey?=?accessKey;????}????public?String?getSecretKey()?{????????return?secretKey;????}????public?void?setSecretKey(String?secretKey)?{????????this.secretKey?=?secretKey;????}????public?String?getNginxHost()?{????????return?nginxHost;????}????public?void?setNginxHost(String?nginxHost)?{????????this.nginxHost?=?nginxHost;????}}

将 application.yaml 中相关的配置注入到这个配置类中来。

接下来我们需要提供一个 MinIOClient,通过这个客户端工具可以操作 MinIO,如下:

@Configuration@EnableConfigurationProperties(MinioProperties.class)public?class?MinioConfig?{????@Autowired????private?MinioProperties?minioProperties;????/**?????*?获取MinioClient?????*/????@Bean????public?MinioClient?minioClient()?{????????return?MinioClient.builder()????????????????.endpoint(minioProperties.getEndpoint())????????????????.credentials(minioProperties.getAccessKey(),?minioProperties.getSecretKey())????????????????.build();????}}

这个也没啥好说的,传入通信地址以及用户名密码,就可以构建出一个 MinioClient 出来。

当文件上传成功之后,我们可以通过 MinIO 去访问,也可以通过 Nginx 访问,所以接下来我们就需要提供一个类,来封装这两个地址:

public?class?UploadResponse?{????private?String?minIoUrl;????private?String?nginxUrl;????public?UploadResponse()?{????}????public?UploadResponse(String?minIoUrl,?String?nginxUrl)?{????????this.minIoUrl?=?minIoUrl;????????this.nginxUrl?=?nginxUrl;????}????public?String?getMinIoUrl()?{????????return?minIoUrl;????}????public?void?setMinIoUrl(String?minIoUrl)?{????????this.minIoUrl?=?minIoUrl;????}????public?String?getNginxUrl()?{????????return?nginxUrl;????}????public?void?setNginxUrl(String?nginxUrl)?{????????this.nginxUrl?=?nginxUrl;????}}

再来提供一个 MinIO 文件上传工具类:

@Componentpublic?class?MinioUtil?{????@Autowired????private?MinioProperties?minioProperties;????@Autowired????private?MinioClient?client;????/**?????*?创建bucket?????*/????public?void?createBucket(String?bucketName)?throws?Exception?{????????if?(!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()))?{????????????client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());????????}????}????/**?????*?上传文件?????*/????public?UploadResponse?uploadFile(MultipartFile?file,?String?bucketName)?throws?Exception?{????????//判断文件是否为空????????if?(null?==?file?||?0?==?file.getSize())?{????????????return?null;????????}????????//判断存储桶是否存在??不存在则创建????????createBucket(bucketName);????????//文件名????????String?originalFilename?=?file.getOriginalFilename();????????//新的文件名?=?存储桶文件名_时间戳.后缀名????????assert?originalFilename?!=?null;????????SimpleDateFormat?format?=?new?SimpleDateFormat("yyyy-MM-dd");????????String?fileName?=?bucketName?+?"_"?+????????????????System.currentTimeMillis()?+?"_"?+?format.format(new?Date())?+?"_"?+?new?Random().nextInt(1000)?+????????????????originalFilename.substring(originalFilename.lastIndexOf("."));????????//开始上传????????client.putObject(????????????????PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(????????????????????????file.getInputStream(),?file.getSize(),?-1)????????????????????????.contentType(file.getContentType())????????????????????????.build());????????String?url?=?minioProperties.getEndpoint()?+?"/"?+?bucketName?+?"/"?+?fileName;????????String?urlHost?=?minioProperties.getNginxHost()?+?"/"?+?bucketName?+?"/"?+?fileName;????????return?new?UploadResponse(url,?urlHost);????}????/**?????*?获取全部bucket?????*?????*?@return?????*/????public?List<Bucket>?getAllBuckets()?throws?Exception?{????????return?client.listBuckets();????}????/**?????*?根据bucketName获取信息?????*?????*?@param?bucketName?bucket名称?????*/????public?Optional<Bucket>?getBucket(String?bucketName)?throws?IOException,?InvalidKeyException,?NoSuchAlgorithmException,?InsufficientDataException,?InvalidResponseException,?InternalException,?ErrorResponseException,?ServerException,?XmlParserException,?ServerException?{????????return?client.listBuckets().stream().filter(b?->?b.name().equals(bucketName)).findFirst();????}????/**?????*?根据bucketName删除信息?????*?????*?@param?bucketName?bucket名称?????*/????public?void?removeBucket(String?bucketName)?throws?Exception?{????????client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());????}????/**?????*?获取?件外链?????*?????*?@param?bucketName?bucket名称?????*?@param?objectName??件名称?????*?@param?expires????过期时间?<=7?????*?@return?url?????*/????public?String?getObjectURL(String?bucketName,?String?objectName,?Integer?expires)?throws?Exception?{????????return?client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).expiry(expires).build());????}????/**?????*?获取?件?????*?????*?@param?bucketName?bucket名称?????*?@param?objectName??件名称?????*?@return??进制流?????*/????public?InputStream?getObject(String?bucketName,?String?objectName)?throws?Exception?{????????return?client.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());????}????/**?????*?上传?件?????*?????*?@param?bucketName?bucket名称?????*?@param?objectName??件名称?????*?@param?stream??????件流?????*?@throws?Exception?https://docs.minio.io/cn/java-client-api-reference.html#putObject?????*/????public?void?putObject(String?bucketName,?String?objectName,?InputStream?stream)?throws????????????Exception?{????????client.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream,?stream.available(),?-1).contentType(objectName.substring(objectName.lastIndexOf("."))).build());????}????/**?????*?上传?件?????*?????*?@param?bucketName??bucket名称?????*?@param?objectName???件名称?????*?@param?stream???????件流?????*?@param?size???????????????*?@param?contextType?类型?????*?@throws?Exception?https://docs.minio.io/cn/java-client-api-reference.html#putObject?????*/????public?void?putObject(String?bucketName,?String?objectName,?InputStream?stream,?long????????????size,?String?contextType)?throws?Exception?{????????client.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream,?size,?-1).contentType(contextType).build());????}????/**?????*?获取?件信息
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
西班牙十大足球场 西甲球场大小排行 西班牙著名球场盘点 ...不仅芳香怡人,还能让人清凉一夏,都有哪些花呢? 天津工业大学新校区建行卡的开户行及地址 我在天津工业大学新校区办的卡丢了现在办挂失单找不到开户行,有谁知道... 天津工业大学新校区附近哪里有光大银行 天津工业大学:学校附近的银行都在哪里? 天津工业大学 缴费一定要用工行的卡吗 鼓励一个一直都不顺利的人用哪些词或成语 陶行知幼儿教育思想内容简介 鼻子被撞出血了怎么办 大学论文一般要求多少字? 做梦梦见死去的外公了,事情是这样的:我外公在几个月前去世的,昨晚做梦... 阿里巴巴校招薪资及各项福利待遇大揭秘~ 汽车方向盘快打到底的时候会被吸转过去是怎么情况 汽车方向盘打多少圈是一度? 洗碗机要预留水槽吗? 汉丰湖在什么地方 汉丰湖的区域位置 ...的开口怎么不是一个方向,这是为什么呢?有什么设计的考虑呢?_百度知 ... 急!急!!鲁讯逝世的相关资料 ...还流鼻涕 过不久就好了,不吃药怎样调理会好?长时间下去会怎样... 工程师职称是怎么评定的呢? ...知道2天时间可以不、有什么好玩的或者好吃的地方转转 过敏性鼻炎,最近鼻塞流鼻涕得厉害,害我没心情上课,如何快速止涕?不要... 孝感晚上哪有好玩的地方,孝感晚上好玩地方,夜市孝感旅游攻略路线 工程师职称评审途径有哪些?流程烦琐吗? 评非公有制中级职称有什么程序 有哪些好听的催眠歌典?要经典的 水泥瓦喷什么油漆耐久 洗衣机桶每天装清水会对洗衣机有害吗? 长春市祥程经贸有限公司公司简介 江苏迎来换季式降温,大风、强降雨组团来袭,如何预防次生灾害的发生? 冷空气杀回马枪引发多地降温,未来天气变化还会有什么需要注意的... “往来争泝沿”的出处是哪里 梦见很漂亮的大鱼变成一个女人约我晚上6点见!!!这鱼长2米多点 是个金... 宿州市符离集吕游烧鸡食品有限公司怎么样? 微信怎样把头像放到最小? 王者荣耀芈月重做技能详解介绍_王者荣耀芈月重做技能详解是什么_百度知 ... 三角形三边中线的交点是三角形的重心,以这点连线到各角,可以分成三个面... 红糖粉跟红糖的区别是什么红糖粉和红糖有什么区别 红糖粉和红糖块一样吗 红糖块和红糖粉有什么区别 雅顿8小时润唇膏上标准的是K1JA20,是生产日期吗?怎么来看生产日期... 保湿滋润*EA雅顿8小时护唇膏润唇膏3.7G 网上卖5.5元,是不是假的??? 雅顿8 小时润唇膏的滋润度怎么样? 总梦见自己处于极端危险的地方好像一不小心就会发生生命危险是... 2020款 宝马5系 2.0T 自动 530Le 先锋版 7万公里保养项目多少钱 相宜本草的护肤品使用效果怎么样? 相宜本草的护肤品使用效果如何? 大学四级英语考试阅读多少分及格 海马m3变速箱油?