使用Java 操作MinIO

概述

MinIO 是一款高性能、分佈式的對象存儲系統。它是一款軟件產品, 可以100%的運行在標準硬件。即X86等低成本機器也能夠很好的運行MinIO。MinIO與傳統的存儲和其他的對象存儲不同的是:它一開始就針對性能要求更高的私有云標準進行軟件架構設計。因爲MinIO一開始就只爲對象存儲而設計。所以他採用了更易用的方式進行設計,它能實現對象存儲所需要的全部功能,在性能上也更加強勁,它不會爲了更多的業務功能而妥協,失去MinIO的易用性、高效性。這樣的結果所帶來的好處是:它能夠更簡單的實現局有彈性伸縮能力的原生對象存儲服務。MinIO在傳統對象存儲用例(例如輔助存儲,災難恢復和歸檔)方面表現出色。同時,它在機器學習、大數據、私有云、混合雲等方面的存儲技術上也獨樹一幟。當然,也不排除數據分析、高性能應用負載、原生雲的支持。

今天我們使用JAVA來操作一下MinIO。

Docker 安裝MinIO

  • 創建目錄和賦予權限

mkdir -p /app/cloud/minio/data
mkdir -p /app/cloud/minio/config
chmod -R 777 /app/cloud/minio/data
chmod -R 777 /app/cloud/minio/config
  • 拉取鏡像docker pull minio:minio

  • 創建容器

docker run -d -p 9000:9000 --name minio \
-e "MINIO_ACCESS_KEY=minio" \
-e "MINIO_SECRET_KEY=Aa123456" \
-v /app/cloud/minio/data:/data \
-v /app/cloud/minio/config:/root/.minio \
minio/minio server /data
  • 瀏覽器訪問http://192.168.1.6:9000賬號 : minio 密碼:Aa123456 登錄右下角加號創建mybucket桶

image.png
  • 開放 mybucket  讀寫權限

image.png

創建項目 操作 MinIO

  • pom.xml 相關依賴

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>LATEST</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
<dependency>
	<groupId>io.minio</groupId>
	<artifactId>minio</artifactId>
	<version>7.0.1</version>
</dependency>
<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.6</version>
</dependency>
  • 編輯配置文件application.properties修改MinIO相關配置

server.port=80
spring.application.name=book-minio

spring.thymeleaf.cache=false

spring.servlet.multipart.max-file-size = 10MB
spring.servlet.multipart.max-request-size=100MB

minio.endpoint=http://192.168.1.6:9000
minio.accesskey=minio
minio.secretKey=Aa123456
  • 連接 MinIO 配置

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@ConfigurationProperties(prefix = "minio")
@Component
public class MinioProp {
    private String endpoint;
    private String accesskey;
    private String secretKey;
}
  • 創建 MinioClient

import io.minio.MinioClient;
import io.minio.errors.InvalidEndpointException;
import io.minio.errors.InvalidPortException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MinioConfiguration {
    @Autowired
    private MinioProp minioProp;

    @Bean
    public MinioClient minioClient() throws InvalidPortException, InvalidEndpointException {
        MinioClient client = new MinioClient(minioProp.getEndpoint(), minioProp.getAccesskey(), minioProp.getSecretKey());
        return client;
    }
}
  • MinIO 查看桶列表,存入,刪除 操作 MinioController

import com.alibaba.fastjson.JSON;
import com.lab.book.minio.common.Res;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.PutObjectOptions;
import io.minio.Result;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.*;

@Slf4j
@RestController
public class MinioController {
    @Autowired
    private MinioClient minioClient;

    private static final String MINIO_BUCKET = "mybucket";

