首页 > WEB开发 > 后台开发 > 文件上传
2014
08-30

文件上传

13. 文件上传5

1、表单的enctype属性

文件上传时,enctype属性必须设置为”multipart/data-form”。该属性指定了提交表单时请求正文的MIME类型,默认值为application/x-www-form-urlencoded。

  • application/x-www-form-urlencoded:username=www.flyne.org&file1=uploadtest.txt
  • multipart/data-form:请求正文见下图

13. 文件上传232

文件上传的原理就是解析出请求正文中的内容

2、Commons-fileupload入门

Commons-fileupload是Apache提供的用来处理文件上传的开源组件。使用该组件需要导入Commons-fileupload及其依赖包:commons-io。Commons-fileupload核心API如下:

13. 文件上传392

FileItem:代表请求正文中的一个表单输入项

ServletFileUpload:核心类,负责解析上传的文件数据,并将表单中每个输入项封装到一个FileItem 对象中。

下面的代码演示了一个简单的文件上传后台处理:

public void doGet(HttpServletRequest request, HttpServletResponse response)	throws ServletException, IOException {

	response.setContentType("text/html;charset=utf-8");
	PrintWriter out = response.getWriter();

	boolean isMultipart = ServletFileUpload.isMultipartContent(request);
	if(!isMultipart){
		out.write("小子,请勿修改encType属性!");
		return;
	}

	//1、创建ServletFileUpload对象
	DiskFileItemFactory factory = new DiskFileItemFactory();
	ServletFileUpload parser = new ServletFileUpload(factory);

	//2、解析请求正文内容,并将数据封装到一个FileItem集合中
	List<FileItem> items = null;
	try {
		items = parser.parseRequest(request);
	} catch (FileUploadException e) {
		throw new RuntimeException("解析上传内容失败,请重试");
	}

	//3、对集合进行迭代,对每个FileItem对象,调用其isFormField方法判断是否是上传文件
	if(items!=null){
		for(FileItem item: items){
			if(item.isFormField()){
			//处理普通表单字段
				processFormField(item);
			}else{
			//处理文件字段
				processUploadFiled(item);
			}
		}
	}

	out.write("上传成功");
}

//处理普通表单字段
private void processFormField(FileItem item) {
	String fieldName = item.getFieldName();
	String fieldValue = item.getString();
	System.out.println(fieldName+":"+fieldValue);
}

//处理文件上传字段
private void processUploadFiled(FileItem item) {
	InputStream in;
	try {
		in = item.getInputStream();
		if(in.available() > 0){
			String filename = item.getName();

			//文件存放路径:位于项目根目录下的files目录,不存在则创建
			String storePath = getServletContext().getRealPath("/files");
			File storeDir = new File(storePath);
			if(!storeDir.exists()){
				storeDir.mkdirs();
			}

			//构建输出流
			OutputStream out = new FileOutputStream(storeDir+File.separator+filename);

			int len = -1;
			byte[] b = new byte[1024];
			while((len = in.read(b)) != -1){
				out.write(b,0,len);
			}
			in.close();
			out.close();
			item.delete();
		}
	} catch (IOException e) {
		throw new RuntimeException(e);
	}
}

3、文件上传详解

1)DiskFileItemFactory

该工厂类用于创建FileItem的实例,对于较小的表单项,FileItem实例的内容保存在内存中,对于大的表单项,FileItem实例的内容会保存在硬盘中的一个临时文件中。大小的阈值和临时文件的路径都可以配置。构造方法如下:

  • DiskFileItemFactory():文件阈值大小为10KB,默认临时文件的路径可以通过System.getProperty(“java.io.tmpdir”) 来获得。
  • DiskFileItemFactory(int sizeThreshold, File repository):你懂得

注:处理文件上传时,自己用IO流处理(如第2节),一定要在流关闭后删除临时文件。
因此建议使用:FileItem.write(File file),会自动删除临时文件。

2)乱码问题

① 普通字段的乱码

解决办法:FileItem.getString(String charset);编码要和客户端一致。

