MinIo前后端实现

这几天想玩玩Minio,整体来说简单使用起来不复杂(当然也有可能是我配置的太少了)

Minio下载

我是通过Dokcer在虚拟机上下载的(Docker真好用啊)

拉取Minio镜像

docker 复制代码
docker pull minio/minio

启动Minio容器

docker 复制代码
docker run -d --name minio -p 9000:9000 -p 9001:9001
-v /root/minio/data:/data 
-v /root/minio/config:/root/.minio 
-e MINIO_ROOT_USER=账号名 -e MINIO_ROOT_PASSWORD=密码 
minio/minio server /data --console-address ":9001"

Minio需要暴露两个端口,9000是API接口,9001是浏览器界面,后端通过9000端口发送请求,9001端口是可视化界面

还需要设置账号和密码MINIO_ROOT_USER MINIO_ROOT_PASSWORD

(密码至少8位 )

以及配置数据卷,Bucket(MinIo中的文件夹)会建立在/data目录下

启动完成后可以打开9001端口查看

后端配置

Minio通过okhttp发送请求,okhttp版本过低会报错

xml 复制代码
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.3</version>
        </dependency>
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.5</version>
        </dependency>

编写配置文件
access-keysecret-key需要在9001端口上手动获取,登录Minio后,点击AccessKey点击CreateAccessKey点击CreateaccessKeysecretKey记住就行

yaml 复制代码
minio:
  endpoint: ip:9000
  access-key: eSKt88NNU3PNKs8htCtf
  secret-key: YadifAfciM8Q5OaShJcSmG0NkEm5dN58UJYFPmO7

编写配置类(记得加上set和get,配置数据注入依赖这两)

java 复制代码
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIoConfig {
    public String endpoint;
    public String accessKey;
    public String secretKey;
    @Bean
    public MinioClient minioClient(){
        return MinioClient
                .builder()
                .endpoint(endpoint)
                .credentials(accessKey,secretKey)
                .build();
    }
}

此时就可以通过自动注入获取到MinioClient对象,完成文件的上传下载等

Minio中的几乎所有方法都是通过构造器模式编写的,参数大多都是BucketExistsArgs.builder().build();

java 复制代码
private final MinioClient minio;
	// 通过构造器注入
    public MinioService(MinioClient minio) {
        this.minio = minio;
    }
	// 创建Bucket 相当于创建一个文件夹
    public boolean createBucket(String bucketName) {
        boolean exist = minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (!exist){
            minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
        return exist;
    }
    // 在指定桶中上传文件
    public boolean uploadFile(String bucketName,MultipartFile file,String newFileName) {
        InputStream inputStream = null;
        try{
            inputStream = file.getInputStream();
            minio.putObject(PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(newFileName)
                            .stream(inputStream,file.getSize(),-1)
                            .contentType(file.getContentType())
                            .build());
        } catch (Exception e){
            return false;
        }finally {
            inputStream.close();
        }
        return true;
    }
    // 根据文件名获取对应桶中的文件
    public InputStream downloadFile(String bucketName,String objectName) {
        return minio.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }
    // 删除指定桶中的文件
    public boolean deleteFile (String bucketName,String objectName){
        try{
            minio.removeObject(
                RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()
            );
        }
        catch (Exception e){
            return false;
        }
        return true;
    }
    // 获取指定桶中的全部数据
    public List<String> getAllFilesInBucket(String bucketName){
        Iterable<Result<Item>> results = minio.listObjects(ListObjectsArgs
                .builder()
                .bucket(bucketName)
                .build());
        List<String> items = new ArrayList<>();
        for (Result<Item> result : results) {
            Item item = result.get();
            items.add(item.objectName());
        }
        return items;
    }

编写完Service类后可以通过访问Controller去创建文件删除文件等操作

因为上传文件名不应该重复,又不确定Minio是否会对文件名重复进行什么操作,所以我将上传的文件通过UUID进行重命名,并通过建立一张sql表存储新文件名和旧文件名之间的对应关系

java 复制代码
    // 获取指定桶中的全部文件
    @GetMapping("/fileList")
    public Result<List<String>> fileList() {
        List<String> fileList = minIo.getAllFilesInBucket("test");
        return Result.success(fileNameConvert(fileList));
    }
    // 上传文件是旧的文件名,通过SQL查询将文件名转换成新文件名
    public List<String> fileNameConvert(List<String> fileList){
        for(int i = 0;i<fileList.size();i++){
            String oldNameByNewName = sqlMapper.getOldNameByNewName(fileList.get(i));
            fileList.set(i,oldNameByNewName);
        }
        return fileList;
    }
	// 获取指定桶中的指定文件
	// 文件的下载需要通过HttpServletResponse类完成,返回数据无所谓(写不写return都行)
    @GetMapping("/getFile")
    public Result<String> getFile(@RequestParam("fileName") String fileName, HttpServletResponse response) {
        MinIoFile file = sqlMapper.getFile(fileName, "1");
        InputStream inputStream = minIo.downloadFile("test", file.getNewFileName());
        response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(fileName, "UTF-8"));
        ServletOutputStream outputStream = response.getOutputStream();
        inputStream.transferTo(outputStream);
        return Result.success("ok");
    }
    // 文件上传
    @Transactional
    @PostMapping("/hello/upload")
    public Result<String> upload(@RequestParam("file") MultipartFile multipartFile){
        String oldFileName = multipartFile.getOriginalFilename();
        String[] files = oldFileName.split("\\.");
        String endName = files[files.length-1];
        
        if (!endName.equals("md")){
            return Result.error("返回一个md结尾的数据");
        }

        String uuid = UUID.randomUUID().toString();
        // 文件存储,原名: 新名 每次查询时从数据库中查找对应的原名
        String newName = uuid +"."+endName;
		// 将数据存储到SQL中
        MinIoFile minIoFile = new MinIoFile();
        minIoFile.setOldFileName(oldFileName);
        minIoFile.setNewFileName(newName);
        minIoFile.setIsDelete(0);
        minIoFile.setCreateTime(new java.sql.Date(System.currentTimeMillis()));
        String userId = "1";
        minIoFile.setCreateUser(userId);
        
        boolean createFileSuccess = sqlMapper.createNewFile(minIoFile);
        if (!createFileSuccess){
            throw new BaseException("创建文件失败");
        }
        try {
            minIo.uploadFile("test",multipartFile,newName);
        } catch (Exception e) {
            throw new BaseException("创建文件失败");
        }
        return Result.success("创建文件成功");
    }
    

