springboot大文件分片上传实测成功(原生JS)

概述

前端:原生js

服务端:springboot

思路:前端js将文件拆分成2M的小片,不断请求上传接口,全部完成后在服务端进行合并

上传目录:d:/uploads/

实测效果:上传2G或5G的大文件毫无压力

 

效果展示

 

 

 

代码文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--log4j2-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--SpringBoot 集成 Thymeleaf 的起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--Apache Commons io FileUtils工具类-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  

 application.properties

#设置POST提交的最大值,不限制
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

#将项静态目资源路径映射到系统资源路径下(确保http://localhost:8080/1.png可访问)
#spring.web.resources.static-locations=file:d:/uploads


# 文件上传路径
file.uploadFolder=D:/uploads/

#log4j配置--------------------------
logging.config=classpath:log4j2.xml


#thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
#//thymeleaf 模版前缀,默认可以不写
spring.thymeleaf.prefix=classpath:/templates/
#//thymeleaf 模版后缀,默认可以不写
spring.thymeleaf.suffix=.html

#upload
#//单个文件最大的size
spring.servlet.multipart.maxFileSize=10MB
#//整个表单中上传文件一共最大的size
spring.servlet.multipart.maxRequestSize=30MB

#tomcat,
#//tomcat的连接超时时间,注意不能太短,我们在这里使用20秒
server.tomcat.connection-timeout=20000
#//tomcat的表单最大post的文件大小
server.tomcat.max-http-form-post-size=30MB

#error
server.error.include-stacktrace=always
#errorlog
logging.level.org.springframework.web=trace

  

upload.xml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>分片上传</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<h1>分片上传</h1>
<p>http://localhost:8080/upload/index</p>


<div>
    <input type="file" onchange="selectFile(event)">
    <button onclick="upload()">分片上传</button>
</div>
</body>

<script>

    let file=null;
    let chunk=0;//当前分片
    let chunks=0;// 总分片数
    let name='';// 文件名


    // 可以根据后端配置的上传文件大小进行判断是否需要分片
    function selectFile(event) {
        console.log(event)
        file = event.target.files[0]
        name = file.name
        console.log(file)
        // 分片大小根据实际需要 调整 可以配置在前端配置文件中
        chunks = Math.ceil(file.size / 1024 / 1024 / 2) // 2MB一片
        console.log('chunks='+chunks)
    }

    function upload() {
        // 现将文件分片
        const formData = new FormData()
        formData.append('file', file.slice(chunk * 1024 * 1024 * 2, (chunk + 1) * 1024 * 1024 * 2))
        formData.append('chunk', chunk)
        formData.append('chunks', chunks)
        formData.append('name', name)
        // 然后递归调用文件上传接口
        axios.post('http://localhost:8080/upload/splitFileUpload', formData)
        .then(() => {
            chunk++
            if (chunk < chunks) {
                upload()
            }
        })
    }
</script>

</html>

  

UploadController.java

package com.example.demo.controller;

import com.example.demo.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.List;

@Slf4j
@CrossOrigin
//@RestController
@Controller
@RequestMapping("/upload/")
public class UploadController {

    // 文件上传路径,从配置项获取
    @Value("${file.uploadFolder}")
    private String uploadFolder;

    // 简单接口,方便测试
    @GetMapping("index")
    public String upload(){
        return "upload";
    }

    /**
     * 分片上传
     *
     * @param file   文件,为二进制内容
     * @param chunk  第几块(如0、1、2、3)
     * @param chunks 总块数(如1076)
     * @param name   文件名(如侏罗纪公园3.mp4)
     * @return {@link ResponseEntity}<{@link String}>
     * @throws IOException ioexception
     */
    @PostMapping("/splitFileUpload")
    public ResponseEntity<String> splitFileUpload(@RequestParam("file") MultipartFile file,
                                                  @RequestParam("chunk") int chunk,
                                                  @RequestParam("chunks") int chunks,
                                                  @RequestParam("name") String name) throws IOException {
        String folder = uploadFolder;
        File dir = new File(folder);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String path = folder + name + "_" + chunk + ".part";//拼接得到分片文件名(如神话.mp4_0.part、神话.mp4_1.part)
        try {
            file.transferTo(new File(path));//把上传成功的文件从临时目录移到上传目录
            log.info("\n 上传成功,路径 {}",path);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件保存失败!");
        }
        if (chunk == chunks - 1) {
            // 合并所有分片
            File[] content = new File[chunks];
            for (int i = 0; i < chunks; i++) {
                content[i] = new File(folder + name + "_" + i + ".part");
            }
            FileUtil.mergeFile(content, new File(folder + name));
            // 删除分片文件
            for (int i = 0; i < chunks; i++) {
                new File(folder + name + "_" + i + ".part").delete();
            }
        }
        return ResponseEntity.ok("上传成功");
    }


}

  

FileUtil

package com.example.demo.utils;

import java.io.*;

/**
 * 文件操作类
 * */
public class FileUtil {
    /**
     * 合并分片文件
     * */
    public static void mergeFile(File[] files, File outputFile) {
        try {
            // 创建一个文件输出流来写入合并后的内容
            try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                for (File file : files) {
                    // 创建一个文件输入流来读取每个分片的内容
                    try (FileInputStream fis = new FileInputStream(file)) {
                        byte[] buffer = new byte[1024 * 1024 * 2];// 将每个分片的内容写入输出流
                        int length;
                        while ((length = fis.read(buffer)) > 0) {
                            fos.write(buffer, 0, length);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  

 

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<!--  %d{HH:mm:ss.SSS}  毫秒的时间
       %p代表输出该条日志的等级
       %t  当前线程名称
       %-5level  日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
       %c{1}  类名
       %L 输出行号
       %msg 日志文本
       %n 换行
  其他常用的占位符有:
       %F 输出所在的类文件名,如Client.java
       %M 输出所在方法名
       %m是输出代码指定的日志信息
       %l  输出语句所在的行数, 包括类名、方法名、文件名、行数 (这个比较强大)  -->
<configuration status="WARN" monitorInterval="30">
    <!--先定义所有的appender-->
    <appenders>
        <!--这个输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] [%t] - %l - %m%n"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
        <File name="log" fileName="E:/log/test.log" append="false">
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] [%t] - %l - %m%n"/>
        </File>

        <!--INFO日志文件输出 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="E:/logs/info.log"
                     filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
        <!-- Warn日志文件输出 -->
        <RollingFile name="RollingFileWarn" fileName="E:/logs/warn.log"
                     filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
        <!-- Error日志文件输出 -->
        <!--<RollingFile name="RollingFileError" fileName="E:/logs/error.log"-->
        <!--filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">-->
        <!--<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>-->
        <!--<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>-->
        <!--<Policies>-->
        <!--<TimeBasedTriggeringPolicy/>-->
        <!--<SizeBasedTriggeringPolicy size="100 MB"/>-->
        <!--</Policies>-->
        <!--</RollingFile>-->
    </appenders>

    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等-->
        <!--<logger name="org.springframework" level="INFO"></logger>-->
        <!--<logger name="org.mybatis" level="INFO"></logger>-->
        <root level="INFO">
            <appender-ref ref="Console"/>
            <appender-ref ref="log"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <!--<appender-ref ref="RollingFileError"/>-->
        </root>
    </loggers>
</configuration>

  

相关截图

 

 

 

posted @ 2024-01-06 11:30  牛大胆V5  阅读(45)  评论(0)    收藏  举报