    @GetMapping("/list")
    public List<Object> list(ModelMap map) throws Exception {
        Iterable<Result<Item>> myObjects = minioClient.listObjects(MINIO_BUCKET);
        Iterator<Result<Item>> iterator = myObjects.iterator();
        List<Object> items = new ArrayList<>();
        String format = "{'fileName':'%s','fileSize':'%s'}";
        while (iterator.hasNext()) {
            Item item = iterator.next().get();
            items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size()))));
        }
        return items;
    }

    @PostMapping("/upload")
    public Res upload(@RequestParam(name = "file", required = false) MultipartFile[] file) {
        Res res = new Res();
        res.setCode(500);

        if (file == null || file.length == 0) {
            res.setMessage("上傳文件不能爲空");
            return res;
        }

        List<String> orgfileNameList = new ArrayList<>(file.length);

        for (MultipartFile multipartFile : file) {
            String orgfileName = multipartFile.getOriginalFilename();
            orgfileNameList.add(orgfileName);

            try {
                InputStream in = multipartFile.getInputStream();
                minioClient.putObject(MINIO_BUCKET, orgfileName, in, new PutObjectOptions(in.available(), -1));
                in.close();
            } catch (Exception e) {
                log.error(e.getMessage());
                res.setMessage("上傳失敗");
                return res;
            }
        }

        Map<String, Object> data = new HashMap<String, Object>();
        data.put("bucketName", MINIO_BUCKET);
        data.put("fileName", orgfileNameList);
        res.setCode(200);
        res.setMessage("上傳成功");
        res.setData(data);
        return res;
    }

    @RequestMapping("/download/{fileName}")
    public void download(HttpServletResponse response, @PathVariable("fileName") String fileName) {
        InputStream in = null;
        try {
            ObjectStat stat = minioClient.statObject(MINIO_BUCKET, fileName);
            response.setContentType(stat.contentType());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

            in = minioClient.getObject(MINIO_BUCKET, fileName);
            IOUtils.copy(in, response.getOutputStream());
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
    }

    @DeleteMapping("/delete/{fileName}")
    public Res delete(@PathVariable("fileName") String fileName) {
        Res res = new Res();
        res.setCode(200);
        try {
            minioClient.removeObject(MINIO_BUCKET, fileName);
        } catch (Exception e) {
            res.setCode(500);
            log.error(e.getMessage());
        }
        return res;
    }

    private static String formatFileSize(long fileS) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize = "0B";
        if (fileS == 0) {
            return wrongSize;
        }
        if (fileS < 1024) {
            fileSizeString = df.format((double) fileS) + " B";
        } else if (fileS < 1048576) {
            fileSizeString = df.format((double) fileS / 1024) + " KB";
        } else if (fileS < 1073741824) {
            fileSizeString = df.format((double) fileS / 1048576) + " MB";
        } else {
            fileSizeString = df.format((double) fileS / 1073741824) + " GB";
        }
        return fileSizeString;
    }
}
  • Res 文件

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@lombok.Data
@AllArgsConstructor
@NoArgsConstructor
public class Res implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer code;
    private Object data = "";
    private String message = "";
}
  • 路由文件 RouterController

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class RouterController {
    @GetMapping({"/", "/index.html"})
    public String index() {
        return "index";
    }

    @GetMapping({"/upload.html"})
    public String upload() {
        return "upload";
    }
}
  • 前端 列表頁面 src\main\resources\templates\index.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8"/>
    <title>圖片列表</title>
    <link rel="stylesheet" href="http://cdn.staticfile.org/element-ui/2.13.1/theme-chalk/index.css">
</head>
<body>

<div id="app">

    <el-link icon="el-icon-upload" href="/upload.html">上傳圖片</el-link>
    <br/>

    <el-table :data="results" stripe style="width: 60%" @row-click="preview">
        <el-table-column type="index" width="50"></el-table-column>
        <el-table-column prop="fileName" label="文件名" width="180"></el-table-column>
        <el-table-column prop="fileSize" label="文件大小"></el-table-column>
        <el-table-column label="操作">
            <template slot-scope="scope">
                <a :href="'/download/' + scope.row.fileName + ''" class="el-icon-download">下載</a>
                <a :href="'/delete/' + scope.row.fileName + ''" @click.prevent="deleteFile($event,scope.$index,results)"
                   class="el-icon-delete">刪除</a>
            </template>
        </el-table-column>
    </el-table>

    <br/>
    <el-link icon="el-icon-picture">預覽圖片</el-link>
    <br/>
    <div class="demo-image__preview" v-if="previewImg">
        <el-image style="width: 100px; height: 100px" :src="imgSrc" :preview-src-list="imgList"></el-image>
    </div>

</div>

<script src="http://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script>
<script src="http://cdn.staticfile.org/axios/0.19.2/axios.min.js"></script>
<script src="http://cdn.staticfile.org/element-ui/2.13.1/index.js"></script>

