手上有个文件上传的需求,并且要支持断点续传最好要兼容性好一些,之前用过uploadify这个jquery上传插件,但是首先它不支持断点续传而且HTML5版本的竟然要收费,秉承中国特色这里就不予考虑了;于是在网上找到了一个叫Stream的支持HTML5和Flash并且支持断点续传的这么一个插件,经过一天的尝试,终于把它整合到项目中去,现勉强能用了后续再优化优化应该能投入上线,废话不多上代码!
HTML+JS:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>断点续传</title>
<link rel="stylesheet" type="text/css" href="<%=basePath%>/statics/thirdparty/stream/css/stream-v1.css">
<script type="text/javascript" src="<%=basePath%>/statics/thirdparty/stream/js/stream-v1.js"></script>
</head>
<body>
<div id="i_select_files">
</div >
<div id="i_stream_files_queue">
</div>
<button onclick="javascript:_t.upload();">开始上传</button>|<button onclick="javascript:_t.stop();">停止上传</button>|<button onclick="javascript:_t.cancel();">取消</button>
|<button onclick="javascript:_t.disable();">禁用文件选择</button>|<button onclick="javascript:_t.enable();">启用文件选择</button>
|<button onclick="javascript:_t.hideBrowseBlock();">隐藏文件选择按钮</button>|<button onclick="javascript:_t.showBrowseBlock();">显示文件选择按钮</button>
|<button onclick="javascript:_t.destroy();_t=null;_t=new Stream(config);">销毁重新生成按钮</button>
<br>
Messages:
<div id="i_stream_message_container" class="stream-main-upload-box" style="overflow: auto;height:200px;">
</div>
</body>
<script type="text/javascript">
/**
* 配置文件(如果没有默认字样,说明默认值就是注释下的值)
* 但是,on*(onSelect, onMaxSizeExceed...)等函数的默认行为
* 是在ID为i_stream_message_container的页面元素中写日志
*/
var config = {
browseFileId : "i_select_files", /** 选择文件的ID, 默认: i_select_files */
browseFileBtn : "<div>请选择文件</div>", /** 显示选择文件的样式, 默认: `<div>请选择文件</div>` */
dragAndDropArea: "i_select_files", /** 拖拽上传区域,Id(字符类型"i_select_files")或者DOM对象, 默认: `i_select_files` */
dragAndDropTips: "<span>可以把文件(文件夹)拖拽到这里</span>", /** 拖拽提示, 默认: `<span>把文件(文件夹)拖拽到这里</span>` */
filesQueueId : "i_stream_files_queue", /** 文件上传容器的ID, 默认: i_stream_files_queue */
filesQueueHeight : 200, /** 文件上传容器的高度(px), 默认: 450 */
messagerId : "i_stream_message_container", /** 消息显示容器的ID, 默认: i_stream_message_container */
maxSize: 4294967296, /** 单个文件的最大大小,默认:2G */
multipleFiles: false, /** 多个文件一起上传, 默认: false */
autoUploading: true, /** 选择文件后是否自动上传, 默认: true */
// autoRemoveCompleted : true, /** 是否自动删除容器中已上传完毕的文件, 默认: false */
// retryCount : 5, /** HTML5上传失败的重试次数 */
// postVarsPerFile : { /** 上传文件时传入的参数,默认: {} */
// param1: "val1",
// param2: "val2"
// },
// swfURL : "/swf/FlashUploader.swf", /** SWF文件的位置 */
tokenURL : "<%=request.getContextPath()%>/tk", /** 根据文件名、大小等信息获取Token的URI(用于生成断点续传、跨域的令牌) */
// frmUploadURL : "/fd;", /** Flash上传的URI */
uploadURL : "<%=request.getContextPath()%>/upload", /** HTML5上传的URI */
// simLimit: 200, /** 单次最大上传文件个数 */
// extFilters: [".txt", ".rpm", ".rmvb", ".gz", ".rar", ".zip", ".avi", ".mkv", ".mp3"], /** 允许的文件扩展名, 默认: [] */
// onSelect: function(list) {alert('onSelect')}, /** 选择文件后的响应事件 */
onMaxSizeExceed: function(size, limited, name) {alert('文件已超过4G');}, /** 文件大小超出的响应事件 */
// onFileCountExceed: function(selected, limit) {alert('onFileCountExceed')}, /** 文件数量超出的响应事件 */
// onExtNameMismatch: function(name, filters) {alert('onExtNameMismatch')}, /** 文件的扩展名不匹配的响应事件 */
// onCancel : function(file) {alert('Canceled: ' + file.name)}, /** 取消上传文件的响应事件 */
// onComplete: function(file) {alert('onComplete')}, /** 单个文件上传完毕的响应事件 */
onQueueComplete: function() {
alert('onQueueComplete');
}, /** 所以文件上传完毕的响应事件 */
// onUploadError: function(status, msg) {alert('onUploadError')} /** 文件上传出错的响应事件 */
// onDestroy: function() {alert('onDestroy')} /** 文件上传出错的响应事件 */
};
var _t = new Stream(config);
</script>
</html>
页面代码精简了下,只留下了关键部分。
TokenServlet:
package com.dnion.oa.stream.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import com.dnion.oa.stream.util.IoUtil;
import com.dnion.oa.stream.util.TokenUtil;
import com.dnion.oa.utils.Configuration;
import com.dnion.oa.utils.Constant;
/**
* According the file name and its size, generate a unique token. And this
* token will be refer to user's file.
*/
public class TokenServlet extends HttpServlet {
private static final long serialVersionUID = 2650340991003623753L;
static final String FILE_NAME_FIELD = "name";
static final String FILE_SIZE_FIELD = "size";
static final String TOKEN_FIELD = "token";
static final String SERVER_FIELD = "server";
static final String SUCCESS = "success";
static final String MESSAGE = "message";
@Override
public void init() throws ServletException {
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = StringUtils.trimToEmpty(req.getParameter(FILE_NAME_FIELD));
String size = StringUtils.trimToEmpty(req.getParameter(FILE_SIZE_FIELD));
String token = TokenUtil.generateToken(name, size);
PrintWriter writer = resp.getWriter();
JSONObject json = new JSONObject();
try {
if (name.equals("")) {
json.put(SUCCESS, false);
json.put(MESSAGE, "文件名为空");
}else if (size.equals("")) {
json.put(SUCCESS, false);
json.put(MESSAGE, "文件大小为空");
}else{
json.put(TOKEN_FIELD, token);
if (Configuration.getInstance().getIsCrossed())
json.put(SERVER_FIELD, Configuration.getInstance().getCrossServer());
json.put(SUCCESS, true);
json.put(MESSAGE, "获取TOKEN成功");
}
} catch (JSONException e) {
e.printStackTrace();
}
/** TODO: save the token. */
writer.write(json.toString());
}
@Override
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doHead(req, resp);
}
@Override
public void destroy() {
super.destroy();
}
}
这是实现上传功能必须实现接口之一,看名字就知道基本功能就是获取token实现‘断点‘功能
StreamServlet:
package com.dnion.oa.stream.servlet;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONException;
import org.json.JSONObject;
import com.dnion.oa.stream.util.IoUtil;
import com.dnion.oa.utils.Configuration;
/**
* File reserved servlet, mainly reading the request parameter and its file
* part, stored it.
*/
public class StreamServlet extends HttpServlet {
private static final long serialVersionUID = -8619685235661387895L;
/** when the has increased to 10kb, then flush it to the hard-disk. */
static final int BUFFER_LENGTH = 10240;
static final String START_FIELD = "start";
public static final String CONTENT_RANGE_HEADER = "content-range";
@Override
public void init() throws ServletException {
}
/**
* Lookup where's the position of this file?
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doOptions(req, resp);
final String token = req.getParameter(TokenServlet.TOKEN_FIELD);
final String size = req.getParameter(TokenServlet.FILE_SIZE_FIELD);
final String fileName = req.getParameter(TokenServlet.FILE_NAME_FIELD);
final PrintWriter writer = resp.getWriter();
/** TODO: validate your token. */
JSONObject json = new JSONObject();
long start = 0;
boolean success = true;
String message = "";
try {
File f = IoUtil.getTokenedFile(token);
start = f.length();
/** file size is 0 bytes. */
if (token.endsWith("_0") &amp;amp;amp;amp;&amp;amp;amp;amp; "0".equals(size) &amp;amp;amp;amp;&amp;amp;amp;amp; 0 == start)
f.renameTo(IoUtil.getFile(fileName));
} catch (FileNotFoundException fne) {
message = "Error: " + fne.getMessage();
success = false;
} finally {
try {
if (success)
json.put(START_FIELD, start);
json.put(TokenServlet.SUCCESS, success);
json.put(TokenServlet.MESSAGE, message);
} catch (JSONException e) {}
writer.write(json.toString());
IoUtil.close(writer);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doOptions(req, resp);
final String token = req.getParameter(TokenServlet.TOKEN_FIELD);
final String fileName = req.getParameter(TokenServlet.FILE_NAME_FIELD);
Range range = IoUtil.parseRange(req);
OutputStream out = null;
InputStream content = null;
final PrintWriter writer = resp.getWriter();
/** TODO: validate your token. */
JSONObject json = new JSONObject();
long start = 0;
boolean success = true;
String message = "";
File f = IoUtil.getTokenedFile(token);
try {
if (f.length() != range.getFrom()) {
/** drop this uploaded data */
throw new StreamException(StreamException.ERROR_FILE_RANGE_START);
}
out = new FileOutputStream(f, true);
content = req.getInputStream();
int read = 0;
final byte[] bytes = new byte[BUFFER_LENGTH];
while ((read = content.read(bytes)) != -1)
out.write(bytes, 0, read);
start = f.length();
} catch (StreamException se) {
success = StreamException.ERROR_FILE_RANGE_START == se.getCode();
message = "Code: " + se.getCode();
} catch (FileNotFoundException fne) {
message = "Code: " + StreamException.ERROR_FILE_NOT_EXIST;
success = false;
} catch (IOException io) {
message = "IO Error: " + io.getMessage();
success = false;
} finally {
IoUtil.close(out);
IoUtil.close(content);
/** rename the file */
if (range.getSize() == start) {
/** fix the `renameTo` bug */
File dst = IoUtil.getFile(fileName);
dst.delete();
f.renameTo(dst);
System.out.println("TK: `" + token + "`, NE: `" + fileName + "`");
/** if `STREAM_DELETE_FINISH`, then delete it. */
if (Configuration.getInstance().getIsDeleteFinished()) {
dst.delete();
}
}
try {
if (success)
json.put(START_FIELD, start);
json.put(TokenServlet.SUCCESS, success);
json.put(TokenServlet.MESSAGE, message);
} catch (JSONException e) {}
writer.write(json.toString());
IoUtil.close(writer);
}
}
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json");
resp.setHeader("Access-Control-Allow-Headers", "Content-Range,Content-Type");
resp.setHeader("Access-Control-Allow-Origin", Configuration.getInstance().getCrossOrigins());
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
}
@Override
public void destroy() {
super.destroy();
}
}
必须实现接口之一,也是最为主要的一个servlet
这两个接口其实就是对应了js代码中tokenURL和uploadURL,跳转关系在web.xml中定义,把下面这段加进去就行了,记得改路径
<servlet>
<servlet-name>TokenServlet</servlet-name>
<servlet-class>com.dnion.oa.stream.servlet.TokenServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TokenServlet</servlet-name>
<url-pattern>/tk</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>StreamServlet</servlet-name>
<servlet-class>com.dnion.oa.stream.servlet.StreamServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StreamServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>FormDataServlet</servlet-name>
<servlet-class>com.dnion.oa.stream.servlet.FormDataServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FormDataServlet</servlet-name>
<url-pattern>/fd</url-pattern>
</servlet-mapping>
基本参数保存在properties文件中
stream-config.properties:
# file stored repository (Chinese words need ASCII, help tool @http://tool.oschina.net/encode?type=3)
STREAM_FILE_REPOSITORY=
# when the file has uploaded, whether delete it.
STREAM_DELETE_FINISH=false
# this server whether allow other different domain[s] upload file to this server
STREAM_IS_CROSS=false
# allowed domain (PS: flash method need modifying the `crossdomain.xml`)
STREAM_CROSS_ORIGIN=*
# when Browser @http:www.A.com, the file will upload to @STREAM_CROSS_SERVER
STREAM_CROSS_SERVER=http://customers.duapp.com
TEMP_UPLOAD_PATH=E\:\\resumeUpload //暂时放在本地 测试用
Configuration.java读取配置文件
Configuration.java:
package com.dnion.oa.utils;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
public class Configuration {
private static Configuration config;
/**
* 版本号
*/
private String version;
private String crossServer;
private String crossOrigins;
private String dnionTranscoderUrl;
private Boolean isDeleteFinished;
private Boolean isCrossed;
private String uploadPath;
private Configuration() {
}
public synchronized static Configuration getInstance() {
if (config == null) {
config = new Configuration();
config.init();
}
return config;
}
/**
* 初始化
*/
private void init() {
PropertiesConfiguration prop = new PropertiesConfiguration();
prop.setEncoding("utf-8");
try {
prop.load("props/stream-config.properties");
this.setCrossServer(prop.getString("STREAM_CROSS_SERVER", ""));
this.setCrossOrigins(prop.getString("STREAM_CROSS_ORIGIN", ""));
this.setDnionTranscoderUrl(prop.getString("DNION_TRANS_CODER_URL",
""));
this.setIsDeleteFinished(Boolean.valueOf(prop.getString(
"STREAM_DELETE_FINISH", "")));
this.setIsCrossed(Boolean.valueOf(prop.getString("STREAM_IS_CROSS",
"")));
this.setChownShellCmd(prop.getString("CHOWN_SHELL_CMD", ""));
this.setUploadPath(prop.getString("TEMP_UPLOAD_PATH", ""));
} catch (ConfigurationException e) {
e.printStackTrace();
}
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
...getter and setters
还有6对,别忘了
}
Maven依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20090211</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.9</version>
</dependency>
发表回复