Minio

Minio

1 基于centos7,docker部署

# 直接使用centos7部署,但是只能通过私有ip访问本机,指定公网ip不行
cd /usr/local/
# 获取minio
wget -q http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
# 授予文件权限
chmod +x minio
#启动minio server服务,指定数据存储目录/mnt/data,最好是指定控制台端口,这个方式创建的minio只能通过本机访问。
./minio server /mnt/data --console-address :9090

#docker 成功
#9090是console中控制台访问,9000是容器中api的端口,java代码用的是api的端口
docker run -d -p 9000:9000 -p 9090:9090 --name docker_minio 
-e "MINIO_ROOT_USER=fengpeng" 
-e "MINIO_ROOT_PASSWORD=fengpeng" 
-v /usr/local/minio/data:/data 
-v /usr/local/minio/config:/root/.minio 
#/data 目录设置为MinIO服务器要管理的对象存储数据的目录(-v /usr/local/minio/data:/data )
# minio/minio 是一个 Docker 镜像,用于启动 MinIO 服务器
minio/minio server /data  
--console-address ":9090"

# 如果有错误,通过docker logs 容器ip查看日志

完整语句

docker run -d -p 9000:9000 -p 9090:9090  --name docker_minio -e "MINIO_ROOT_USER=fengpeng" -e "MINIO_ROOT_PASSWORD=fengpeng" -v /usr/local/minio/data:/data -v /usr/local/minio/config:/root/.minio  minio/minio server /data  --console-address ":9090"

image-20240124213042170

2 springboot整合minio

pom.xml

<!--minio依赖-->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.1</version>
</dependency>
<!--minio相关依赖-->
<dependency>
    <groupId>me.tongfei</groupId>
    <artifactId>progressbar</artifactId>
    <version>0.9.5</version>
</dependency>
<!--minio相关依赖-->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>5.0.0-alpha.10</version>
</dependency>

image-20240220214243814

application.yml

#minio配置
minio:
  endpoint: http://8.137.103.17:9000
  accessKey: 
  secretKey: 
  bucketName: test

image-20240220214032257

MinioController

package com.feng.controller;

import com.alibaba.fastjson2.JSON;
import com.feng.utils.DataResult;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author fengpeng
 * @version V1.0
 * Copyright (c) 2024, fengpeng@hwadee.com All Rights Reserved.
 * @ProjectName:big_event
 * @Title: MinioController
 * @Package com.feng.controller
 * @Description: minio控制类
 * @date 2024/1/25 9:35
 */

@RestController
@RequestMapping("/minio")
@Slf4j
public class MinioController {

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${minio.endpoint}")
    private String endpoint;

    @GetMapping("/list")
    public List<Object> list() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        //获取bucket列表
        Iterable<Result<Item>> myObjects = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        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 DataResult upload(@RequestParam(name = "file",required = false) MultipartFile[] files) throws Exception {
        if (Objects.isNull(files) || files.length == 0){
            return DataResult.error("请选择文件");
        }

        List<String> orgFileNameList = new ArrayList<>(files.length);
        for (MultipartFile multipartFile : files) {
            //获取文件名
            String orgFileName = multipartFile.getOriginalFilename();
            //用时间作为不重复的名字
            Date date = new Date(System.currentTimeMillis());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
            String fileName = sdf.format(date) + orgFileName.substring(orgFileName.lastIndexOf("."));
            orgFileNameList.add(fileName);
            try {
                InputStream in = multipartFile.getInputStream();
                minioClient.putObject(
                            PutObjectArgs.builder().bucket(bucketName)
                                    .object(fileName)
                                    .stream(in, multipartFile.getSize(),-1)
                                    .contentType(multipartFile.getContentType())
                                    .build());
                in.close();
            } catch (Exception e) {
                log.error(e.getMessage());
                return DataResult.error("上传失败");
            }
        }

        /*Map<String, Object> data = new HashMap<>();
        data.put("bucketName", bucketName);
        //默认获取第一个文件
        data.put("fileName", orgFileNameList.get(0));*/
        /*记得将minio的console的bucket权限设置为public*/
        String fileUrl = endpoint + "/" + bucketName + "/" + orgFileNameList.get(0);
        return DataResult.success(fileUrl);
    }

