通過Java WebService介面從服務端下載檔案
阿新 • • 發佈:2019-01-22
一、 前言
本文講述如何通過webservice介面從伺服器下載檔案到客戶端。適用於跨系統間的檔案互動,傳輸檔案不大的情況(控制在幾百M以;);在這種情況下搭建一個FTP伺服器增加了系統部署的複雜度和系統對外暴露的埠。採用在服務端讀取檔案,返回位元組流到客戶端再寫入檔案的方式比較簡單。
下面的實現採用restful的介面方式,程式碼拷貝到eclipse中即可執行,功能自測試執行正常。測試樣例程式碼的服務端和客戶端在同一臺PC上執行,放到不同PC上執行改一下發布服務和請求服務的IP地址即可。
二、 環境準備
2.1 CXF元件:用於釋出WebService服務的開源元件,內部自帶jetty Web容器。百度一下官網下載。
2.2 Eclipse:Java開發IDE。
三、 檔案下載服務端開發
3.1 新建服務端Java專案,匯入CXF lib目錄下的Jar包。
3.2 定義restful的WebService介面,用於下載檔案。
/** * 下載報告檔案WebService介面, 對於大於20M的檔案分多次傳輸。這裡不對檔案先讀取快取,再分批返回; * 而是每次重新讀取檔案,目的是為了讓本服務無狀態,能夠通過ngnix反向代理多個例項,解決服務的可靠性 * 和負載均衡問題。這樣做有一個風險,在分批傳送過程中,如果檔案被修改或者刪除了將導致檔案讀取失敗。 * * @author Elon * @version 1.0 2015-06-30 */ @Path("/DownloadFileWS") public class DownloadFileWS { // 單次傳送最大位元組數20M。 private final static int maxsize_once; static { maxsize_once = 1024 * 1024 * 20; } /** * 下載檔案。讀取檔案內容轉換為位元組流,大於10M分多次傳輸。 * @param req 請求引數 * @return 下載響應 */ @POST @Path("/downloadFile") public DownloadFileResponseVO downloadFile(DownloadFileRequestVO req){ try { return readFileByte(req); } catch (IOException e) { e.printStackTrace(); DownloadFileResponseVO vo = new DownloadFileResponseVO(); vo.setErrCode(DownloadErrCodeEnum.READ_FILE_EXCEPTION); return vo; } } /** * 讀取檔案內容,構建檔案位元組流返回物件。 * @param req 請求引數 * @return 讀取檔案返回值。 * @throws IOException IO異常 */ private DownloadFileResponseVO readFileByte(DownloadFileRequestVO req) throws IOException { DownloadFileResponseVO vo = new DownloadFileResponseVO(); // 獲取判斷檔案最近修改時間 File fileObject = new File(req.getFilePath()); final long fileLastModifiedTime = fileObject.lastModified(); // 判斷分批傳過程中檔案是否修改 if (fileLastModifiedTime != req.getFileLastModifiedTime() && req.getFileLastModifiedTime() != -1) { vo.setErrCode(DownloadErrCodeEnum.FILE_HAS_MODIFIED_WHILE_DOWNLOAD); return vo; } // 讀取檔案位元組流。 ByteArrayOutputStream fileStream = new ByteArrayOutputStream(1024); FileInputStream file = new FileInputStream(req.getFilePath()); byte[] readbuff = new byte[1024]; while(file.read(readbuff) != -1) { fileStream.write(readbuff); } file.close(); // 構建返回檔案位元組資訊。超過20M, 一次只返回20M。 final byte[] fileBuff = fileStream.toByteArray(); int end = 0; if (fileBuff.length - req.getStart() > maxsize_once) { end = req.getStart() + maxsize_once; vo.setEof(false); } else { end = fileBuff.length; vo.setEof(true); } // 拷貝[start, end)範圍內的位元組到返回值中。 vo.setFileByteBuff(Arrays.copyOfRange(fileBuff, req.getStart(), end)); vo.setStart(end); vo.setErrCode(DownloadErrCodeEnum.DOWN_LOAD_SUCCESS); vo.setFileLastModifiedTime(fileLastModifiedTime); fileStream.close(); return vo; } }
3.3 介面中使用輸入引數、返回值、錯誤碼定義
3.3.1 輸入引數型別定義
/** * 下載檔案請求引數型別。 * * @author Elon * @version 1.0 2015-06-30 */ @XmlRootElement(name = "DownloadFileRequest") public class DownloadFileRequestVO implements Serializable { /** * 序列化編碼 */ private static final long serialVersionUID = 3142085277564296839L; // 檔案路徑 private String filePath; // 讀檔案資料起始位置 private int start; // 第一次讀取檔案時間(用於在分批傳送檔案過程中判斷檔案是否被修改了) private long fileLastModifiedTime; DownloadFileRequestVO() { setFilePath(""); setStart(0); setFileLastModifiedTime(-1); } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public long getFileLastModifiedTime() { return fileLastModifiedTime; } public void setFileLastModifiedTime(long fileLastModifiedTime) { this.fileLastModifiedTime = fileLastModifiedTime; } @Override public String toString() { return "filePath: " + filePath + "\n" + "start: " + String.valueOf(start); } }
3.3.2 返回值型別定義
/**
* 檔案下載介面返回值型別
*
* @author Elon
* @version 1.0 2015-06-30
*/
@XmlRootElement(name = "FileVO")
public class DownloadFileResponseVO implements Serializable
{
/**
* 序列化編號
*/
private static final long serialVersionUID = -2218217669316014388L;
// 檔案位元組流快取
private byte[] fileByteBuff;
// 標識檔案傳輸是否結束
private boolean eof;
// 下一批讀取檔案資料起始位置
private int start;
// 第一次讀取檔案時間(用於在分批傳送檔案過程中判斷檔案是否被修改了)
private long fileLastModifiedTime;
// 錯誤碼
private DownloadErrCodeEnum errCode;
public DownloadFileResponseVO() {
setFileByteBuff(null);
setEof(false);
setStart(0);
setErrCode(DownloadErrCodeEnum.ERR_CODE_NA);
setFileLastModifiedTime(-1);
}
public byte[] getFileByteBuff() {
return fileByteBuff;
}
public void setFileByteBuff(byte[] fileByteBuff) {
this.fileByteBuff = fileByteBuff;
}
public boolean isEof() {
return eof;
}
public void setEof(boolean eof) {
this.eof = eof;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public DownloadErrCodeEnum getErrCode() {
return errCode;
}
public void setErrCode(DownloadErrCodeEnum errCode) {
this.errCode = errCode;
}
public long getFileLastModifiedTime() {
return fileLastModifiedTime;
}
public void setFileLastModifiedTime(long fileLastModifiedTime) {
this.fileLastModifiedTime = fileLastModifiedTime;
}
@Override
public String toString() {
return "fileByteBuff: " + fileByteBuff.toString() + "\n"
+ "eof: " + String.valueOf(eof) + "\n"
+ "start: " + String.valueOf(start);
}
}
3.3.3 錯誤碼列舉型別定義
/**
* 下載檔案錯誤碼
* @author Elon
* @version 1.0 2015-06-30
*/
public enum DownloadErrCodeEnum
{
DOWN_LOAD_SUCCESS, // 下載成功
READ_FILE_EXCEPTION, // 讀取檔案異常
FILE_HAS_MODIFIED_WHILE_DOWNLOAD, // 在分批下載檔案的過程中檔案發生了修改
ERR_CODE_NA, // 無效錯誤碼
}
3.3.4 釋出restful服務
public class StartServer
{
public static void main(String[] args)
{
publishWS();
}
private static void publishWS()
{
JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
bean.setAddress("http://10.61.67.246:10011/download");
bean.setServiceBean(new DownloadFileWS());
bean.create();
// 阻塞執行緒、等待外部訊息請求。
while(true)
{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
四、 檔案下載客戶端開發
4.1 新建客戶端Java專案,匯入CXF lib目錄下的Jar包。
4.2 呼叫介面下載檔案,檔案位元組流寫入目標檔案儲存。
public class StartClient
{
public static void main(String[] args) throws IOException
{
downloadFileClient();
}
private static void downloadFileClient() throws IOException
{
DownloadFileRequestVO req = new DownloadFileRequestVO();
req.setFilePath("D:\\TEMP\\測試報告壓縮包檔案.zip");
req.setStart(0);
// 迴圈下載檔案位元組流,直到下載完檔案所有內容。
FileOutputStream out = new FileOutputStream("D://TEMP/123.zip");
while (true)
{
WebClient webClient = WebClient.create("http://10.61.67.246:10011");
webClient.encoding("UTF-8");
Response response = webClient.path("download/DownloadFileWS/downloadFile")
.post(req);
GenericType<DownloadFileResponseVO> vo = new GenericType<DownloadFileResponseVO>(){};
DownloadFileResponseVO rsp = response.readEntity(vo);
webClient.close();
if (rsp.getErrCode() != DownloadErrCodeEnum.DOWN_LOAD_SUCCESS)
{
System.err.println("Download file err: " + rsp.getErrCode());
break;
}
// 將位元組流寫到檔案
out.write(rsp.getFileByteBuff());
if (rsp.isEof())
{
break;
}
else
{
req.setStart(rsp.getStart());
req.setFileLastModifiedTime(rsp.getFileLastModifiedTime());
}
}
// 輸出、關閉檔案
try
{
out.flush();
out.close();
System.err.println("Download file success!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
上述程式碼為研究測試用,服務端和客戶端都在本地PC上執行,指定的下載檔案路徑和儲存檔案路徑都是本機的檔案路徑。實際應用時,客戶端可以指定一個服務端上的檔案路徑下載。