Skip to content

Commit f41f434

Browse files
authored
Merge pull request #305 from JiazhenBao/main
Httpclient支持异步传输文件
2 parents 0edfa31 + c8fb251 commit f41f434

File tree

8 files changed

+390
-53
lines changed

8 files changed

+390
-53
lines changed

currency-convert_wjiming/pom.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.wjm</groupId>
88
<artifactId>currency-convert</artifactId>
9-
<version>0.0.2</version>
9+
<version>0.0.5</version>
1010
<name>人民币美元大小写转换</name>
1111
<description>提供了人民币大小写转换和美元大小写转换的逻辑</description>
1212

@@ -29,11 +29,6 @@
2929
<scope>system</scope>
3030
<systemPath>${project.basedir}/jar/nasl-metadata-collector-0.8.0.jar</systemPath>
3131
</dependency>
32-
<dependency>
33-
<groupId>org.slf4j</groupId>
34-
<artifactId>slf4j-simple</artifactId>
35-
<version>1.7.30</version>
36-
</dependency>
3732
</dependencies>
3833
<build>
3934
<plugins>

httpclient/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<artifactId>httpclient</artifactId>
1717
<name>httpclient</name>
1818
<description>httpclient</description>
19-
<version>1.1.0</version>
19+
<version>1.2.0</version>
2020

2121
<dependencies>
2222
<dependency>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.netease.http.dto;
2+
3+
import com.netease.lowcode.core.annotation.NaslStructure;
4+
5+
@NaslStructure
6+
public class LocalFileCacheDto {
7+
public String resBody;
8+
public String fileName;
9+
/**
10+
* 文件下载状态,0-不存在,1-文件下载中,2-本地已下载完成,待上传,3-上传中,4-上传完成,5-文件上传失败,6-文件上传失败
11+
*/
12+
public Integer downloadStatus;
13+
14+
public LocalFileCacheDto() {
15+
}
16+
17+
public LocalFileCacheDto(String resBody, String fileName, Integer downloadStatus) {
18+
this.resBody = resBody;
19+
this.fileName = fileName;
20+
this.downloadStatus = downloadStatus;
21+
}
22+
23+
public String getResBody() {
24+
return resBody;
25+
}
26+
27+
public void setResBody(String resBody) {
28+
this.resBody = resBody;
29+
}
30+
31+
public String getFileName() {
32+
return fileName;
33+
}
34+
35+
public void setFileName(String fileName) {
36+
this.fileName = fileName;
37+
}
38+
39+
public Integer getDownloadStatus() {
40+
return downloadStatus;
41+
}
42+
43+
public void setDownloadStatus(Integer downloadStatus) {
44+
this.downloadStatus = downloadStatus;
45+
}
46+
}

httpclient/src/main/java/com/netease/http/dto/UploadFileParam.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
public class UploadFileParam {
1010
@Required
1111
public String fileUrl;
12+
/**
13+
* form-data请求中文件的key,默认:file
14+
*/
1215
@Required
1316
public String fileKey;
1417

httpclient/src/main/java/com/netease/http/httpclient/HttpClientService.java

Lines changed: 176 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,97 @@
22

33
import com.alibaba.fastjson.JSONObject;
44
import com.netease.http.dto.DtoConvert;
5+
import com.netease.http.dto.LocalFileCacheDto;
6+
import com.netease.http.dto.RequestParam;
57
import com.netease.http.dto.RequestParamAllBodyTypeInner;
8+
import com.netease.http.exception.TransferCommonException;
69
import com.netease.http.util.FileNameValidator;
710
import org.slf4j.Logger;
811
import org.slf4j.LoggerFactory;
12+
import org.springframework.core.io.FileSystemResource;
913
import org.springframework.http.*;
1014
import org.springframework.stereotype.Component;
15+
import org.springframework.util.LinkedMultiValueMap;
16+
import org.springframework.util.MultiValueMap;
1117
import org.springframework.util.StringUtils;
18+
import org.springframework.web.client.RequestCallback;
1219
import org.springframework.web.client.RestTemplate;
1320
import org.springframework.web.util.UriComponentsBuilder;
1421

15-
import java.io.File;
16-
import java.io.FileOutputStream;
17-
import java.io.IOException;
22+
import java.io.*;
1823
import java.net.URI;
1924
import java.net.URLDecoder;
20-
import java.util.HashMap;
21-
import java.util.List;
22-
import java.util.Objects;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
import java.nio.file.StandardOpenOption;
29+
import java.util.*;
30+
import java.util.concurrent.CompletableFuture;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.concurrent.atomic.AtomicInteger;
2333

2434
@Component
2535
public class HttpClientService {
2636
private static final Logger logger = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER");
37+
private final Map<String, LocalFileCacheDto> fileCache = new ConcurrentHashMap<>();
38+
39+
public LocalFileCacheDto getFileCache(String fileKey) {
40+
return fileCache.get(fileKey);
41+
}
42+
43+
public LocalFileCacheDto asyncUploadFileExchangeCommon(String fileTimeMillisKey, RestTemplate restTemplateFinal, RequestParam requestParam, String fileKey, File file) {
44+
LocalFileCacheDto localFileCacheDto = new LocalFileCacheDto(null, file.getName(), 3);
45+
fileCache.put(fileTimeMillisKey, localFileCacheDto);
46+
CompletableFuture.runAsync(() -> {
47+
try {
48+
String resBody = uploadFileExchangeCommon(restTemplateFinal, requestParam, fileKey, file);
49+
localFileCacheDto.setResBody(resBody);
50+
localFileCacheDto.setDownloadStatus(4);
51+
localFileCacheDto.setFileName(file.getName());
52+
fileCache.put(fileTimeMillisKey, localFileCacheDto);
53+
} catch (Exception e) {
54+
logger.error("上传文件失败", e);
55+
localFileCacheDto.setDownloadStatus(6);
56+
localFileCacheDto.setFileName(file.getName());
57+
localFileCacheDto.setResBody("上传文件失败");
58+
fileCache.put(fileTimeMillisKey, localFileCacheDto);
59+
}
60+
});
61+
return localFileCacheDto;
62+
}
63+
64+
/**
65+
* 上传文件到第三方系统通用方法
66+
*
67+
* @param requestParam
68+
* @param fileKey
69+
* @param file
70+
* @return
71+
*/
72+
public String uploadFileExchangeCommon(RestTemplate restTemplateFinal, RequestParam requestParam, String fileKey, File file) {
73+
RequestParamAllBodyTypeInner requestParamInner = new RequestParamAllBodyTypeInner();
74+
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
75+
if (requestParam.getBody() != null) {
76+
requestParam.getBody().forEach(body::add);
77+
}
78+
if (StringUtils.isEmpty(fileKey)) {
79+
fileKey = "file";
80+
}
81+
body.add(fileKey, new FileSystemResource(file));
82+
requestParamInner.setBody(body);
83+
if (StringUtils.isEmpty(requestParam.getHttpMethod())) {
84+
requestParam.setHttpMethod(HttpMethod.GET.name());
85+
}
86+
requestParamInner.setHttpMethod(requestParam.getHttpMethod());
87+
requestParamInner.setUrl(requestParam.getUrl());
88+
requestParamInner.setHeader(requestParam.getHeader());
89+
ResponseEntity<String> exchange = this.exchangeInner(requestParamInner, restTemplateFinal, String.class);
90+
if (exchange.getStatusCode() == HttpStatus.OK) {
91+
return exchange.getBody();
92+
} else {
93+
throw new TransferCommonException(exchange.getStatusCodeValue(), JSONObject.toJSONString(exchange));
94+
}
95+
}
2796

2897
public <T> ResponseEntity<T> exchangeInner(RequestParamAllBodyTypeInner requestParam, RestTemplate restTemplateFinal, Class<T> responseType) {
2998
try {
@@ -51,6 +120,87 @@ public <T> ResponseEntity<T> exchangeInner(RequestParamAllBodyTypeInner requestP
51120
}
52121
}
53122

123+
/**
124+
* 下载文件
125+
*
126+
* @param requestParam
127+
* @param restTemplateFinal
128+
* @param fileName
129+
* @return 文件路径
130+
*/
131+
public String asyncDownloadFile(RequestParamAllBodyTypeInner requestParam, RestTemplate restTemplateFinal, String fileName) throws IOException {
132+
String fileKey = System.currentTimeMillis() + "d";
133+
// 流式下载
134+
Path parentFile = Paths.get("./local_file").toAbsolutePath().normalize();
135+
File file = new File(parentFile.toUri());
136+
if (!file.exists()) {
137+
Files.createDirectories(parentFile);
138+
}
139+
CompletableFuture.runAsync(() -> {
140+
try {
141+
downloadFileBigFile(requestParam, restTemplateFinal, fileName, fileKey, parentFile);
142+
} catch (Exception e) {
143+
logger.error("下载文件失败", e);
144+
fileCache.put(fileKey, new LocalFileCacheDto(null, fileName, 5));
145+
}
146+
});
147+
return fileKey;
148+
}
149+
150+
public void downloadFileBigFile(RequestParamAllBodyTypeInner requestParam, RestTemplate restTemplateFinal, String fileName, String fileKey, Path parentFile) {
151+
RequestCallback requestCallback = request ->
152+
request.getHeaders().setAccept(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
153+
AtomicInteger count = new AtomicInteger();
154+
restTemplateFinal.execute(requestParam.getUrl(), HttpMethod.resolve(requestParam.getHttpMethod().toUpperCase()), requestCallback, response -> {
155+
String fileNameFinal = fileName;
156+
if (count.get() == 0) {
157+
if (StringUtils.isEmpty(fileName)) {
158+
// 解析文件名
159+
fileNameFinal = Optional.ofNullable(response.getHeaders().getContentDisposition())
160+
.map(ContentDisposition::getFilename)
161+
.orElseGet(() -> {
162+
String path = requestParam.getUrl().split("\\?")[0];
163+
return path.substring(path.lastIndexOf('/') + 1);
164+
});
165+
}
166+
if (StringUtils.isEmpty(fileNameFinal)) {
167+
fileNameFinal = System.currentTimeMillis() + ".xlsx";
168+
}
169+
fileCache.put(fileKey, new LocalFileCacheDto(null, fileNameFinal, 1));
170+
} else {
171+
fileNameFinal = fileCache.get(fileKey).getFileName();
172+
}
173+
count.getAndIncrement();
174+
175+
Path targetFile = parentFile.resolve(fileNameFinal);
176+
// 初始8KB,根据下载速度动态扩大(上限256KB)
177+
int bufferSize = 8 * 1024;
178+
long lastTime = System.currentTimeMillis();
179+
long totalRead = 0;
180+
try (InputStream is = response.getBody();
181+
OutputStream os = Files.newOutputStream(targetFile, StandardOpenOption.CREATE)) {
182+
byte[] buffer = new byte[bufferSize];
183+
int bytesRead;
184+
while ((bytesRead = is.read(buffer)) != -1) {
185+
os.write(buffer, 0, bytesRead);
186+
// 每1MB数据评估一次
187+
totalRead += bytesRead;
188+
if (totalRead % (1024 * 1024) == 0) {
189+
long timeSpent = System.currentTimeMillis() - lastTime;
190+
double speedKBps = 1024 / (timeSpent / 1000.0);
191+
// 动态调整缓冲区(范围8KB~256KB)
192+
bufferSize = (int) Math.min(256 * 1024, Math.max(8 * 1024, speedKBps * 10));
193+
buffer = new byte[bufferSize];
194+
lastTime = System.currentTimeMillis();
195+
}
196+
}
197+
}
198+
fileCache.put(fileKey, new LocalFileCacheDto(null, fileNameFinal, 2));
199+
return null;
200+
});
201+
}
202+
203+
54204
/**
55205
* 下载文件
56206
*
@@ -64,23 +214,10 @@ public File downloadFile(RequestParamAllBodyTypeInner requestParam, RestTemplate
64214
if (response.getStatusCode() == HttpStatus.OK) {
65215
byte[] fileData = response.getBody();
66216
if (fileData == null) {
67-
logger.error(requestParam.getUrl()+"请求返回文件为空");
217+
logger.error(requestParam.getUrl() + "请求返回文件为空");
68218
return null;
69219
}
70-
List<String> resHeaders = response.getHeaders().get("Content-Disposition");
71-
if (StringUtils.isEmpty(fileName)) {
72-
fileName = System.currentTimeMillis() + ".xlsx";
73-
if (resHeaders != null) {
74-
for (String resHeader : resHeaders) {
75-
if (resHeader.startsWith("filename") || resHeader.startsWith("attachment")) {
76-
String fileNameTmp = resHeader.split("filename=")[1].replace("\"", "");
77-
if (FileNameValidator.isValidFileName(fileNameTmp)) {
78-
fileName = URLDecoder.decode(fileNameTmp);
79-
}
80-
}
81-
}
82-
}
83-
}
220+
fileName = getFileNameFromResponse(fileName, response);
84221
File file = null;
85222
try {
86223
String fileExt = "";
@@ -106,6 +243,24 @@ public File downloadFile(RequestParamAllBodyTypeInner requestParam, RestTemplate
106243
return null;
107244
}
108245

246+
public <T> String getFileNameFromResponse(String fileName, ResponseEntity<T> response) {
247+
List<String> resHeaders = response.getHeaders().get("Content-Disposition");
248+
if (StringUtils.isEmpty(fileName)) {
249+
fileName = System.currentTimeMillis() + ".xlsx";
250+
if (resHeaders != null) {
251+
for (String resHeader : resHeaders) {
252+
if (resHeader.startsWith("filename") || resHeader.startsWith("attachment")) {
253+
String fileNameTmp = resHeader.split("filename=")[1].replace("\"", "");
254+
if (FileNameValidator.isValidFilename(fileNameTmp, 0)) {
255+
fileName = URLDecoder.decode(fileNameTmp);
256+
}
257+
}
258+
}
259+
}
260+
}
261+
return fileName;
262+
}
263+
109264
public <T> ResponseEntity<T> exchangeWithoutUriEncode(RequestParamAllBodyTypeInner requestParam, RestTemplate restTemplateFinal, Class<T> responseType) {
110265
if (Objects.isNull(requestParam.getHeader())) {
111266
requestParam.setHeader(new HashMap<>());

0 commit comments

Comments
 (0)