近期,接到一個新的需求,涉及到文件下載,因為我的服務全是發在內網的,項目又是完全的前后端分離的,前端服務通過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 | 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兼容模式可用。