(1)精简“带进度条文件上传组件”的设计与实现
XMLHttpRequest第二版为我们提供了便利的progress事件,通过为xhr.upload.onprogress指定处理函数,可以快速制作进度条,JQuery插件参考代码如下:
(function($) {
$.fn.uploader = function(userOptions) {
var options = {
id : "uploader",
url : "uploadAction.action?fileName=",//处理上传文件的服务器url
width : 540,//整个控件div层的宽度
height : 180
//整个控件div层的高度
};
$.extend(options, userOptions);
var createUploader = function(where) {
var uploader = $("<div id=‘" + options.id + "‘></div>").css({
"display" : "block",
"position" : "relative",
"width" : options.width,
"height" : options.height,
"overflow" : "hidden"
});
//创建上传文件控件
var fileUI = $("<input id=‘file‘ type=‘file‘>").addClass("");
uploader.append(fileUI);
//创建上传文件按钮,并实现点击事件
var uploadBtn = $("<input id=‘uploadBtn‘ type=‘button‘ value=‘上传‘>")
.click(
function() {
var fileUI = document.getElementById("file");
if (fileUI.files.length > 0) {
var file = fileUI.files[0];//取出文件blob
var xhr = new XMLHttpRequest();
//指定处理上传进度的函数
xhr.upload.onprogress = updateProgress;
//处理上传
xhr.open("POST", options.url
+ encodeURIComponent(file.name),
true);
xhr.overrideMimeType("application/octet-stream");
xhr.send(file);
//处理响应
xhr.onreadystatechange = function() {
if (xhr.readyState == 4
&& xhr.status == 200) {
console.log(xhr.responseText);
} else {
console.log(xhr.statusText);
}
};
console.log("upload complete");
} else {
alert("请选择上传文件!");
}
});
uploader.append(uploadBtn);
var progressBar = $("<div></div>").addClass("progressBar");
var progress = $("<p></p>");
progressBar.append(progress);
uploader.append(progressBar);
var updateProgress = function(event) {
if (event.lengthComputable) {
var percentage = Math.round((event.loaded * 100)
/ event.total);
console.log("percentage:" + percentage);
if (percentage < 100) {
progress.css("width", (percentage * 2) + "px");
progress.text(percentage + "%");
} else {
progress.css("width", "200px");
progress.text("100%");
}
}
};
where.append(uploader);
}
createUploader(this);
}
}(jQuery));
服务器端可以利用struts2对上传请求进行处理,将其保存到文件系统,参考代码如下:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class FileUploadAction extends ActionSupport {
private static String SEPARATOR = File.separator;
public InputStream inputStream = null;
public InputStream getIn() {
return inputStream;
}
@Override
public String execute() {
BufferedInputStream fileIn = null;
BufferedOutputStream fileOut = null;
String fileName = ServletActionContext.getRequest().getParameter("fileName");
String path = ServletActionContext.getRequest().getSession().getServletContext().getRealPath("/");
try{
fileIn = new BufferedInputStream(
ServletActionContext.getRequest().getInputStream());
File file = new File(path + SEPARATOR + fileName);
byte[] buf = new byte[1024];
fileOut = new BufferedOutputStream(
new FileOutputStream(file, true));
int length = 0;
while ((length = fileIn.read(buf)) != -1) {
fileOut.write(buf, 0, length);
}
} catch(IOException e){
e.printStackTrace();
return ERROR;
} finally {
if(fileIn != null){
try {
fileIn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(fileOut != null){
try {
fileOut.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
try {
inputStream = new ByteArrayInputStream("upload finished".getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
}
Struts2配置参考:
<package name="myjson" namespace="" extends="json-default">
<action name="uploadAction" class="FileUploadAction">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
</package>
进度条可以简单的利用div进行实现,参考样式如下:
.progressBar {
width: 200px;
height: 20px;
border: 1px solid #000;
-moz-border-radius: 8px;
margin-bottom: 10px;
}
.progressBar p {
margin: 0px;
width: 0px;
height: 20px;
-moz-border-radius: 8px;
background-color: #00FF00;
}
使用插件实现文件上传功能:
(2)改进一下,如何实现暂停和重启
暂停和重启功能,前端需要依赖Blob对象(事实上一个File原型链的上一层就是Blob)的分割(slice)方法,典型的分割函数如下:
var sliceBlob = function(blob, start, end, type) {
var force_saveable_type = ‘application/octet-stream‘;
if (type && type == force_saveable_type) {
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
blob = slice.call(blob, start, end, type);
}
return blob;
}
通过分割函数我们就可以完成对文件进行分块上传并且控制是否暂停和进度条的显示逻辑,JQuery插件参考代码如下:
(function($){
$.fn.uploader = function(userOptions){
var options = {
id : "uploader",
url: "uploadAction.action?fileName=",
width : 540,//整个控件div层的宽度
height : 180//整个控件div层的高度
};
$.extend(options, userOptions);
var sliceBlob = function(blob, start, end, type) {
var force_saveable_type = ‘application/octet-stream‘;
if (type && type == force_saveable_type) {
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
blob = slice.call(blob, start, end, type);
}
return blob;
}
var sender = {
pause : false,
send : function(uploadXhr){
uploadXhr.onreadystatechange = function(){
if ( uploadXhr.readyState == 4 && uploadXhr.status == 200 ) {
sender.updateProgress(sender.file.size, sender.end);
sender.send(uploadXhr);//如果HTTP响应状态正常,继续发送文件
console.log( uploadXhr.responseText );
} else if(uploadXhr.status == 500){
console.log( uploadXhr.statusText );
} else {
console.log( uploadXhr.statusText );
}
};
if(this.start < this.file.size && !this.pause){//判断是否为文件末尾或用户暂停操作
uploadXhr.open("POST",
this.url + encodeURIComponent(this.file.name) + "&uuid=" + this.uuid + "&status=" + this.finished,
true);
uploadXhr.overrideMimeType("application/octet-stream");
uploadXhr.send(sliceBlob(this.file, this.start, this.end, "application/octet-stream"));//使用sliceBlob对文件进行分割上传
this.start = this.end;
if((this.end + 1000 * 1000) > this.file.size){//判断是否分割到文件末尾
this.end = this.file.size;//最后分割到文件末尾
this.finished = true;
$("#uploadBtn").attr("disabled","");
} else {
this.end += 1000 * 1000;//按固定大小继续连续分割文件块
}
return;
} else {
return;
}
},
updateProgress : function (total, loaded) {
var progressBar = $(".progressBar p:first");
var percentage = Math.round(( loaded * 100) / total);
console.log("percentage:" + percentage);
if (percentage < 100)
{
progressBar.css("width",(percentage * 2) + "px");
progressBar.text(percentage + "%");
} else {
progressBar.css("width", "200px");
progressBar.text("100%");
}
},
uuid : "",
file : null,
start : 0,
end : 1000 * 1000,
url : options.url,
finished : false
};
var createUploader = function(where) {
var uploader = $("<div id=‘" + options.id + "‘></div>")
.css({
"display": "block",
"position": "relative",
"width": options.width,
"height": options.height,
"overflow": "hidden"
});
var fileUI = $("<input id=‘file‘ type=‘file‘>").addClass("");
uploader.append(fileUI);
/**
* 创建上传按钮
*/
var uploadBtn = $("<input id=‘uploadBtn‘ type=‘button‘ value=‘上传‘>").click(
function () {
var fileUI = document.getElementById("file");
if(fileUI.files.length > 0){
uploadBtn.attr("disabled","disabled");
var file = fileUI.files[0];
sender.file = file;
$.get("getUuidAction.action?fileName=" + encodeURIComponent(file.name), function(data, status){//上传生成uuid
if(status == "success") {
var uuid = data;
console.log(data);
var uploadXhr = new XMLHttpRequest();
sender.uuid = uuid;
sender.start = 0;
sender.end = 1000 * 1000;
sender.url = options.url;
sender.finished = false;
sender.send(uploadXhr);
} else {
console.log(status);
}
});
console.log("upload complete");
} else {
alert("请选择上传文件!");
}
}
);
uploader.append(uploadBtn);
/**
* 创建暂停按钮
*/
var pauseBtn = $("<input id=‘pauseBtn‘ type=‘button‘ value=‘暂停‘>").click(function(){
if(pauseBtn.attr("value") == "暂停") {
sender.pause = true;
pauseBtn.attr("value","启动");
} else {
sender.pause = false;
var uploadXhr = new XMLHttpRequest();
console.log(sender.pause);
sender.send(uploadXhr);
pauseBtn.attr("value","暂停");
}
});
uploader.append(pauseBtn);
var progressBar = $("<div></div>").addClass("progressBar");
var progress = $("<p></p>");
progressBar.append(progress);
uploader.append(progressBar);
where.append(uploader);
}
//创建整个控件
createUploader(this);
}
}(jQuery));
后台构建处理逻辑,需要通过简单地建立FileUploader对象池来保持上传会话的一致性,不会因为暂停和重启操作破坏文件完整性,为每一个上传文件指定UUID来确保唯一性,对象池实际是一个ConcurrentHashMap类构造的键值对象,参考代码如下:
public class PauseableFileUploaderMap {
private static int MAX_FILE_NUM = 100;
private static ConcurrentHashMap<String, PauseableFileUploader> objMap =
new ConcurrentHashMap<String, PauseableFileUploader>(MAX_FILE_NUM);
public void put(String uuid, PauseableFileUploader uploader){
objMap.put(uuid, uploader);
}
public void remove(String uuid){
objMap.remove(uuid);
}
public PauseableFileUploader get(String uuid){
return objMap.get(uuid);
}
}
在控制时,采用有限实例模式方法来实现对FileUploader对象的使用,参考代码如下:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class PauseableFileUploader {
private static int MAX_BUFFER = 1024;
private PauseableFileUploader(){};
private static PauseableFileUploaderMap map = new PauseableFileUploaderMap();
/**
* 有限实例模式,实例按uuid数量建立
* @param uuid
* @return uuid对应的PauseableFileUploader对象
*/
public static PauseableFileUploader getInstance(String uuid){
PauseableFileUploader uploader = null;
//Lazy处理
if((uploader = map.get(uuid)) != null){//存在uuid对应的实例则直接返回该实例
return uploader;
} else {//没有,则新建实例
uploader = new PauseableFileUploader();
map.put(uuid, uploader);
return uploader;
}
}
/**
* 销毁uuid对应的PauseableFileUploader对象
* @param uuid
*/
public void destoryInstance(String uuid){
map.remove(uuid);
}
/**
* 处理上传
* @param file 待保存的文件对象
* @param blob 文件上传分块流
*/
public void upload(File file, InputStream blob){
byte[] buf = new byte[MAX_BUFFER];
BufferedInputStream fileIn = null;
BufferedOutputStream fileOut = null;
try {
fileIn = new BufferedInputStream(blob);
fileOut = new BufferedOutputStream(new FileOutputStream(file, true));
int length = 0;
while ((length = fileIn.read(buf)) != -1) {
fileOut.write(buf, 0, length);
}
fileOut.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileIn != null) {
try {
fileIn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (fileOut != null) {
try {
fileOut.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
服务器端可以仍然利用struts2对上传请求进行处理,参考代码如下:
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class PauseableFileUploadAction extends ActionSupport{
private static String SEPARATOR = File.separator;
public InputStream inputStream = null;
public InputStream getInputStream() {
return inputStream;
}
/**
* 生成UUID,并根据UUID新建目录和文件
* @return struts2状态
*/
public String getUuid(){
try {
//Linux OS:
//String fileName = new String(ServletActionContext.getRequest().getParameter("fileName").getBytes("ISO8859-1"));
String fileName = ServletActionContext.getRequest().getParameter("fileName");
String path = ServletActionContext.getRequest().getSession().getServletContext().getRealPath("/");
//生成UUID并创建上传目录
UUID uuid = UUID.randomUUID();
File dir = new File(path + uuid.toString());
if(dir.exists()){
dir.delete();
}
dir.mkdir();
//创建上传文件
File file = new File(path + uuid.toString() + SEPARATOR + fileName);
file.createNewFile();
//Response返回UUID
inputStream = new ByteArrayInputStream(uuid.toString().getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ERROR;
} finally {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
/**
* 处理文件上传
* @return struts2状态
*/
public String upload(){
try {
String fileName = new String(ServletActionContext.getRequest().getParameter("fileName"));
String uuid = ServletActionContext.getRequest().getParameter("uuid");
String path = ServletActionContext.getRequest().getSession().getServletContext().getRealPath("/");
File file = new File(path + uuid + SEPARATOR + fileName);
//获取上传UUID对应的PauseableFileUploader对象
PauseableFileUploader pfu = PauseableFileUploader.getInstance(uuid);
//处理分块上传
synchronized(pfu){
pfu.upload(file, ServletActionContext.getRequest().getInputStream());
inputStream = new ByteArrayInputStream(uuid.toString().getBytes());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ERROR;
} finally{
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
}
Struts2配置参考:
<package name="myjson" namespace="" extends="json-default">
<action name="getUuidAction" class="PauseableFileUploadAction" method="getUuid">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
<action name="uploadAction" class="PauseableFileUploadAction" method="upload">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
</package>
使用插件实现文件上传功能:
(3)扩展思路,如何实现并行异步上传
我们为什么不一口气将所有的文件分块发送给服务器处理呢?
实现分块并行异步上传,需要处理一些额外的细节:
- 由于浏览器端JavaScript的处理是单线程的,多个XMLHttpRequest对象一起向服务器端发送请求会造成“假死”现象,因此,需要调节发送间隔;
- 服务器端需要处理分块发送顺序问题,来保证文件拼接顺序的正确;
- 暂停和进度条的控制逻辑需要交予服务器进行处理。
和上节一样仍需要一个FileUploader对象池来保持上传会话的一致性,不同的是FileUploader对象中的上传方法只是一个伪方法,将所有分块按顺序号保存到一个Map中,以待真正的处理方法将其按顺序保存到文件系统,参考代码如下:
public class MultiFileUploader {
private static MultiFileUploaderMap map = new MultiFileUploaderMap();
private FileInputStreamMap inputStreamMap = new FileInputStreamMap();
private MultiFileUploadHandleThread thread = new MultiFileUploadHandleThread();
private int fileUploadProgress = 0;
private MultiFileUploader(){};
/*
* 有限实例模式,实例按uuid数量建立
*/
public static MultiFileUploader getInstance(String uuid){
MultiFileUploader uploader = null;
if((uploader = map.get(uuid)) != null){//存在uuid对应的实例则直接返回该实例
return uploader;
} else {//没有,则新建实例
uploader = new MultiFileUploader();
map.put(uuid, uploader);
return uploader;
}
}
public void destoryInstance(String uuid){
map.remove(uuid);
}
/**
* 伪上传处理
* @param seq 分块顺序号
* @param blob 上传的文件分块
*/
public void upload(int seq, byte[] blob){
//伪上传处理,将文件分块blob放入内存中,真实的处理由UploadHandlerThread完成
inputStreamMap.put(seq, blob);
}
public FileInputStreamMap getInputStreamMap() {
return inputStreamMap;
}
public MultiFileUploadHandleThread getHandleThread() {
return thread;
}
public int getFileUploadProgress() {
return fileUploadProgress;
}
public void setFileUploadProgress(int fileUploadProgress) {
this.fileUploadProgress = fileUploadProgress;
}
}
真正的文件保存方法是在创建文件UUID时,启动一个线程进行定时轮询分块Map来实现的,该线程还要负责处理对暂停和进度条的控制,参考代码如下:
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class MultiFileUploadHandleThread extends Thread {
private volatile Thread linker = null;
private static int MAX_BUFFER = 1024;
private static int SLEEP_TIME = 10;
private Object lock = new Object();
private File file = null;
private String uuid = null;
private int num;
private boolean pause = false;
/**
* 初始化线程
* @param file 待保存的文件对象
* @param uuid 对应的uuid
* @param num 一共需处理保存的文件分块总数
*/
public void init(File file, String uuid, int num){
this.file = file;
this.uuid = uuid;
this.num = num;
}
@Override
public void start() {
linker = new Thread(this);
linker.start();
}
@Override
public void run() {
Thread thread = Thread.currentThread();
MultiFileUploader mfu = MultiFileUploader.getInstance(uuid);
int seq = 1;
while (seq != num + 1 && thread == linker) {
// 轮询处理
synchronized(lock){//利用锁来处理暂停
if( pause ){
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;//使用interrupt停止该线程,跳出循环
}
}
}
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;//使用interrupt停止该线程,跳出循环
}
if (mfu.getInputStreamMap().get(seq) != null) {
try {
upload(file, mfu.getInputStreamMap().get(seq));
mfu.setFileUploadProgress(progress(seq));
} catch (IOException e) {
//处理IO异常,销毁对象,并结束线程
mfu.destoryInstance(uuid);
e.printStackTrace();
break;
}
//保存分块后,清除引用
mfu.getInputStreamMap().remove(seq);
seq++;//顺序处理下一个分块
}
}
//需要客户端判断
//mfu.destoryInstance(uuid);
}
/**
* 终止线程
*/
public void stopThread() {
Thread tmpThread = linker;
linker = null;
if (tmpThread != null) {
tmpThread.interrupt();
}
}
/**
* 处理文件上传
* @param file 待保存的文件对象
* @param blob 文件分块
* @throws IOException
*/
private void upload(File file, byte[] blob) throws IOException{
BufferedOutputStream fileOut = null;
ByteArrayInputStream fileIn = null;
byte[] buf = new byte[MAX_BUFFER];
try {
fileIn = new ByteArrayInputStream(blob);
fileOut = new BufferedOutputStream(new FileOutputStream(file, true));
int length = 0;
while ((length = fileIn.read(buf)) != -1) {
fileOut.write(buf, 0, length);
}
fileOut.flush();
} finally {
if (fileIn != null) {
fileIn.close();
}
if (fileOut != null) {
fileOut.close();
}
}
}
/**
* 计算上传进度
* @param seq 上传分块的顺序号
* @return
*/
private int progress(int seq){
return (int)((double)seq / this.num * 100);
}
/**
* 暂停线程
*/
public void pause() {
this.pause = true;
}
/**
* 重启线程
*/
public void restart(){
this.pause = false;
synchronized(lock){
lock.notify();
}
}
}
服务器端可以仍然利用struts2对上传请求进行处理,参考代码如下:
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.UUID;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class MultiFileUploadAction extends ActionSupport{
private static String SEPARATOR = File.separator;
public InputStream inputStream = null;
public InputStream getInputStream() {
return inputStream;
}
/**
* 生成UUID,并根据UUID新建目录和文件
* @return struts2状态
*/
public String getUuid(){
try {
//Linux OS:
//String fileName = new String(ServletActionContext.getRequest().getParameter("fileName").getBytes("ISO8859-1"));
String fileName = ServletActionContext.getRequest().getParameter("fileName");
String path = ServletActionContext.getRequest().getSession().getServletContext().getRealPath("/");
int num = Double.valueOf(ServletActionContext.getRequest().getParameter("num")).intValue();
UUID uuid = UUID.randomUUID();
File dir = new File(path + uuid.toString());
if(dir.exists()){
dir.delete();
}
dir.mkdir();
File file = new File(path + uuid.toString() + SEPARATOR + fileName);
file.createNewFile();
MultiFileUploader mfu = MultiFileUploader.getInstance(uuid.toString());
MultiFileUploadHandleThread thread = mfu.getHandleThread();
thread.init(file, uuid.toString(), num);
thread.start();
inputStream = new ByteArrayInputStream(uuid.toString().getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ERROR;
} finally {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
/**
* 处理文件上传
* @return struts2状态
*/
public String upload(){
try {
String uuid = ServletActionContext.getRequest().getParameter("uuid");
int seq = Integer.valueOf(ServletActionContext.getRequest().getParameter("seq"));//该分块顺序号
MultiFileUploader mfu = MultiFileUploader.getInstance(uuid);
BufferedInputStream in = null;
ByteArrayOutputStream out = null;
ByteBuffer bb = ByteBuffer.allocateDirect(1000 * 1000);
byte[] buf = new byte[1024];
try {
synchronized(mfu){
in = new BufferedInputStream(ServletActionContext.getRequest().getInputStream());
//也可以使用ByteArrayOutputStream处理文件分块输入流,但使用ByteBuffer可以在JVM外部申请额外内存
//out = new ByteArrayOutputStream(1024);
int length = 0;
bb.clear();
while ((length = in.read(buf)) != -1) {
//out.write(buf, 0, length);
bb.put(buf, 0, length);
}
//byte[] content = out.toByteArray();
byte[] content = new byte[bb.position()];
bb.flip();
bb.get(content);
mfu.upload(seq, content);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
inputStream = new ByteArrayInputStream(uuid.toString().getBytes());
} finally{
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
/**
* 处理暂停
* @return struts2状态
*/
public String pause(){
String uuid = ServletActionContext.getRequest().getParameter("uuid");
MultiFileUploader mfu = MultiFileUploader.getInstance(uuid);
mfu.getHandleThread().pause();
try{
inputStream = new ByteArrayInputStream(uuid.toString().getBytes());
}finally{
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
/**
* 处理重启上传
* @return struts2状态
*/
public String restart(){
String uuid = ServletActionContext.getRequest().getParameter("uuid");
MultiFileUploader mfu = MultiFileUploader.getInstance(uuid);
mfu.getHandleThread().restart();
try{
inputStream = new ByteArrayInputStream(uuid.toString().getBytes());
}finally{
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
/**
* 获取服务器端保存进度
* @return struts2状态
*/
public String getProgress(){
String uuid = ServletActionContext.getRequest().getParameter("uuid");
MultiFileUploader mfu = MultiFileUploader.getInstance(uuid);
int progressStatus = mfu.getFileUploadProgress();
try{
inputStream = new ByteArrayInputStream(String.valueOf(progressStatus).getBytes());
}finally{
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return SUCCESS;
}
}
Struts2配置参考:
<package name="myjson" namespace="" extends="json-default">
<action name="getUuidAction" class="MultiFileUploadAction" method="getUuid">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
<action name="uploadAction" class="MultiFileUploadAction" method="upload">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
<action name="getProgressAction" class="MultiFileUploadAction" method="getProgress">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
<action name="pauseAction" class="MultiFileUploadAction" method="pause">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
<action name="restartAction" class="MultiFileUploadAction" method="restart">
<result type="stream">
<param name="contentType">
text/html
</param>
<param name="inputName">
inputStream
</param>
</result>
</action>
</package>
最后,前端JQuery插件需要根据后端的变化加以变化,参考代码如下:
(function($) {
$.fn.uploader = function(userOptions) {
var options = {
id : "uploader",
url : "uploadAction.action?fileName=",
width : 540,//整个控件div层的宽度
height : 180
//整个控件div层的高度
};
$.extend(options, userOptions);
var sliceBlob = function(blob, start, end, type) {
var force_saveable_type = ‘application/octet-stream‘;
if (type && type == force_saveable_type) {
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
blob = slice.call(blob, start, end, type);
}
return blob;
};
var sender = {
pause : false,
send : function() {
var num = this.file.size / this.end + 1;
var progressBar = $(".progressBar p:first");
while (this.start < this.file.size && !this.pause) {//并发处理文件分块上传
var uploadXhr = new XMLHttpRequest();
var size = this.end - this.start;
var progress = 1;
uploadXhr.open("POST", this.url
+ encodeURIComponent(this.file.name) + "&uuid="
+ this.uuid + "&end=" + false + "&seq=" + this.seq
+ "&size=" + size, true);
uploadXhr.overrideMimeType("application/octet-stream");
uploadXhr.onreadystatechange = function() {
if (uploadXhr.readyState == 4
&& uploadXhr.status == 200) {
console.log(uploadXhr.responseText);
} else if (uploadXhr.status == 500) {
console.log(uploadXhr.statusText);
$("#uploadBtn").attr("disabled", "");
} else {
console.log(uploadXhr.statusText);
}
};
//模拟前端异步,以保证可以有效向服务器发送暂停/重启命令,以及接收上传进度信息
//注:如果不需要控制暂停和重启可以去除该模拟过程
setTimeout((function(file, start, end, uploadXhr) {
return function() {
uploadXhr.send(sliceBlob(file, start, end,
"application/octet-stream"));
}
})(this.file, this.start, this.end, uploadXhr),
50 * this.seq);
this.start = this.end;
this.seq++;
if ((this.end + 1000 * 1000) > this.file.size) {
this.end = this.file.size;
} else {
this.end += 1000 * 1000;
}
}
},
updateProgress : function(num) { //从服务器端获取进度条数据来更新进度条
var progressBar = $(".progressBar p:first");
var id = setInterval(function() {
$.get("getProgressAction.action?uuid=" + sender.uuid,
function(data, status) {
if (status == "success" && !sender.pause) {
var percentage = Math.floor(data);
if (percentage < 100) {
progressBar.css("width",
(percentage * 2) + "px");
progressBar.text(percentage + "%");
} else {
progressBar.css("width", "200px");
progressBar.text("100%");
clearInterval(id);
$("#uploadBtn").attr("disabled", "");
}
} else {
console.log(status);
}
});
}, 200);
return id;
},
uuid : "",
file : null,
seq : 1,
start : 0,
end : 1000 * 1000,
url : options.url,
finished : false
};
var createUploader = function(where) {
var uploader = $("<div id=‘" + options.id + "‘></div>").css({
"display" : "block",
"position" : "relative",
"width" : options.width,
"height" : options.height,
"overflow" : "hidden"
});
var fileUI = $("<input id=‘file‘ type=‘file‘>").addClass("");
uploader.append(fileUI);
/**
* 创建上传按钮
*/
var uploadBtn = $("<input id=‘uploadBtn‘ type=‘button‘ value=‘上传‘>")
.click(
function() {
var fileUI = document.getElementById("file");
if (fileUI.files.length > 0) {
uploadBtn.attr("disabled", "disabled");
var file = fileUI.files[0];
sender.file = file;
var num = file.size / (1000 * 1000) + 1;
$.get("getUuidAction.action?fileName="
+ encodeURIComponent(file.name)
+ "&num=" + num, function(data,
status) {//上传生成uuid
if (status == "success") {
var uuid = data;
console.log(data);
sender.uuid = uuid;
sender.start = 0;
sender.end = 1000 * 1000;
sender.url = options.url;
sender.finished = false;
sender.seq = 1;
sender.updateProgress(num);
sender.send();
} else {
console.log(status);
}
});
console.log("upload complete");
} else {
alert("请选择上传文件!");
}
});
uploader.append(uploadBtn);
/**
* 创建暂停按钮
*/
var pauseBtn = $("<input id=‘pauseBtn‘ type=‘button‘ value=‘暂停‘>")
.click(function() {
if (pauseBtn.attr("value") == "暂停") {
sender.pause = true;
$.get("pauseAction.action?uuid=" + sender.uuid);
pauseBtn.attr("value", "启动");
} else {
sender.pause = false;
console.log(sender.pause);
sender.send();
$.get("restartAction.action?uuid=" + sender.uuid);
pauseBtn.attr("value", "暂停");
}
});
uploader.append(pauseBtn);
var progressBar = $("<div></div>").addClass("progressBar");
var progress = $("<p></p>");
progressBar.append(progress);
uploader.append(progressBar);
where.append(uploader);
}
//创建整个控件
createUploader(this);
}
}(jQuery));
(4)还需要什么?
- 分块上传带来的问题,比如增大了文件完整性被破坏的风险,需要增加校验功能来监视上传前后的分块是否一致;
- 取消上传过程的功能;
- 拖拽上传操作的功能;
- 你还能做的更多……
版权声明:本文为博主原创文章,未经博主允许不得转载。