<script>
    new Vue({
        el: '#app',
        data: {
            bucketURL: 'http://192.168.1.6:9000/mybucket/',
            previewImg: true,
            results: [],
            imgSrc: '',
            imgList: []
        },
        methods: {
            init() {
                axios.get('/list').then(response => {
                    this.results = response.data;
                    if (this.results.length == 0) {
                        this.imgSrc = '';
                        this.previewImg = false;
                    } else {
                        for (var i = 0; i < this.results.length; i++) {
                            this.imgList.push(this.bucketURL + this.results[i].fileName);
                            if (i == 0) {
                                this.imgSrc = this.bucketURL + this.results[0].fileName;
                            }
                        }
                    }
                });
            },
            preview(row, event, column) {
                this.imgSrc = this.bucketURL + row.fileName;
                this.previewImg = true;
            },
            deleteFile(e,index,list) {
                axios.delete(e.target.href, {}).then(res => {
                    if (res.data.code == 200) {
                        this.$message('刪除成功!');
                        list.splice(index, 1);
                        this.previewImg = false;
                    } else {
                        this.$message('刪除失敗!');
                    }
                });
            }
        },
        mounted() {
            this.init();
        }
    });
</script>

</body>
</html>
  • 前端上傳頁面 src\main\resources\templates\upload.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>圖片上傳</title>
    <link rel="stylesheet" type="text/css" href="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.css">
    <script type="text/javascript" src="https://cdn.staticfile.org/jquery/3.5.0/jquery.min.js"></script>
    <script type="text/javascript" src="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.min.js"></script>
</head>

<body>

<div id="uploader-demo">
    <div id="fileList" class="uploader-list"></div>
    <div id="filePicker">選擇圖片</div>
</div>
<br/>
<a href="/index.html">返回圖片列表頁面</a>

<script type="text/javascript">
    var uploader = WebUploader.create({
        auto: true,
        swf: 'http://cdn.staticfile.org/webuploader/0.1.5/Uploader.swf',
        server: '/upload',
        pick: '#filePicker',
        accept: {
            title: 'Images',
            extensions: 'gif,jpg,jpeg,bmp,png',
            mimeTypes: 'image/*'
        }
    });

    uploader.on('fileQueued', function (file) {
        var $li = $(
            '<div id="' + file.id + '" class="file-item thumbnail">' +
            '<img>' +
            '<div class="info">' + file.name + '</div>' +
            '</div>'
            ),
            $img = $li.find('img');

        var $list = $("#fileList");
        $list.append($li);

        uploader.makeThumb(file, function (error, src) {
            if (error) {
                $img.replaceWith('<span>不能預覽</span>');
                return;
            }
            $img.attr('src', src);
        }, 100, 100);
    });

    uploader.on('uploadProgress', function (file, percentage) {
        var $li = $('#' + file.id),
            $percent = $li.find('.progress span');

        if (!$percent.length) {
            $percent = $('<p class="progress"><span></span></p>')
                .appendTo($li)
                .find('span');
        }
        $percent.css('width', percentage * 100 + '%');
    });

    uploader.on('uploadSuccess', function (file) {
        $('#' + file.id).addClass('upload-state-done');
    });

    uploader.on('uploadError', function (file) {
        var $li = $('#' + file.id),
            $error = $li.find('div.error');

        if (!$error.length) {
            $error = $('<div class="error"></div>').appendTo($li);
        }
        $error.text('上傳失敗');
    });

    uploader.on('uploadComplete', function (file) {
        $('#' + file.id).find('.progress').remove();
    });
</script>

</body>
</html>

運行項目

image.png
  • 上傳頁面,批量上傳圖片

image.png
  • 上傳效果

image.png
  • 查看 MinIO Browser

image.png
  • 列表頁面,下載,刪除,預覽操作

image.png
  • 預覽圖片

image.png
  • 刪除圖片

image.png
  • 下載圖片

image.png

Java 操作 MinIO 官方 demo

https://github.com/minio/minio-java/tree/master/examples

好了,各位朋友們,本期的內容到此就全部結束啦,能看到這裏的同學都是優秀的同學,下一個升職加薪的就是你了!
如果覺得這篇文章對你有所幫助的話請掃描下面二維碼加個關注。"
轉發" 加 "在看",養成好習慣!咱們下期再見!

 

熱文推薦

☞ 數據庫優化之SQL優化
☞ 數據庫優化之實例優化
☞ Docker基礎與實戰,看這一篇就夠了!
☞ Docker-Compose基礎與實戰,看這一篇就夠了!
 OAuth2.0最簡嚮導(多圖預警)
 構建三維一體立體化監控體系
☞ SpringCloud實戰系列

JAVA日知錄

長按左邊二維碼關注我們,精彩文章第一時間推送

        >>>技術交流羣<<<

朕已閱 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章