近期,接到一个新的需求,涉及到文件下载,因为我的服务全是发在内网的,项目又是完全的前后端分离的,前端服务通过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兼容模式可用。