需求:從ftp取文件並http調用某接口上傳此文件
偷懶的話可以從ftp上取文件存到本地,再調用接口上傳文件,如下
String ftpPath = "/ftp/path/file.bin";
RestTemplate restTemplate = new RestTemplateBuilder().build();
FtpCilent ftp = new FtpClient();
ftp.setFileType(FTP.BINARY_FILE_TYPE);
ftp.setCharset("utf-8");
ftp.setControlEncoding("utf-8");
……// ftp連接並登陸
File tmpFile = File.createTempFile(UUID.randomUUID().toString, null);
try(OutputStream outputStream = new FileOutputStream(tmpFile)){
ftp.retreiveFile(ftpPath, outputStream);// 保存到本地
}
ftp.disconnect();
MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<>();
dataMap.add("filename", new FileSystemResource(tmpFile));// 添加文件到表單
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(dataMap, requestHeaders);
restTemplate.exchange(...);
tmpFile.delete();
如上即可完成需求,但如果只是這樣的話我肯定不會水這一貼了,這樣做有個很明顯的缺點,要先將文件下載到本地,再將此文件上傳,分成了兩步,還多了個臨時文件得刪,而下面的改進版代碼會將兩步合並為一步提高效率並不額外占用磁盤空間。
String ftpPath = "/ftp/path/file.bin";
RestTemplate restTemplate = new RestTemplateBuilder().build();
FtpCilent ftp = new FtpClient();
ftp.setFileType(FTP.BINARY_FILE_TYPE);
ftp.setCharset("utf-8");
ftp.setControlEncoding("utf-8");
.....// ftp連接並登陸
FTPFile ftpFile = ftp.mlistFile(ftpPath);// 獲取文件信息
try(InputStream in = ftp.retreiveFileStream(ftpPath);){
InputStreamResource fileResource = new InputStreamResource(in){
@Override
public long contentLength(){
return ftpFile.getSize();
}
@Override
public String getFilename(){
return ftpFile.getName();
}
};
MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<>();
dataMap.add("filename", fileResource);// 添加文件到表單
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(dataMap, requestHeaders);
restTemplate.exchange(...);
ftp.disconnect();
}
總體思路就是將FileSystemResource換成了InputStreamResource,但這里有兩個重點,我重寫了它的contentLength
方法和getFilename
方法
先說getFilename
,如果不重寫這個方法,並且文件有一定大小,那么服務端會出現異常
The multi-part request contained parameter data (excluding uploaded files) that exceeded
這時你百度會看到人讓你設置什么max-request-size啥的,但沒用的,我試過設置幾個G也沒用還是上傳不了幾十M的文件,有必要設置文件名。
再說contentLength
,如果不重寫這個方法會出現異常
do not use inputstreamresource if a stream needs to be read multiple times
經排查,原因是在上傳文件時resttemplate會通過這個方法得到inputstream的大小,而這個方法會直接讀取inputstream的所有數據來得到大小,當它真正要讀取內容的時候發現流已經被讀完了,不得不說這方法實現的非常滑稽,有必要重寫這個方法。