22
33import com .alibaba .fastjson .JSONObject ;
44import com .netease .http .dto .DtoConvert ;
5+ import com .netease .http .dto .LocalFileCacheDto ;
6+ import com .netease .http .dto .RequestParam ;
57import com .netease .http .dto .RequestParamAllBodyTypeInner ;
8+ import com .netease .http .exception .TransferCommonException ;
69import com .netease .http .util .FileNameValidator ;
710import org .slf4j .Logger ;
811import org .slf4j .LoggerFactory ;
12+ import org .springframework .core .io .FileSystemResource ;
913import org .springframework .http .*;
1014import org .springframework .stereotype .Component ;
15+ import org .springframework .util .LinkedMultiValueMap ;
16+ import org .springframework .util .MultiValueMap ;
1117import org .springframework .util .StringUtils ;
18+ import org .springframework .web .client .RequestCallback ;
1219import org .springframework .web .client .RestTemplate ;
1320import 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 .*;
1823import java .net .URI ;
1924import 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
2535public 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