1. 程式人生 > >通過Java WebService介面從服務端下載檔案

通過Java WebService介面從服務端下載檔案

一、 前言

本文講述如何通過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上執行,指定的下載檔案路徑和儲存檔案路徑都是本機的檔案路徑。實際應用時,客戶端可以指定一個服務端上的檔案路徑下載。