minio文件加密/文件切割合并

2022-05-16 16:17:02

springboot2.x + minio7.0.2

本文记录 minio的调用关键步骤和关键文档.  主要记录大文件分片上传和文件合并记录

- minio官网地址  http://www.minio.org.cn

- minio 安装参考官网说明

- mino 文件加密需要https证书. 可以使用签名证书

- minio 服务启动脚本 start.sh

#!/bin/bash

export MINIO_ACCESS_KEY=admin
export MINIO_SECRET_KEY=admin123

# minio 存放位置
dir=/data/menhu/minio

# https证书位置minio/certs 
$dir/minio --certs-dir $dir/certs/ server $dir/data/minio &> $dir/logs/minio.log &

minio 客户端maven配置

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.0.2</version>
</dependency>

minio 配置类

/**
 * minio 配置类
 *
 * @author dennis
 * @date 2021/5/26
 */
@Component
public class MinioConfig {

    @Value("${minio.host:https://play.min.io}")
    private String host;

    @Value("${minio.accessKey:Q3AM3UQ867SPQQA43P2F}")
    private String accessKey;

    @Value("${minio.secretKey:zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG}")
    private String secretKey;
    
    // 文件是否加密存储
    @Value("${minio.encryption:false}")
    private Boolean encryption;

    @Bean
    public MinioClient getMinioClient() {
        try {
            MinioClient minioClient = new MinioClient(host, accessKey, secretKey);
            minioClient.ignoreCertCheck(); //忽略自签名证书有效性
            return minioClient;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ValidatorException(e.fillInStackTrace().getMessage());
        }
    }
    
    /**
     * 如何是https环境可以启用 文件加密
     * @return
     */
    public SecretKey getSecretKey() {
        if (encryption && StringUtils.startsWith(host, "https")) {
            try {
                KeyGenerator aes = KeyGenerator.getInstance("AES");
                aes.init(256);
                return aes.generateKey();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }
}

minio 工具类

/**
 * minio 工具类
 *
 * @author dennis
 * @date 2021/5/26
 */
@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600;

    /**
     * 检查存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return
     * @throws Exception
     */
    public boolean bucketExists(String bucketName) throws Exception {
        return minioClient.bucketExists(bucketName);
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     * @return
     * @throws Exception
     */
    public boolean makeBucket(String bucketName) throws Exception {
        boolean flag = bucketExists(bucketName);
        if (!flag) {
            minioClient.makeBucket(bucketName);
            return true;
        }
        return false;
    }

    /**
     * 文件上传
     *
     * @param bucketName
     * @param multipartFile
     */
    public void putObject(String bucketName, MultipartFile multipartFile, String filename, SecretKey secretKey) throws Exception {
        makeBucket(bucketName);
        PutObjectOptions putObjectOptions = new PutObjectOptions(multipartFile.getSize(), PutObjectOptions.MIN_MULTIPART_SIZE);
        putObjectOptions.setContentType(multipartFile.getContentType());
        if (null != secretKey) {
            putObjectOptions.setSse(ServerSideEncryption.withCustomerKey(secretKey));
        }

        minioClient.putObject(bucketName, filename, multipartFile.getInputStream(), putObjectOptions);
    }

    /**
     * 以流的形式获取一个文件对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    public InputStream getObject(String bucketName, String objectName, SecretKey secretKey) throws Exception {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            ObjectStat statObject = statObject(bucketName, objectName, secretKey);
            if (statObject != null && statObject.length() > 0) {
                if (null != secretKey) {
                    return minioClient.getObject(bucketName, objectName, ServerSideEncryption.withCustomerKey(secretKey));
                }
                return minioClient.getObject(bucketName, objectName);
            }
        }
        return null;
    }

    /**
     * 以流的形式获取一个文件对象(断点下载)
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度 (可选,如果无值则代表读到文件结尾)
     * @return
     */
    public InputStream getObject(String bucketName, String objectName, long offset, Long length, SecretKey secretKey) throws Exception {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            ObjectStat statObject = statObject(bucketName, objectName, secretKey);
            if (statObject != null && statObject.length() > 0) {
                if (null != secretKey) {
                    return minioClient.getObject(bucketName, objectName, offset, length, ServerSideEncryption.withCustomerKey(secretKey));
                }
                return minioClient.getObject(bucketName, objectName, offset, length);
            }
        }
        return null;
    }

    /**
     * 获取对象的元数据
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    public ObjectStat statObject(String bucketName, String objectName, SecretKey secretKey) throws Exception {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            try {
                if (null != secretKey) {
                    return minioClient.statObject(bucketName, objectName, ServerSideEncryption.withCustomerKey(secretKey));
                }
                return minioClient.statObject(bucketName, objectName);
            } catch (Exception e) {
            }
        }
        return null;
    }

    /**
     * 删除一个对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     */
    public boolean removeObject(String bucketName, String objectName) throws Exception {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            minioClient.removeObject(bucketName, objectName);
            return true;
        }
        return false;
    }