创表语句

sql 复制代码
create table file(
	id int primary key auto_increment,
    old_file_name varchar(50) not null,
    new_file_name varchar(50) not null unique,
    is_delete int default 0,
    create_time datetime,
    create_user varchar(50)
)

Mapper接口

java 复制代码
    @Insert("insert into file values(null,#{oldFileName},#{newFileName},#{isDelete},#{createTime},#{createUser})")
    boolean createNewFile (MinIoFile minIoFile);
    
    @Select("select * from file where old_file_name = #{oldFileName} and create_user = #{userId}")
    MinIoFile getFile(@Param("oldFileName") String oldFileName,@Param("userId") String userId);
    
    @Select("select old_file_name from file where new_file_name = #{newFileName}")
    String getOldNameByNewName(String newFileName);

前端

前端直接调用后端Controller接口就可以,没有什么特殊写法,所以我界面写的很简陋

唯二需要注意的是后端接收不到file的问题和Liunx系统下换行符是\r\n需要用正则匹配替换成\n

因为我上传下载的都是纯文本文件,如果有需要的话可以整个富文本框架展示

html 复制代码
<body>
    <button id="fileList">查看文件列表</button>
    <input type="file" id="file">
    <button id="uploadFile">上传文件</button>
    <input type="text" id="fileName" placeholder="输入文件名">
    <button id="downloadFile">查看文件</button>
    <p></p>
    <script>
        const fileListButton = document.querySelector('#fileList')
        const uploadFileButton = document.querySelector('#uploadFile')
        const p = document.querySelector('p')
        fileListButton.addEventListener("click", () => {
            axios({
                method: 'get',
                url: "http://localhost:8080/fileList"
            }).then((res) => {
                console.log(res)
            })
        })
        uploadFileButton.addEventListener("click", () => {
            const file = document.querySelector('#file').files[0]
            const data = new FormData();
            data.append('file', file);
            axios({
                method: 'post',
                url: 'http://localhost:8080/upload',
                data: data,
                headers: {
                    'Content-Type': 'multipart/form-data'
                }
            })
        })
        const downloadFileButton = document.querySelector('#downloadFile')
        downloadFileButton.addEventListener("click", () => {
            const fileName = document.querySelector('#fileName').value
            axios({
                method: 'get',
                url: "http://localhost:8080/getFile",
                params: { fileName: fileName }
            }).then((res) => {
                const str = res.data.replace('/\r\n/g', '\n')
                console.log(str)
                p.innerText = str
                console.log(res)
            })
        })
    </script>
</body>

总结

用起来还是十分方便的,我没有做JWT之类区分用户,正常来说估计需要通过用户信息创建不同的桶实现数据隔离(多租户),以及前端界面的展示,数据的流式上传下载等

Minio还有许多功能比如设置桶的权限,文件访问权限等,大伙可以自行研究

相关推荐
老朋友此林8 分钟前
Redisson 实现分布式锁源码浅析
java·redis·分布式
大有数据可视化17 分钟前
数字孪生像魔镜,映照出无限可能的未来
前端·html·webgl
爱分享的淘金达人20 分钟前
25年教师资格认定材料及认定详细流程‼
java·python·考研·小程序·tomcat
一个处女座的程序猿O(∩_∩)O22 分钟前
使用 Docker 部署前端项目全攻略
前端·docker·容器
是一个Bug22 分钟前
docker基本应用和相关指令
docker
其实我就是个萌新23 分钟前
使用spring data MongoDB对MongoDB进行简单CURD操作示例
java·mongodb·spring
小小鸭程序员1 小时前
NPM版本管理终极指南:掌握依赖控制与最佳实践
java·前端·spring·npm·node.js
字节源流1 小时前
【SpringMVC】入门版
java·后端
爪哇哇哇哇1 小时前
docker部署jenkins,安装使用一条龙教程
docker·容器·jenkins
simplesin1 小时前
docker 增加镜像(忘记什么bug了)
docker·bug