JavaWeb之 FileUpLoad框架下文件上传过程中的问题盘点

关于文件上传问题,就不过多的阐述了,网上的框架很多,基本随取随用了,这里主要就文件上传过程中的一些细节进行一个盘点,对各种问题的解决也做一个总结,也希望能帮到每个走在IT路上的童鞋们。

直接用IO流进行文件的上传和下载可以实现没问题,但是有点太耗时了,用别人封装好的比较成熟稳定的框架是一个不错的选择,今天的样例以阿帕奇的fileUpload框架为基础进行

先上一段我自己写的利用fileUpload框架上传文件的原始核心代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	/*
	 * 实现思路:
	 * 1、判断表单enctype属性是否为multipart/form-data类型
	 * 2、创建一个DiskFileItemFactory类
	 * 3、通过DiskFileItemFactory类创建一个ServletFileUpload对象
	 * 4、解析request,得到一个表单Item的集合List<FlieItem>	
	 * 5、遍历list,判断是Items是否为文件,如果是,则创建流读取
	 * 		5.1不是文件类型,则控制台输出
	 * 		5.2是文件,则创建流读取
	 * 
	 *  */
		//因为要输出提示内容,所以需要先设置response字符集
		response.setContentType("text/html;charset=utf-8");
		PrintWriter writer = response.getWriter();
		String basePath = this.getServletContext().getRealPath("/files");
		System.out.println(basePath);
		//利用ServletFileUpload的静态方法判断表单enctype属性是否为multipart/form-data
		boolean ismultipart = ServletFileUpload.isMultipartContent(request);
		if (ismultipart) {

//			创建一个DiskFileItemFactory类
			DiskFileItemFactory factory = new DiskFileItemFactory();
//			利用factory类构建一个ServletFileUpload 对象
			ServletFileUpload sfu = new ServletFileUpload(factory);
//			解析request,获取List集合
			List<FileItem> Items;
			try {
				Items = sfu.parseRequest(request);
//				遍历集合
				for (FileItem fileItem : Items) {
					if (fileItem.isFormField()) {
//						如果是表单字段则打印出来
						String name = fileItem.getName();
						String value = fileItem.getString();
						System.out.println(name+","+value);
					} else {
//						上传字段,则保存到服务器
//						获取文件名
						String filePath = fileItem.getName();
//						截取文件名
						String fileName = filePath.substring(filePath.lastIndexOf(File.separator)+1);
//						获取文件输入流
						InputStream is = fileItem.getInputStream();
//						使用绝对路径构建一个文件
						File file = new File(basePath);
						if (!file.exists()) {
							file.mkdirs();
						}
						
						byte[] by = new byte[1024];
						int len = -1;
						
						OutputStream os = new FileOutputStream(basePath+File.separator+fileName);
						while ((len = is.read(by))!=-1) {
							os.write(by, 0, len);
						}
						is.close();
						os.close();

					}
				}			
				
			} catch (FileUploadException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			
		} else {
			writer.println("请检查你的表单是否支持文件上传");
		}
		
		
	}

上面这段代码是实现文件上传功能最简单最核心的实现过程,但是这段代码中也存在诸多安全隐患及各种问题需要处理,接下来,我们就一个一个的盘点,然后找出解决之道,使程序的健壮性更强。

问题一、文件的安全问题。

涉及到文件的上传,一般是用户行为,我们对用户上传的文件以及意图一般是无法把控的,如果不进行防范,就存在用户这就涉及到安全问题。

一般的安全问题都是用户通过上传问题文件导致的,比如一个.jsp文件,然后通过浏览器访问,以执行文件中的代码,如果该文件中有恶意代码,就会影响服务端的正常工作,所以,这个安全问题是必须考虑的。
解决此问题的办法就是将上传的文件放到 WEB-INF之下,使客户端无法通过客户端访问上传的文件,无法执行恶意代码
如下:

	String basePath = this.getServletContext().getRealPath("/WEB-INF/files");

如此一来,客户无法通过浏览器访问问题文件,就可大大提高程序的安全性。

问题二、中文乱码问题。

中文乱码一般有两个地方涉及,其一是普通字段的中文乱码问题,其二是中文文件名的乱码问题。
第一种的解决方案是:利用FileItem对象的getString方法设置字符集

item.getString("UTF-8");

第二种的解决方案是:设置请求对象字符集解决中文文件名乱码问题

request.setCharacterEncoding("UTF-8");

问题三、文件同名问题。

