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还有许多功能比如设置桶的权限,文件访问权限等,大伙可以自行研究

相关推荐
Chen-Edward8 分钟前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
jianghx102421 分钟前
Docker部署ES,开启安全认证并且设置账号密码(已运行中)
安全·elasticsearch·docker·es账号密码设置
magic3341656327 分钟前
Springboot整合MinIO文件服务(windows版本)
windows·spring boot·后端·minio·文件对象存储
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
future_studio1 小时前
聊聊 Unity(小白专享、C# 小程序 之 日历、小闹钟)
前端·html
逆光的July1 小时前
Hikari连接池
java
微风粼粼2 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud