springboot整合minio

4.6.2 Minio准备工作(非场景启动器第三方集成)
  • 引入Minio Maven依赖

    pom.xml文件增加如下内容:

    xml 复制代码
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>8.5.7</version>
    </dependency>
  • 配置Minio相关参数

    application.yml中配置Minio的endpointaccessKeysecretKeybucketName等参数

    yml 复制代码
    minio:
      endpoint: http://<hostname>:<port>
      access-key: <access-key>
      secret-key: <secret-key>
      bucket-name: <bucket-name>

    注意 :上述<hostname><port>等信息需根据实际情况进行修改。

  • 项目 中创建com.atguigu.exam.config.properties.MinioProperties,内容如下

    java 复制代码
    @ConfigurationProperties(prefix = "minio")
    @Data
    public class MinioProperties {
    
        private String endpoint;
    
        private String accessKey;
    
        private String secretKey;
        
        private String bucketName;
    }
  • common模块 中创建com.atguigu.exam.config.MinioConfiguration,内容如下

    java 复制代码
    @Configuration
    @EnableConfigurationProperties(MinioProperties.class)
    public class MinioConfiguration {
    
        @Autowired
        private MinioProperties properties;
    
        @Bean
        public MinioClient minioClient() {
            return MinioClient.builder().endpoint(properties.getEndpoint()).credentials(properties.getAccessKey(), properties.getSecretKey()).build();
        }
    }
    • @EnableConfigurationProperties(MinioProperties.class)

      作用 :这个注解用于启用对指定配置属性类(这里是 MinioProperties.class )的支持。如果没有这个注解,即使定义了带有 @ConfigurationProperties 注解的配置类,Spring 也不会自动将配置文件中的属性绑定到该类的实例上。

4.6.3 文件上传服务实现
  • FileUploadService接口

    java 复制代码
    /**
     * 文件上传服务
     * 支持MinIO和本地文件存储两种方式
     */
    public interface FileUploadService {
    
        /**
         * 上传文件(自动选择MinIO或本地存储)
         * @param file 上传的文件
         * @param folder 文件夹名称(如:banners, avatars等)
         * @return 返回图片可访问地址
         */
        String uploadFile(MultipartFile file,String folder) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException;
    } 
  • FileUploadServiceImpl实现类

    java 复制代码
    @Service
    @Slf4j
    public class FileUploadServiceImpl implements FileUploadService {
    
        @Autowired
        private MinioClient minioClient;
        @Autowired
        private MinioProperties minioProperties;
    
        @Override
        public String uploadFile(String folder, MultipartFile file) throws Exception {
            //1. 判断桶是否存在
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
            //2. 不存在,创建桶,同时设置访问权限
            if (!bucketExists) {
                //创建桶
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
                String config = """
                            {
                                  "Statement" : [ {
                                    "Action" : "s3:GetObject",
                                    "Effect" : "Allow",
                                    "Principal" : "*",
                                    "Resource" : "arn:aws:s3:::%s/*"
                                  } ],
                                  "Version" : "2012-10-17"
                            }
                        """.formatted(minioProperties.getBucketName());
                minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                                .bucket(minioProperties.getBucketName())
                                .config(config)
                        .build());
            }
            //3. 处理上传的对象名(影响,minio桶中的文件结构!)
            //现在: 桶名 / folder / ai.png  缺点: 所有文件都平铺(banner,video)不好区分! 核心缺点,可能覆盖!
            //小知识点: x/x/x.png -> exam0625 /x/x/ x.png
            //解决覆盖问题: 确保对象和文件的名字唯一即可!! uuid - - -
            //1.需要添加文件夹 2.添加uuid确保不重复
            String objectName = folder + "/" + new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/" +
                    UUID.randomUUID().toString().replaceAll("-","")+"_"+ file.getOriginalFilename();
    
            log.debug("文件上传核心业务方法,处理后的文件对象名:{}",objectName);
    
            //4. 上传文件 putObject方法
            //putObject . 上传文件数据 .steam(文件输入流)
            //uploadObject .上传文件数据 .filename(文件的磁盘地址 c:\\)
            minioClient.putObject(PutObjectArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .contentType(file.getContentType())
                            .object(objectName) //对象
                            .stream(file.getInputStream(),file.getSize(),-1) //-1 我们不指定文件切割大小!让minio自动处理!
                    .build());
    
            //5. 拼接回显地址 【端点 + 桶 + 对象名】
            String url = String.join("/", minioProperties.getEndpoint(), minioProperties.getBucketName(), objectName);
            log.info("文件上传核心业务,完成{}文件上传,返回地址为:{}",objectName,url);
            return url;
        }
    }
4.6.4 全局异常处理

在使用上述配置实现功能时,若所有 Controller 层方法都直接写 try - catch 处理异常,会让代码冗余又难维护。而 Spring MVC 全局异常处理 能把所有异常逻辑集中,统一处理各类异常 。这样无需每个 Controller 重复写 try - catch,代码更简洁,后续改异常处理逻辑也只需改一处,维护轻松,让代码结构更清晰合理 。

具体用法如下,详细信息可参考官方文档

common模块 中创建com.atguigu.exam.common.GlobalExceptionHandler类,内容如下

java 复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result exception(Exception e) {
        e.printStackTrace();
        //记录异常日志
        log.error("服务器发生运行时异常!异常信息为:{}",e.getMessage());
        //返回对应的提示
        return Result.error(e.getMessage());
    }
}

上述代码中的关键注解的作用如下

@ControllerAdvice用于声明处理全局Controller方法异常的类

@ExceptionHandler用于声明处理异常的方法,value属性用于声明该方法处理的异常类型

@ResponseBody表示将方法的返回值作为HTTP的响应体

4.7 上传轮播图图接口( post /api/banners/upload-image

4.7.1 接口分析

接口地址 :/api/banners/upload-image

请求方式 :POST

请求数据类型 :application/x-www-form-urlencoded,application/json

响应数据类型 :*/*

接口描述:将图片文件上传到MinIO服务器,返回可访问的图片URL

响应参数:

json 复制代码
{
	"code": 200,
	"message": "操作成功",
	"data": "imgUrl访问地址"
}
4.7.2 功能实现
  • BannerController层

    java 复制代码
    @Autowired
    private BannerService bannerService;
    
    /**
      * 上传轮播图图片
      * @param file 图片文件
      * @return 图片访问URL
      */
    @PostMapping("/upload-image")  // 处理POST请求
    @Operation(summary = "上传轮播图图片", description = "将图片文件上传到MinIO服务器,返回可访问的图片URL")  // API描述
    public Result<String> uploadBannerImage(
        @Parameter(description = "要上传的图片文件,支持jpg、png、gif等格式,大小限制5MB") 
        @RequestParam("file") MultipartFile file) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
    
        String imgUrl = bannerService.uploadImage(file);
    
        return Result.success(imgUrl, "图片上传成功");
    }
  • BannerService接口

    java 复制代码
    /**
     * 轮播图服务接口
     */
    public interface BannerService extends IService<Banner> {
    
        /**
         * 上传图片页面
         * @param file
         * @return 返回图片
         */
        String uploadImage(MultipartFile file) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException;
    }
  • BannerServiceImpl实现类

    java 复制代码
    @Slf4j
    @Service
    public class BannerServiceImpl extends ServiceImpl<BannerMapper, Banner> implements BannerService {
    
        @Autowired
        private FileUploadService fileUploadService;
    
        /**
         *
         * 实现逻辑:
         *   核心校验 【1.文件非空校验 2.格式校验需要是image 3. 文件大小限制】
         *   文件上传
         * 上传图片页面
         * @param file
         * @return 返回图片
         */
        @Override
        public String uploadImage(MultipartFile file) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            //1. 非空校验
            if (file.isEmpty()) {
                //配合全局异常处理,快速返回失败结果!!
                throw new RuntimeException("请选择要上传的文件!");
            }
            //2. 图片格式校验
            //获取文件的mimetype类型
            String contentType = file.getContentType();
            if (ObjectUtils.isEmpty(contentType) || !contentType.startsWith("image")) {
                //配合全局异常处理,快速返回失败结果!!
                throw new RuntimeException("轮播图只能上传图片文件!");
            }
            //3. 文件大小限制
            if (file.getSize() > 5 * 1024 * 1024) {
                //配合全局异常处理,快速返回失败结果!!
                throw new RuntimeException("图片文件大小不能超过5MB");
            }
            //4. 调用文件上传业务
            String imgUrl = fileUploadService.upload(file, "banners");
            //5. 返回结果
            log.info("完成banner图片上传,图片回显地址:{}",imgUrl);
            return imgUrl;
        }
    }
  • 测试

    访问:http://localhost:8080/doc.html进行测试

4.7.3 知识点
  • 默认上传文件限制

    默认servlet限制上传数据大小为10MB, 测试大图片会出现!

    json 复制代码
    {
      "code": 500,
      "message": "Maximum upload size exceeded",
      "data": null
    }

    修改添加如何配置:

    yaml 复制代码
    spring:      
      servlet:
        multipart:
          max-file-size: 100MB
          max-request-size: 150MB    

    所以,可以省去代码中大小校验代码!

  • 文件类型校验

    MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)是一种互联网标准,用字符串标记文件的「类型 + 格式」 。比如:

    • image/jpeg 代表 JPEG 格式的图片
    • video/mp4 代表 MP4 格式的视频
    • application/pdf 代表 PDF 文档

    它的核心作用是 让系统(浏览器、服务器、应用)快速识别文件本质内容 ,避免「后缀名造假」等问题(比如把 .exe 改成 .jpg ,但 MIME 仍会暴露其真实类型)

    java 复制代码
    //获取文件的mimetype类型
    String contentType = file.getContentType();
    if (ObjectUtils.isEmpty(contentType) || !contentType.startsWith("image")) {
    	//配合全局异常处理,快速返回失败结果!!
    	throw new RuntimeException("轮播图只能上传图片文件!");
    }

4.8 保存轮播图接口( post /api/banners/add

4.8.1 接口分析

接口地址 :/api/banners/add

请求方式 :POST

请求数据类型 :application/x-www-form-urlencoded,application/json

响应数据类型 :*/*

接口描述:

创建新的轮播图,需要提供图片URL、标题、跳转链接等信息

json 复制代码
{
  "title": "智能考试系统介绍",
  "description": "基于AI技术的智能考试平台,支持在线考试、智能组卷等功能",
  "imageUrl": "https://example.com/images/banner1.jpg",
  "linkUrl": "https://example.com/about",
  "sortOrder": 1,
  "isActive": true
}

响应参数:

json 复制代码
{
	"code": 200,
	"message": "操作成功",
	"data": ""
}
相关推荐
alonewolf_9915 小时前
Java类加载机制深度解析:从双亲委派到热加载实战
java·开发语言
云游15 小时前
Jaspersoft Studio community edition 7.0.3的应用
java·报表
程序员Agions15 小时前
程序员邪修手册:那些不能写进文档的骚操作
前端·后端·代码规范
帅气的你15 小时前
Spring Boot 集成 AOP 实现日志记录与接口权限校验
java·spring boot
肌肉娃子15 小时前
20260109.反思一个历史的编程的结构问题-更新频率不一致的数据不要放在同一个表
后端
zhglhy16 小时前
Spring Data Slice使用指南
java·spring
win x16 小时前
Redis 主从复制
java·数据库·redis
凌览16 小时前
2026年1月编程语言排行榜|C#拿下年度语言,Python稳居第一
前端·后端·程序员
码事漫谈16 小时前
【深度解析】为什么C++有了malloc,还需要new?
后端