最近项目较为清闲,研究了下Html5新增的几个重要技术点,如操作文件系统、获取摄像头麦克风、本地图片预览、Ajax上传文件等。灵光一现,我有了一个“墨迹”的想法:通过浏览器获取影音数据并实时存储到本地磁盘上,再接着后台默默上传该文件,达到视频录像伪实时备份的目的。然而经过两三天的琢磨研究,有一问题怎么也未能解决:从UserMedia中如何获得视音频流?(目前能力有限,先留着以后继续研究)。哎,功亏一篑啊。
不过,一事还是值得记录和分享的,那就是大文件断点续传!总体思路:用js获取文件的相关属性如name、size、type、lastModified等,发送给后台用于确定文件唯一性(我用的是它们4个的MD5散列值,最好是JS给出整个文件的MD5值);上传之前先check,看看已经上传多少字节了;后续上传时只上传未曾上传过的字节。废话不多说,上代码!注释很清晰哦!
界面效果
Html代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>大文件断点续传</title>
<link rel="stylesheet" href="../public/css/bootstrap.css">
</head>
<body>
<div class="container-fluid">
<input type="file" id="file" name="file">
<progress id="progress"></progress>
<button οnclick="upload()">上传</button>
<button οnclick="abort()">暂停</button>
</div>
</body>
<script>
var xhr = new XMLHttpRequest();//上传请求
xhr.onreadystatechange = function() {//上传状态监听
console.log(xhr.readyState + "-" + xhr.status + "-" + xhr.responseText);
if (xhr.readyState == 4 && xhr.status == 200) {
alert("上传成功");
}
};
var proEle = document.getElementById("progress");
var loaded = 0;//记录服务器上已上传成功的字节数
xhr.upload.onprogress = function(e) {//上传进度监听
proEle.value = loaded + e.loaded;
};
//暂停方法
function abort() {
xhr.abort();
}
/**上传方法。
* 上传之前先check,查询已上传字节数。
* 若字节数为“0”表示文件未上传过,若字节数等于待上传文件大小表示文件曾经上传成功过,无需再次上传。
*/
function upload() {
var fileEle = document.getElementById("file");
var file = fileEle.files[0];
var param = "name=" + file.name + "&size=" + file.size + "&type="
+ file.type + "&lastModified=" + file.lastModified;//后台通过name、size、type、lastModified四个属性来确定文件唯一性。
/**
* 进度条初始化
*/
proEle.value = 0;
proEle.max = file.size;
var xhrCheck = new XMLHttpRequest();//检查请求
xhrCheck.onreadystatechange = function() {//检查状态监听,成功后执行发送上传请求
if (xhrCheck.status == 200 && xhrCheck.readyState == 4) {
loaded = parseInt(xhrCheck.responseText);
proEle.value = loaded;
if (loaded < file.size) {
xhr.open("POST", "/web/api/device/upload?" + param, true);
xhr.send(file.slice(loaded, file.size, file.type));//通过File.slice方法获得未上传过的数据信息,再上传(重点)
} else {
alert('文件已存在');
}
}
};
xhrCheck.open("GET", "/web/api/device/check?" + param, true);
xhrCheck.send();//发送检查请求
}
</script>
</html>
Java代码
@RequestMapping(value = "/check", method = RequestMethod.GET)
@ResponseBody
/**
* 上传检查。
* 上传之前先check,查询已上传字节数。文件不存在就返回0
* @param request
* @return
*/
public String check(HttpServletRequest request) {
String name = request.getParameter("name");
String size = request.getParameter("size");
String type = request.getParameter("type");
String lastModified = request.getParameter("lastModified");
String fileID = new SimpleHash("MD5", name, ByteSource.Util.bytes(size
+ type + lastModified), 2).toHex();// 通过name、size、type、lastModified四个属性来确定文件唯一性——MD5散列值。
String fileName = fileID + "-" + name;
File file = new File("D://" + fileName);
if (!file.exists()) {
return "0";
} else {
return file.length() + "";
}
}
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
/**
* 上传方法
* 主体就是将InputStream流中的数据写入目标文件中,注意是append
* @param request
* @return
*/
public String upload(HttpServletRequest request) {
String name = request.getParameter("name");
String size = request.getParameter("size");
String type = request.getParameter("type");
String lastModified = request.getParameter("lastModified");
String fileID = new SimpleHash("MD5", name, ByteSource.Util.bytes(size
+ type + lastModified), 2).toHex();// 通过name、size、type、lastModified四个属性来确定文件唯一性——MD5散列值。
String fileName = fileID + "-" + name;
ServletInputStream is = null;
FileOutputStream os = null;
File file = null;
try {
is = request.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] b = new byte[1024 * 1024];
file = new File("D://" + fileName);
if (!file.exists()) {
file.createNewFile();
}
os = new FileOutputStream(file, true);// append
int n = 0;
while ((n = bis.read(b)) > 0) {
os.write(b, 0, n);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return "SUCCESS";
}
写在最后
断点续传老的做法是将大文件分割成N个的小文件再已Form表单形式上传,进而后台进行类似文件拼接的相关操作。效率还可以,但颗粒度就没有上面新的方法细了,哈哈。上面的新方法颗粒度直接精确到字节了。科技在发展,社会在进步,码农也能少费点脑子敲代码,留着打DOTA了!
Best Wishes For You!