拿来即用:SpringBoot+Minio+vue-uploader实现分片上传

Hi,大家好,我是抢老婆酸奶的小肥仔。

在日常开发中,我们常常需要文件上传,传统上实现上传是直接将文件保存到本地磁盘,然后通过磁盘路径进行下载查看。这样子会造成磁盘被大量占用,同时一不小心手欠的话就会将文件删除。这时候,我们可能会想到用一些文件存储系统,例如:我们熟悉的FastDFS等,我们今天介绍的主角不是FastDFS,而是Minio。

废话不多说,开整。

1、Minio简介及部署

Minio:一种分布式文件存储,具有高性能,轻量级,速度快,容错率高等特点,兼容亚马逊S3云存储服务接口,并可以作为一个独立的存储后端。

minio提供了纠删码策略,即将数据进行切分,同时计算校验块,采用Reed-Solomon code将对象拆分成N/2数据和N/2奇偶校验块,假如有8块盘,数据则被分成4个数据块,4个奇偶校验块。即使这个对象丢了4块盘,数据依然可以进行恢复,因此即使我们一不小心删除了一些盘,也不用担心数据会丢失。

1.1 Minio部署

我们以win10环境为例,进行Minio部署。

1.1.1 下载Minio

minio下载地址:https://www.minio.org.cn/download.shtml

选择windows版本进行下载,下载完成后是一个exe文件。

1.1.2 部署minio

在windows下创建一个文件夹用来放置minio执行文件,例如:我将其放在D盘下的soft/minio下。

Minio的部署可以分为:单节点单磁盘,单节点多磁盘,多节点方式。

单节点单磁盘执行执行exe文件即可,我们就说说单节点多次盘,多节点两种部署方式。

1.1.2.1 单节点多磁盘
1.1.2.1.1 创建目录

即通过一个节点进行访问,数据被保存在不同磁盘下。

我们创建4个文件夹来模拟四块不同的磁盘。如图:

1.1.2.1.2 启动minio

minio启动需要编写脚本,在存放minio.exe文件夹下创建一个minio.bat文件,文件内容如下:

@echo off
set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678
cd "D:\soft\minio"
start minio.exe server --console-address ":9000" D:\soft\minio\dataOne D:\soft\minio\dataTwo D:\soft\minio\dataThree D:\soft\minio\dataFour

--console-address ":9000" :管理页的ip和端口,缺省时默认是127.0.0.1:9000

--address "127.0.0.1:9090" :代表接口的ip和端口

1.1.2.1.3 访问

直接在浏览器上输入:http://localhost:9000即可进行访问。

1.1.2.2 多节点

即端口不一样,数据磁盘路径一致。

例如我们设置四个不同端口:9001,9002,9003,9004。分别执行如下脚本:

set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678
start minio.exe server --console-address "127.0.0.1:9001"  --address "127.0.0.1:9091"
http://127.0.0.1:9091/D:\soft\minio\dataOne
http://127.0.0.1:9092/D:\soft\minio\dataTwo
http://127.0.0.1:9093/D:\soft\minio\dataThree
http://127.0.0.1:9094/D:\soft\minio\dataFour

其他端口执行,只需要更改端口就好。

启动minio

由于涉及到多个端口,访问资源时,不可能对每个节点进行访问显然不合理,因此我们可以通过nginx来进行代理。配置如下:

upstream minio{
  server 127.0.0.1:9001;
  server 127.0.0.1:9002;
  server 127.0.0.1:9003;
  server 127.0.0.1:9004;
}

server{
    listen       8888;
    server_name  127.0.0.1;
    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-Host  $host:$server_port;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto  $http_x_forwarded_proto;
        proxy_set_header   Host $http_host;
        proxy_connect_timeout 300;
        proxy_http_version 1.1;
        chunked_transfer_encoding off;
        proxy_ignore_client_abort on;
        proxy_pass http://minio;
    }
}

2、分片上传

在minio中实现上传会自动进行分块,然后再将分块上传到Minio服务器,最后在进行合并。但是我们在使用时必须要将整个文件上传到系统,然后再调用Minio接口进行文件上传,当文件比较大时,就会占用太多宽带,从而大致上传慢,甚至使服务挂掉。因此我们需要进行优化。

在minio中提供了三个方法completeMultipartUpload,createMultipartUpload,listMultipart通过这三个方法我们可以将文件进行分片,分片后返回分片上传连接,等所有分片上传完成后,再进行分片合并,从而完成整个文件的上传。大致流程:

1、用户在前端使用vue-uploader上传文件,判断文件的大小。

2、如果文件大小小于5M则直接通过接口上传到minio服务器,如果大于5M时,计算分片数,调用分片接口获取每个分片对应的上传地址。

3、根据分片计算每个分片的大小,将文件按大小进行分片,调用分片地址将分片进行上传。

4、分片上传完成后,将分片进行合并,在服务器上形成文件。

2.1 代码实现

2.1.1 后端实现

基于minio的分片上传主要是重写三个接口,即completeMultipartUpload,createMultipartUpload,listMultipart,这三个接口在minio包中是protected的,我们如果想要使用这三个方法,只能重写这三个方法即可。

completeMultipartUpload 即完成分片上传后,进行分片合并。

createMultipartUpload 即返回每个分片对应Id及上传的url。

listMultipart 即查询分片信息。

1、定义PearMinioClient

PearMinioClient主要是集成MinioClient,重写MinioClient中的三个方法。


