From 47ad2d44185b977546c32798384df5d54ce56881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Miller=20=28=E9=94=BA=E4=BF=8A=29?= Date: Wed, 18 Aug 2021 14:51:35 +0800 Subject: [PATCH] perf($OSS): enhance file merge process by detecting content type --- .../controller/WriteResourceController.java | 9 ++--- .../entity/UploadResourceChunkPayload.java | 25 ++++++++++++ .../write/service/WriteResourceService.java | 9 ++--- .../impl/WriteResourceServiceImpl.java | 38 +++++++++++++------ 4 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/entity/UploadResourceChunkPayload.java diff --git a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/controller/WriteResourceController.java b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/controller/WriteResourceController.java index fa17128e..3f6d9bbe 100644 --- a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/controller/WriteResourceController.java +++ b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/controller/WriteResourceController.java @@ -3,6 +3,7 @@ import com.jmsoftware.maf.common.bean.ResponseBodyBean; import com.jmsoftware.maf.osscenter.write.entity.MergeResourceChunkPayload; import com.jmsoftware.maf.osscenter.write.entity.ObjectResponse; +import com.jmsoftware.maf.osscenter.write.entity.UploadResourceChunkPayload; import com.jmsoftware.maf.osscenter.write.service.WriteResourceService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -38,13 +39,11 @@ public ResponseBodyBean uploadSingleResource(@RequestParam("file LocaleContextHolder.getLocale())); } - @PostMapping("/upload/chunk/{chunkNumber}") + @PostMapping("/upload/chunk") @ApiOperation(value = "Upload chunk of resource", notes = "Upload chunk of resource") public ResponseBodyBean uploadResourceChunk(@RequestParam("file") MultipartFile multipartFile, - @RequestParam(required = false) String bucket, - @PathVariable Integer chunkNumber) { - return ResponseBodyBean.ofSuccess( - this.writeResourceService.uploadResourceChunk(multipartFile, bucket, chunkNumber)); + @Valid UploadResourceChunkPayload payload) { + return ResponseBodyBean.ofSuccess(this.writeResourceService.uploadResourceChunk(multipartFile, payload)); } @PutMapping("/merge/chunk") diff --git a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/entity/UploadResourceChunkPayload.java b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/entity/UploadResourceChunkPayload.java new file mode 100644 index 00000000..38fd5f7f --- /dev/null +++ b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/entity/UploadResourceChunkPayload.java @@ -0,0 +1,25 @@ +package com.jmsoftware.maf.osscenter.write.entity; + +import com.jmsoftware.maf.osscenter.write.service.WriteResourceService; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + * Description: UploadResourceChunkPayload, change description here. + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 8/18/2021 9:29 AM + **/ +@Data +public class UploadResourceChunkPayload { + private String bucket; + @NotNull + @Range(max = WriteResourceService.MAX_CHUNK_NUMBER) + private Integer chunkNumber; + @NotBlank + @Pattern(regexp = "^[^<>:;,?\"*|/]+$") + private String filename; +} diff --git a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/WriteResourceService.java b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/WriteResourceService.java index a970872a..60cb1ba2 100644 --- a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/WriteResourceService.java +++ b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/WriteResourceService.java @@ -2,8 +2,7 @@ import com.jmsoftware.maf.osscenter.write.entity.MergeResourceChunkPayload; import com.jmsoftware.maf.osscenter.write.entity.ObjectResponse; -import org.hibernate.validator.constraints.Range; -import org.springframework.lang.Nullable; +import com.jmsoftware.maf.osscenter.write.entity.UploadResourceChunkPayload; import org.springframework.validation.annotation.Validated; import org.springframework.web.multipart.MultipartFile; @@ -33,13 +32,11 @@ public interface WriteResourceService { * Upload resource chunk string. * * @param multipartFile the multipart file - * @param bucket the bucket - * @param chunkNumber the chunk number + * @param payload the payload * @return the string */ ObjectResponse uploadResourceChunk(@NotNull MultipartFile multipartFile, - @Nullable String bucket, - @NotNull @Range(max = MAX_CHUNK_NUMBER) Integer chunkNumber); + @Valid @NotNull UploadResourceChunkPayload payload); /** * Merge resource chunk string. diff --git a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/impl/WriteResourceServiceImpl.java b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/impl/WriteResourceServiceImpl.java index 9cca9272..3957ee4f 100644 --- a/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/impl/WriteResourceServiceImpl.java +++ b/oss-center/src/main/java/com/jmsoftware/maf/osscenter/write/service/impl/WriteResourceServiceImpl.java @@ -3,25 +3,27 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; +import com.jmsoftware.maf.osscenter.read.service.ReadResourceService; import com.jmsoftware.maf.osscenter.write.entity.MergeResourceChunkPayload; import com.jmsoftware.maf.osscenter.write.entity.ObjectResponse; +import com.jmsoftware.maf.osscenter.write.entity.UploadResourceChunkPayload; import com.jmsoftware.maf.osscenter.write.service.WriteResourceService; import com.jmsoftware.maf.springcloudstarter.minio.MinioHelper; import io.minio.ComposeSource; +import lombok.Cleanup; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.apache.tika.Tika; import org.apache.tika.mime.MediaType; -import org.hibernate.validator.constraints.Range; -import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; import javax.validation.constraints.NotNull; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -85,19 +87,19 @@ public ObjectResponse uploadSingleResource(@NotNull MultipartFile multipartFile) @Override @SneakyThrows public ObjectResponse uploadResourceChunk(@NotNull MultipartFile multipartFile, - @Nullable String bucket, - @NotNull @Range(max = MAX_CHUNK_NUMBER) Integer chunkNumber) { + @Valid @NotNull UploadResourceChunkPayload payload) { if (StrUtil.isBlank(multipartFile.getOriginalFilename())) { throw new IllegalArgumentException("File name required"); } MediaType mediaType = null; - if (StrUtil.isBlank(bucket)) { + if (StrUtil.isBlank(payload.getBucket())) { mediaType = this.parseMediaType(multipartFile); } // bucketName is either mediaType of given 'bucket' - val bucketName = StrUtil.isBlank(bucket) ? Objects.requireNonNull(mediaType).getType() : bucket; - val orderedFilename = String.format("%s.chunk%s", multipartFile.getOriginalFilename(), - NumberUtil.decimalFormat("000", chunkNumber)); + val bucketName = StrUtil.isBlank(payload.getBucket()) ? + Objects.requireNonNull(mediaType).getType() : payload.getBucket(); + val orderedFilename = String.format("%s.chunk%s", payload.getFilename(), + NumberUtil.decimalFormat("000", payload.getChunkNumber())); val objectResponse = new ObjectResponse(); objectResponse.setBucket(bucketName); objectResponse.setObject(orderedFilename); @@ -109,16 +111,20 @@ public ObjectResponse uploadResourceChunk(@NotNull MultipartFile multipartFile, } @Override + @SneakyThrows public ObjectResponse mergeResourceChunk(@Valid @NotNull MergeResourceChunkPayload payload) { val objectName = this.validateObject(payload.getObjectList()); val sources = payload.getObjectList() .stream() .map(object -> ComposeSource.builder().bucket(payload.getBucket()).object(object).build()) .collect(Collectors.toList()); - val statObjectResponse = this.minioHelper.statObject(payload.getBucket(), - CollUtil.getFirst(payload.getObjectList())); + @Cleanup val firstChunk = this.minioHelper.getObject(payload.getBucket(), + CollUtil.getFirst(payload.getObjectList()), + 0, + ReadResourceService.TINY_CHUNK_SIZE.toBytes()); + val mediaType = this.parseMediaType(firstChunk); val headers = new HashMap(4); - headers.put("Content-Type", statObjectResponse.contentType()); + headers.put("Content-Type", mediaType.toString()); val objectWriteResponse = this.minioHelper.composeObject(payload.getBucket(), objectName, sources, headers); val objectResponse = new ObjectResponse(); objectResponse.setBucket(objectWriteResponse.bucket()); @@ -151,4 +157,14 @@ private MediaType parseMediaType(MultipartFile multipartFile) throws IOException } return MediaType.parse(detectedMediaType); } + + private MediaType parseMediaType(InputStream inputStream) throws IOException { + val tika = new Tika(); + val detectedMediaType = tika.detect(inputStream); + log.info("Detected media type: {}", detectedMediaType); + if (StrUtil.isBlank(detectedMediaType)) { + throw new IllegalStateException("Media extension detection failed!"); + } + return MediaType.parse(detectedMediaType); + } }