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>
相关截图




大文件分片上传的简单实现,包括全部完整代码
浙公网安备 33010602011771号