Spring Boot 整合 Minio(分布式存储服务)

Minio 属性类(放置在common model)

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

/**
 * @author 梦欣
 * @version 1.0
 * @description MinIo属性配置类
 * @date 2023/2/10 22:52
 */
@Data
@Component
@ConfigurationProperties(
        prefix = "minio"
)
public class MinIoProperties {

    @ApiModelProperty(value = "登陆用户名")
    private String accessKey;

    @ApiModelProperty(value = "登陆密码")
    private String secretKey;

    @ApiModelProperty(value = "服务端地址")
    private String endpoint;

    @ApiModelProperty(value = "桶对象")
    Bucket bucket = new Bucket();
    @Data
    public class Bucket{

        @ApiModelProperty(value = "存储文件的Bucket Names")
        private String files;

        @ApiModelProperty(value = "存储视频的Bucket Names")
        private String video;

    }
}

Nacos Minio属性配置

spring:
  # 设置文件上传大小
  servlet:
    multipart:
      enabled: true
      max-file-size: 5TB
      max-request-size: 5TB

# MinIo 属性配置
minio:
  endpoint: 
  accessKey: mengxin
  secretKey: mengxin520.*
  bucket:
    files: media-files # 存储文件的Bucket Names
    video: media-video # 存储视频的Bucket Names

Minio Config配置类(放置在common model)

import com.meng.common.properties.MinioProperties;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
 * Minio 配置类
 */
@SpringBootConfiguration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfig {

    @Autowired
    private MinioProperties minioProperties;


    @Bean
    public MinioClient createMinioClient() {
        return MinioClient.builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
    }
}

Minio配置导入至微服务

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ComponentScan;

/**
 * 导入Minio 配置类
 * @author 梦欣
 */
@SpringBootConfiguration
@ComponentScan(basePackages = {"com.meng.common.minio"})
public class ImportConfig {
}

Minio是通用的, 放置在common model中目的是为了方便, 不需要重复配置, 导入模块即可

Upload Controller(例)

    /**
     * 上传商品Log
     */
    @PostMapping("/image")
    public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file){
        log.info("上传商品图片log: {}", file);

        return ResponseEntity.ok(uploadService.uploadImage(file));
    }

Upload Service(例)

import com.meng.common.exception.constant.ExceptionEnum;
import com.meng.common.exception.entity.MengException;
import com.meng.common.properties.MinioProperties;
import com.meng.upload.service.UploadService;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.StatObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 文件上传
 * @author 梦欣
 */
@Service
public class UploadServiceImpl implements UploadService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioProperties minioProperties;

    @Override
    @ApiOperation(value = "文件上传, 通用接口")
    public MediaFileUploadVo uploadCourseFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {
        // 创建文件Id, MD5值
        String fileId = DigestUtils.md5Hex(bytes);

        // 若文件夹名称为null, 则根据日期条件参数为文件夹名称
        if (StringUtils.isBlank(folder)) {
            folder = getFolderName(new Date(), true, true, true) + companyId.toString() + "/";
        } else if (!folder.endsWith("/")) {
            // 若指定的目录中的尾部没有 / , 则添加斜杠
            folder = folder + "/";
        }


        // 若文件名称为null, 则设定 随机MD5值 + 用户上传的文件名称的扩展名 为默认值
        if (StringUtils.isBlank(objectName)) {
            objectName = fileId + uploadFileParamsDto.getFilename().substring(uploadFileParamsDto.getFilename().lastIndexOf("."));
        }

        // 获取文件字节输出流, 用于上传使用
        InputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

        // 构建对象写入请求响应对象 及 视图响应对象
        ObjectWriteResponse objectWriteResponse;
        MediaFileUploadVo mediaFileUploadVo;

        try {
            // 构建文件上传参数
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(minIoProperties.getBucket().getFiles())
                    .object(folder + objectName)
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                    .contentType(uploadFileParamsDto.getContentType())
                    .build();

            // 上传文件
            objectWriteResponse = minioClient.putObject(putObjectArgs);

            log.info("文件上传信息: {}", objectWriteResponse.headers());

            // 查询数据库中是否有当前上传文件信息
            MediaFiles mediaFiles = getById(fileId);

            // 若查询结果为null, 则是新上传文件, 若不为null, 则说明这个文件已经上传过, 是重复上传, 不需要修改文件信息
            if (ObjectUtils.isEmpty(mediaFiles)) {
                // 构建MediaFiles对象, 插入到Media表中, new一个新对象的原因是因为旧对象中的值已经为null, 而不是空值
                mediaFiles = new MediaFiles();
                BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
                mediaFiles.setId(fileId);
                mediaFiles.setCompanyId(companyId);
                mediaFiles.setCompanyName("梦欣");
                mediaFiles.setFilePath(folder + objectName);
                mediaFiles.setFileId(fileId);
                mediaFiles.setUrl(minIoProperties.getBucket().getFiles() + "/" + folder + objectName);
                mediaFiles.setBucket(objectWriteResponse.bucket());
                mediaFiles.setStatus("1");
                mediaFiles.setCreateDate(LocalDateTime.now());
                mediaFiles.setChangeDate(LocalDateTime.now());
                mediaFiles.setAuditStatus(DictionaryConstants.COURSE_AUDIT_STATUS_PASS_AUDIT);
                mediaFiles.setAuditMind("审核通过");

                // 插入到MediaFiles表
                boolean saveResult = save(mediaFiles);

                // 若插入失败, 则抛出异常
                if (!saveResult) {
                    throw new XuechengEduException(ExceptionEnum.INSERT_OPERATION_FAIL);
                }
            }

            // 构建视图对象
            mediaFileUploadVo = BeanHelper.copyProperties(mediaFiles, MediaFileUploadVo.class);

            // 若拷贝失败, 则抛出异常
            if (ObjectUtils.isEmpty(mediaFileUploadVo)) {
                throw new XuechengEduException(ExceptionEnum.OBJECT_COPY_ERROR);
            }

        } catch (Exception e) {
            throw new XuechengEduException(ExceptionEnum.FILE_UPLOAD_ERROR);
        }

        return mediaFileUploadVo;
    }

    // 根据设定的日期条件拼接目录名
    public String getFolderName(Date date, Boolean year, Boolean month, Boolean day) {
        // 获取当前日期的字符串
        String format = new SimpleDateFormat("yyyy-MM-dd").format(date);

        // 取出年月日
        String[] dateArray = format.split("-");
        StringBuilder folderName = new StringBuilder();

        // 只要年份
        if (year) {
            folderName.append(dateArray[0]).append("/");
        }

        // 只要月份
        if (month) {
            folderName.append(dateArray[1]).append("/");
        }

        // 只要日期
        if (day) {
            folderName.append(dateArray[2]).append("/");
        }

        return folderName.toString();
    }

    /**
     * 判断文件是否存在
     * @param bucketName 桶名称
     * @param fileName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
     * @return true存在, 反之
     */
    public Boolean checkFilesExist(String bucketName, String fileName){
        // 判断上传的文件是否已存在
        try {
            minioClient.statObject(
                    StatObjectArgs
                            .builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build()
            );
        } catch (Exception e) {
            return false;
        }

        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherish Xin And Meng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值