Ajax方式文件下載(后台返回為json或文件流)


在應用開發中,經常需要下載文件(如導出Excel),調用后台接口時,如果后台報錯需要彈出錯誤信息,如果沒有報錯正常下載文件。本文主要介紹前台及后台(基於java)的處理方法,文中使用到的軟件版本:Spring 4.3.9、Java 1.8.0_191、Jquery 1.12.4、Chrome 85.0.4183.121。

1、實現思路

方法一:通過原生Ajax響應頭來區分是文本還是流,然后通過blob來處理返回數據

方法二:分成兩個接口,先調用生成文件的接口,如果文件成功生成則再調用文件下載的接口;如果文件生成失敗則彈出失敗信息

2、方法一(Ajax響應頭區分文本還是流)

2.1、后台

/**
 * 根據sql導成csv;成功返回文件流,失敗返回錯誤信息
 * @param sql 導出sql
 * @param col 字段信息,以逗號分隔
 * @param title 文件頭信息,以逗號分隔
 * @param separator csv文件的分隔符
 * @param baseServiceName
 * @param request
 * @param response
 */
@RequestMapping("exportForCsvStream")
@ResponseBody
public R<String> exportForCsvStream(String sql, String col, String title, String separator, String fileName, String baseServiceName, HttpServletRequest request, HttpServletResponse response) {
    logger.info("sql={},title={},separator={},fileName={},baseServiceName={}", sql, title, separator, fileName, baseServiceName);
    try {
        if (StringUtils.isBlank(separator)) {
            separator = ",";
        }
        if (StringUtils.isBlank(fileName)) {
            fileName = "data.csv";
        }
        setResponse(fileName, request, response);
        OutputStream os = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);
//調用service把文件寫入到輸出流,請自行實現
this.getBaseService(baseServiceName).exportForCsv(os, sql, col, title, separator); os.close(); return null; } catch(Exception e) { e.printStackTrace(); response.reset(); response.setContentType("application/json"); response.setHeader("Content-Disposition", ""); return R.error(-1, "發生異常"); } } private void setResponse(String fileName, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException { String urlFileName = ""; if (request.getHeader("User-Agent").toLowerCase().indexOf("firefox") > 0) { urlFileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1"); } else { urlFileName = java.net.URLEncoder.encode(fileName, "UTF-8"); } response.reset(); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + urlFileName + "\""); response.setHeader("Connection", "close"); }

2.2、前台

2.2.1、拼接參數

function expForCsv() {
    let sql = "";//請自行設置
    let col = "";//請自行設置
    let title = "";//請自行設置
let fileName = "";//請自行設置

$.messager.progress({title : '處理中...'});//easyui控件,可用其他控件    let xhr = new XMLHttpRequest(); xhr.open("post", "${pageContext.request.contextPath}/common/export/exportForCsvStream.do", true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.responseType = "blob"; xhr.onload = function() {
     $.messager.progress('close');
if (xhr.getResponseHeader("Content-type") == 'application/octet-stream') { let url = window.URL.createObjectURL(xhr.response); let a = document.createElement("a"); a.href = url; a.style.display = 'none' a.download = fileName; a.click(); window.URL.revokeObjectURL(url); a.remove(); } else { let reader = new FileReader(); reader.addEventListener('loadend', function (e) { let data = JSON.parse(e.target.result); alert('提示', "系統出錯,錯誤信息為:" + data.description + ",請將該信息提供給代維人員尋求幫助"); }); reader.readAsText(xhr.response); } }; xhr.send('sql=' + sql + '&col=' + col + '&title=' + title + '&separator=|&t=' + new Date().getTime()); }

2.2.2、使用FormData

function expForCsv(type) {
let sql = "";//請自行設置 let col
= "";//請自行設置 let title = "";//請自行設置 let fileName = ";//請自行設置
$.messager.progress({title : '處理中...'});//easyui控件,可用其他控件 let xhr = new XMLHttpRequest(); xhr.open(
"post", "${pageContext.request.contextPath}/common/export/exportForCsvStream.do", true); //xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.responseType = "blob"; xhr.onload = function() { $.messager.progress('close'); if (xhr.getResponseHeader("Content-type") == 'application/octet-stream') { let url = window.URL.createObjectURL(xhr.response); let a = document.createElement("a"); a.href = url; a.style.display = 'none' a.download = fileName; a.click(); window.URL.revokeObjectURL(url); a.remove(); } else { let reader = new FileReader(); reader.addEventListener('loadend', function (e) { let data = JSON.parse(e.target.result); alert('提示', "系統出錯,錯誤信息為:" + data.description + ",請將該信息提供給代維人員尋求幫助"); }); reader.readAsText(xhr.response); } }; let data = new FormData(); data.append('sql', getCsql(type)); data.append('col', col); data.append('title', title); data.append('separator', '|'); data.append('t', new Date().getTime() + ""); xhr.send(data); }

3、方法二(兩個接口)

3.1、后台

3.1.1、生成文件接口

/**
 * 根據sql導成csv;成功返回生成的文件名稱,失敗返回錯誤信息
 * @param sql 導出sql
 * @param col 字段信息,以逗號分隔
 * @param title 文件頭信息,以逗號分隔
 * @param separator csv文件分隔符
 * @param baseServiceName
 * @return
 */
