SpringBoot通过Minio实现大文件分片上传

最近开发功能需要大文件上传,正常会在业务代码中封装一层minio的上传,为了避免中转,实现了minio直传的方式。实现过程中也确实碰到了不少坑,只能查询文档,本着开箱即用,简单方便的原则,java版本的Minio sdk默认是不允许单独调用分片的相关方法,但是升级到8.0.3后可以通过继承MinioClient实现分片方法的使用,后来又碰到官方Minio版本的bug,提交issue,还好Minio github开发者处理非常迅捷,再次感谢,2021-02-04修复的,使用的开发者需要注意下。 大致描述下流程:

  1. 用户调用初始化接口,后端调用minio初始化,得到uploadId,生成每个分片的minio上传url
  2. 用户调用对应分片的上传地址,多次上传会覆盖
  3. 调用完成接口,后端查询所有上传的分片并合并
  • 自定义minioClient

public class CustomMinioClient extends MinioClient {

    protected CustomMinioClient(MinioClient client) {
        super(client);
    }

    public String initMultiPartUpload(String bucket, String region, String object, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {
        CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams);

        return response.result().uploadId();
    }

    public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {

        return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }

    public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }
}
  • 后端处理类

public Map<String, Object> initMultiPartUpload(String bucketName, String objectName, int totalPart) {
        Map<String, Object> result = new HashMap<>();
        try {
            String uploadId = customMinioClient.initMultiPartUpload(bucketName, null, objectName, null, null);

            result.put("uploadId", uploadId);
            List<String> partList = new ArrayList<>();

            Map<String, String> reqParams = new HashMap<>();
            //reqParams.put("response-content-type", "application/json");
            reqParams.put("uploadId", uploadId);
            for (int i = 1; i <= totalPart; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String uploadUrl = minioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.PUT)
                                .bucket(bucketName)
                                .object(objectName)
                                .expiry(1, TimeUnit.DAYS)
                                .extraQueryParams(reqParams)
                                .build());
                partList.add(uploadUrl);
            }
            result.put("uploadUrls", partList);
        } catch (Exception e) {
            logger.error("error: {}", e.getMessage(), e);
            return null;
        }

        return result;
    }

    public boolean mergeMultipartUpload(String bucketName, String objectName, String uploadId) {
        try {
            Part[] parts = new Part[1000];
            //此方法注意2020.02.04之前的minio服务端有bug
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
        } catch (Exception e) {
            logger.error("error: {}", e.getMessage(), e);
            return false;
        }

        return true;
    }

注意:此处做了很多简单的操作还需自行优化,比如:

  • 设定了最大仅允许1000个分片
  • 初始化生成上传签名地址header头相关操作

整个流程看起来很简单,开始做的时候查询资料网上没有这么操作的,再加上又碰到了官方小bug(bug:listParts),还好社区活跃,不然只能老实的在业务中自己实现分片了。

完整的Demo地址:minio-multipart-upload,仅为实现流程,实现比较糙,需要进行优化改造。

参考文档:

AWS-CreateMultipartUpload

阿里-分片上传

首先,需要引入minio的依赖: ``` <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.0.2</version> </dependency> ``` 然后,在application.properties中配置minio的连接信息: ``` minio.url=http://localhost:9000 minio.accessKey=minioadmin minio.secretKey=minioadmin minio.bucketName=test ``` 接着,创建一个MinioService类,用于处理文件上传和下载: ``` @Service public class MinioService { @Value("${minio.url}") private String minioUrl; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secretKey}") private String secretKey; @Value("${minio.bucketName}") private String bucketName; private final MinioClient minioClient = new MinioClient(minioUrl, accessKey, secretKey); public void upload(MultipartFile file) throws Exception { // 生成文件名 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); // 上传文件 minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); } public InputStream download(String fileName) throws Exception { // 下载文件 return minioClient.getObject(GetObjectArgs.builder() .bucket(bucketName) .object(fileName) .build()); } } ``` 最后,在Controller中使用MinioService处理上传和下载请求: ``` @RestController public class MinioController { @Autowired private MinioService minioService; @PostMapping("/upload") public String upload(@RequestParam("file") MultipartFile[] files) { try { for (MultipartFile file : files) { minioService.upload(file); } return "上传成功"; } catch (Exception e) { e.printStackTrace(); return "上传失败"; } } @GetMapping("/download") public ResponseEntity<InputStreamResource> download(@RequestParam("fileName") String fileName) { try { InputStream inputStream = minioService.download(fileName); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=" + fileName); return ResponseEntity.ok().headers(headers).contentLength(inputStream.available()) .contentType(MediaType.parseMediaType("application/octet-stream")).body(inputStreamResource); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); } } } ``` 这样,就可以使用Spring Boot和Minio实现文件批量上传了。
评论 66
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值