    /**
     * 分享。
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param expires    失效时间(以秒为单位),默认是7天,不得大于七天
     * @return
     */
    public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            if (null == expires) {
                expires = DEFAULT_EXPIRY_TIME;
            }
            if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
                throw new RuntimeException("expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
            }
            try {
                return minioClient.getPresignedObjectUrl(Method.GET, bucketName, objectName, expires, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 获取一个临时可以上传的url
     *
     * @param bucketName
     * @param objectName
     * @return
     * @throws Exception
     */
    public String presignedPutObject(String bucketName, String objectName) throws Exception {
        return minioClient.presignedPutObject(bucketName, objectName);
    }

    /**
     * 列出某个存储桶中的所有对象
     *
     * @param bucketName
     * @param objectNamePrefix 前缀
     * @return
     * @throws Exception
     */
    public List<ComposeSource> listObjectsSource(String bucketName, String objectNamePrefix) throws Exception {
        List<ComposeSource> sources = new ArrayList<>();
        Iterable<Result<Item>> results = minioClient.listObjects(bucketName, objectNamePrefix);
        for (Result<Item> result : results) {
            Item item = result.get();
            ComposeSource source = new ComposeSource(bucketName, item.objectName());
            sources.add(source);
        }
        return sources;
    }

    /**
     * 文件合并
     *
     * @param bucketName
     * @param objectName
     * @param sources
     * @param secretKey
     * @throws Exception
     */
    public void composeObject(String bucketName, String objectName, List<ComposeSource> sources, SecretKey secretKey) throws Exception {
        if (null != secretKey) {
            minioClient.composeObject(bucketName, objectName, sources, null, ServerSideEncryption.withCustomerKey(secretKey));
        }
        minioClient.composeObject(bucketName, objectName, sources, null, null);

        for (ComposeSource source : sources) {
            minioClient.removeObject(source.bucketName(), source.objectName());
        }
    }

}

service 方法实现文件切割

实现思路

- 判断文件大小是否需要分片上传

- 生成每个分片上传的minio地址, 前端直接调用对应的地上上传分片

- 前端上传完所有分片之后, 调用文件合并接口完成大文件上传

/**
 * 文件分割
 *
 * @param md5
 * @param fileSize
 * @return
 * @throws Exception
 */
public JSONObject partInitUpload(String md5, double fileSize) throws Exception {
    // 存储桶名称
    String bucketName = Constants.FILE_CAT_BUCKET_NAME;
    minioUtil.makeBucket(bucketName);

    String username = ThreadLocalContextHolder.getCurrentUsername();

    JSONObject jsonObject = new JSONObject(true);
    // 判断文件大小, 是否需要切割
    if (Constants.FILE_CAT_SIZE > fileSize) {
        return null;
    }
    // 判断需要切割多少片
    int partSize = (int) Math.ceil(fileSize / Constants.FILE_CAT_FILE_PART);
    jsonObject.put("count", partSize);

    JSONArray uploadInfos = new JSONArray();
    for (long i = 0; i < partSize; i++) {
        JSONObject uploadInfo = new JSONObject();
        String objectName = username + "/" + md5 + "_" + i;
        ObjectStat statObject = minioUtil.statObject(bucketName, objectName, null);
        if (statObject != null && statObject.length() > 0) {
            continue;
        }

        // 获取每一片文件的上传地址
        String uploadUrl = minioUtil.presignedPutObject(bucketName, objectName);
        uploadInfo.put("part", i);
        uploadInfo.put("url", uploadUrl);

        uploadInfos.add(uploadInfo);
    }

    jsonObject.put("uploadInfos", uploadInfos);
    return jsonObject;
}

/**
 * 文件合并
 *
 * @param md5
 * @param fileSize
 * @param fileName
 * @param folderId
 * @param diskType
 * @return
 * @throws Exception
 */
public FileStorage composeFilePart(String md5, double fileSize, String fileName, String folderId, DiskType diskType) throws Exception {
    // 存储桶 名称
    String bucketName = Constants.FILE_CAT_BUCKET_NAME;
    String username = ThreadLocalContextHolder.getCurrentUsername();

    String objectName = username + "/" + md5;
    List<ComposeSource> composeSources = minioUtil.listObjectsSource(bucketName, objectName);
    int partSize = (int) Math.ceil(fileSize / Constants.FILE_CAT_FILE_PART);
    if (null != composeSources && partSize == composeSources.size()) {
        String randomCode = getRandomCode();
        String extension = FilenameUtils.getExtension(fileName);
        objectName = diskType + "/" + username + "/" + Constants.DATA_YEAR_MONTH.format(new Date()) + "/" + randomCode + "." + extension;
        bucketName = ThreadLocalContextHolder.getCurrentEcode();

        // 判断文件是否需要加密存储
        SecretKey secretKey = minioConfig.getSecretKey();
        minioUtil.composeObject(bucketName, objectName, composeSources, secretKey);

        FileStorage fileStorage = new FileStorage();
        //  todo
        // 存文件相关信息
        return fileStorageRepository.save(fileStorage);
    }
    return null;
}
  • 作者:dennis345
  • 原文链接:https://blog.csdn.net/weixin_44104421/article/details/119996955
    更新时间:2022-05-16 16:17:02