同名文件问题也是一个问题,如果同一目录下有上传了两个同名同类型的文件,后面的文件会将前面的文件覆盖掉,所以,这个问题也是需要解决的,解决方法很简单,原则就是文件命名时不使重名就可以了
两种解决方案:

1、利用时间戳命名文件

long time = System.currentTimeMillis();
String currentFileName = String.valueOf(time)+"_"+fileName;

2、利用UUID随机数命名文件

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

问题四、文件的目录问题

一般来说,每个文件夹中的文件数量是有一定限制的,根据系统和存储格式不同有不同的限制标准,再退一步说,即使文件夹中可以存放无限个文件,这样以来对于文件的查找和使用,也是非常不方便的,所以,我们不可能将文件统一放到同一个文件夹中,这时就需要进行分目录存放。

方案一、使用固定日期格式存储:

使用日期来创建文件目录:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
String datePath = format.format(new Date());

但是如果遇到的是类似与记录日志的功能,按照日期存储仍然可能出现一个目录下文件过多的情况,这样就可能无法解决我们的问题,此时,我们可以采用下面这个方法

方案二、使用使用UUID文件的hashCode计算两级目录

int code = UUIDFileName.hashCode();
int dir1 = 0xf&code;
int dir2 = (0xf0&code)>>4;

问题五、文件大小问题

对于用户上传文件,我们一般都会限制上传文件的大小,以减少资源的消耗和服务器的压力,对于upload框架来说,提供了两个非常好用的方法可以解决这个问题
1、限制单个文件的大小

	upload.setFileSizeMax(10*1024*1024);

2、限制文件总大小

	upload.setSizeMax(20*1024*1024);

问题六、文件类型问题

我们很多时候做上传文件需求时,往往需要限制用户上传的文件类型,比如上传头像或者其他文件类型,等等,这个就需要我们对用户上传的文件类型做限制,可是,这个限制往往也跟浏览器的功能有关,我们无法做到绝对的限制住用户上传的文件类型(如果有童鞋知道其他解决方案,也欢迎交流分享啊),只能最大限度的规避。

	//						判断文件的后缀是否为要求的类型
						String extention = fileName.substring(fileName.lastIndexOf(".")+1);
//						定义允许上传文件的类型
						String extentions = "txt";
						int e =extentions.indexOf(extention);
						if(e<0){
							writer.write("请确认您上传的文件类型是否符合要求");
							return;
						}
//						通过FileItem的一个方法来获取文件内容的MIME类型
						if (!fileItem.getContentType().startsWith("text")) {
							writer.write("请确认您上传的文件类型是否符合要求");
							return;
						}

问题七、用户没传全的问题

有时候,我们需要用户上传多个文件,而用户并未选择全部上传,而是选择部分上传,我们就需要对这种情况进行处理

	String fileName = fileItem.getName();
	if ("".equals(fileName)) {
//							如果上传多个文件时有文件为空,则继续循环
		continue;
	} 

问题八、 临时文件问题

FileUpload在上传文件过程中,如果文件大小在10k以内,一般使用内存作为缓存,但针对较大的文件,如视频音频等,便会用到临时文件,所以我们有时候需要创建一个临时文件目录,以供程序使用

File file = new File("D:/temp");
//判断文件目录是否存在
	if(!file.exists()){
 //创建一个临时文件目录
		file.mkdirs();
	}
//设置临时文件的存放目录
	 factory.setRepository(file);
//删除临时文件,注意要放到关闭流之后
	 fileItem.delete();

至此,总结了八个关于上传文件过程中需要注意的问题以及解决方案,以下是优化后的代码,仅供参考!

package com.icbc.fileuploaddemo.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 * 上传文件中的问题:
 * 1、安全问题
 * 		将文件放到WEB-INF文件夹下面
 * 2、文件乱码问题
 * 		2.1、非上传字段乱码问题:获取非上传字段文件名时传入字符集
 * 		2.2、上传文件中文名称问题,需要设置request的字符集
 * 3、文件大小问题
 * 4、文件类型问题
 * 5、文件重名问题
 * 6、分目录存储问题
 * 7、用户没传全
 * 8、临时文件和缓存问题
 */
@WebServlet("/FileUploadServlet3")
public class FileUploadServlet3 extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public FileUploadServlet3() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	/*
	 * 实现思路:
	 * 1、判断表单enctype属性是否为multipart/form-data类型
	 * 2、创建一个DiskFileItemFactory类
	 * 3、通过DiskFileItemFactory类创建一个ServletFileUpload对象
	 * 4、解析request,得到一个表单Item的集合List<FlieItem>	
	 * 5、遍历list,判断是Items是否为文件,如果是,则创建流读取
	 * 		5.1不是文件,则控制台输出
	 * 		5.2是文件,则创建流读取
	 * 
	 *  */
		//因为要输出提示内容,所以需要先设置response字符集
		response.setContentType("text/html;charset=utf-8");
		PrintWriter writer = response.getWriter();
//		设置请求字符集
		request.setCharacterEncoding("UTF-8");
		String basePath = this.getServletContext().getRealPath("/WEB-INF/files");
		System.out.println(basePath);
		//利用ServletFileUpload的静态方法判断表单enctype属性是否为multipart/form-data
		boolean ismultipart = ServletFileUpload.isMultipartContent(request);
		if (ismultipart) {

//			创建一个DiskFileItemFactory类
			DiskFileItemFactory factory = new DiskFileItemFactory();
			File temp = new File("D:/temp");
			//判断文件目录是否存在
			if (!temp.exists()) {
				 //创建一个临时文件目录
				temp.mkdirs();
			}
			//设置临时文件的存放目录
			factory.setRepository(temp);
//			利用factory类构建一个ServletFileUpload 对象
			ServletFileUpload sfu = new ServletFileUpload(factory);
//			限制上传单个文件大小
//			sfu.setFileSizeMax(10*1024*1024);
//			限制总文件大小
//			sfu.setSizeMax(20*1024*1024);
//			解析request,获取List集合
			List<FileItem> Items;
			try {
				Items = sfu.parseRequest(request);
//				遍历集合
				for (FileItem fileItem : Items) {
					if (fileItem.isFormField()) {
//						如果是表单字段则打印出来
						String name = fileItem.getFieldName();
						String value = fileItem.getString("UTF-8");
						
						System.out.println(name+","+value);
					} else {
//						上传字段,则保存到服务器
//						获取文件名
						String fileName = fileItem.getName();
						if ("".equals(fileName)) {
//							如果上传多个文件时有文件为空,则继续循环
							continue;
						} 
//						判断文件的后缀是否为要求的类型
//						String extention = fileName.substring(fileName.lastIndexOf(".")+1);
						定义允许上传文件的类型
//						String extentions = "txt";
//						int e =extentions.indexOf(extention);
//						if(e<0){
//							writer.write("请确认您上传的文件类型是否符合要求");
//							return;
//						}
//						通过FileItem的一个方法来获取文件内容的MIME类型
//						if (!fileItem.getContentType().startsWith("text")) {
//							writer.write("请确认您上传的文件类型是否符合要求");
//							return;
//						}
//						截取文件名
						fileName = fileName.substring(fileName.lastIndexOf(File.separator)+1);
						String fullPath = dealFilePath(basePath, fileName);
//						获取文件输入流
						InputStream is = fileItem.getInputStream();

						byte[] by = new byte[1024];
						int len = -1;
						
						OutputStream os = new FileOutputStream(fullPath);
						while ((len = is.read(by))!=-1) {
							os.write(by, 0, len);
						}
						is.close();
						os.close();
						//删除临时文件,注意要放到关闭流之后
						fileItem.delete();

					}
				}
				
				
				
				
				
			} catch (FileUploadBase.FileSizeLimitExceededException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (FileUploadBase.SizeLimitExceededException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (FileUploadException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			
		} else {
			writer.println("请检查你的表单是否支持上传");
		}
		
		
	}
	
	/**
	 * 处理同名文件问题和文件目录问题
	 * @param path
	 * @param fileName
	 * @return
	 */
	protected String dealFilePath(String basePath,String fileName) {
//		1、利用时间戳命名文件
//		long time = System.currentTimeMillis();		
//		String currentFileName = String.valueOf(time)+"_"+fileName;
//		2、使用UUID命名文件
		String currentFileName = UUID.randomUUID().toString()+"_"+fileName;
//		使用日期命名文件夹
//		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//		String datePatten = sdf.format(new Date());
//		使用UUID的hashcode命名文件夹
		int code = UUID.randomUUID().hashCode();
		int dir1 = 0xf&code;
		int dir2 = (0xf0&code)>>4;
		
//		String fullPath = basePath+File.separator+datePatten+File.separator+currentFileName;
		String fullPath = basePath+File.separator+dir1+File.separator+dir2+File.separator+currentFileName;
//		使用绝对路径构建一个文件
		File file = new File(fullPath);
		if (!file.exists()) {
			file.getParentFile().mkdirs();
			try {
				file.createNewFile();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
//			file.mkdirs();
		}
		
		
		return fullPath;
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值