Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)

处理文件上传需要注意的细节

在上一讲文章《Java Web基础入门第六十五讲 文件上传——文件上传概述》中,我们写过一个文件上传的入门案例,但是在实际开发中,你若这样写,无疑就会成为众矢之的,因为在处理文件上传时,需要注意很多的细节,只有彻底地明了这些细节,你才有可能成为一个合格的Java Web开发人员。为了能够一个一个的详解我们要注意的细节,先编写用于测试的文件上传页面——upload.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传页面title>
head>
<body>
	<form action="${pageContext.request.contextPath }/FileUploadServlet" enctype="multipart/form-data" method="post">
		上传用户:<input type="text" name="username" /><br/>
		上传文件1:<input type="file" name="file1"><br/>
		上传文件2:<input type="file" name="file2"><br/>
		<input type="submit" value="上传" />
	form>
body>
html>

上传文件的中文乱码问题

如若处理文件上传的FileUploadServlet,我们是这样写的:

package cn.liayun.web.servlet;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

//处理上传数据
@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		try {
			//创建解析工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();//内存缓冲区的大小,默认值为10K
			
			//创建解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//调用解析器解析request,得到保存了所有上传数据的List集合
			List<FileItem> list = upload.parseRequest(request);
			//迭代List集合,拿到封装了每一个输入项数据的FileItem对象
			for (FileItem item : list) {
				//判断FileItem的类型,如果是普通字段,则直接获取数据,如果是上传文件,则调用流获取数据写到本地硬盘
				if (item.isFormField()) {
					//为普通输入项的数据
					String inputName = item.getFieldName();
					String inputValue = item.getString();
					System.out.println(inputName + "=" + inputValue);
				} else {
					//代表当前处理的item里面封装的是上传文件
					//IE6浏览器获取到的上传文件的名称是D:\a.txt;而IE7则是a.txt
					String filename = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
					InputStream in = item.getInputStream();
					int len = 0;
					byte[] buffer = new byte[1024];
					
					FileOutputStream out = new FileOutputStream("D:\\server_pic\\" + filename);
					while ((len = in.read(buffer)) > 0) {
						out.write(buffer, 0, len);
					}
					out.close();
					in.close();
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

要是上传用户名、上传文件名是中文的话,如:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第1张图片
那么在Eclipse的控制台中就会输出类似下面这样的中文乱码,
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第2张图片
并且有可能(注意:我说的是有可能)D:\server_pic目录中上传的文件会是下面这个鸟样:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第3张图片
这时显然就出现了上传文件的中文乱码问题了。出现问题,就要着手解决问题了,要解决上传文件的中文乱码问题,须设置解析器的编码,到底设置什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的!既然这样,我们立马查看上传文件页面的信息,发现表单的数据是以UTF-8的编码提交的。
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第4张图片
接着我们将FileUploadServlet的代码修改为:

package cn.liayun.web.servlet;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

//处理上传数据
@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		try {
			//创建解析工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();//内存缓冲区的大小,默认值为10K
			
			//创建解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			//解决上传文件的中文名称的中文乱码问题,设置解析器的编码,到底设什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的!
            upload.setHeaderEncoding("UTF-8");
			
			//调用解析器解析request,得到保存了所有上传数据的List集合
			List<FileItem> list = upload.parseRequest(request);
			//迭代List集合,拿到封装了每一个输入项数据的FileItem对象
			for (FileItem item : list) {
				//判断FileItem的类型,如果是普通字段,则直接获取数据,如果是上传文件,则调用流获取数据写到本地硬盘
				if (item.isFormField()) {
					//为普通输入项的数据
					String inputName = item.getFieldName();
					String inputValue = item.getString();
					System.out.println(inputName + "=" + inputValue);
				} else {
					//代表当前处理的item里面封装的是上传文件
					//IE6浏览器获取到的上传文件的名称是D:\a.txt;而IE7则是a.txt
					String filename = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
					InputStream in = item.getInputStream();
					int len = 0;
					byte[] buffer = new byte[1024];
					
					FileOutputStream out = new FileOutputStream("D:\\server_pic\\" + filename);
					while ((len = in.read(buffer)) > 0) {
						out.write(buffer, 0, len);
					}
					out.close();
					in.close();
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

此时上传用户名、上传文件名依然是中文的话,发现上传文件的文件名是没有中文乱码问题了,但是Eclipse的控制台还是输出了username=中国这样的中文乱码,我们不禁就要深究出现这种现象的原因了,原来是Apache开源组织提供的一个用来处理表单文件上传的开源组件(commons-fileupload)的问题,这个组件由于是老外做出来的,所以内部使用的是他们自己的码表,即ISO8859-1。既然是这样,有些人就可能像下面这样子做解决上传普通输入项的中文乱码问题,
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第5张图片
但是,像上面那样设置request.setCharacterEncoding("UTF-8");也是白设置,无效,所以只能手工转换了。
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第6张图片
此时不管是上传普通输入项还是上传文件输入项的中文乱码问题,都予以解决了。考虑到有人还不会手工处理,所以commons-fileupload组件还提供了一个重载的getString方法,可以指定码表,因此FileUploadServlet的代码又可以这样写:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第7张图片

总结

关于上传文件的中文乱码问题:

  • 解决上传文件的中文名称的中文乱码,可使用ServletFileUpload.setHeaderEncoding(“UTF-8”);
  • 解决普通输入项的中文乱码(注意,表单类型为multipart/form-data的时候,设置request的编码是无效的),可使用FileItem.setString(“UTF-8”)。

在处理表单之前须判断提交表单的类型

如果处理文件上传的FileUploadServlet一上来不管三七二十一就劈头盖脸地用解析器进行解析数据,而不管表单类型是不是multipart/form-data,这样做是不合理的。合理的做法应该是:在处理表单之前,要记得调用ServletFileUpload.isMultipartContent方法判断提交表单的类型,如果该方法返回true,则按上传方式处理,否则按照传统方式处理表单即可。所以FileUploadServlet的代码应该修改为:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第8张图片

当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件

DiskFileItemFactory是创建FileItem对象的工厂,其内存缓冲区的大小默认为10K,当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。比方说我们要上传一部电影,电影通常都在几百兆左右,这么大的一个文件显然就已经超出了内存缓冲区的大小,那这个时候我们便可以使用临时文件来缓存上传文件了。
首先,我们在项目的WebRoot根目录下新建一个保存临时文件的temp目录。接着,我们可以设置DiskFileItemFactory的内存缓冲区大小为1M,即内存缓冲区开辟1M大小的空间。如果上传文件没有超过1M,那么fileupload组件将使用内存缓冲区缓存上传文件;如果上传文件超过1M,那么fileupload组件将使用临时文件缓存上传文件。相应地,代码就应该是这样的:
在这里插入图片描述
然后还要指定临时文件的目录,那代码就应该是这样的:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第9张图片
最后用一个流去关联,通过流去读上传数据,其实这个流去读的时候读的是临时文件的数据。这么一路分析下来,FileUploadServlet的代码就该是:

package cn.liayun.web.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

//处理上传数据
@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			//创建解析工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();//内存缓冲区的大小,默认值为10K
			factory.setSizeThreshold(1024 * 1024);//设置内存缓冲区的大小为1M
			factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
			
			//创建解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//提交的表单类型不是multipart/form-data,则没必要用解析器进行解析数据,按照传统方式获取表单数据即可
			if (!upload.isMultipartContent(request)) {
				//按照传统方式获取表单数据
				request.getParameter("username");
				//balabala......
				return;
			}
			
			//解决上传文件的中文名称的中文乱码问题,设置解析器的编码,到底设什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的!
            upload.setHeaderEncoding("UTF-8");
			
			//调用解析器解析request,得到保存了所有上传数据的List集合
			List<FileItem> list = upload.parseRequest(request);
			//迭代List集合,拿到封装了每一个输入项数据的FileItem对象
			for (FileItem item : list) {
				//判断FileItem的类型,如果是普通字段,则直接获取数据,如果是上传文件,则调用流获取数据写到本地硬盘
				if (item.isFormField()) {
					//为普通输入项的数据
					String inputName = item.getFieldName();
					String inputValue = item.getString("UTF-8");//解决普通输入项的中文乱码问题,这句代码等同于下面这句代码
					//inputValue = new String(inputValue.getBytes("ISO8859-1"), "UTF-8");
					System.out.println(inputName + "=" + inputValue);
				} else {
					//代表当前处理的item里面封装的是上传文件
					//IE6浏览器获取到的上传文件的名称是D:\a.txt;而IE7则是a.txt
					String filename = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
					InputStream in = item.getInputStream();
					int len = 0;
					byte[] buffer = new byte[1024];
					
					FileOutputStream out = new FileOutputStream("D:\\server_pic\\" + filename);
					while ((len = in.read(buffer)) > 0) {
						out.write(buffer, 0, len);
					}
					out.close();
					in.close();
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

测试,发现不仅D:\server_pic目录下有我们上传的视频文件,而且{tomcat安装目录}\webapps\day18\temp目录下还有一份上传的视频文件,如下图所示。
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第10张图片
这就等于如若我们上传一个视频文件,服务器就要保存两份,这样的话,你服务器的硬盘买都买不及。因此在上传完了之后,务必调用FileItem.delete方法删除临时文件。那么,FileUploadServlet的代码应该就是这样的了:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第11张图片
大家千万要注意:delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。为了确保流关闭以及删除掉临时文件,最好把这些代码放到finally代码块中。

上传文件的保存目录

大家在做上传文件时,总该会想我们上传的文件会保存在哪儿吧?绝大部分的程序员可能会在WebRoot根目录下新建一个upload目录,在此目录下保存上传的文件,那么我们的FileUploadServlet就要修改为:

package cn.liayun.web.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

//处理上传数据
@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			//创建解析工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();//内存缓冲区的大小,默认值为10K
			factory.setSizeThreshold(1024 * 1024);//设置内存缓冲区的大小为1M
			factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
			
			//创建解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//提交的表单类型不是multipart/form-data,则没必要用解析器进行解析数据,按照传统方式获取表单数据即可
			if (!upload.isMultipartContent(request)) {
				//按照传统方式获取表单数据
				request.getParameter("username");
				//balabala......
				return;
			}
			
			//解决上传文件的中文名称的中文乱码问题,设置解析器的编码,到底设什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的!
            upload.setHeaderEncoding("UTF-8");
			
			//调用解析器解析request,得到保存了所有上传数据的List集合
			List<FileItem> list = upload.parseRequest(request);
			//迭代List集合,拿到封装了每一个输入项数据的FileItem对象
			for (FileItem item : list) {
				//判断FileItem的类型,如果是普通字段,则直接获取数据,如果是上传文件,则调用流获取数据写到本地硬盘
				if (item.isFormField()) {
					//为普通输入项的数据
					String inputName = item.getFieldName();
					String inputValue = item.getString("UTF-8");//解决普通输入项的中文乱码问题,这句代码等同于下面这句代码
					//inputValue = new String(inputValue.getBytes("ISO8859-1"), "UTF-8");
					System.out.println(inputName + "=" + inputValue);
				} else {
					//代表当前处理的item里面封装的是上传文件
					//IE6浏览器获取到的上传文件的名称是D:\a.txt;而IE7则是a.txt
					String filename = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
					InputStream in = item.getInputStream();
					int len = 0;
					byte[] buffer = new byte[1024];
					
					//FileOutputStream out = new FileOutputStream("D:\\server_pic\\" + filename);
					FileOutputStream out = new FileOutputStream(this.getServletContext().getRealPath("/upload") + "\\" + filename);
					while ((len = in.read(buffer)) > 0) {
						out.write(buffer, 0, len);
					}
					out.close();
					in.close();
					
					/*
                     * 上传文件完了之后,定要删除临时文件,
                     * 千万注意:这句代码一定要放在流关闭之后,否则,还有流和它相关联,那就删除不掉临时文件,
                     * 为了确保流关闭、删除掉临时文件,最好把这些代码放到finally代码块中。
                     */
					item.delete();//删除临时文件,必须位于流关闭之后
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

测试,如下:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第12张图片
注意:此时上传文件时不能只上传一个文件,如我只上传1.jsp文件,此时会抛如下异常:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第13张图片
为什么会抛出以上异常呢?在后一讲中,我就会分析其原因了。此处,给大家留一个悬念,读者也可以适当思考一下。
要是我们上传的文件有一个1.jsp这样的文件,该文件内容如下:
在这里插入图片描述
意思即运行以上代码将会在200秒后强行关闭计算机。
我们点击上传按钮,可以看到在诸如{tomcat安装目录}\webapps\day18\upload这样的目录下就会有我们上传的1.jsp文件,这个文件是可以被外界直接访问的,我们通过浏览器输入URL地址http://localhost:8080/day18/upload/1.jsp进行访问,这时就会弹出诸如这样的窗口:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第14张图片
而且这样的窗口还特难关闭,只有通过命令行窗口执行shutdown -a的命令才能关闭。这说明了上传文件的保存目录绝对不能让外界直接访问到,如果要是让破坏者知道了上传文件的保存目录是暴露的,那么他就可以写一个jsp文件上传到服务器,然后访问该jsp页面,对服务器为所欲为了。所以为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。
于是,我们可以在WebRoot根目录下的WEB-INF目录中新建一个upload目录,在此目录下保存上传的文件,这样,FileUploadServlet的代码就应该修改为:

package cn.liayun.web.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

//处理上传数据
@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			//创建解析工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();//内存缓冲区的大小,默认值为10K
			factory.setSizeThreshold(1024 * 1024);//设置内存缓冲区的大小为1M
			factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
			
			//创建解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//提交的表单类型不是multipart/form-data,则没必要用解析器进行解析数据,按照传统方式获取表单数据即可
			if (!upload.isMultipartContent(request)) {
				//按照传统方式获取表单数据
				request.getParameter("username");
				//balabala......
				return;
			}
			
			//解决上传文件的中文名称的中文乱码问题,设置解析器的编码,到底设什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的!
            upload.setHeaderEncoding("UTF-8");
			
			//调用解析器解析request,得到保存了所有上传数据的List集合
			List<FileItem> list = upload.parseRequest(request);
			//迭代List集合,拿到封装了每一个输入项数据的FileItem对象
			for (FileItem item : list) {
				//判断FileItem的类型,如果是普通字段,则直接获取数据,如果是上传文件,则调用流获取数据写到本地硬盘
				if (item.isFormField()) {
					//为普通输入项的数据
					String inputName = item.getFieldName();
					String inputValue = item.getString("UTF-8");//解决普通输入项的中文乱码问题,这句代码等同于下面这句代码
					//inputValue = new String(inputValue.getBytes("ISO8859-1"), "UTF-8");
					System.out.println(inputName + "=" + inputValue);
				} else {
					//代表当前处理的item里面封装的是上传文件
					//IE6浏览器获取到的上传文件的名称是D:\a.txt;而IE7则是a.txt
					String filename = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
					InputStream in = item.getInputStream();
					int len = 0;
					byte[] buffer = new byte[1024];
					
					//FileOutputStream out = new FileOutputStream("D:\\server_pic\\" + filename);
					//FileOutputStream out = new FileOutputStream(this.getServletContext().getRealPath("/upload") + "\\" + filename);
					FileOutputStream out = new FileOutputStream(this.getServletContext().getRealPath("/WEB-INF/upload") + "\\" + filename);
					while ((len = in.read(buffer)) > 0) {
						out.write(buffer, 0, len);
					}
					out.close();
					in.close();
					
					/*
                     * 上传文件完了之后,定要删除临时文件,
                     * 千万注意:这句代码一定要放在流关闭之后,否则,还有流和它相关联,那就删除不掉临时文件,
                     * 为了确保流关闭、删除掉临时文件,最好把这些代码放到finally代码块中。
                     */
					item.delete();//删除临时文件,必须位于流关闭之后
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

总结

在做文件上传系统时,千万要注意上传文件的保存目录,这个上传文件的保存目录绝对不能让外界直接访问到。

限制上传文件的类型

我们在做文件上传系统的时候,可以限制上传文件的类型,如我这个文件上传系统只支持XXX.jpg、XXX.gif、XXX.avi、XXX.txt等格式的文件上传,其他格式的文件我是拒绝的。要做到这点其实不难,只要在处理上传文件时,判断上传文件的后缀名是不是允许的即可。这样,FileUploadServlet的代码就应该修改为:

package cn.liayun.web.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

//处理上传数据
@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		List<String> types = Arrays.asList(".jpg", ".gif", ".avi", ".txt");
		
		try {
			//创建解析工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();//内存缓冲区的大小,默认值为10K
			factory.setSizeThreshold(1024 * 1024);//设置内存缓冲区的大小为1M
			factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
			
			//创建解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//提交的表单类型不是multipart/form-data,则没必要用解析器进行解析数据,按照传统方式获取表单数据即可
			if (!upload.isMultipartContent(request)) {
				//按照传统方式获取表单数据
				request.getParameter("username");
				//balabala......
				return;
			}
			
			//解决上传文件的中文名称的中文乱码问题,设置解析器的编码,到底设什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的!
            upload.setHeaderEncoding("UTF-8");
			
			//调用解析器解析request,得到保存了所有上传数据的List集合
			List<FileItem> list = upload.parseRequest(request);
			//迭代List集合,拿到封装了每一个输入项数据的FileItem对象
			for (FileItem item : list) {
				//判断FileItem的类型,如果是普通字段,则直接获取数据,如果是上传文件,则调用流获取数据写到本地硬盘
				if (item.isFormField()) {
					//为普通输入项的数据
					String inputName = item.getFieldName();
					String inputValue = item.getString("UTF-8");//解决普通输入项的中文乱码问题,这句代码等同于下面这句代码
					//inputValue = new String(inputValue.getBytes("ISO8859-1"), "UTF-8");
					System.out.println(inputName + "=" + inputValue);
				} else {
					//代表当前处理的item里面封装的是上传文件
					//IE6浏览器获取到的上传文件的名称是D:\a.txt;而IE7则是a.txt
					String filename = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
					
					//拿到文件的扩展名
					String ext = filename.substring(filename.lastIndexOf("."));
					if (!types.contains(ext)) {
						request.setAttribute("message", "本系统不支持" + ext + "这种类型文件的上传");
						request.getRequestDispatcher("/message.jsp").forward(request, response);
						return;
					}
					
					InputStream in = item.getInputStream();
					int len = 0;
					byte[] buffer = new byte[1024];
					
					//FileOutputStream out = new FileOutputStream("D:\\server_pic\\" + filename);
					//FileOutputStream out = new FileOutputStream(this.getServletContext().getRealPath("/upload") + "\\" + filename);
					FileOutputStream out = new FileOutputStream(this.getServletContext().getRealPath("/WEB-INF/upload") + "\\" + filename);
					while ((len = in.read(buffer)) > 0) {
						out.write(buffer, 0, len);
					}
					out.close();
					in.close();
					
					/*
                     * 上传文件完了之后,定要删除临时文件,
                     * 千万注意:这句代码一定要放在流关闭之后,否则,还有流和它相关联,那就删除不掉临时文件,
                     * 为了确保流关闭、删除掉临时文件,最好把这些代码放到finally代码块中。
                     */
					item.delete();//删除临时文件,必须位于流关闭之后
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

限制上传文件的大小

我们也可以设置单个上传文件的大小,只要超出我们设置的值,就不让其上传,并给用户以友好提示。有些人还是要以身试险的,如我调用解析器的setFileSizeMax方法设置上传文件的最大值为5M,即ServletFileUpload.setFileSizeMax(1024*1024*5);,只要有人上传的文件大于5M,试想解析器在解析这份文件时,应如何做呢?可以想见,解析器就会抛出一个异常,翻阅commons-fileupload组件的文档,就能找到这样一个异常——FileUploadBase.FileSizeLimitExceededException,不错,我们就是要抛它,然后程序员通过是否抓到这个异常,进而就可以给用户友好提示。那么,FileUploadServlet的代码就应该修改为:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第15张图片
这时,如果我们上传一个大于5M的文件,照理说会跳转到全局消息显示页面,给我们以文件大小不能超过5M的友好提示,但是我们发现非但跳转不过去,而且Chrome浏览器会报出如下异常:
Java Web基础入门第六十六讲 文件上传——处理文件上传需要注意的细节(上)_第16张图片
这是为什么呢?我真不明所以啊,哪个好心人给明示一下啊!容我最后再插一嘴,上面只是做了限制单个文件上传的大小,而限制上传文件总量,比较麻烦,并且在实际开发中一般不会有这个需求,所以在此我没有详解。

你可能感兴趣的:(Java,Web基础入门)