② 上传的中文文件名乱码

解决办法:request.setCharacterEncoding(“UTF-8″);编码要和客户端一致。

3)文件重名问题

当上传的两个文件同名时,第二个文件会覆盖掉第一个文件。

解决方法:a.txt –> UUID_a.txt,使得存入服务器的文件名唯一。

String fileName = item.getName();
fileName = UUID.randomUUID().toString() + "_" + fileName;

4)文件夹中文件过多问题

解决思路:分目录存储。下面提供两种解决方案:

① 按照日期分目录存储:

//按日期分目录存储,程序中修改storePath,形如/files/2014/08/29
DateFormat df = new SimpleDateFormat("/yyyy/MM/dd");
String childDir = df.format(new Date());
//文件存放路径:位于项目根目录下的files目录,不存在则创建
String storePath = getServletContext().getRealPath("/files"+childDir);

② 按照文件名的hashCode计算存储目录(推荐)

//按文件名的hashCode分目录存储
String childDir = mkChildDir(fileName);
//文件存放路径:位于项目根目录下的files目录,不存在则创建
String storePath = getServletContext().getRealPath("/files"+childDir);
-------------------------------------------------------
private String mkChildDir(String fileName) {
	int hashCode = fileName.hashCode();
	int dir1 = hashCode & 0xf;//取hashCode低4位
	int dir2 = (hashCode & 0xf0) >> 4; //取hashCode的低5~8位

	return "/" + dir1 + "/" + dir2;
}

5)限制上传文件的大小和类型

有时候需要对用户上传的文件大小和类型作出限制,如上传头像、证件照时不会很大,且文件MIME类型均为image/*,此时需要在服务器端进行判断。

① 限制大小

  • 单文件大小:ServletFileUpload.setFileSizeMax(2*1024*1024); //限制单文件大小为2M
  • 总文件大小(多文件上传):ServletFileUpload.setSizeMax(6*1024*1024);
// 创建parser对象(ServletFileUpload是核心类,解析请求内容)
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload parser = new ServletFileUpload(factory);

//限制单个文件大小和总文件大小(多文件上传)
parser.setFileSizeMax(2*1024*1024);//限制单文件大小为2M
parser.setSizeMax(6*1024*1024);//总文件大小为6M

//在解析出各个表单输入项时可能会抛出文件大小异常
List<FileItem> items = null;
try {
	items = parser.parseRequest(request);
} catch(FileUploadBase.FileSizeLimitExceededException e){
	out.write("上传文件超出了2M");
	return;
} catch(FileUploadBase.SizeLimitExceededException e){
	out.write("总文件超出了6M");
	return;
} catch (FileUploadException e) {
	throw new RuntimeException("解析上传内容失败,请重试");
}

② 限制类型

一般判断:根据文件扩展名进行判断,缺点是如果更改了扩展名,如a.txt –> a.jpg,这种方法是无法检测出来的。

稍微高级点的:根据文件MIME类型进行判断,缺点是只对IE浏览器有效,对Chrome、FireFox无效,因为后者请求头中文件的MIME类型就是依据扩展名的。

//文件后缀名库
String[] exts = {"jpg","jpeg","png","gif"};
//获取文件后缀名、MIME类型
String ext = FilenameUtils.getExtension(fileName);
String contentType = item.getContentType();

if(Arrays.asList(exts).contains(ext)&&contentType.startsWith("image/")){
	//后续处理
}else{
	//文件扩展名不正确的后续处理
}

100%有效:文件字节的前几位,含有某种类型特有的标记,有兴趣的可以上网搜下。

6)服务器安全问题

假设用户知道你的上传目录,并上传了一个含有Runtime.getRuntime().exec(“xxx”);脚本的JSP文件,就会严重威胁服务器的安全。

解决方法:把存放文件的目录,放到WEB-INF下面。

// 文件存放路径:位于项目根目录下的/WEB-INF/files目录
String storePath = getServletContext().getRealPath("/WEB-INF/files" + childDir);

留下一个回复

你的email不会被公开。