Spring MVC + Bootstrap 实现文件上传,并显示进度条

创建 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. 前端页面接收到上传出错的请求,将进度条样式设置为红色

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值