SpringBoot + MinIO 輕松構(gòu)建對(duì)象存儲(chǔ)服務(wù),支持私有化部署!
01、背景介紹
在實(shí)際的軟件系統(tǒng)開(kāi)發(fā)過(guò)程中,經(jīng)常避免不了需要用到文件存儲(chǔ)服務(wù)。
例如,對(duì)于小型的網(wǎng)站系統(tǒng),通常會(huì)將文件存儲(chǔ)服務(wù)和網(wǎng)站系統(tǒng)部署在一臺(tái)服務(wù)器中,以實(shí)現(xiàn)低成本的資源投入,如果訪問(wèn)量不大,基本上沒(méi)什么問(wèn)題。當(dāng)訪問(wèn)量逐漸升高,此時(shí)網(wǎng)站的文件資源讀取越來(lái)越頻繁,單臺(tái)服務(wù)器可能難以承載較大的請(qǐng)求量,這個(gè)時(shí)候網(wǎng)站可能會(huì)出現(xiàn)打不開(kāi),甚至系統(tǒng)異常等問(wèn)題。
當(dāng)出現(xiàn)這個(gè)場(chǎng)景,很容易第一時(shí)間想到將文件采用云存儲(chǔ)服務(wù)來(lái)解決。所謂云存儲(chǔ)服務(wù),簡(jiǎn)單的說(shuō),就是將訪問(wèn)很頻繁的文件資源服務(wù),由本地改成云廠商提供的文件存儲(chǔ)服務(wù),比如阿里云 OSS、七牛云、騰訊云、百度云等等,遷移之后,網(wǎng)站的訪問(wèn)壓力會(huì)得到極大的釋放,服務(wù)也會(huì)變得更加穩(wěn)定。但是,這些云存儲(chǔ)服務(wù)大部分都是收費(fèi)的,以阿里云為例,數(shù)據(jù)存儲(chǔ)通常按照 0.12 元/GB/月的標(biāo)準(zhǔn)來(lái)收費(fèi),雖然便宜,但是日積月累下來(lái)也是一筆不小的開(kāi)支啊。
為了節(jié)省成本,很多項(xiàng)目團(tuán)隊(duì)會(huì)自己搭建一套云存儲(chǔ)服務(wù),比如采用開(kāi)源的 fastDFS 工具來(lái)作為文件存儲(chǔ)服務(wù)器,雖然能性能不錯(cuò),但是軟件安裝環(huán)境非常復(fù)雜,最重要的是沒(méi)有一個(gè)完整的技術(shù)文檔,大部分都是某某公司或者某某網(wǎng)友自己總結(jié)的文檔,每次維護(hù)起來(lái)很是麻煩。
直到出現(xiàn)了 MinIO,云存儲(chǔ)服務(wù)工具又多了一個(gè)新的可選項(xiàng)。
MinIO 是一款號(hào)稱世界上速度最快的對(duì)象存儲(chǔ)服務(wù)器,專為大規(guī)模數(shù)據(jù)存儲(chǔ)和分析而設(shè)計(jì)。它支持在各種環(huán)境中部署,包括物理服務(wù)器、虛擬機(jī)、容器等,最關(guān)鍵的是它的技術(shù)文檔非常完善,非常容易上手;同時(shí),對(duì)個(gè)人用戶是完全開(kāi)源免費(fèi)的。
今天通過(guò)這篇文章,我們一起了解一下如何利用 MinIO 來(lái)搭建一套屬于自己的云存儲(chǔ)服務(wù)。
02、方案實(shí)踐
2.1、minio 快速安裝
minio 工具的安裝非常簡(jiǎn)單,如果你本機(jī)安裝了 Docker 容器,可以通過(guò) Docker 命令一鍵實(shí)現(xiàn)安裝操作。
以 windows 操作系統(tǒng)為例,安裝命令如下。
docker run \
-p 9000:9000 \
-p 9001:9001 \
--name minio1 \
-v D:\minio\data:/data \
-e "MINIO_ROOT_USER=ROOTUSER" \
-e "MINIO_ROOT_PASSWORD=CHANGEME123" \
quay.io/minio/minio server /data --console-address ":9001"
相關(guān)參數(shù)解讀:
- docker run:表示啟動(dòng)運(yùn)行容器
- -p:表示為容器綁定一個(gè)本地的端口
- -name:表示為容器創(chuàng)建一個(gè)本地的名字
- -v:表示將文件路徑設(shè)置為容器使用的持久卷位置。當(dāng) MinIO 將數(shù)據(jù)寫入 /data時(shí),該數(shù)據(jù)會(huì)鏡像到本地路徑~/minio/data, 使其能夠在容器重新啟動(dòng)時(shí)保持持久化。您可以設(shè)置任何具有讀取、寫入和刪除權(quán)限的文件路徑來(lái)使用。
- -e:表示設(shè)置登陸控制臺(tái)的用戶名和密碼。其中控制臺(tái)的訪問(wèn)地址為http://本機(jī)ip:9001,api 的訪問(wèn)地址為http://本機(jī)ip:9000。
如果沒(méi)有 docker 容器,可以采用軟件包方式進(jìn)行安裝,具體實(shí)現(xiàn)方式可以參考官網(wǎng)文檔,地址如下。
https://minio.org.cn/docs/minio/container/index.html
服務(wù)啟動(dòng)成功之后,在瀏覽器中訪問(wèn)http://127.0.0.1:9001地址,會(huì)看到類似于如下界面。
圖片
輸入上文設(shè)置的用戶名和密碼,即可登陸!
2.2、minio 使用介紹
登陸成功之后,會(huì)看到類似于如下的主界面。
圖片
由于官方并沒(méi)有提供漢化版,如果想要實(shí)現(xiàn)中文展示,可以使用瀏覽器插件進(jìn)行翻譯,翻譯之后的內(nèi)容如下。
圖片
在對(duì)象存儲(chǔ)服務(wù)里面,所有的文件都是以桶的形式來(lái)組織的。簡(jiǎn)單的說(shuō),你可以將桶看作是目錄,這個(gè)目錄下有很多的文件或者文件夾,這和其它云存儲(chǔ)服務(wù)基本一致。
下面我們一起來(lái)快速體驗(yàn)一下!
2.2.1、創(chuàng)建存儲(chǔ)桶
所有的文件必須要存儲(chǔ)到桶中,因此我們需要先創(chuàng)建一個(gè)存儲(chǔ)桶。
圖片
如果想要修改存儲(chǔ)桶信息,點(diǎn)擊左側(cè)的Buckets菜單,就可以看到相關(guān)的存儲(chǔ)桶配置信息。
2.2.2、上傳和下載文件
存儲(chǔ)桶創(chuàng)建完成之后,就可以上傳文件了。
點(diǎn)擊Object Browser菜單,可以看到剛剛創(chuàng)建的存儲(chǔ)桶public-bucket,點(diǎn)擊進(jìn)入,上傳我們想要存儲(chǔ)的文件了。
圖片
圖片
圖片
如果想要下載文件或者預(yù)覽文件,點(diǎn)擊文件,右側(cè)會(huì)彈出相關(guān)的操作按鈕,點(diǎn)擊相應(yīng)的操作按鈕就可以了。
2.2.3、設(shè)置文件公開(kāi)訪問(wèn)
默認(rèn)創(chuàng)建的存儲(chǔ)桶,都是私有桶,也就是說(shuō)無(wú)法被公開(kāi)訪問(wèn)。
圖片
以上文的文件為例,如果以 api 的方式直接訪問(wèn),會(huì)提示無(wú)權(quán)限,示例如下:
圖片
通常來(lái)說(shuō),我們會(huì)將數(shù)據(jù)寫入操作進(jìn)行控制;對(duì)于讀操作,很多不涉及安全問(wèn)題的,我們希望能被互聯(lián)網(wǎng)公開(kāi)訪問(wèn),以便加快文件的訪問(wèn)速度,此時(shí)如何實(shí)現(xiàn)呢?
可以在存儲(chǔ)桶里面配置,將數(shù)據(jù)讀取權(quán)限設(shè)置為公開(kāi)訪問(wèn),操作示例如下:
圖片
此時(shí),我們?cè)俅我?api 的方式訪問(wèn),結(jié)果如下:
圖片
可以清晰的看到,此時(shí)文件可以公開(kāi)訪問(wèn)了。
2.3、springBoot 集成 minio 實(shí)現(xiàn)文件存儲(chǔ)
最后,我們一起來(lái)看看,如何在 Spring Boot 工程中集成 minio 客戶端以便實(shí)現(xiàn)文件存儲(chǔ)服務(wù)。
2.3.1、創(chuàng)建用戶訪問(wèn)密鑰
MinIO 支持通過(guò)用戶、密碼來(lái)管理存儲(chǔ)桶,我們可以利用 minio 客戶端來(lái)實(shí)現(xiàn)文件的上傳和下載。
點(diǎn)擊Access Keys菜單,創(chuàng)建用戶名和密碼并將其保存,下文會(huì)用到。
圖片
2.3.2、引入依賴包
在 Spring Boot 工程,引入 minio 客戶端依賴包。
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.4</version>
</dependency>
2.3.3、添加相關(guān)配置
在application.properties文件中,添加 minio 相關(guān)的配置信息.
minio.endpoint=http://127.0.0.1:9000
minio.access-key=o1TJJL9noE69KIgZtKQ0
minio.secret-key=KAi91ZUYHXCzCn1XUiHJ3qQflp50XFqlTCFt6Ik3
minio.bucket-name=public-bucket
2.3.4、編寫 Minio 客戶端配置類
基于上文的配置信息,編寫 Minio 客戶端配置類。
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String minioEndpoint;
@Value("${minio.access-key}")
private String minioAccessKey;
@Value("${minio.secret-key}")
private String minioSecretKey;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(minioEndpoint)
.credentials(minioAccessKey, minioSecretKey)
.build();
}
}
2.3.5、編寫上傳和文件預(yù)覽服務(wù)
接著利用 minioClient 客戶端,編寫上傳和文件預(yù)覽服務(wù)。
@RestController
public class FileController {
@Value("${minio.bucket-name}")
private String bucketName;
@Autowired
private MinioClient minioClient;
/**
* 文件上傳
* @param file
* @return
* @throws IOException
*/
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
try {
ObjectWriteResponse response = minioClient.putObject(
PutObjectArgs
.builder()
.bucket(bucketName)
.object(file.getOriginalFilename())
.stream(file.getInputStream(), file.getInputStream().available(), -1)
.contentType(file.getContentType())
.build()
);
return "upload file success,tagId:" + response.etag();
} catch (Exception e) {
e.printStackTrace();
return "upload file error";
}
}
/**
* 構(gòu)建預(yù)覽地址
* @param fileName
* @return
* @throws Exception
*/
@GetMapping("/getPreviewUrl")
public String getPreviewUrl(@RequestParam("fileName") String fileName) throws Exception {
// 構(gòu)建預(yù)覽地址,默認(rèn)15秒過(guò)期,無(wú)論是私有桶還是公有桶,文件通過(guò)鏈接都可以訪問(wèn)
String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName) //存儲(chǔ)桶
.object(fileName) //文件名
.expiry(15) // 設(shè)置過(guò)期時(shí)間,單位秒
.build());
return url;
}
/**
* 構(gòu)建永久訪問(wèn)地址
* @param fileName
* @return
* @throws Exception
*/
@GetMapping("/getPublicUrl")
public String getDownloadUrl(@RequestParam("fileName") String fileName) throws Exception {
// 構(gòu)建永久訪問(wèn)地址,前提是這個(gè)存儲(chǔ)桶允許公開(kāi)訪問(wèn)
String url = minioClient.getObjectUrl(bucketName, fileName);
return url;
}
}
2.3.6、編寫上傳頁(yè)面
在resources/static目錄下,創(chuàng)建index.html文件,編寫上傳頁(yè)面。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上傳</title>
</head>
<body>
<h1>文件上傳</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">上傳</button>
</form>
</body>
</html>
2.3.7、最后驗(yàn)證一下服務(wù)
最后,將服務(wù)啟動(dòng),一起來(lái)驗(yàn)證一下代碼的正確性。
1)上傳服務(wù)驗(yàn)證
在瀏覽器端,訪問(wèn)http://127.0.0.1:8080/,選擇文件并上傳,示例如下。
圖片
回到 minio 控制臺(tái),可以看到剛剛上傳的文件信息。
圖片
2)文件預(yù)覽地址驗(yàn)證
在瀏覽器端,訪問(wèn)http://127.0.0.1:8080/getPreviewUrl?fileName=圖片.jpeg,會(huì)返回一段帶有簽名的文件預(yù)覽地址,示例如下。
圖片
將其地址復(fù)制出來(lái)直接訪問(wèn),可以清晰的看到圖片能正常展示。
圖片
通過(guò)getPresignedObjectUrl()方法生成的文件地址鏈接,無(wú)論是是公有桶還是私有桶,都可以正常訪問(wèn)。與getObjectUrl()方法生成的文件預(yù)覽地址相比,它帶有過(guò)期時(shí)間,這樣設(shè)計(jì)的目的也是為了保護(hù)文件資源,避免頻繁竊取。
03、小結(jié)
最后總結(jié)一下,本文主要圍繞利用 minio 實(shí)現(xiàn)對(duì)象存儲(chǔ)服務(wù),進(jìn)行了一次知識(shí)內(nèi)容的總結(jié)。
在實(shí)際的使用過(guò)程中,通常會(huì)這樣處理。
- 如果當(dāng)前文件不包含隱私信息,比如圖片,可以配置公共訪問(wèn)權(quán)限,構(gòu)建永久訪問(wèn)鏈接。
- 如果當(dāng)前文件包含隱私信息,比如營(yíng)業(yè)執(zhí)照?qǐng)D片,可以配置私有桶,構(gòu)建帶有有效時(shí)長(zhǎng)的訪問(wèn)鏈接,比如配置過(guò)期時(shí)間1小時(shí)等。
示例代碼地址:
https://gitee.com/pzblogs/spring-boot-example-demo
04、參考
1.https://minio.org.cn/docs/minio/container/index.html