    @GetMapping("/download")
    public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) throws Exception {
        InputStream in = null;
        try {
            //获取对象信息
            StatObjectResponse stat = minioClient.statObject
                    (StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
            //设置响应头
            response.setContentType(stat.contentType());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            //文件下载
            in =  minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            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 DataResult delete(@PathVariable("fileName") String fileName) throws Exception {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        }catch (Exception e){
            log.error(e.getMessage());
            return DataResult.error("删除文件失败");
        }
        return DataResult.success();
    }

    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;
    }
}

前端代码

image-20240220214542932

UserAvatar.vue

<script setup>
    import { Plus, Upload } from '@element-plus/icons-vue'
    import {ref} from 'vue'
    import avatar from '@/assets/default.png'
    const uploadRef = ref()
    import {useTokenStore} from '@/stores/token.js'
    const tokenStore = useTokenStore()
    
    import useUserInfoStore from '@/stores/userInfo.js'
    const userInfoStore = useUserInfoStore()
    //用户头像地址
    const imgUrl= ref(userInfoStore.info.userPic)

    //图片上传成功的回调函数
    const uploadSuccess = (res) => {
        imgUrl.value = res.data
    }

    import {updateAvatarUpdateService} from '@/api/user.js'
    import {ElMessage} from 'element-plus'
    //头像修改
    const updateAvatar = async() => {
        //调用接口
        let url = imgUrl.value
        let res = await updateAvatarUpdateService(url.slice(url.lastIndexOf('/') + 1))
        ElMessage.success(res.message ? res.message : '修改成功')

        //修改pinia中的数据
        userInfoStore.info.userPic = imgUrl.value
    }
    
    </script>
    
    <template>
        <el-card class="page-container">
            <template #header>
                <div class="header">
                    <span>更换头像</span>
                </div>
            </template>
            <el-row>
                <el-col :span="12">
                    <el-upload 
                        ref="uploadRef"
                        class="avatar-uploader" 
                        :show-file-list="false"
                        :auto-upload="true"
                        action="/api/minio/upload"
                        name="file"
                        :headers="{'Authorization': tokenStore.token}"
                        :on-success="uploadSuccess"
                        >
                        <!-- 选择图片后,自动上传到服务器,返回图片在服务器上的地址 -->
                        <img v-if="imgUrl" :src="imgUrl" class="avatar" />
                        <!-- 默认图片 -->
                        <img v-else :src="avatar" width="278" />
                    </el-upload>
                    <br />
                    <el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">
                        选择图片
                    </el-button>
                    <el-button type="success" :icon="Upload" size="large" @click="updateAvatar">
                        上传头像
                    </el-button>
                </el-col>
            </el-row>
        </el-card>
    </template>
    
    <style lang="scss" scoped>
    .avatar-uploader {
        :deep() {
            .avatar {
                width: 278px;
                height: 278px;
                display: block;
            }
    
            .el-upload {
                border: 1px dashed var(--el-border-color);
                border-radius: 6px;
                cursor: pointer;
                position: relative;
                overflow: hidden;
                transition: var(--el-transition-duration-fast);
            }
    
            .el-upload:hover {
                border-color: var(--el-color-primary);
            }
    
            .el-icon.avatar-uploader-icon {
                font-size: 28px;
                color: #8c939d;
                width: 278px;
                height: 278px;
                text-align: center;
            }
        }
    }
    </style>

注意

image-20240220214707372

不设置成Public,无法直接通过返回的图片url访问bucket中的图片

posted @ 2024-02-20 21:55  千夜ん  阅读(86)  评论(0编辑  收藏  举报