背景
在專案中發現,檔案下載時有可能出現檔案不完全導致的檔案無法開啟的情況,考慮在後臺響應中加入檔案MD5,與前臺取得檔案後生成的MD5值作一次校驗,來判斷檔案是否正確下載。
問題
此功能的難點是如何在response中加入MD5值。原檔案下載介面中使用的是HttpServletResponse,然後在前臺使用a標籤的點選事件來實現,在開發過程中,首先想到的是在response的headers中加入MD5資訊,然後在前臺想辦法取到,即
...
// response.setHeader("md5",md5sum(bytes));
...
private String md5sum(byte[] bytes){
...
}
在實際開發中發現,這個方法無法正常下載檔案,所以應該通過其他途徑來實現。(這種想法還是很天真的2333)
方案
之前的開發中有用到Blob物件,在前臺中可以使用Blob物件來實現檔案下載,即
...
var url = window.URL.createObjectURL(blob);
var a = document.createElement(`a`);
a.href = url;
a.download = filename;
a.click();
...
那麼肯定可以在後臺介面中加上檔案的bytearray,再在JS中使用var blob = new Blob([bytearray]);
來宣告一個blob物件實現檔案下載,那麼也可以在返回的JSON中加入生成的MD5值了。
程式碼
後臺
import org.apache.commons.io.IOUtils;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
import java.security.MessageDigest;
...
private final char HEX_DIGITS[] = {`0`,`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`,`A`,`B`,`C`,`D`,`E`,`F`};
...
@Override
public Object downloadFile(String burketName, String fileName){
try {
InputStream is = ...;
byte[] byteArray = IOUtils.toByteArray(is);
String fileMD5 = md5sum(byteArray);
Map map = new HashMap();
map.put("file",byteArray);
map.put("md5",fileMD5.toUpperCase());
if(is!=null){
is.close();
}
return map;
}catch (Exception ex){
ex.printStackTrace();
}
return "";
}
private String toHexString(byte[] b) {
StringBuilder sb = new StringBuilder(b.length * 2);
for (int i = 0; i < b.length; i++) {
sb.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]);
sb.append(HEX_DIGITS[b[i] & 0x0f]);
}
return sb.toString();
}
private String md5sum(byte[] byteArray) {
InputStream is = new ByteArrayInputStream(byteArray);
byte[] buffer = new byte[1024];
int numRead = 0;
MessageDigest md5;
try{
md5 = MessageDigest.getInstance("MD5");
while((numRead=is.read(buffer)) > 0) {
md5.update(buffer,0,numRead);
}
return toHexString(md5.digest());
} catch (Exception e) {
System.out.println("parse md5 error");
return null;
}
}
...
前臺
...
// 前臺在生成MD5時使用了SparkMD5外掛
fetch(url, {headers:headers).then(function (res) {
return res.json();
}).then(function (blob) {
var spark = new SparkMD5.ArrayBuffer();
//這個是介面中返回的MD5
var serverMD5=blob.md5;
var byteBuffer=_base64ToArrayBuffer(blob.file);
spark.append(byteBuffer);
var clientMD5 = spark.end().toUpperCase();
// 判斷檔案是否下載完全
if(serverMD5 === clientMD5){
var f = new Blob([byteBuffer]);
var url = window.URL.createObjectURL(f);
var a = document.createElement(`a`);
a.href = url;
a.download =filename;
a.click();
window.URL.revokeObjectURL(f);
}
else{
console.log("檔案下載時出錯,請重新下載");
}
});
function _base64ToArrayBuffer (base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i=0;i<len;i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
總結
以上就是在開發檔案下載校驗時的思路和程式碼分享,開發的關鍵就是在檔案下載介面返回值中加入後臺計算的檔案MD5,主要就是通過後臺介面中返回檔案的bytearray和用作校驗的MD5值,前臺接收到介面返回後生成一個MD5來與介面返回的MD5作校驗,再在前臺中使用返回的bytearray,通過Blob來實現檔案下載。
如果你還有更好的方案,歡迎給我留言,不勝感激。