本文介绍两种上传方式来获取进度条,第一种是论坛里写的比较用心的博客(我将这些博客又细分了一下,主要为了避坑),第二种方式是一base64的方式上传和接收的。
目录
3.上传页面(传统的上传组件,在上传过程中,你要获得上传文件对象)
第一种上传方式file对象的方式
1 准备SSM的Maven环境,也就是jar包的准备
任意的ssm环境的项目,非maven的也可,但是注意在SpringMVC的Controller中使用@ResponseBody
注解的时候必须引入jackson
的依赖包,否则该注解无法使用.
<!-- jackson 版本 -->
<jackson.version>2.5.4</jackson.version>
<!-- springMVC依赖的jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
2 前端页面
前端页面的重点是ajax文件上传,因为一直无法获得文件的对象,所以在这里费的时间比较长,需要注意js获得文件对象的方式。
引入BootStrap的样式
此处非必须,不是BootStrap也不影响,可以自己去下载
<!-- 导入css样式 -->
<link href="${pageContext.request.contextPath}/static/bootstrap3/css/bootstrap.min.css" rel="stylesheet" />
<!-- 导入jQuery库 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/static/bootstrap3/jquery/jquery-1.11.3.min.js" ></script>
<!-- 导入js库 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/static/bootstrap3/js/bootstrap.min.js" ></script>
<style type="text/css">
fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}
legend{padding:.5em;border:0;width:auto}
</style>
3.上传页面(传统的上传组件,在上传过程中,你要获得上传文件对象)
<fieldset>
<h1>Ajax文件上传进度条</h1>
<!--
ajax提交后form表单中如果存在button按钮的话ajax完成之后form表单
还是会自动提交
-->
<form action="${pageContext.request.contextPath}/pd/upload2.action"
method="post" id="form1" name="form1"
enctype="multipart/form-data"
onsubmit="return false;" role="form" class="form-inline">
<input type="file" name="fileObj" id="fileObj" class="form-group" />
</form>
<!-- 动画进度条 -->
<div id="pgID" style="display:block;width:300px;" >
<div class="progress progress-striped active" >
<div id="pdBar" class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
style="width:0%;">
<span id="pdNum" class="sr-only">40% 完成</span>
</div>
</div>
</div>
<div>
<button class="btn btn-primary" id="upload" >上传</button>
<button class="btn btn-danger" id="cancel" >取消</button>
</div>
<p>开始时间:<font id="startTime" color="red"></font></p>
<p>现在时间:<font id="currentTime" color="red"></font></p>
<p>已经传输了的时间(s):<font id="time" color="red"></font></p>
<p>传输速度(byte/s):<font id="velocity" color="red"></font></p>
<p>估计总时间:<font id="totalTime" color="red"></font></p>
<p>估计剩余时间:<font id="timeLeft" color="red"></font></p>
<p>上传百分比:<font id="percent" color="red"></font></p>
<p>已完成数:<font id="length" color="red"></font></p>
<p>总长度(M):<font id="totalLength" color="red"></font></p>
</fieldset>
JS代码部分
需要注意的是JS获取文件的时候var fileObj = $("#fileObj")[0].files[0];
必须这样写,否则取不到文件 js可以根据自己的需求来改动
<script type="text/javascript">
var timeInfo = 0;
$(function(){
$("#upload").click(function(){
uploadFile();
timeInfo = setInterval("getUploadInfo()",1000);
});
$("#cancel").click(function(){
$("#pdBar").css({"width":"0%"});
clearInterval(timeInfo);
});
});
//文件上传
function uploadFile(){
var fileObj = $("#fileObj")[0].files[0];//必须这样写,否则取不到文件
console.log(fileObj);
//$('#form1').serialize() 无法序列化二进制文件,这里采用formData上传
//需要浏览器支持:Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。
var formData = new FormData(); // FormData 对象
// formData.append("author", "hooyes"); // 可以增加表单数据
formData.append("fileObj", fileObj); // 文件对象
console.log("开始上传..."+formData);
$.ajax({
type:"POST",
url:"${pageContext.request.contextPath}/pd/upload.action",
data:formData,
async: true,
cache: false,
dataType:"json",
contentType: false,/*必须false才会自动加上正确的Content-Type */
processData: false,/*必须false才会避开jQuery对 formdata 的默认处理XMLHttpRequest会对 formdata 进行正确的处理*/
success:function(res){
console.log(res);
if(res.tag === true){
clearInterval(timeInfo);
getUploadInfo();//修正得到文件上传信息
setPdWidth(100);//修正文件上传为100%
alert("上传成功!");
}else{
clearInterval(timeInfo);
alert("上传失败!");
}
},
error:function(){
clearInterval(timeInfo);
alert("上传错误!");
}
});
}
//打开进度条
function openProgess(){
/* var modalHeight=$(window).height() / 2 - $('#pgID').height() / 2;
$("#pgID").css({"display":"block","margin":modalHeight}); */
$("#pgID").css({"display":"block","width":"300px"});
}
//关闭进度条
function closeProgess(){
$("#pgID").css({"display":"none","width":"300px"});
$("#pdBar").css({"width":"0%"});
}
//得到上传文件进度信息
function getUploadInfo(){
$.ajax({
url:"${pageContext.request.contextPath}/pd/getInfo.action",
data:{time:new Date()},
type:"post",
dataType:"json",
cache:false,
success:function(res){
if(res.percent == 100){
clearInterval(timeInfo);
return;
}
console.log(res);
setPdWidth(res.percent);
setUploadInfo(res);
},
error:function(){
clearInterval(timeInfo);
console.log("得到上传文件信息出错!");
}
});
}
//设置上传文件进度信息
function setUploadInfo(res){
$("#startTime").text(res.startTime);
$("#currentTime").text(res.currentTime);
$("#time").text(res.time);
$("#velocity").text(res.velocity);
$("#totalTime").text(res.totalTime);
$("#timeLeft").text(res.timeLeft);
$("#percent").text(res.percent);
$("#length").text(res.length);
$("#totalLength").text(res.totalLength);
}
function setPdWidth(percent){
$("#pdBar").css({"width":percent+"%"});
}
</script>
后端代码
Progress实体类
创建一个进度条的实体类,此处省略getter和setter方法
/**
* 进度条的entity
* @author
*
*/
public class Progress {
private long bytesRead;
private long contentLength;
private long items;
private long startTime = System.currentTimeMillis(); // 开始上传时间,用于计算上传速率
public Progress() {
}
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;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
}
上传文件监听器
我们要获得上传文件的实时详细信息,必须继承org.apache.commons.fileupload.ProgressListener
类,获得信息的时候将进度条对象Progress
放在该监听器的session
对象中。
package com.bart.module.pb.listener;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.ProgressListener;
import org.springframework.stereotype.Component;
import com.bart.module.pb.entity.Progress;
@Component
public class FileUploadProgressListener implements ProgressListener {
private HttpSession session;
public void setSession(HttpSession session){
this.session=session;
Progress status = new Progress();//保存上传状态
session.setAttribute("status", status);
}
@Override
public void update(long bytesRead, long contentLength, int items) {
Progress status = (Progress) session.getAttribute("status");
status.setBytesRead(bytesRead);//已读取数据长度
status.setContentLength(contentLength);//文件总长度
status.setItems(items);//正在保存第几个文件
}
}
文件解析器
SpringMVC默认有一个文件解析器CommonsMultipartResolver用来解析上传的文件,我们需要重写该类,自己重写的监听器放到org.apache.commons.fileupload.FileUpload中,还需要将session放到自定义的监听器中。
package com.bart.module.pb.resolver;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import com.bart.module.pb.listener.FileUploadProgressListener;
public class CustomMultipartResolver extends CommonsMultipartResolver {
// 注入第二步写的FileUploadProgressListener
@Autowired // 此处一定要检查是否注入成功了,不成功的话会报错
private FileUploadProgressListener progressListener;
public void setFileUploadProgressListener(FileUploadProgressListener progressListener) {
this.progressListener = progressListener;
}
@Override
public MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
progressListener.setSession(request.getSession());
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);
}
}
}
配置SpringMVC的配置文件解析器
需要配置文件解析器为自定义的
<!-- ====================== 配置自定义的MutilpartResover解析器处理文件上传进度条 ====================== class后的路径放置你自定义的类路径 一定要指定正确,不然获取不到值 -->
<bean id="multipartResolver"
class="com.bart.module.pb.resolver.CustomMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000000"></property>
</bean>
Controller代码
在开发时,将上传方法和得到上传文件详细信息的方法一个类里面
上传文件
@ResponseBody
@RequestMapping("/getInfo")
public Map<String, Object> getUploadInfo(HttpServletRequest request, HttpServletResponse response){
Map<String, Object> result = new HashMap<>();
response.setHeader("Cache-Control", "no-store"); //禁止浏览器缓存
response.setHeader("Pragrma", "no-cache"); //禁止浏览器缓存
response.setDateHeader("Expires", 0); //禁止浏览器缓存
Progress status = (Progress) request.getSession(true).getAttribute("status");//从session中读取上传信息
if(status == null){
result.put("error", "没发现上传文件!");
return result;
}
long startTime = status.getStartTime(); //上传开始时间
long currentTime = System.currentTimeMillis(); //现在时间
long time = (currentTime - startTime) /1000 +1;//已经传顺的时间 单位:s
double velocity = status.getBytesRead()/time; //传输速度:byte/s
double totalTime = status.getContentLength()/velocity; //估计总时间
double timeLeft = totalTime -time; //估计剩余时间
int percent = (int)(100*(double)status.getBytesRead()/(double)status.getContentLength()); //百分比
double length = status.getBytesRead()/1024/1024; //已完成数
double totalLength = status.getContentLength()/1024/1024; //总长度 M
result.put("startTime",startTime);
result.put("currentTime",currentTime);
result.put("time",time);
result.put("velocity",velocity);
result.put("totalTime",totalTime);
result.put("timeLeft",timeLeft);
result.put("percent",percent);
result.put("length",length);
result.put("totalLength",totalLength);
return result;
}
到此第一种方法就全部完成了,第二种方式呢只是上的方式不一样其他配置都一样,主要变得地方就是Controller和JSP其他配置都一样的
第二种 base64方式
这种方式和第一种方式差不多,只是后台接收方式变了而已,那我们就把Controller贴出来 ,因为是post提交,所以大家不用研究下面的代码,只看接受方式即可
@Controller
@RequestMapping("/up")
public class UploadServlet extends HttpServlet {
/*
* 上传文件进度条
* author:qzx
*/
@RequestMapping("/uploadTest")
public String codeList(@RequestParam(defaultValue="add")String type) {
System.out.println("测试页面");
String webpage = null;
switch (type) {
case "add":
webpage = "WEB-INF/page/admin/code/uploadTest";
break;
}
return webpage;
}
@ResponseBody
@RequestMapping(value="/upload", method = RequestMethod.POST)
public JSONObject index(HttpServletRequest request, HttpServletResponse response ,@RequestParam("filename") String filename,@RequestParam("ImgData") String base64Img) throws IOException {
Map<String, Object> map = new HashMap<String, Object>();
System.out.println("开始上传照片");
String customerId="";
if(!StringUtils.isEmpty(filename)){
customerId = "originalImage"+"/"+filename;
}else{
customerId = "originalImage";
}
String ret_fileName = null;// 返回给前端已修改的图片名称
// 临时文件路径
//String dirTemp = "\\upload\\temp";
String dirTemp = "\\uploads";
String uploadImg = "uploads";
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
map.put("msg", "上传图片失败");
map.put("status", "false");
}
// response.setContentType("text/html; charset=UTF-8");
// PrintWriter out = response.getWriter();
String realPath = request.getServletContext().getRealPath("");
String tempPath = request.getServletContext().getRealPath("/")
+ dirTemp;
File file_normer = new File(realPath + uploadImg + "/" + customerId);
if (!file_normer.exists()) {
file_normer.mkdirs();
}
// 用于设置图片过大,存入临时文件
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(20 * 1024 * 1024); // 设定使用内存超过5M时,将产生临时文件并存储于临时目录中。
factory.setRepository(new File(tempPath)); // 设定存储临时文件的目录。
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
if (base64Img == null) // 图像数据为空
map.put("msg", "上传图片失败");
map.put("status", false);
map.put("tag",true);
// base64Img = base64Img.replaceAll("data:image/jpeg;base64,", "");
if (base64Img.contains("data:image/jpg;base64")) {
base64Img = base64Img.replaceAll("data:image/jpg;base64,", "");
} else if (base64Img.contains("data:image/jpeg;base64")) {
base64Img = base64Img.replaceAll("data:image/jpeg;base64,", "");
} else if (base64Img.contains("data:image/png;base64")) {
base64Img = base64Img.replaceAll("data:image/png;base64,", "");
}
BASE64Decoder decoder = new BASE64Decoder();
try {
// Base64解码
byte[] b = decoder.decodeBuffer(base64Img);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {// 调整异常数据
b[i] += 256;
}
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
String dateNowStr = sdf.format(new Date());
System.out.println("格式化后的日期:" + dateNowStr);
// 生成jpeg图片
ret_fileName = new String((dateNowStr + ".jpg").getBytes("gb2312"), "ISO8859-1");
System.out.println(realPath);
File file = new File(realPath + uploadImg + "/" + customerId + "/" + ret_fileName);
OutputStream out = new FileOutputStream(file);
out.write(b);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
String image_url = request.getSession().getServletContext() .getContextPath()+ "/uploads/" + customerId + "/" + ret_fileName;
String uploadUrlWithfilename = FileHandlerZipUtils.getUploadUrlWithfilename(ret_fileName, customerId);
System.out.print("image_url= " +image_url);
// 将已修改的图片url对应的id返回前端
if(!StringUtils.isEmpty(uploadUrlWithfilename)){
map.put("msg", "上传图片成功");
map.put("status", "true");
map.put("image_url",uploadUrlWithfilename);
map.put("filepath",uploadUrlWithfilename.substring(0,uploadUrlWithfilename.length()-19));
map.put("tag",true);
}
JSONObject json = JSONObject.fromObject(map);
System.out.println(json.toString());
return json;
}
@ResponseBody
@RequestMapping("/getInfo")
public JSONObject getUploadInfo(HttpServletRequest request, HttpServletResponse response){
Map<String, Object> result = new HashMap<>();
response.setHeader("Cache-Control", "no-store"); //禁止浏览器缓存
response.setHeader("Pragrma", "no-cache"); //禁止浏览器缓存
response.setDateHeader("Expires", 0); //禁止浏览器缓存
Progress status = (Progress) request.getSession(true).getAttribute("status");//从session中读取上传信息
if(status == null){
result.put("error", "没发现上传文件!");
JSONObject json = JSONObject.fromObject(result);
return json;
}
long startTime = status.getStartTime(); //上传开始时间
long currentTime = System.currentTimeMillis(); //现在时间
long time = (currentTime - startTime) /1000 +1;//已经传顺的时间 单位:s
double velocity = status.getBytesRead()/time; //传输速度:byte/s
int totalTime = (int)(status.getContentLength()/velocity); //估计总时间
int timeLeft = (int)(totalTime -time); //估计剩余时间
int percent = (int)(100*(double)status.getBytesRead()/(double)status.getContentLength()); //百分比
double length = status.getBytesRead()/1024/1024; //已完成数
double totalLength = status.getContentLength()/1024/1024; //总长度 M
// 传输速度的大小 转换成M KB B等
String MB =getPrintSize(Math.round(status.getBytesRead()/time));
result.put("startTime",startTime);
result.put("currentTime",currentTime);
result.put("time",time);
result.put("velocity",MB);
result.put("totalTime", totalTime);
result.put("timeLeft",timeLeft);
result.put("percent",percent);
result.put("length",length);
result.put("totalLength",totalLength);
JSONObject json = JSONObject.fromObject(result);
System.out.println("--------------------------------"+json.toString());
return json;
}
public static String getPrintSize(long size) {
//如果字节数少于1024,则直接以B为单位,否则先除于1024,后3位因太少无意义
if (size < 1024) {
return String.valueOf(size) + "B";
} else {
size = size / 1024;
}
//如果原字节数除于1024之后,少于1024,则可以直接以KB作为单位
//因为还没有到达要使用另一个单位的时候
//接下去以此类推
if (size < 1024) {
return String.valueOf(size) + "KB";
} else {
size = size / 1024;
}
if (size < 1024) {
//因为如果以MB为单位的话,要保留最后1位小数,
//因此,把此数乘以100之后再取余
size = size * 100;
return String.valueOf((size / 100)) + "."
+ String.valueOf((size % 100)) + "MB";
} else {
//否则如果要以GB为单位的,先除于1024再作同样的处理
size = size * 100 / 1024;
return String.valueOf((size / 100)) + "."
+ String.valueOf((size % 100)) + "GB";
}
}
}