erosynthesis

erosynthesis

springboot + minio for file upload

Here is an example code for file chunk upload, breakpoint resume, and instant upload based on Spring Boot and MinIO:

@RestController
@RequestMapping("/file")
public class FileController {

    private final MinioClient minioClient;

    @Autowired
    public FileController(MinioClient minioClient) {
        this.minioClient = minioClient;
    }

    @PostMapping("/upload")
    public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
        String md5 = DigestUtils.md5Hex(file.getInputStream());
        String objectName = md5 + "/" + file.getOriginalFilename();
        boolean objectExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucket").build());
        if (!objectExists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket("my-bucket").build());
        }
        if (minioClient.objectExists(ObjectExistsArgs.builder().bucket("my-bucket").object(objectName).build())) {
            return ResponseEntity.ok("Instant upload successful");
        }
        int partSize = 5 * 1024 * 1024;
        long objectSize = file.getSize();
        int partCount = (int) Math.ceil(objectSize / (double) partSize);
        List<PartETag> partETags = new ArrayList<>(partCount);
        InputStream inputStream = file.getInputStream();
        for (int i = 0; i < partCount; i++) {
            long offset = i * partSize;
            long size = Math.min(partSize, objectSize - offset);
            UploadPartArgs uploadPartArgs = UploadPartArgs.builder()
                    .bucket("my-bucket")
                    .object(objectName)
                    .uploadId(md5)
                    .partNumber(i + 1)
                    .partSize(size)
                    .stream(inputStream, size, offset)
                    .build();
            partETags.add(minioClient.uploadPart(uploadPartArgs));
        }
        CompleteMultipartUploadArgs completeMultipartUploadArgs = CompleteMultipartUploadArgs.builder()
                .bucket("my-bucket")
                .object(objectName)
                .uploadId(md5)
                .partETags(partETags)
                .build();
        minioClient.completeMultipartUpload(completeMultipartUploadArgs);
        return ResponseEntity.ok("Upload successful");
    }

    @GetMapping("/download")
    public ResponseEntity<InputStreamResource> download(@RequestParam("filename") String filename) throws Exception {
        String md5 = DigestUtils.md5Hex(new ByteArrayInputStream(filename.getBytes()));
        String objectName = md5 + "/" + filename;
        boolean objectExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucket").build());
        if (!objectExists) {
            return ResponseEntity.notFound().build();
        }
        boolean objectPartExists = minioClient.objectExists(ObjectExistsArgs.builder().bucket("my-bucket").object(objectName).build());
        if (!objectPartExists) {
            return ResponseEntity.notFound().build();
        }
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket("my-bucket")
                .object(objectName)
                .build();
        InputStream inputStream = minioClient.getObject(getObjectArgs);
        InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
                .contentLength(inputStream.available())
                .body(inputStreamResource);
    }

    @GetMapping("/resume")
    public ResponseEntity<String> resume(@RequestParam("file") MultipartFile file) throws Exception {
        String md5 = DigestUtils.md5Hex(file.getInputStream());
        String objectName = md5 + "/" + file.getOriginalFilename();
        boolean objectExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucket").build());
        if (!objectExists) {
            return ResponseEntity.notFound().build();
        }
        boolean objectPartExists = minioClient.objectExists(ObjectExistsArgs.builder().bucket("my-bucket").object(objectName).build());
        if (!objectPartExists) {
            return ResponseEntity.notFound().build();
        }
        int partSize = 5 * 1024 * 1024;
        long objectSize = file.getSize();
        int partCount = (int) Math.ceil(objectSize / (double) partSize);
        List<PartETag> partETags = new ArrayList<>(partCount);
        InputStream inputStream = file.getInputStream();
        for (int i = 0; i < partCount; i++) {
            long offset = i * partSize;
            long size = Math.min(partSize, objectSize - offset);
            UploadPartArgs uploadPartArgs = UploadPartArgs.builder()
                    .bucket("my-bucket")
                    .object(objectName)
                    .uploadId(md5)
                    .partNumber(i + 1)
                    .partSize(size)
                    .stream(inputStream, size, offset)
                    .build();
            partETags.add(minioClient.uploadPart(uploadPartArgs));
        }
        CompleteMultipartUploadArgs completeMultipartUploadArgs = CompleteMultipartUploadArgs.builder()
                .bucket("my-bucket")
                .object(objectName)
                .uploadId(md5)
                .partETags(partETags)
                .build();
        minioClient.completeMultipartUpload(completeMultipartUploadArgs);
        return ResponseEntity.ok("Resume upload successful");
    }

}

In the above code, we use MinioClient to connect to the MinIO server, implementing file upload, download, and resume functionalities.

When uploading a file, we first calculate the MD5 value of the file, then use the MD5 value as the folder name and save the uploaded file in that folder. If the file already exists on the MinIO server, we directly return Instant upload successful, otherwise, we split the file into several chunks for upload, and finally merge the chunks into a complete file.

When downloading a file, we first calculate the MD5 value of the file, then use the MD5 value as the folder name to download the specified file from that folder.

When resuming a file upload, we first calculate the MD5 value of the file, then check if the file already exists on the MinIO server. If it exists, we resume the upload in the same way as uploading a file; otherwise, we return an error message File does not exist.

It is important to note that in actual use, you need to replace my-bucket in the code with your own bucket name and ensure that the MinIO server is correctly configured and started.

Here is a simple Vue3 code example that implements file chunk upload and breakpoint resume functionality.

<template>
  <div>
    <input type="file" @change="onFileChange" />
    <button @click="upload">Upload</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      file: null,
      uploaded: 0,
      total: 0,
      progress: 0,
      chunkSize: 1024 * 1024,
      chunks: [],
      uploadedChunks: [],
    };
  },
  methods: {
    onFileChange(event) {
      this.file = event.target.files[0];
      this.total = this.file.size;
      const chunksCount = Math.ceil(this.total / this.chunkSize);
      for (let i = 0; i < chunksCount; i++) {
        const start = i * this.chunkSize;
        const end = Math.min(start + this.chunkSize, this.total);
        const chunk = this.file.slice(start, end);
        this.chunks.push(chunk);
        this.uploadedChunks.push(false);
      }
    },
    async upload() {
      const config = {
        onUploadProgress: (progressEvent) => {
          this.uploaded = progressEvent.loaded;
          this.progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
        },
      };
      const url = 'http://localhost:8080/upload';
      for (let i = 0; i < this.chunks.length; i++) {
        if (this.uploadedChunks[i]) {
          continue;
        }
        const formData = new FormData();
        formData.append('file', this.chunks[i]);
        formData.append('index', i.toString());
        formData.append('chunksCount', this.chunks.length.toString());
        await axios.post(url, formData, config);
        this.uploadedChunks[i] = true;
      }
      console.log('Upload complete');
    },
  },
};
</script>

In this example, we use the axios library to send requests for uploading files. In the onFileChange method, we split the file into multiple equally sized chunks and store them in the chunks array. We also create an uploadedChunks array to track which chunks have been uploaded.

In the upload method, we loop through the chunks array and use the FormData object to upload each chunk to the server. During the upload process, we use the onUploadProgress callback to track the upload progress and update the uploaded and progress variables.

Please note that this code is just an example, and you need to modify it according to your specific needs. If you need more detailed code examples or tutorials, please let me know.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.