Spring Boot 整合 MinIO 实现全场景文件管理:从基础操作到分片上传

Minio快速入门(Linux) MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储。MinIO的快速入门和环境的快速搭建 阅读详情

最近项目中需要处理大量文件存储和管理的需求,对比了 Nginx、FastDFS、阿里云 OSS 等多种方案后,最终选择了 MinIO。今天就来和大家分享一下 SpringBoot 整合 MinIO 的全过程,从为什么选择 MinIO,到各种文件操作的实现,包括简单的文件上传、批量上传、文件下载、文件预览,再到大文件分片上传和秒传功能。

一、为什么选择 MinIO?

在选择文件存储方案时,我们需要考虑多个因素,如功能、性能、成本、扩展性等。对比其他常见的文件存储方案,MinIO 具有以下优势:

1. 功能丰富

MinIO 支持标准的 S3 协议,可以与其他支持 S3 协议的工具和服务无缝集成。同时,它还提供了丰富的 API,包括文件上传、下载、预览、删除、版本控制等,满足各种文件管理需求。

2. 高性能

MinIO 专为高性能设计,采用分布式架构,可以横向扩展,支持 PB 级数据存储。在读写性能方面,MinIO 表现出色,尤其适合大文件的存储和处理。

3. 开源免费

MinIO 是开源项目,采用 AGPL v3 许可证,企业可以免费使用。对于中小企业来说,这无疑是一个很大的优势。

4. 易于部署和管理

MinIO 提供了简单易用的命令行工具和 Web 界面,部署和管理都非常方便。可以在几分钟内完成部署,并开始使用。

5. 数据安全

MinIO 支持数据加密、访问控制、多因素认证等安全功能,保障数据的安全性和隐私性。

对比其他方案

  • Nginx:主要用于静态文件服务,不支持分布式存储和大规模文件管理。
  • FastDFS:功能相对简单,缺乏统一的管理界面,扩展性有限。
  • 阿里云 OSS:云服务成本较高,依赖于网络环境,不适合对数据隐私要求较高的场景。

综上所述,MinIO 是一个功能强大、性能出色、易于部署和管理的文件存储方案,非常适合作为企业级文件存储系统。

二、环境准备

1. 安装 MinIO

可以通过 Docker 快速安装 MinIO:

docker run -p 9000:9000 -p 9001:9001 \
  --name minio \
  -v /data/minio/data:/data \
  -v /data/minio/config:/root/.minio \
  -e "MINIO_ROOT_USER=minioadmin" \
  -e "MINIO_ROOT_PASSWORD=minioadmin" \
  minio/minio server /data --console-address ":9001"

安装完成后,可以通过访问http://localhost:9001进入 MinIO 管理界面,使用用户名minioadmin和密码minioadmin登录。

2. 创建 SpringBoot 项目

使用 Spring Initializr 创建一个 SpringBoot 项目,添加以下依赖:

  • Spring Web
  • Lombok
  • MinIO Client

三、整合 MinIO

1. 添加依赖

在pom.xml中添加 MinIO 客户端依赖:

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

2. 配置 MinIO 连接信息

在application.yml中添加 MinIO 配置信息:

minio:
  endpoint: http://localhost:9000
  access-key: minioadmin
  secret-key: minioadmin
  bucket-name: test-bucket

3. 创建 MinIO 配置类

创建一个配置类,用于创建 MinIO 客户端:

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MinIOConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.access-key}")
    private String accessKey;

    @Value("${minio.secret-key}")
    private String secretKey;

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

四、创建 MinIO 工具类

为了方便使用 MinIO 的各种功能,我们创建一个工具类,封装 MinIO 的常用操作:

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucket-name}")
    private String defaultBucketName;

    /**
     * 检查存储桶是否存在
     * @param bucketName 存储桶名称
     * @return 是否存在
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 获取所有存储桶
     * @return 存储桶列表
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 删除存储桶
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 简单文件上传
     * @param file       文件
     * @param bucketName 存储桶名称
     * @return 文件信息
     */
    @SneakyThrows
    public Map<String, String> uploadFile(MultipartFile file, String bucketName) {
        if (file == null || file.isEmpty()) {
            return null;
        }

        if (!bucketExists(bucketName)) {
            makeBucket(bucketName);
        }

        String originalFilename = file.getOriginalFilename();
        String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .contentType(file.getContentType())
                .stream(file.getInputStream(), file.getSize(), -1)
                .build());

        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("fileName", fileName);
        resultMap.put("originalFilename", originalFilename);
        resultMap.put("url", getObjectUrl(bucketName, fileName, 7));

        return resultMap;
    }

    /**
     * 简单文件上传(使用默认存储桶)
     * @param file 文件
     * @return 文件信息
     */
    public Map<String, String> uploadFile(MultipartFile file) {
        return uploadFile(file, defaultBucketName);
    }

    /**
     * 批量文件上传
     * @param files      文件列表
     * @param bucketName 存储桶名称
     * @return 文件信息列表
     */
    public List<Map<String, String>> uploadFiles(List<MultipartFile> files, String bucketName) {
        return files.stream()
                .map(file -> uploadFile(file, bucketName))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 批量文件上传(使用默认存储桶)
     * @param files 文件列表
     * @return 文件信息列表
     */
    public List<Map<String, String>> uploadFiles(List<MultipartFile> files) {
        return uploadFiles(files, defaultBucketName);
    }

    /**
     * 下载文件
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @return 输入流
     */
    @SneakyThrows
    public InputStream downloadFile(String bucketName, String objectName) {
        return minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 下载文件(使用默认存储桶)
     * @param objectName 对象名称
     * @return 输入流
     */
    public InputStream downloadFile(String objectName) {
        return downloadFile(defaultBucketName, objectName);
    }

    /**
     * 删除文件
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     */
    @SneakyThrows
    public void deleteFile(String bucketName, String objectName) {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 删除文件(使用默认存储桶)
     * @param objectName 对象名称
     */
    public void deleteFile(String objectName) {
        deleteFile(defaultBucketName, objectName);
    }

    /**
     * 批量删除文件
     * @param bucketName  存储桶名称
     * @param objectNames 对象名称列表
     * @return 删除错误列表
     */
    @SneakyThrows
    public List<DeleteError> deleteFiles(String bucketName, List<String> objectNames) {
        List<DeleteObject> objects = objectNames.stream()
                .map(DeleteObject::new)
                .collect(Collectors.toList());

        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                .bucket(bucketName)
                .objects(objects)
                .build());

        List<DeleteError> errors = new ArrayList<>();
        for (Result<DeleteError> result : results) {
            errors.add(result.get());
        }
        return errors;
    }

    /**
     * 批量删除文件(使用默认存储桶)
     * @param objectNames 对象名称列表
     * @return 删除错误列表
     */
    public List<DeleteError> deleteFiles(List<String> objectNames) {
        return deleteFiles(defaultBucketName, objectNames);
    }

    /**
     * 获取文件URL
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param expires    过期时间(天)
     * @return 文件URL
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName, int expires) {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(expires, TimeUnit.DAYS)
                .build());
    }

    /**
     * 获取文件URL(使用默认存储桶)
     * @param objectName 对象名称
     * @param expires    过期时间(天)
     * @return 文件URL
     */
    public String getObjectUrl(String objectName, int expires) {
        return getObjectUrl(defaultBucketName, objectName, expires);
    }

    /**
     * 检查文件是否存在
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @return 是否存在
     */
    @SneakyThrows
    public boolean objectExists(String bucketName, String objectName) {
        try {
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 检查文件是否存在(使用默认存储桶)
     * @param objectName 对象名称
     * @return 是否存在
     */
    public boolean objectExists(String objectName) {
        return objectExists(defaultBucketName, objectName);
    }

    /**
     * 列出存储桶中的所有对象
     * @param bucketName 存储桶名称
     * @return 对象列表
     */
    @SneakyThrows
    public List<Item> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                .bucket(bucketName)
                .build());

        List<Item> items = new ArrayList<>();
        for (Result<Item> result : results) {
            items.add(result.get());
        }
        return items;
    }

    /**
     * 列出存储桶中的所有对象(使用默认存储桶)
     * @return 对象列表
     */
    public List<Item> listObjects() {
        return listObjects(defaultBucketName);
    }

    /**
     * 创建分片上传
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @return 上传ID
     */
    @SneakyThrows
    public String createMultipartUpload(String bucketName, String objectName) {
        CreateMultipartUploadResponse response = minioClient.createMultipartUpload(CreateMultipartUploadArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
        return response.result().uploadId();
    }

    /**
     * 上传分片
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param uploadId   上传ID
     * @param partNumber 分片编号
     * @param stream     输入流
     * @param size       大小
     * @return 分片ETag
     */
    @SneakyThrows
    public String uploadPart(String bucketName, String objectName, String uploadId, int partNumber, InputStream stream, long size) {
        UploadPartResponse response = minioClient.uploadPart(UploadPartArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .uploadId(uploadId)
                .partNumber(partNumber)
                .stream(stream, size, -1)
                .build());
        return response.etag();
    }

    /**
     * 完成分片上传
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param uploadId   上传ID
     * @param etags      分片ETag列表
     */
    @SneakyThrows
    public void completeMultipartUpload(String bucketName, String objectName, String uploadId, List<String> etags) {
        List<CompletePart> completeParts = new ArrayList<>();
        for (int i = 0; i < etags.size(); i++) {
            completeParts.add(new CompletePart(i + 1, etags.get(i)));
        }

        minioClient.completeMultipartUpload(CompleteMultipartUploadArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .uploadId(uploadId)
                .parts(completeParts)
                .build());
    }

    /**
     * 生成文件哈希值(用于秒传判断)
     * @param file 文件
     * @return 哈希值
     */
    @SneakyThrows
    public String generateFileHash(MultipartFile file) {
        // 这里使用简单的文件大小和修改时间作为哈希值,实际应用中应使用MD5或SHA-1等算法
        return file.getSize() + "-" + file.getOriginalFilename();
    }
}

五、创建 Controller

接下来,我们创建一个 Controller,提供各种文件操作的接口:

import io.minio.messages.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/minio")
public class MinioController {

    @Autowired
    private MinioUtil minioUtil;

    /**
     * 简单文件上传
     */
    @PostMapping("/upload")
    public ResponseEntity<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            Map<String, String> fileInfo = minioUtil.uploadFile(file);
            if (fileInfo != null) {
                result.put("code", 200);
                result.put("message", "上传成功");
                result.put("data", fileInfo);
                return ResponseEntity.ok(result);
            } else {
                result.put("code", 500);
                result.put("message", "上传失败");
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
            }
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "上传异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 批量文件上传
     */
    @PostMapping("/upload/batch")
    public ResponseEntity<Map<String, Object>> uploadFiles(@RequestParam("files") List<MultipartFile> files) {
        Map<String, Object> result = new HashMap<>();
        try {
            List<Map<String, String>> fileInfos = minioUtil.uploadFiles(files);
            result.put("code", 200);
            result.put("message", "上传成功");
            result.put("data", fileInfos);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "上传异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 文件下载
     */
    @GetMapping("/download/{fileName}")
    public ResponseEntity<byte[]> downloadFile(@PathVariable("fileName") String fileName) {
        try {
            InputStream inputStream = minioUtil.downloadFile(fileName);
            byte[] bytes = inputStream.readAllBytes();
            inputStream.close();

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", fileName);

            return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(null, null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 文件预览
     */
    @GetMapping("/preview/{fileName}")
    public ResponseEntity<Map<String, Object>> previewFile(@PathVariable("fileName") String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            String url = minioUtil.getObjectUrl(fileName, 1);
            result.put("code", 200);
            result.put("message", "获取成功");
            result.put("url", url);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "获取异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 删除文件
     */
    @DeleteMapping("/delete/{fileName}")
    public ResponseEntity<Map<String, Object>> deleteFile(@PathVariable("fileName") String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            minioUtil.deleteFile(fileName);
            result.put("code", 200);
            result.put("message", "删除成功");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "删除异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 列出所有文件
     */
    @GetMapping("/list")
    public ResponseEntity<Map<String, Object>> listFiles() {
        Map<String, Object> result = new HashMap<>();
        try {
            List<Item> items = minioUtil.listObjects();
            result.put("code", 200);
            result.put("message", "获取成功");
            result.put("data", items);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "获取异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 初始化分片上传
     */
    @PostMapping("/multipart/init")
    public ResponseEntity<Map<String, Object>> initMultipartUpload(@RequestParam("fileName") String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            String uploadId = minioUtil.createMultipartUpload("test-bucket", fileName);
            result.put("code", 200);
            result.put("message", "初始化成功");
            result.put("uploadId", uploadId);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "初始化异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 上传分片
     */
    @PostMapping("/multipart/upload")
    public ResponseEntity<Map<String, Object>> uploadPart(
            @RequestParam("fileName") String fileName,
            @RequestParam("uploadId") String uploadId,
            @RequestParam("partNumber") int partNumber,
            @RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            String etag = minioUtil.uploadPart("test-bucket", fileName, uploadId, partNumber, file.getInputStream(), file.getSize());
            result.put("code", 200);
            result.put("message", "分片上传成功");
            result.put("etag", etag);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "分片上传异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 完成分片上传
     */
    @PostMapping("/multipart/complete")
    public ResponseEntity<Map<String, Object>> completeMultipartUpload(
            @RequestParam("fileName") String fileName,
            @RequestParam("uploadId") String uploadId,
            @RequestParam("etags") List<String> etags) {
        Map<String, Object> result = new HashMap<>();
        try {
            minioUtil.completeMultipartUpload("test-bucket", fileName, uploadId, etags);
            result.put("code", 200);
            result.put("message", "分片合并成功");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "分片合并异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 文件秒传检查
     */
    @PostMapping("/check")
    public ResponseEntity<Map<String, Object>> checkFile(@RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            String fileHash = minioUtil.generateFileHash(file);
            // 这里应该查询数据库或缓存,检查是否存在相同哈希值的文件
            // 为简化示例,直接返回不存在
            boolean exists = false;

            result.put("code", 200);
            result.put("message", "检查成功");
            result.put("exists", exists);
            result.put("fileHash", fileHash);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "检查异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }
}

六、大文件分片上传和秒传实现原理

1. 大文件分片上传

大文件分片上传是将一个大文件分成多个小片段,分别上传这些片段,最后在服务器端将这些片段合并成一个完整的文件。实现步骤如下:

  1. 前端:将文件切成固定大小的片段(如 1MB / 片),为每个片段生成唯一标识(如序号),按顺序上传这些片段。
  2. 后端:
  3. 接收前端上传的片段,保存到临时目录。
  4. 记录已上传的片段信息(如文件名、片段序号、ETag 等)。
  5. 当所有片段上传完成后,按顺序合并这些片段。

2. 秒传功能

秒传功能是指当用户上传一个文件时,系统首先检查该文件是否已经存在,如果存在则直接返回文件链接,无需重新上传。实现步骤如下:

  1. 前端:计算文件的哈希值(如 MD5、SHA-1),并将哈希值发送给后端。
  2. 后端:
  3. 根据哈希值查询数据库或缓存,检查是否存在相同哈希值的文件。
  4. 如果存在,返回文件链接;如果不存在,通知前端正常上传。

七、测试与验证

1. 简单文件上传测试

使用 Postman 或其他工具,向/api/minio/upload接口发送 POST 请求,上传一个文件,验证是否能成功上传并返回文件信息。

2. 批量文件上传测试

向/api/minio/upload/batch接口发送 POST 请求,上传多个文件,验证是否能成功批量上传。

3. 文件下载测试

访问/api/minio/download/{fileName}接口,验证是否能成功下载文件。

4. 文件预览测试

访问/api/minio/preview/{fileName}接口,验证是否能获取文件预览链接。

5. 大文件分片上传测试

使用前端工具(如 webuploader、plupload 等)实现大文件分片上传功能,调用后端提供的分片上传接口,验证大文件是否能成功上传。

6. 秒传功能测试

上传一个文件,记录文件哈希值,再次上传相同文件,验证是否能秒传成功。

Java使用minio批量上传文件 它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据。例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。MinlO是一个非常轻量的服务 可以很简单的和其他应用的结合,类似NodeJS, Redis或者MySQL。MinIO是Kubernetes的原生产品,是唯一一个可在每个公共云、每个Kubernetes发行版、私有云和边缘上使用的对象存储套件。MinIO是软件定义的,在GNU AGPL v3下是100%开源的。 阅读详情

相关推荐

Spring Boot整合MinIO实战:从Docker部署到文件上传完整流程

本文详细介绍了Spring Boot整合MinIO对象存储的完整实战流程。通过Docker Compose部署MinIO服务,在Spring Boot项目中集成官方Java SDK,并封装了包含文件上传、下载及生成预签名URL等核心功能的健壮服务层。文章重点阐述了生产环境下的安全性配置与性能优化最佳实践,为开发者构建企业级文件管理系统提供了可靠指南。

weixin_29246195的博客 237

MinIO

MinIO使用心得 MinIO集群搭建 MinIO HTTPs Nginx MinIO与Nginx MinIO、Https、Nginx

You_are_my_zing的博客 3716

3步整合Spring Boot + MinIO + kkFileView:你的文件预览系统为何总卡顿?

文件预览系统性能优化方案 针对传统文件预览系统的性能瓶颈,本文提出基于MinIO+kkFileView的高效解决方案。通过对比测试,传统本地存储方案在1000并发下响应时间达12秒,而优化方案性能提升3倍。方案核心优势: 技术选型:采用MinIO对象存储实现横向扩展和SSD加速,配合kkFileView支持200+文件格式和大文件分片预览 实现步骤: 搭建MinIO集群服务 集成kkFileView提供多格式支持 开发Spring Boot接口实现文件上传预览 关键改进: 响应时间从12s降至4s 支持10

java专栏 799

文件存储系统minio搭建

服务器安装开源的文件上传系统minio,并且在springboot中集成实现文件上传系下载

chenpeiyanbing的博客 1522

Spring Boot集成MinIO实战:手把手教你实现文件上传、下载与批量删除

本文提供了一份Spring Boot集成MinIO的完整实战指南,涵盖从Windows环境部署MinIO服务到Spring Boot后端集成的全过程。详细讲解了文件上传、下载与批量删除等核心功能的实现,并提供了面向生产环境的工具类封装、RESTful API设计及安全加固建议,帮助开发者快速构建高效、可靠的企业级文件存储服务。

weixin_28363123的博客 126

Minio文件存储管理

本文介绍了基于Minio的文件上传功能开发设计。主要内容包括:1) 使用Nacos配置Minio服务;2) 通过MinioController提供API接口;3) 实现包含文件上传的POST表单请求工具类。该工具类利用Apache HttpComponents库,通过MultipartEntityBuilder构建multipart/form-data请求,支持同时传输文件和表单数据,并处理了中文乱码问题。代码封装了完整的HTTP请求过程,包括请求头设置、参数处理和响应解析,为系统提供了可靠的文件上传功能实

专注于计算机研发知识和资讯分享 147

Spring Boot 整合 MinIO 实现全场景文件管理:从基础操作到分片上传的完整方案

最近项目中需要处理大量文件存储和管理的需求,对比了 Nginx、FastDFS、阿里云 OSS 等多种方案后,最终选择了 MinIO。今天就来和大家分享一下 SpringBoot 整合 MinIO 的全过程,从为什么选择 MinIO,到各种文件操作的实现,包括简单的文件上传、批量上传、文件下载、文件预览,再到大文件分片上传和秒传功能。

Minoz_wl的博客 1346

[Java实战]Spring Boot整合MinIO:分布式文件存储与管理实战(三十)

[Java实战]Spring Boot整合MinIO:分布式文件存储与管理实战(三十)

曼岛_的博客 1218

SpringBoot+MinIO 保姆级教程!文件管理全场景实战,含分片上传企业级方案

最近项目中需要处理大量文件存储和管理的需求,对比了Nginx、FastDFS、阿里云OSS等多种方案后,最终选择了MinIO。今天就来和大家分享一下SpringBoot整合MinIO的全过程,从为什么选择MinIO,到各种文件操作的实现,包括简单的文件上传、批量上传、文件下载、文件预览,再到大文件分片上传和秒传功能。

jj1245_的博客 1011

ArcGIS 10.2 保姆级安装与破解教程(含License Manager启动失败解决方案)

本文提供ArcGIS 10.2中文版的保姆级安装与破解教程,详细讲解从环境检查到License Manager配置的全流程,特别针对License Manager启动失败等常见问题提供专业解决方案,帮助用户顺利完成地理信息系统软件的安装与激活。

weixin_26757925的博客 1181

【新手必看】挖漏洞流程指南,黑客手把手教你挖漏洞月入过万!收藏这篇就够了!(附学习笔记)

【新手必看】挖漏洞流程指南,黑客手把手教你挖漏洞月入过万!收藏这篇就够了!(附学习笔记)【新手必看】挖漏洞流程指南,黑客手把手教你挖漏洞月入过万!收藏这篇就够了!(附学习笔记)

weixin_57514792的博客 1105

Minio对象存储与Springboot整合分片上传功能代码:高效的文件管理解决方案

Minio对象存储与Springboot整合分片上传功能代码:高效的文件管理解决方案 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 随着云计算技术的飞速发展,对象存储已成为企业级应用中不可或缺的部分。Minio作为一个高性能、开源的对象存储解决方案,在处理大规模数据存储和访问时表现优异。本项目带来了Minio对象存储与Spring Boot整合的分片上传功能代码,使得...

gitblog_06703的博客 739

电力抄表协议DL/T645和698.45到底有啥区别?一张图帮你选对协议

本文深入对比了电力抄表协议DL/T645和DL/T698.45的技术差异,从协议架构、报文结构、功能覆盖到实施成本进行全面分析。通过实际案例和数据,揭示698.45协议在通信效率、业务扩展性和长期成本上的优势,并提供清晰的选型决策流程图,帮助工程师根据具体需求选择最适合的电力抄表协议。

weixin_38168696的博客 401

Spring Boot整合MinioClient实战:从配置到文件管理的完整指南

本文提供了Spring Boot深度整合MinIO的完整实战指南。详细讲解了从多环境配置、统一服务层抽象、异常熔断处理到高级特性(如大文件分片上传)与性能调优的全过程,旨在帮助开发者构建稳定、可维护的企业级文件存储服务。

hill8的博客 952

Fast DDS源代码走读

DDS, PDP, EPD, DataWriter, DataReader

weixin_43082062的博客 713

DevExpress中使用BandGridView实现复合(多行)表头、设置多行表头背景颜色、表格边框颜色

一、实现效果 二、 实现方法 2.1、创建复合表头 ①将创建的GridControl下的GirdView1转化为BandGridView类型; ②创建需要展示的列(指定列的名称【Name】、描述【caption】、数据字段名称【FieldName】) ③绑定列实现复合表头。 注意:如果复合表头有多行,则需要设置新增Band的Row Count,然后设置【Band properties下的AutoFillDown=false】 详细操作如下: 注意:如果要隐藏表格的设计字段内容,则需.

提供C#相关开发 、云计算、运维测试、网络安全相关技术分享与服务,有意向可联系。 2893

SpringBoot + minio实现分片上传、秒传、续传

API发送分片请求。同时,需要实现断点续传的逻辑,即在网络中断后,可以从上次中断的地方继续上传剩余的分片。前端需要实现分片上传、秒传和续传的逻辑,可以使用JavaScript的。🔥系列专栏:Spring Boot实战。5、前端实现分片上传、秒传和续传逻辑。4、实现分片上传、秒传和续传接口。2、配置minio客户端。3、创建MinIO配置类。1、添加minio依赖。方法进行分片,然后使用。

2301_76419561的博客 546

【深度学习新浪潮】算力上太空:NVIDIA Space-1 Vera Rubin Module 深度技术解析

NVIDIA Space-1 Vera Rubin Module 不只是一款芯片模组,它是天地一体化 AI 的基础设施底座。它用成熟的 GPU 架构 + 航天级加固,把地面 AI 生态直接平移到太空。下一个 AI 创新战场,不在云端,而在轨道。在轨 LLM星上多模态实时处理卫星自主组网与分布式推理太空算力成为云计算的一级节点参考资料NVIDIA 官方博客:NVIDIA 推出太空计算,将 AI 送入太空轨道GTC 2026 黄仁勋主题演讲。

专注:太空算力、AI infra、异构调度、大模型成本工程 1125

Win10下Minio与RuoYiPlus整合实战:从零配置到文件上传成功

本文详细介绍了在Win10系统下如何将Minio与RuoYiPlus框架进行整合,从零开始配置到实现文件上传的全过程。内容包括Minio的本地环境部署、RuoYiPlus的文件管理模块配置、文件上传流程优化以及常见问题排查与性能优化建议,帮助开发者快速搭建高效可靠的对象存储服务。

pea55的博客 803

推荐系统[九]项目技术细节讲解z2:搜索Query理解[Term Weight、Query 改写、同义词扩写]和语义召回技术

在 pooling 方法上,我们也尝试过不同层 pooling,或者在多层上增加 attention 聚合 BERT 多层结果,效果和最后一层所有 token 做 average pooling 相当。损失函数方面,在排序阶段使用pairwise比 pointwise 损失函数的效果会更好。

丨汀、的博客 1109

Fluent验证案例03:管道中湍流流动压降计算——采用kw-sst模型计算压降更为准确。

阅读(4053) 评论(0)本案例利用Fluent计算管道中湍流流动压降。

qq_15963745的博客 877

SpringBoot集成Minio实现文件存储管理

文章使用springboot项目集成minio实现文件存储管理,举例使用文件分片上传和文件分块的合并。

qq_50867577的博客 1789

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

SpringBoot 整合 minIO 打造高效文件管理系统实战指南

本文提供了一份SpringBoot整合minIO构建高效文件管理系统的实战指南。详细介绍了从MinIO服务安装配置、SpringBoot项目集成、核心工具类封装到RESTful API构建的全过程,并重点讲解了文件上传、下载、预览及删除等核心功能的实现与最佳实践,帮助开发者快速搭建私有云存储解决方案。

weixin_28347369的博客 433

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Minio 7.1.0版本 Java SDK 工具类

package com.anx.minio.controller; import io.minio.*; import io.minio.http.Method; import io.minio.messages.Item; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation

anxyh_name的博客 3371

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

MinIO与SpringBoot的完美结合:如何打造企业级文件管理系统

本文深入探讨了如何将Spring Boot与高性能对象存储MinIO深度整合,以构建高可用、可扩展的企业级文件管理系统。文章详细介绍了MinIO集群的架构设计、生产环境部署策略,以及如何在Spring Boot应用中实现文件上传、下载、分片上传、权限控制等核心功能,并提供了性能优化与安全加固的实战方案。

carrot的博客 191
上一篇: Nginx 配置实战:从摸鱼到部署,手把手教你搞定生产级配置
下一篇: 利用SpringBoot中的RestTemplate实现反向代理
程序员职业指南
博客等级 码龄4年 2136粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值