Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Integrating MinIO Object Storage with Spring Boot Applications

Tech May 16 1

MinIO Object Storage Integration Guide

Installing MinIO

Docker Installation

# Create directory for MinIO data storage
mkdir -p /opt/docker/minio/data

# Launch MinIO with specified ports
docker run \
  -p 9000:9000 \
  -p 5001:5001 \
  --name minio \
  -v /opt/docker/minio/data:/data \
  -e "MINIO_ROOT_USER=minioadmin" \
  -e "MINIO_ROOT_PASSWORD=minioadmin" \
  -d minio/minio server /data --console-address ":5001"
 
# Configure to start automatically with Docker
docker update --restart=always

Official Binary Download

The official website provides multiple versions for different platofrms: https://www.minio.org.cn/download.shtml#/windows

Adding Required Dependencies

<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.4.0</version>
</dependency>

Application Configuration

storage:
  minio:
    endpoint: http://192.168.218.131:9000 # MinIO server address
    bucket: file-storage # Bucket name
    access-key: minioadmin # Access key
    secret-key: minioadmin # Secret key

MinIO Configuration Class

@Data
@Configuration
@ConfigurationProperties(prefix = "storage.minio")
public class StorageConfiguration {

    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucket;

    @Bean
    public MinioClient storageClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

Storage Service Implementation

@Component
@Slf4j
public class FileStorageService {
    @Autowired
    private StorageConfiguration config;

    @Resource
    private MinioClient storageClient;
    
    /**
     * Check if bucket exists
     * @param bucketName bucket name to check
     * @return true if bucket exists
     */
    public boolean verifyBucket(String bucketName) {
        try {
            return storageClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error("Error checking bucket existence", e);
            return false;
        }
    }

    /**
     * Create a new bucket
     * @param bucketName name of the bucket to create
     * @return true if creation was successful
     */
    public boolean createBucket(String bucketName) {
        try {
            storageClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("Error creating bucket", e);
            return false;
        }
    }
    
    /**
     * Remove a bucket
     * @param bucketName name of the bucket to remove
     * @return true if removal was successful
     */
    public boolean deleteBucket(String bucketName) {
        try {
            storageClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("Error deleting bucket", e);
            return false;
        }
    }
    
    /**
     * Get all available buckets
     * @return list of buckets
     */
    public List<Bucket> fetchAllBuckets() {
        try {
            return storageClient.listBuckets();
        } catch (Exception e) {
            log.error("Error listing buckets", e);
            return Collections.emptyList();
        }
    }

    /**
     * Upload a file to storage
     * @param file the file to upload
     * @return the generated object name or null if failed
     */
    public String storeFile(MultipartFile file) {
        String originalName = file.getOriginalFilename();
        if (StringUtils.isBlank(originalName)) {
            throw new IllegalArgumentException("File name cannot be empty");
        }
        
        String fileExtension = originalName.substring(originalName.lastIndexOf("."));
        String uniqueFileName = generateUniqueIdentifier() + fileExtension;
        String objectPath = buildDatePath() + "/" + uniqueFileName;
        
        try {
            PutObjectArgs uploadArgs = PutObjectArgs.builder()
                    .bucket(config.getBucket())
                    .object(objectPath)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build();
            
            storageClient.putObject(uploadArgs);
            return objectPath;
        } catch (Exception e) {
            log.error("Error uploading file", e);
            return null;
        }
    }

    /**
     * Generate a preview URL for an image
     * @param objectName the object name
     * @return preview URL or null if failed
     */
    public String getPreviewUrl(String objectName) {
        try {
            GetPresignedObjectUrlArgs previewArgs = GetPresignedObjectUrlArgs.builder()
                    .bucket(config.getBucket())
                    .object(objectName)
                    .method(Method.GET)
                    .build();
            
            return storageClient.getPresignedObjectUrl(previewArgs);
        } catch (Exception e) {
            log.error("Error generating preview URL", e);
            return null;
        }
    }

    /**
     * Download a file
     * @param objectName the object name
     * @param response the HTTP response
     */
    public void retrieveFile(String objectName, HttpServletResponse response) {
        try (GetObjectResponse fileResponse = storageClient.getObject(
                GetObjectArgs.builder()
                        .bucket(config.getBucket())
                        .object(objectName)
                        .build())) {
            
            byte[] buffer = new byte[1024];
            int bytesRead;
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            
            while ((bytesRead = fileResponse.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            
            outputStream.flush();
            byte[] fileData = outputStream.toByteArray();
            
            response.setCharacterEncoding("utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + objectName);
            
            try (ServletOutputStream out = response.getOutputStream()) {
                out.write(fileData);
                out.flush();
            }
        } catch (Exception e) {
            log.error("Error downloading file", e);
        }
    }

    /**
     * List all objects in the bucket
     * @return list of objects
     */
    public List<Item> listAllObjects() {
        try {
            Iterable<Result<Item>> results = storageClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(config.getBucket())
                            .build());
            
            List<Item> items = new ArrayList<>();
            for (Result<Item> result : results) {
                items.add(result.get());
            }
            return items;
        } catch (Exception e) {
            log.error("Error listing objects", e);
            return Collections.emptyList();
        }
    }

    /**
     * Delete a file
     * @param objectName the object name to delete
     * @return true if deletion was successful
     */
    public boolean deleteFile(String objectName) {
        try {
            storageClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(config.getBucket())
                            .object(objectName)
                            .build());
            return true;
        } catch (Exception e) {
            log.error("Error deleting file", e);
            return false;
        }
    }
    
    private String generateUniqueIdentifier() {
        return UUID.randomUUID().toString();
    }
    
    private String buildDatePath() {
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM/dd"));
    }
}

File Management Controller

@Api(tags = "File Management API")
@Slf4j
@RestController
@RequestMapping("/api/files")
public class FileManagementController {

    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private StorageConfiguration storageConfig;

    @ApiOperation(value = "Verify bucket existence")
    @GetMapping("/verify-bucket")
    public ResponseEntity<?> verifyBucket(@RequestParam("bucketName") String bucketName) {
        boolean exists = fileStorageService.verifyBucket(bucketName);
        return ResponseEntity.ok(Map.of("exists", exists));
    }

    @ApiOperation(value = "Create new bucket")
    @PostMapping("/create-bucket")
    public ResponseEntity<?> createBucket(@RequestParam("bucketName") String bucketName) {
        boolean created = fileStorageService.createBucket(bucketName);
        return ResponseEntity.ok(Map.of("created", created));
    }

    @ApiOperation(value = "Delete bucket")
    @DeleteMapping("/delete-bucket")
    public ResponseEntity<?> deleteBucket(@RequestParam("bucketName") String bucketName) {
        boolean deleted = fileStorageService.deleteBucket(bucketName);
        return ResponseEntity.ok(Map.of("deleted", deleted));
    }

    @ApiOperation(value = "List all buckets")
    @GetMapping("/buckets")
    public ResponseEntity<?> getAllBuckets() {
        List<Bucket> buckets = fileStorageService.fetchAllBuckets();
        return ResponseEntity.ok(Map.of("buckets", buckets));
    }

    @ApiOperation(value = "Upload file")
    @PostMapping("/upload")
    public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) {
        String objectPath = fileStorageService.storeFile(file);
        if (objectPath != null) {
            String fileUrl = storageConfig.getEndpoint() + "/" + storageConfig.getBucket() + "/" + objectPath;
            return ResponseEntity.ok(Map.of("url", fileUrl, "path", objectPath));
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("File upload failed");
    }

    @ApiOperation(value = "Get file preview URL")
    @GetMapping("/preview")
    public ResponseEntity<?> getPreviewUrl(@RequestParam("path") String objectPath) {
        String previewUrl = fileStorageService.getPreviewUrl(objectPath);
        if (previewUrl != null) {
            return ResponseEntity.ok(Map.of("previewUrl", previewUrl));
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Preview URL generation failed");
    }

    @ApiOperation(value = "Download file")
    @GetMapping("/download")
    public void downloadFile(@RequestParam("path") String objectPath, HttpServletResponse response) {
        fileStorageService.retrieveFile(objectPath, response);
    }

    @ApiOperation(value = "Delete file")
    @DeleteMapping("/delete")
    public ResponseEntity<?> deleteFile(@RequestParam("url") String fileUrl) {
        String objectPath = extractObjectPathFromUrl(fileUrl);
        boolean deleted = fileStorageService.deleteFile(objectPath);
        return ResponseEntity.ok(Map.of("deleted", deleted, "path", objectPath));
    }
    
    private String extractObjectPathFromUrl(String fileUrl) {
        return fileUrl.substring(fileUrl.lastIndexOf(storageConfig.getBucket() + "/") + 
                               storageConfig.getBucket().length() + 1);
    }
}

Tags: Spring Boot

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.