@RequestMapping("exportForCsv")
@ResponseBody
public CallResult<String> exportForCsv(String sql, String col, String title, String separator) {
    logger.info("sql={}", sql);
    logger.info("col={}", col);
    logger.info("title={}", title);
    logger.info("separator={}", separator);
    logger.info("baseServiceName={}", baseServiceName);
    OutputStream os = null;
    CallResult<String> result = new CallResult<>();
    try {
        String fileName = "數據信息(" + DateUtil.getCurrentDateString("yyyyMMddHHmmsss") + ").xlsx";
        os = new FileOutputStream(new File(WebconfigFactory.getTempFolder() + "數據信息(" + DateUtil.getCurrentDateString("yyyyMMddHHmmsss") + ").xlsx"));
     
//調用service生成文件,請自行實現 getService().exportForCsv(os, sql, col, title, separator); result.setResult(fileName); }
catch(Exception e) { result = new CallResult<>(-1, "發生異常"); e.printStackTrace(); } finally { FileUtil.close(os); } return result; }

3.1.2、下載文件接口

/**
 * 下載方法
 * 1.點擊頁面下載,需傳參數:
 *         fileName:磁盤上文件的名稱,必需
 *         filePath或pathType或webPath:
 *             文件路徑,傳其中之一參數即可;
 *             filePath是磁盤的絕對路徑;
 *             pathType是路徑類型,對應webconfig.properties中的key
 *             webPath是相對web應用的路徑,如/spa
 *         fileRealName:下載保存后的文件名稱,可選;如果為空則與fileName一樣
 *   
 * 2.后台導出文件下載,需傳屬性:
 *         fileName:磁盤上文件的名稱,必需
 *         fileRealName:下載保存后的文件名稱,可選;如果為空則與fileName一樣
 */
@RequestMapping("download")
public void download(HttpServletRequest request, HttpServletResponse response) {
    String filePath = "";
    String fileName = request.getParameter("fileName");
    String fileRealName = "";
    if (!Util.isNull(fileName)) {
        filePath = request.getParameter("filePath");
        if (Util.isNull(filePath)) {
            String webPath = request.getParameter("webPath");
            if (Util.isNull(webPath)) {
                String pathType = request.getParameter("pathType");
                filePath = WebconfigFactory.getValue(pathType);
            } else {
                //filePath = request.getRealPath(webPath) + System.getProperty("file.separator");
                filePath = request.getServletContext().getRealPath(webPath) + System.getProperty("file.separator");
            }
        }
        fileRealName = request.getParameter("fileRealName");
    } else {//后台導出文件下載
        fileName = (String) request.getAttribute("fileName");
        filePath = WebconfigFactory.getTempFolder();
        fileRealName = (String) request.getAttribute("fileRealName");
    }
    
    if (Util.isNull(fileRealName)) {
        fileRealName = fileName;
    }
    
    BufferedInputStream bis = null;
    OutputStream out = null;
    try {
        String userInfoHeader = "User-Agent";
        String firefoxName = "firefox";
        String urlFileName = "";
        if (request.getHeader(userInfoHeader).toLowerCase().indexOf(firefoxName) > 0) {
            urlFileName = new String(fileRealName.getBytes("UTF-8"), "ISO8859-1");
        } else {
            urlFileName = java.net.URLEncoder.encode(fileRealName, "UTF-8");
        }
        
        response.reset();
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + urlFileName + "\"");
        response.setHeader("Connection", "close");
        
        bis = new BufferedInputStream(new FileInputStream(filePath + fileName), BUFFER_SIZE);
        out = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);
        byte[] buf = new byte[BUFFER_SIZE];
        int len;
        while ((len = bis.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        FileUtil.close(bis);
        FileUtil.close(out);
    }
}

3.2、前台

function exp() {
    let sql = "";//請自行設置
    let col = "";//請自行設置
    let title = "";//請自行設置
    $.messager.progress({title : '處理中...'});//easyui控件,可用其他控件
    $.getJSON(
        "${pageContext.request.contextPath}/common/export/exportForCsv.do",
        {
            sql : sql,
            col : col,
            title : title,
separator: "|", t :
new Date().getTime() }, function(data) { $.messager.progress('close');//easyui控件,可用其他控件 if (data.returnCode == 0) { location.href = "${pageContext.request.contextPath}/common/upDownload/download.do?fileName=" + encodeURI(data.result) + "&fileRealName=" + encodeURI("數據.csv") + "&pathType=path.temp.folder"; } else { $.messager.alert('提示', "系統出錯,錯誤信息為:" + data.description + ",請將該信息提供給代維人員尋求幫助"); } } ); }

3、公共類R

package com.inspur.common.entity;


/**
 * 返回數據
 */
public class R<T> {
    /**
     * 返回碼
     * 0 正常,其他異常
     */
    private int returnCode = 0;

    /**
     * 描述
     */
    private String description = "OK";

    /**
     * 結果數據
     */
    private T result;

    public int getReturnCode() {
        return returnCode;
    }
    public String getDescription() {
        return description;
    }
    public T getResult() {
        return result;
    }

    public static R ok() {
        return new R();
    }

    public static <T> R<T> ok(T result) {
        R<T> r = new R<>();
        r.result = result;
        return r;
    }

    public static <T> R<T> error() {
        R<T> r = new R();
        r.returnCode = -1;
        r.description = "未知異常,請聯系管理員";
        return r;
    }

    public static <T> R<T> error(String description) {
        R<T> r = new R();
        r.returnCode = -1;
        r.description = description;
        return r;
    }

    public static <T> R<T> error(int returnCode, String description) {
        R<T> r = new R();
        r.returnCode = returnCode;
        r.description = description;
        return r;
    }

}
R.java

 

注意例子里sql是從台傳到后台,這樣容易引起SQL注入攻擊;嚴謹的做法應該是前台傳參數,后台拼sql,並使用PrepareStatement來執行sql。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM