前后端分離項目文件下載解決方案


近期,接到一個新的需求,涉及到文件下載,因為我的服務全是發在內網的,項目又是完全的前后端分離的,前端服務通過nginx轉發到外網,而且我的文件是傳到內網文件服務器的,所以如何下載文件成為這個問題的難點。因為之前做過圖片base64傳輸的需求,所以我首先想到的就是同時base64傳輸,然后前端將base64轉成文件下載,查詢了很多資料和博客,踩了很多坑,然后就有了這篇文章。

原理

先說思路,然后我們再貼代碼,具體流程如下圖:

用戶發出文件訪問請求時,nginx將請求轉發至內網前端服務,然后前端服務訪問后端接口,后端接口根據用戶請求的文件名,請求文件服務器,並將文件轉換成base64字符串返回給前端,前端將base64還原成文件,然后模擬下載。

代碼實現

原理很簡單,接下來我們看下如何實現:

前端代碼

按照流程,我們先看前端代碼,核心代碼還是js:

 function clickBt() {
        console.log("網頁加載完畢");
        $.ajax({
            type:"POST",
            url: "file/download",
            data: {fileName:"myfile.pdf"},
            success: function (data) {
             var downLoad =  $('#download');
                console.log("base64:" , data);
                console.log("msg:",data.msg)
                console.log("success:",data.success)
                if (data.success) {
                    var blob = b64toBlob(data.obj, "application/pdf");
                    console.log(blob);
                    var url = window.URL.createObjectURL(blob);
                    console.log('url:', url);
                    downLoad.attr("href", url);
                    dataURLtoFile(url, "test.pdf");
                } else {
                    alert("獲取文件失敗:", data.msg);
                }
            }
        });
    }
   /***
     * 下載文件
     * @param blobUrl:blob文件鏈接,例如:blob:http://localhost:8081/283340bd-f3c5-49d9-a3ac-b9e48ea08228
     * @param filename: 保存的文件名
     * */
    function dataURLtoFile(blobUrl, filename) {//將base64轉換為文件
            var eleLink = document.createElement('a')
            eleLink.download = filename
            eleLink.style.display = 'none'
            eleLink.href = blobUrl
            // 觸發點擊
            document.body.appendChild(eleLink)
            eleLink.click()
            // 然后移除
            document.body.removeChild(eleLink);
    }

    /**
     * base64轉Blob
     * @param b64Data:base64字符串,不含類型,如:JVBERi0xLjQKJeLjz9MKNCAwIG9iago8P……
     * @param contentType:類型,比如:"application/pdf"
     * @param sliceSize
     * @returns {Blob}
     */
    function b64toBlob(b64Data, contentType, sliceSize) {
        contentType = contentType || '';
        sliceSize = sliceSize || 512;

        var byteCharacters = window.atob(b64Data);
        console.log("byteCharacters:",byteCharacters)
        var byteArrays = [];

        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            var slice = byteCharacters.slice(offset, offset + sliceSize);

            var byteNumbers = new Array(slice.length);
            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }

            var byteArray = new Uint8Array(byteNumbers);

            byteArrays.push(byteArray);
        }

        var blob = new Blob(byteArrays, { type: contentType });
        console.log("blob", blob)
        return blob;
    }

data類型

這里是文件類型:

序號 文件類型 data類型
1 txt data:text/plain;base64,
2 doc data:application/msword;base64,
3 docx data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,
4 xls data:application/vnd.ms-excel;base64,
5 xlsx data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,
6 pdf data:application/pdf;base64,
7 pptx data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,
8 ppt data:application/vnd.ms-powerpoint;base64,

如果是圖片的額話:

序號 圖片類型 data類型
1 png data:image/png;base64,
2 jpg data:image/jpeg;base64,
3 gif data:image/gif;base64,
4 svg data:image/svg+xml;base64,
5 ico data:image/x-icon;base64,
6 bmp data:image/bmp;base64,

后端代碼

controller
@Api(value = "file", description = "文件管理")
@Controller
@RequestMapping("/file")
public class FileManagerController {

    @Value("${my.fileServer.url}") // http://127.0.0.1/file-Server/
    private String remotUrl;

    @RequestMapping("/download")
    @ResponseBody
    public AjaxJson downLoad(String fileName) {
        if(StringUtils.isEmpty(fileName)) {
            return new AjaxJson(new Exception("文件名不能為空"));
        }
        String base64 = Base64Util.remotePdfToBase64(remotUrl + fileName);
        AjaxJson result = new AjaxJson("請求成功", true);
        result.setObj(base64);
        return result;
    }

}

base64工具類:

public class Base64Util {
	private transient static Logger log = LoggerFactory.getLogger(Base64Util.class);

    /**
     * <p>將base64字符解碼保存文件</p>
     * @param base64Code
     * @param targetPath
     * @throws Exception
     */
    public static void decoderBase64File(String base64Code,String targetPath) throws Exception {
        byte[] buffer = new BASE64Decoder().decodeBuffer(base64Code);
        FileOutputStream out = new FileOutputStream(targetPath);
        out.write(buffer);
        out.close();
    }

    /**
     *  將base64編碼轉換成PDF
     *  @param base64String
     *  1.使用BASE64Decoder對編碼的字符串解碼成字節數組
     *  2.使用底層輸入流ByteArrayInputStream對象從字節數組中獲取數據;
     *  3.建立從底層輸入流中讀取數據的BufferedInputStream緩沖輸出流對象;
     *  4.使用BufferedOutputStream和FileOutputSteam輸出數據到指定的文件中
     */
    public static void base64StringToPDF(String base64String, File file){
        BASE64Decoder decoder = new BASE64Decoder();
        BufferedInputStream bin = null;
        FileOutputStream fout = null;
        BufferedOutputStream bout = null;
        try {
            //將base64編碼的字符串解碼成字節數組
            byte[] bytes = decoder.decodeBuffer(base64String);
            //創建一個將bytes作為其緩沖區的ByteArrayInputStream對象
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            //創建從底層輸入流中讀取數據的緩沖輸入流對象
            bin = new BufferedInputStream(bais);
            //創建到指定文件的輸出流
            fout  = new FileOutputStream(file);
            //為文件輸出流對接緩沖輸出流對象
            bout = new BufferedOutputStream(fout);

            byte[] buffers = new byte[1024];
            int len = bin.read(buffers);
            while(len != -1){
                bout.write(buffers, 0, len);
                len = bin.read(buffers);
            }
            //刷新此輸出流並強制寫出所有緩沖的輸出字節,必須這行代碼,否則有可能有問題
            bout.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bout.close();
                fout.close();
                bin.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * PDF轉換為Base64編碼
     * @param file
     * @return
     */
    public static String remotePdfToBase64(String file) {
        BASE64Encoder encoder = new BASE64Encoder();
        InputStream fin =null;
        BufferedInputStream bin =null;
        ByteArrayOutputStream baos = null;
        BufferedOutputStream bout =null;
        try {
            URL url = new URL(file);
            fin = url.openStream();
            bin = new BufferedInputStream(fin);
            baos = new ByteArrayOutputStream();
            bout = new BufferedOutputStream(baos);
            byte[] buffer = new byte[1024];
            int len = bin.read(buffer);
            while(len != -1){
                bout.write(buffer, 0, len);
                len = bin.read(buffer);
            }
            //刷新此輸出流並強制寫出所有緩沖的輸出字節
            bout.flush();
            byte[] bytes = baos.toByteArray();
            return encoder.encodeBuffer(bytes).trim();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fin != null)
                    fin.close();
                if (bin != null)
                    bin.close();
                if (baos != null)
                    baos.close();
                if (bout != null)
                    bout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    public static void main(String[] args) {
        try {
            String base64Code =remotePdfToBase64("http://127.0.0.1/file-Server/myfile.pdf");
            log.info(base64Code);
            decoderBase64File(base64Code, "D://z2.pdf");
            base64StringToPDF(base64Code, new File("D://z3.pdf"));
            //toFile(base64Code, "D://z.pdf");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	
}

總結

以上代碼即可實現前后端分離項目得文件下載,如果需要兼容IE,需要考慮IE下兼容base64和blob文件格式,因為后來需要pdf文件預覽,也就沒有深入研究。隨后我會發出pdf文件預覽的相關解決方案,360兼容模式可用。


免責聲明!

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



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