近来做一个需要有附件管理的公告版,于是发现了这个FineUploader,纯Javascript实现,Ajax方式,上传文件不刷新页面,不依赖于任何第三方库,非常好用。但刚开始使用时google了很多资料,包括官方的文档和demo。发现网上的很多资料要么很老,要么对我参考意义不大。至这个功能完成,写下自己的心得,给自己留点记录,如果能对别人有点参考价值则更佳。
FineUploader官方网站:http://fineuploader.com/ ,打包好的js是要收费的,价格还不便宜。
开源的项目:https://github.com/FineUploader/fine-uploader
本文中用到的FineUploader已上传到http://download.youkuaiyun.com/detail/jxiaoliu/8071341(已精简,去掉了所有用不到的文件,包括模板)
一、FineUploader所需的代码
首先,引入必须的js文件和css
<link href="<%=request.getContextPath()%>/fineuploader5/fineuploader-5.0.2.css" rel="stylesheet"/>
<script src="<%=request.getContextPath()%>/fineuploader5/fineuploader-5.0.2.js" type="text/javascript"></script>
OK,就这两个文件就可以了。
接下来,就可以在js中实例化这个uploader:
var submitFile = false;//用于控制仅在”提交“按钮按下时提交文件
var uploader = null;
function createUploader() {
uploader = new qq.FineUploader({
element: document.getElementById('fine-uploader'),
autoUpload: false,
request: {
endpoint: '<%=request.getContextPath()%>/updownfile/UploadFile?op=add'
},
session: {
endpoint: '<%=request.getContextPath()%>/manage/Bulletin?op=getattach',
params: {'id': ${bulletin.id}}
},
deleteFile: {
enabled: true,
endpoint: '<%=request.getContextPath()%>/manage/Bulletin?op=del',
method: 'POST',
forceConfirm: true,
confirmMessage: '确定要删除文件 {filename} 吗? 不可恢复!!',
deletingFailedText: '删除失败!'
},
editFilename: {
enabled: false
},
callbacks: {
onAllComplete: function(successIDs, failIDs) {
if(submitFile)
submitdata(successIDs);
}
}
});
}
必须在页面初始化时进行此操作,继续加入下面这句:
window.onload = createUploader;
FineUploader必须实例化到html页面中的某个“容器”内,一般为<div>元素,第一个选项“element”即指出对应的html元素。所以在页面中必须有如下元素存在:
<div id="fine-uploader"></div>
此外,由于FineUploader分为core和UI两个类别,其中core应该是用户完全定制到自己的JS程序中去的,需要写自己的用户界面。对于初学者或一般用户来说,可以直接使用FineUploader提供的模板(template,用户可以去开源项目中下载),这里是直接拷贝模板文件(default.html)中相应的代码段到当前html页面的任意位置:
<script type="text/template" id="qq-template">
<div class="qq-uploader-selector qq-uploader">
<div class="qq-upload-button-selector qq-upload-button">
<div>上传附件</div>
</div>
<ul class="qq-upload-list-selector qq-upload-list">
<li>
<div class="qq-progress-bar-container-selector">
<div class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon"></span>
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-upload-size-selector qq-upload-size"></span>
<a class="qq-upload-cancel-selector qq-upload-cancel" href="#">放弃上传</a>
<a class="qq-upload-retry-selector qq-upload-retry" href="#">Retry</a>
<a class="qq-upload-delete-selector qq-upload-delete" href="#">删除</a>
<span class="qq-upload-status-text-selector qq-upload-status-text"></span>
</li>
</ul>
</div>
</script>
很多文档或资料说最外层的<script>标记可以不要,但本人测试不可以,必须要。其中的id属性
id="
qq-template"至关重要,否则FineUploader不知道何处去查找自己的UI界面并展示。
该模板的样式可以通过fineuploader-5.0.2.css文件进行所需的修改,这里不再赘述。
至此,FineUploader已完整,当然要运行必须要后台代码的上传支持。
二、后台Java代码
借助于apache.commons.fileupload组件进行文件的上传,代码如下:
try {
MultipartUploadParser UploadParser = new MultipartUploadParser(request, new File(System.getProperty("java.io.tmpdir")));
FileItem uploadfile = UploadParser.getFirstFile();
Path file = java.nio.file.Paths.get(uploadfile.getName());
uploadfile.write(new File(rootpath + "/temp/" + file.getFileName().toString()));
result.put("success", true);
} catch (Exception e) {
result.put("success", false);
response.getWriter().print(result.toString());
e.printStackTrace();
}
必须注意的是,文件上传成功后,必须返回前台一个JSON格式的对象,其中必须含有一个"success"对象,其值为true或者false,代表着文件上传的成功或失败。
另外,当用户选择多个文件并上传时,是以多线程的方式各自独立同时进行的。每一个文件上传成功都会触发callbacks中的onComplete事件,所有文件上传成功后触发onAllComplete事件。
其中的MultipartUploadParser类对象定义如下:
package updownfile;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
public class MultipartUploadParser
{
final Logger log = LoggerFactory.getLogger(MultipartUploadParser.class);
private Map
params = new HashMap
();
private List
files = new ArrayList
();
// fileItemsFactory is a field (even though it's scoped to the constructor) to prevent the
// org.apache.commons.fileupload.servlet.FileCleanerCleanup thread from attempting to delete the
// temp file before while it is still being used.
//
// FileCleanerCleanup uses a java.lang.ref.ReferenceQueue to delete the temp file when the FileItemsFactory marker object is GCed
private DiskFileItemFactory fileItemsFactory;
@SuppressWarnings("unchecked")
public MultipartUploadParser(HttpServletRequest request, File repository) throws Exception
{
if (!repository.exists() && !repository.mkdirs())
{
throw new IOException("Unable to mkdirs to " + repository.getAbsolutePath());
}
fileItemsFactory = setupFileItemFactory(repository);
ServletFileUpload upload = new ServletFileUpload(fileItemsFactory);
List
formFileItems = upload.parseRequest(request);
parseFormFields(formFileItems);
if (files.isEmpty())
{
log.warn("No files were found when processing the requst. Debugging info follows.");
writeDebugInfo(request);
throw new FileUploadException("No files were found when processing the requst.");
}
else
{
if (log.isDebugEnabled())
{
writeDebugInfo(request);
}
}
}
private DiskFileItemFactory setupFileItemFactory(File repository)
{
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(10*1024*1024);
factory.setRepository(repository);
// FileCleaningTracker pTracker = FileCleanerCleanup.getFileCleaningTracker(context);
// factory.setFileCleaningTracker(pTracker);
return factory;
}
private void writeDebugInfo(HttpServletRequest request)
{
log.debug("-- POST HEADERS --");
for (String header : Collections.list(request.getHeaderNames()))
{
log.debug("{}: {}", header, request.getHeader(header));
}
log.debug("-- POST PARAMS --");
for (String key : params.keySet())
{
log.debug("{}: {}", key, params.get(key));
}
}
private void parseFormFields(List
items) throws UnsupportedEncodingException { for (FileItem item : items) { if (item.isFormField()) { String key = item.getFieldName(); String value = item.getString("UTF-8"); if (StringUtils.isNotBlank(key)) { params.put(key, StringUtils.defaultString(value)); } } else { files.add(item); } } } public Map
getParams() { return params; } public List
getFiles() { if (files.isEmpty()) { throw new RuntimeException("No FileItems exist."); } return files; } public FileItem getFirstFile() { if (files.isEmpty()) { throw new RuntimeException("No FileItems exist."); } return files.iterator().next(); } }
三、代码中各选项含义
- autoUpload选项:控制用户选择文件后是否立即自动上传选中的文件,默认为true,即选中后自动上传到服务器,上传完毕后,会依次显示文件名、文件大小及一个“删除”链接(删除上传文件的解释见deleteFile选项)。这里设置值为false,表示用户选择文件后仅列出文件(此时文件名右侧会出现“放弃”链接,点此链接会去除对应的文件),在需要时手动通过uploadStoredFiles()方法进行上传。
- multiple选项:默认为true,用户可以一次选择多个文件并上传。
- request选项:后台接受文件上传的URL路径。设置其中的endpoint属性。
- editFilename选项:设置是否可修改上传的文件名(本人测试时好像没发现有什么作用~~)
- callbacks选项:回调函数,可以监听各种事件。具体的可参见官方文档。
- deleteFile选项:用于删除已上传的文件。可以删除的文件必须是在同一个页面中已经成功上传,或者由session选项初始化的文件列表。其重要的子选项解释如下:
endpoint:后台用于删除文件的URL地址。返回值和上传时一样必须含有“success”属性
method:重要,必须设置为'post'
forceConfirm:是否出现删除确认的对话框,默认值为false - session选项:该选项确定控件初始化时需要初始化的文件列表。若不设置该项,则初始化控件时其文件列表为空。若需要给出用户以前已经上传的文件列表,则必须设置其子选项endpoint的值以从后台返回文件列表。后台返回的值必须是一个JSON格式的数组,每个数组的对象是JSON格式的对象,必须包含“name”和“uuid”两个属性,以分别指定文件名和文件的唯一代号。具体可参看官方文档。
四、几张简单的样图
1. 文件已选择,待上传: