S3--AWS SDK for Java

摘要

示例代码

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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package com.hanqf.controller;

import com.hanqf.utils.AmazonS3V2Util;
import com.hanqf.utils.MyIOUtil;
import com.hanqf.utils.S3ClientFactory;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;

@Slf4j
@RestController
public class DemoController {

/**
* 文件下载,支持断点续传,支持分段下载
* 分段下载的好处就是可以进行多线程下载,提高下载速度,下载完成后再将所有的分段文件进行合并成一个完整的文件
* <p>
* 可以先下载一个字节的数据,此时可以获取文件总的字节数,然后基于这个总字节数决定分段大小
*/
@GetMapping("/file-down-range")
public void fileDownRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 服务器上的文件路径,这里只是为了演示方便,实际使用时,需要根据文件的实际存储方式进行处理
String localFileName = request.getParameter("fileName");
if (!StringUtils.hasText(localFileName)) {
throw new RuntimeException("没有上传fileName!");
}

File file = new File(localFileName);
if (!file.exists()) {
throw new RuntimeException(localFileName + ":文件不存在!");
}

final long contentLength = file.length();

String range = request.getHeader("Range");

String start = null;
String end = null;
if (StringUtils.hasText(range) && (range.contains("bytes=") && range.contains("-"))) {
start = org.apache.commons.lang.StringUtils.substringBetween(range, "bytes=", "-");
end = org.apache.commons.lang.StringUtils.substringAfter(range, "-");
}

long startIndex = 0;
long endIndex = contentLength - 1;

if (StringUtils.hasText(start)) {
startIndex = Long.parseLong(start);
}
if (StringUtils.hasText(end)) {
endIndex = Long.parseLong(end);
}
log.info("range:" + startIndex + "~" + endIndex);

String fileName = localFileName;
if (localFileName.contains("/")) {
fileName = localFileName.substring(localFileName.lastIndexOf("/") + 1);
}

if (StringUtils.hasText(start)) {
fileName = fileName + "." + startIndex + "-" + endIndex;
}
long length = endIndex - startIndex + 1;

response.setContentType("application/octet-stream");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes " + startIndex + "-" + endIndex + "/" + contentLength);
response.setHeader("Content-Length", length + "");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);

// 读取文件流
try (ServletOutputStream outputStream = response.getOutputStream(); InputStream inputStream = Files.newInputStream(file.toPath())) {
MyIOUtil.copyDataFromInputStreamToOutputStream(inputStream, startIndex, length, outputStream, true);
}
}


/**
* S3文件下载,分段下载
* num 分段号,从1开始
*/
@GetMapping("/s3-down/{num}")
public void s3Down(@PathVariable int num, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 分段下载测试
String remoteFileName = request.getParameter("fileName");
if (!StringUtils.hasText(remoteFileName)) {
throw new RuntimeException(remoteFileName + ":文件不存在!");
}

String fileName = remoteFileName;
if (remoteFileName.contains("/")) {
fileName = remoteFileName.substring(remoteFileName.lastIndexOf("/") + 1);
}

fileName = fileName + "." + num;

final long contentLength = AmazonS3V2Util.getObjectInfo(S3ClientFactory.BUCKET_NAME, remoteFileName).contentLength();
final java.util.List<Long> positions = AmazonS3V2Util.positions(contentLength);
positions.add(contentLength);
long start = positions.get(num - 1);
long end = positions.get(num) - 1;
log.info("range:" + start + "~" + end);

response.setContentType("application/octet-stream");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + contentLength);
response.setHeader("Content-Length", (end - start + 1) + "");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
ServletOutputStream outputStream = response.getOutputStream();
AmazonS3V2Util.downloadSubsectionAndCloseOutputStream(S3ClientFactory.BUCKET_NAME, remoteFileName, start, end, outputStream);

}

/**
* S3文件下载,支持断点续传,支持分段下载
*/
@GetMapping("/s3-down-range")
public void s3DownRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 分段下载测试
String remoteFileName = request.getParameter("fileName");
if (!StringUtils.hasText(remoteFileName)) {
throw new RuntimeException(remoteFileName + ":文件不存在!");
}
final long contentLength = AmazonS3V2Util.getObjectInfo(S3ClientFactory.BUCKET_NAME, remoteFileName).contentLength();
String range = request.getHeader("Range");

String start = null;
String end = null;
if (StringUtils.hasText(range) && (range.contains("bytes=") && range.contains("-"))) {
start = org.apache.commons.lang.StringUtils.substringBetween(range, "bytes=", "-");
end = org.apache.commons.lang.StringUtils.substringAfter(range, "-");

}

long startIndex = 0;
long endIndex = contentLength - 1;

if (StringUtils.hasText(start)) {
startIndex = Long.parseLong(start);
}
if (StringUtils.hasText(end)) {
endIndex = Long.parseLong(end);
}
log.info("range:" + startIndex + "~" + endIndex);

String fileName = remoteFileName;
if (remoteFileName.contains("/")) {
fileName = remoteFileName.substring(remoteFileName.lastIndexOf("/") + 1);
}

if (StringUtils.hasText(start)) {
fileName = fileName + "." + startIndex + "-" + endIndex;
}

response.setContentType("application/octet-stream");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes " + startIndex + "-" + endIndex + "/" + contentLength);
response.setHeader("Content-Length", (endIndex - startIndex + 1) + "");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
ServletOutputStream outputStream = response.getOutputStream();
AmazonS3V2Util.downloadSubsectionAndCloseOutputStream(S3ClientFactory.BUCKET_NAME, remoteFileName, startIndex, endIndex, outputStream);

}

/**
* 上传文件到S3,支持分段上传
*/
@PostMapping("/uploadToS3")
public String uploadFileToS3(MultipartFile file, String remoteFileName) throws IOException {
if (file != null && StringUtils.hasText(remoteFileName)) {
log.info("上传文件到S3:" + remoteFileName);
AmazonS3V2Util.multipartUpload(S3ClientFactory.BUCKET_NAME, remoteFileName, file.getBytes(), true);
return "上传成功";
}
return "上传失败";
}
}

源码地址