/**
 * @author: jiangjs
 * @description: 分片上传MinioClient继承MinioClient,主要暴露:
 *       createMultipartUpload:创建分片请求,返回uploadId
 *       listMultipart:查询分片信息
 *       completeMultipartUpload:根据uploadId合并已上传的分片
 * @date: 2023/10/24 14:08
 **/
public class PearMinioClient extends MinioClient {
   
   

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

    @Override
    public ObjectWriteResponse completeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
   
   
        return super.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }

    @Override
    public CreateMultipartUploadResponse createMultipartUpload(String bucketName, String region, String objectName, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
   
   
        return super.createMultipartUpload(bucketName, region, objectName, headers, 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 ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
   
   
        return super.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }
}
2、设置minio配置

获取配置文件中配置的minio相关属性,如地址等。

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/10/20 10:27
 **/
@Component
@Data
@ConfigurationProperties(prefix = "minio")
public class MinioEndPointInfo {
   
   
    /**
     * minio节点地址
     */
    private String endpoint;
    /**
     * 登录用户名
     */
    private String accessKey;
    /**
     * 密码
     */
    private String secretKey;

}

其中accessKey,secretKey分别在创建服务用户时创建。

创建minio配置类

/**
 * @author: jiangjs
 * @description: minio相关配置
 * @date: 2023/10/20 10:46
 **/
@Configuration
@EnableConfigurationProperties(MinioEndPointInfo.class)
public class MinioConfig {
   
   
    @Resource
    private MinioEndPointInfo minioEndPointInfo;

    @Bean
    public PearMinioClient createPearMinioClient(){
   
   
        MinioClient minioClient = MinioClient.builder()
                .endpoint(minioEndPointInfo.getEndpoint())
                .credentials(minioEndPointInfo.getAccessKey(), minioEndPointInfo.getSecretKey())
                .build();
        return new PearMinioClient(minioClient);
    }
}

上述minio配置类中,创建了minioClient来初始化我们定义的PearMinioClient。

3、创建minio分片工具类
/**
 * @author: jiangjs
 * @description: minio分片上传工具
 * @date: 2023/10/30 9:34
 **/
@Component
@Slf4j
public class MinioPearUploadUtil {
   
   

    @Resource
    private PearMinioClient pearMinioClient;

    /**
     * 校验当前bucket是否存在,不存在则创建
     * @param bucketName 桶
     */
    private void existBucket(String bucketName){
   
   
        try {
   
   
            boole
### 文件上传进度条实现方案 在Spring Boot中使用MinIO实现文件上传的同时展示进度条,主要依赖于前后端的协同工作。前端负责收集用户的文件输入以及向用户反馈当前的上传状态;而后端则需处理实际的文件接收逻辑,并适时地将上传进度返回给前端。 #### 前端部分 对于前端而言,`vue-simple-uploader`是一个不错的选择来构建具有友好UI界面的应用程序[^3]。此库允许开发者轻松集成文件选择器,并且内置了对分片上传的支持特性。为了实现实时更新上传进度,在每次接收到服务器响应后调整进度条的位置: ```javascript import Vue from 'vue'; import uploader from 'vue-simple-uploader'; Vue.use(uploader); new Vue({ el: '#app', methods: { handleProgress(event, file){ console.log('Upload Progress:', event.percent); this.uploadPercentage = Math.round(event.percent); // 更新百分比数值用于视图渲染 } }, }); ``` 上述代码片段展示了如何监听上传过程中的事件变化,并据此改变页面上显示的进度比例。 #### 后端部分 后端方面,基于Spring Boot框架配合MinIO服务端SDK完成文件流式的读取操作。每当从前端接收到一部分数据包时即刻写入目标对象内而不必等到整个文件传输完毕才开始保存至磁盘或云存储空间里。这样不仅降低了单次请求的数据量也使得中途暂停后再继续成为可能[^5]。 下面给出一段简化版的服务端控制器示例代码,它接受来自客户端发送过来的部分文件内容并记录下已接收字节数以便后续查询: ```java @RestController @RequestMapping("/api/files") public class FileController { @PostMapping(value="/upload", consumes="application/octet-stream") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile multipartFile, HttpServletRequest request) throws IOException{ long receivedBytes = 0L; InputStream inputStream = null; try { String originalFilename = multipartFile.getOriginalFilename(); Long fileSize = multipartFile.getSize(); // 获取InputStream实例化MinioClient... while ((receivedBytes += inputStream.read(buffer)) != -1 ) { minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(inputStream, fileSize, PartSize.DEFAULT_MIN_PART_SIZE) .contentType(multipartFile.getContentType()) .build()); // 返回当前接收了多少字节的信息给前端 Map<String,Object> progressInfo=new HashMap<>(); progressInfo.put("total", fileSize); progressInfo.put("loaded", receivedBytes); HttpServletResponse response=(HttpServletResponse)request.getAttribute(HttpAttributes.RESPONSE); ObjectMapper mapper = new ObjectMapper(); PrintWriter out=response.getWriter(); out.print(mapper.writeValueAsString(progressInfo)); out.flush(); } return ResponseEntity.ok().body("success"); } catch (Exception e) { throw new RuntimeException(e.getMessage(),e.getCause()); } finally { if(null!=inputStream){ inputStream.close(); } } } } ``` 需要注意的是这段Java代码仅为示意性质,具体实现细节可能会有所不同取决于项目的实际情况和个人偏好。另外,考虑到网络波动等因素可能导致连接中断的情况发生,建议加入重试机制以增强系统的健壮性[^2]。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值