参考文献

准备工作

前端

  • WebUploader

    WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+。两套运行时,同样的调用方式,可供用户任意选用。

    采用大文件分片并发上传,极大的提高了文件上传效率。

  • 需要下载WebUploader包

后端

  • SpringBoot 2.5.6

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <properties>
    <java.version>1.8</java.version>
    <commons.fileupload.version>1.4</commons.fileupload.version>
    <commons.io.version>2.11.0</commons.io.version>
    </properties>
    <dependencies>
    <!-- 上传 -->
    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>${commons.fileupload.version}</version>
    </dependency>
    <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>${commons.io.version}</version>
    </dependency>
    <!-- 做断点下载使用 -->
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    </dependency>
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    </dependency>
    </dependencies>

分片上传

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<!DOCTYPE html>
<script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<!--引入CSS-->
<link rel="stylesheet" type="text/css" href="./webuploader-0.1.5/webuploader.css">
<!--引入JS-->
<script type="text/javascript" src="./webuploader-0.1.5/webuploader.js"></script>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<div id="upload-container" style="margin-top: 100px">
<!--用来存放文件信息-->
<div id="file-list" class="uploader-list"></div>
<div class="btns">
<div id="picker">选择文件</div>
<button id="ctlBtn" class="btn btn-default">开始上传</button>
</div>
</div>

</body>
</html>
<script>
let uploader = WebUploader.create({
auto: true,
// swf文件路径
swf: './webuploader-0.1.5/Uploader.swf',
server: 'http://localhost:8094/upload/up',
dnd: '#upload-container',
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick: '#picker',
multiple: true, //选择多个
chunked: true, //开启分片
// 如果要分片,分多大一片? 默认大小为5M.
// 100MB
chunkSize: 100 * 1024 * 1024,
// 并发数,Chrome浏览器默认并发线程数为6
threads: 6,
method: 'POST',
// 10GB
fileSizeLimit: 10 * 1024 * 1024 * 1024, //单个文件大小限制
// 10GB
fileSingleSizeLimit: 10 * 1024 * 1024 * 1024, //总文件大小
fileVal: 'upload',
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
resize: false,
thumb: {
width: 110,
height: 110,

// 图片质量,只有type为`image/jpeg`的时候才有效。
quality: 70,

// 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
allowMagnify: true,

// 是否允许裁剪。
crop: true,

// 为空的话则保留原有图片格式。
// 否则强制转换成指定的类型。
type: 'image/jpeg'
}
});
uploader.on("beforeFileQueued", function (file) {
console.log(file); //获取文件后缀
});
uploader.on('fileQueued', function (file) {
//选中文件要做的事
console.log(file.ext);
console.log(file.size);
console.log(file.name);
$('#file-list').append('<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">等待上传...</p>' +
'</div>');
uploader.md5File(file) //给文件定义唯一的md5值,当再次上传相同文件时,就不用传了 大文件秒传实际上是没传,直接拷贝之前文件地址
//显示进度
.progress(function (percentage) {
console.log('Percentage:', percentage);
})
//完成
.then(function (val) {
console.log('md5 result', val);
});

});
// 文件上传过程中创建进度条实时显示。
uploader.on('uploadProgress', function (file, percentage) {
let $li = $('#' + file.id), $percent = $li.find('.progress .progress-bar');
// 避免重复创建
if (!$percent.length) {
console.log('percentage:' + percentage);
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'</div>' +
'</div>').appendTo($li).find('.progress-bar');
}
$li.find('p.state').text('上传中' + Math.ceil(percentage * 100) + '%');
$percent.css('width', percentage * 100 + '%');
});

uploader.on('uploadSuccess', function (file) {
$('#' + file.id).find('p.state').text('已上传');
});

uploader.on('uploadError', function (file) {
$('#' + file.id).find('p.state').text('上传出错');
});

