此文标题提到的三个功能其实都是可以整合在一起的,于是我就写在了同一段代码中了。
先还是老规矩:index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<form action="<%=request.getContextPath() %>/upload2" method="post" enctype="multipart/form-data">
文件1:<input type="file" name="file" /><br/>
文件1的说明:<input type="text" name="desc1"/><br/>
文件2:<input type="file" name="file" /><br/>
文件2的说明:<input type="text" name="desc2"/><br/>
<input type="submit" value="提交" />
</form>
</body>
</html>
然后是正文了:UploadServlet2.java
package cn.hncu.servlets.upload;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadServlet2 extends HttpServlet {
//☆防黑1--在地址栏直接提交
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("不支持get方式上传!!");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
final PrintWriter out=response.getWriter();
//☆防黑2--非multipart表单提交
/*手动方式
String type=request.getContentType();
if (!type.contains("multipart/form-data")){
out.print("不支持普通表单提交!");
return;
}
*/
//使用上传工具的方式
boolean boo=ServletFileUpload.isMultipartContent(request);
if (!boo){
out.print("不支持普通表单提交!");
return;
}
//缓冲文件
DiskFileItemFactory factory=new DiskFileItemFactory();
factory.setSizeThreshold(1024*8);
factory.setRepository(new File("d:/a/b"));//文件上传需要临时目录(如果不指定,那么该目录就是tomcat/temp )
/*
上面三句其实就相当于下面两句
File tempDir=new File("d:/a/b");
DiskFileItemFactory fileFactory=new DiskFileItemFactory(1024*8, tempDir);//创建用于解析文件的工厂类,同时设置缓冲区的位置和大小
*/
ServletFileUpload upload=new ServletFileUpload(factory);
upload.setHeaderEncoding("utf-8");//用于设置文件名的编码,相当于:request.setCharacterEncoding("utf-8");
String path=getServletContext().getRealPath("/imgs");//获取所接收文件要保存的路径
//☆文件上传进度功能---设置监听器
upload.setProgressListener(new ProgressListener() {
private int pre=0;
//参数解析---- pBytesRead:已上传字节数 pContentLength:上传的总字节数 pItems:文件序号(从1开始的)
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
double p=pBytesRead*100.0/pContentLength;//上传的总字节数
int pp=(int) p;
if (pre!=pp){
out.print(pp+"%<br/>");
pre=pp;
}
}
});
FileItem fi=null;
try {
List<FileItem> list=upload.parseRequest(request);
for (FileItem fi2:list){
fi=fi2;
if (fi.isFormField()){//普通表单组件,如:<input type="text" name="desc1"/>
String str=fi.getString("utf-8");//※以指定编码的方式获取,来解决普通表单组件的中文乱码问题
System.out.println("普通表单组件提交的内容:"+str);
} else {//表单中的:file组件
//☆防黑3--在file组件中不选择文件
if (fi.getSize()==0){
continue;
}
//文件名
String fileName=fi.getName();
fileName=fileName.substring(fileName.lastIndexOf("/")+1);
String ext=fileName.substring(fileName.lastIndexOf("."));
//文件名不能用中文,必须转换成ascii码的格式,而且文件名不能重复(必须保证唯一),因此采用UUID来实现
String newFileName=UUID.randomUUID().toString().replace("-", "")+ext;
//☆打散目录(因为对于普通机器,一个文件夹如果存储的文件个数超过1000,性能将会急剧下降!! )
String dir1=Integer.toHexString((fileName.hashCode()) & 0xf);//一级目录
String dir2=Integer.toHexString(((fileName.hashCode()) & 0xf0)>>4);//二级目录
File dir=new File(path+"/"+dir1+"/"+dir2);
if (!dir.exists()){
dir.mkdirs();
}
File file=new File(path+"/"+dir1+"/"+dir2+newFileName);
fi.write(file);
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fi==null){
fi.delete();//清临时文件
}
}
}
}
这里的文件上传使用到了apache的两个公共包,commons-fileupload-1.2.2.jar和commons-io-2.1.jar
将这两个包导到项目的WEB-INF下的lib目录下即可。
关于文件上传:
1、文中使用ServletFileUpload类新建了一个upload对象,这个对象还有两个方法没有出现在文中,但是也是经常使用的,setFileSizeMax(long fileSizeMax)设置所有上传文件大小之和的最大值和setSizeMax(long sizeMax)设置每个文件最大值。如下:
upload.setFileSizeMax(1024*1024*8);//设置每个文件最大为8M
upload.setSizeMax(1024*1024*20);//所有上传文件大小之和的最大值为20M
2、request.setCharacterEncoding("utf-8");这句解决上传文件名的中文乱码
注意,上面这句设置中文,如果是“multipart/form-data”表单,可以设置其中file组件的文件名,但对其中的普通表单组件无效。
注意,上面这句设置中文,如果是我们以前用的“application/x-www-form-urlencoded”表单,可以设置其中的普通表单组件。
3、代码中最后将fi写到指定目录时,还可以使用如下方法:
FileUtils.copyInputStreamToFile(fi.getInputStream(), file);
//真正的文件内容其实在 fi.getInputStream() 当中
关于打散目录:
对于每一个字符串,他都有一个hash值与之对应,这里就是用他的最后四位与最后四位全为16(十六进制的 f ),其他全为0的十六进制数进行与运算得出来的十六进制数转换成字符串作为一级目录;用他的后五到八位全为16(十六进制的 f ),其他全为0的十六进制数进行与运算得出来的十六进制数全部右移四位再转换成字符串作为二级目录。
关于进度条:
这里我们只是做了一个最简单的进度条的演示,而且还不能做成平时电脑上操作时的那种进度条,我这里只是将其输出到网页上而已,效果如下:
这种看上去会让人失望,但的确是有在监控的,因为这里没有使用ajax技术,无法做到点对点的监控,等后面我会使用ajax技术重新做一个的。
这里其实就是使用了ServletFileUpload类中的一个函数setProgressListener(),早代码中已经体现出来了。
最后就是关于抓异常最后的finally中清缓冲区中的临时文件的,在那里清除的意义在于防黑,假如有人上传一半就中断了,那么就会跳到finally那里去,这个时候就应该清除缓存了。