创建 Progress 类,这个类里的成员变量对应于 ProgressListener 接口里 update 方法的三个参数
/**
* 文件上传进度实体类
* @author zpc
*/
public class Progress {
/** 已读字节数 */
private long bytesRead;
/** 文件字节总长 */
private long contentLength;
private long items;
public long getBytesRead() {
return bytesRead;
}
public void setBytesRead(long bytesRead) {
this.bytesRead = bytesRead;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public long getItems() {
return items;
}
public void setItems(long items) {
this.items = items;
}
}
创建 FileUploadProgressListener 类,该类继承 ProgressListener 接口,实现文件上传进度的监听;此类在 Spring 配置文件中注册,不要忘了配置<context:component-scan base-package="类所在的 package " />和 @Component
**
* 文件上传进度监听器
* @author zpc
*/
@Component
public class FileUploadProgressListener implements ProgressListener {
/** 用户与服务器之间的会话 */
private HttpSession session;
/** 创建并设置会话后,在其中设置一个进度(progress)实例 */
public void setSession(HttpSession session) {
this.session = session;
Progress progress = new Progress();
session.setAttribute("progress", progress);
}
/**
* 更新文件上传监听器状态信息
* @param pBytesRead 到目前为止已读取的字节总数
* @param pContentLength 正在读取的字节的总数。可能是-1,如果这个数字是未知的。
* @param pItems 当前正在读取的字段的数目
*/
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
// 因 session 中存放的是属性的内存地址,因此属性改变后,session 中的值也会改变
Progress progress = (Progress) session.getAttribute("progress");
progress.setBytesRead(pBytesRead);
progress.setContentLength(pContentLength);
progress.setItems(pItems);
}
}
创建 CustomMultipartResolver 类,该类继承 CommonsMultipartResolver 类,并覆写其 parseRequest 方法
/**
* 自定义的文件上传解析器,继承自 CommonsMultipartResolver,重写其 parseRequest 方法
* @author zpc
*/
public class CustomMultipartResolver extends CommonsMultipartResolver {
@Autowired
private FileUploadProgressListener progressListener;
public void setProgressListener(FileUploadProgressListener progressListener) {
this.progressListener = progressListener;
}
/**
* 解析给定的 servlet 请求,解析它的 multipart 元素
* @param request 要解析的请求
* @return 解析的结果
* @throws MultipartException 如果 multipart 解析失败
*/
public MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
progressListener.setSession(request.getSession()); // 在文件上传进度监听器中设置 session
fileUpload.setProgressListener(progressListener); // 设置文件上传进度监听器
try {
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
}
catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
}
catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}
}
在 Spring 配置文件中写如下配置:
<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="包路径.CustomMultipartResolver">
<!-- 指定所上传文件的总大小不能超过120M。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 -->
<property name="maxUploadSize" value="120000000"/>
<property name="defaultEncoding" value="utf-8"></property>
</bean>
在 Controller 中实现如下方法:
/**
* 获取文件上传进度,由前端异步方法调用
* @param request
* @return 一个封装了文件上传进度的 JSON 对象
*/
@ResponseBody
@GetMapping(value="/upload/progress")
public JSONResult uploadProgress(HttpServletRequest request) {
// 上传文件时,注册了进度监听器,并把监听的进度对象放到了 session 中
Progress progress = (Progress) request.getSession().getAttribute("progress");
return JSONResult.buildSuccessWithObj(progress);
}
在前端 JS 文件中实现如下方法,并使用 setInterval 方法定时调用,直到上传完成:
/** 监听文件上传进度的方法 */
var uploadProgress = function(){
$.ajax({
url: $("#basePath").val() + "/model/upload/progress",
type: "get",
cache: false,
processData: false,
contentType: false,
dataType: 'json',
success: function(data){
if(data.statusCode == 200){
var result = data.result;
var progressBar = $(".progress-bar");
var progress = Math.round(result.bytesRead / result.contentLength * 100) + "%";
progressBar.width(progress);
progressBar.text(progress);
if(Math.round(result.bytesRead / result.contentLength * 100) == 100){
progressBar.removeClass("active");
window.clearInterval(intId);
}
}else{
}
},
error: function(e){
swal({
title: "保存失败",
text: e,
icon: "error",
timer: 2000
});
}
});
};
var up = window.setInterval(uploadProgress, 500);
前端 HTML 中的进度条代码,引用 Bootstrap 进度条:
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
</div>
</div>
问题:
当前实现有如下缺点,若上传出错时,前端未收到出错信息,进度条依然会显示成功。
设想解决方案的步骤:
1. 在 Progress 中添加一个成员变量 status,记录当前上传进度是正常状态”normal“,还是出错状态”failure“
2. 在 FileUploadProgressListener 的 upload 方法里,判断文件的 pContentLength 是否小于等于 0,若小于等于 0,则设置为出错状态”failure“
3. 在 ModelServiceImpl 的 uploadModel方法中,若向文件系统写入文件时抛异常,则设置为出错状态”failure“
4. 在 ModelController 的 uploadProgress 方法里获取出错状态”failure“,并向前端页面返回
5. 前端页面接收到上传出错的请求,将进度条样式设置为红色