uploader.on('uploadComplete', function (file) {
$('#' + file.id).find('.progress').fadeOut();
});
</script>
<style>
.progress {
height: 20px;
width: 200px;
margin-bottom: 20px;
overflow: hidden;
background-color: #f5f5f5;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}

.progress.active .progress-bar {
-webkit-animation: progress-bar-stripes 2s linear infinite;
animation: progress-bar-stripes 2s linear infinite;
}

.progress-striped .progress-bar {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-size: 40px 40px;
}

.progress-bar {
background-image: -webkit-linear-gradient(top, #428bca 0, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0, #3071a9 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
}

.progress-bar {
float: left;
height: 100%;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: #428bca;
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
transition: width .6s ease;
}
</style>

后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@PostMapping("/up")
public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setCharacterEncoding(utf8);
//长传时候会有多个分片,需要记录当前为那个分片
Integer sChunk = null;
//总分片数
Integer sChunks = null;
//名字
String name = null;
//文件目录
String path = "xxx";
BufferedOutputStream os = null;
try {
//设置缓冲区大小 先读到内存里在从内存写
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024);
factory.setRepository(new File(path));
//解析
ServletFileUpload upload = new ServletFileUpload(factory);
//设置单个大小与最大大小
upload.setFileSizeMax(5L * 1024L * 1024L * 1024L);
upload.setSizeMax(10L * 1024L * 1024L * 1024L);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
//获取分片数赋值给遍量
if ("chunk".equals(item.getFieldName())) {
sChunk = Integer.parseInt(item.getString(utf8));
}
if ("chunks".equals(item.getFieldName())) {
sChunks = Integer.parseInt(item.getString(utf8));
}
if ("name".equals(item.getFieldName())) {
name = item.getString(utf8);
}
}
}
//取出文件基本信息后
for (FileItem item : items) {
if (!item.isFormField()) {
//有分片需要临时目录
String temFileName = name;
if (name != null) {
if (sChunk != null) {
temFileName = sChunk + "_" + name;
}
//判断文件是否存在
File tempFile = new File(path, temFileName);
//断点续传 判断文件是否存在,若存在则不传
if (!tempFile.exists()) {
item.write(tempFile);
}
}
}
}
//文件合并 当前分片为最后一个就合并
if (sChunk != null) {
assert sChunks != null;
if (sChunk == sChunks - 1) {
assert name != null;
File tempFile = new File(path, name);
os = new BufferedOutputStream(Files.newOutputStream(tempFile.toPath()));
//根据之前命名规则找到所有分片
for (int i = 0; i < sChunks; i++) {
File file = new File(path, i + "_" + name);
//并发情况 需要判断所有 因为可能最后一个分片传完,之前有的还没传完
while (!file.exists()) {
//不存在休眠100毫秒后在从新判断
Thread.sleep(100);
}
//分片存在 读入数组中
byte[] bytes = FileUtils.readFileToByteArray(file);
os.write(bytes);
os.flush();
Files.deleteIfExists(file.toPath());
}
os.flush();
}
}
response.getWriter().write("上传成功");
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

文件下载

使用RestTemplate

  • 使用RestTemplate发送POST请求下载文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void convert(String content, Path targetPath) {
Assert.isTrue(StringUtils.isNotEmpty(content), "参数不能为空");
final ConvertRequestParam requestParam = ConvertRequestParam.builder().content(content).build();
final String jsonBody = JSONUtil.parseObj(requestParam).toString();
//创建请求头,加入json
HttpHeaders headers = new HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8);
HttpEntity<String> httpEntity = new HttpEntity<String>(jsonBody, headers);
//使用RestTemplate提供的方法创建RequestCallback
RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity);
try {
//对响应进行流式处理而不是将其全部加载到内存中
restTemplate.execute(fullUrl, HttpMethod.POST, requestCallback, clientHttpResponse -> {
try (InputStream inputStream = clientHttpResponse.getBody()) {
Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
return null;
});
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException("调用服务失败");
}

}