一、文件上传的原理
1、文件上传的前提:
a、form表单的method必须是post
b、form表单的enctype必须是multipart/form-data(决定了POST请求方式,请求正文的数据类型)
注意:当表单的enctype是multipart/form-data,传统的获取请求参数的方法失效。
---------------------------------------------------------------------------------------------------------------------------
请求正文:(MIME协议进行描述的,正文是多部分组成的)
-----------------------------7dd32c39803b2 (// 分界符)
Content-Disposition: form-data; name="username"
wzhting
-----------------------------7dd32c39803b2
Content-Disposition: form-data; name="f1"; filename="C:\Documents and Settings\wzhting\妗岄潰\a.txt"
Content-Type: text/plain
aaaaaaaaaaaaaaaaaa
-----------------------------7dd32c39803b2
Content-Disposition: form-data; name="f2"; filename="C:\Documents and Settings\wzhting\妗岄潰\b.txt"
Content-Type: text/plain
bbbbbbbbbbbbbbbbbbb
-----------------------------7dd32c39803b2--
---------------------------------------------------------------------------------------------------------------------------
c、form中提供input的type是file类型的文件上传域
二、利用第三方组件实现文件上传
1、commons-fileupload组件:
jar:commons-fileupload.jar 和 commons-io.jar
2、 fileupload组件工作流程
3、核心类或接口
DiskFileItemFactory:设置环境
public void setSizeThreshold(int?sizeThreshold) :
设置缓冲区大小。默认是10Kb。
当上传的文件超出了缓冲区大小,fileupload组件将使用临时文件缓存上传文件
public void setRepository(java.io.File repository):
设置临时文件的存放目录。默认是系统的临时文件存放目录。
ServletFileUpload:核心上传类(主要作用:解析请求的正文内容)
boolean isMultipartContent(HttpServletRequest?request):
判断用户的表单的enctype是否是multipart/form-data类型的。
List parseRequest(HttpServletRequest request):
解析请求正文中的内容
setFileSizeMax(4*1024*1024); //设置单个上传文件的大小
upload.setSizeMax(6*1024*1024); //设置总文件大小
FileItem:代表表单中的一个输入域。
boolean isFormField(): 是否是普通字段
String getFieldName: 获取普通字段的字段名
String getString(): 获取普通字段的值
InputStream getInputStream(): 获取上传字段的输入流
String getName(): 获取上传的文件名
getContentType(); 得到上传文件的MIME类型
FileItem.delete(); 方法删除临时文件。但一定要在关闭流之后。
4、 文件上传案例
-------------------------------------------------------------------------------------------------------------
<body>
<form action="${pageContext.request.contextPath}/servlet/UploadServlet3" enctype="multipart/form-data" method="post">
用户名: <input type="text" name="username" /> <br/><br/>
文件1: <input type="file" name="f1" /> <br/><br/>
文件2: <input type="file" name="f2" /> <br/><br/>
<input type="submit" value="保存" />
</form>
</body>
-------------------------------------------------------------------------------------------------------------
// 存储目的地: tomacate/webapp/day21/files
public class UploadServlet2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 设置环境
DiskFileItemFactory factory = new DiskFileItemFactory();
// 得到存放上传文件的真实路径
String storePath = getServletContext().getRealPath("/WEB-INF/files");
// 判断一下form是否为enctype=multipart/file-data
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart){
System.out.println("大傻鸟,快去将表单的enctype的值设置为multipart/form-data");
return;
}
// ServletFileUpload核心类
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析
List<FileItem> items = upload.parseRequest(request);
for(FileItem item : items){
if(item.isFormField()){
// 是普通字段
String fieldName = item.getFieldName();
String fieldValue = item.getString();
System.out.println(fieldName+"="+fieldValue);
}else{
// 是上传字段
InputStream in = item.getInputStream();
// 上传的文件名
// 注: 有的浏览器为: c:\Documents and Settings\wzhting\妗岄潰\a.txt 有的为 a.txt
String fileName = item.getName();
fileName = fileName.substring(fileName.lastIndexOf("\\")+1); // 得到文件名: a.txt
// 构件输出流
String storeFile = storePath + "\\" + fileName;
OutputStream out = new FileOutputStream(storeFile);
byte[] b = new byte[1024];
int len = 0;
while((len=in.read(b))!=-1){
out.write(b, 0, len);
}
out.close();
in.close();
}
}
} catch (FileUploadException e) {
throw new RuntimeException("服务器繁忙");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
-------------------------------------------------------------------------------------------------------------
三、文件上传中要注意的9个问题
1、如何保证服务器的安全
把保存上传文件的目录放到WEB-INF目录中。
2、中文乱码问题
2.1普通字段的中文请求参数
解决办法:String value = FileItem.getString("UTF-8");
2.2上传的文件名是中文
解决办法:request.setCharacterEncoding("UTF-8");
3、重名文件被覆盖的问题
System.currentMillions()+"_"+a.txt(乐观, 可能重复)
UUID+"_"+a.txt:保证文件名唯一
4、分目录存储上传的文件
方式一:当前日期建立一个文件夹,当前上传的文件都放到此文件夹中。
方式二:利用文件名的hash码打散目录来存储。
--------------------------------------------------------------------------------------
int hashCode = fileName.hashCode();
执行运算; hashCode&0xf;
1001 1010 1101 0010 1101 1100 1101 1010
& 0000 0000 0000 0000 0000 0000 0000 1111
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1010
取hashCode的后4位 范围:0000~1111:整数0~15共16个
执行运算: (hashCode&0xf0)>>4
1001 1010 1101 0010 1101 1100 1101 1010
& 0000 0000 0000 0000 0000 0000 1111 0000
---------------------------------------------------
0000 0000 0000 0000 0000 0000 1101 0000 >>4
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1101
0000~1111:整数0~15共16个
--------------------------------------------------------------------------------------
5、限制用户上传的文件类型
通过判断文件的扩展名来限制是不可取的。
通过判断其Mime类型才靠谱。FileItem.getContentType();
6、如何限制用户上传文件的大小
6.1单个文件大小限制。超出了大小友好提示
抓异常进行提示:
org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
6.2总文件大小限制。超出了大小友好提示
抓异常进行提示:
org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
7、临时文件的问题
commons-fileupload组件不会删除超出缓存的临时文件。
FileItem.delete()方法删除临时文件。但一定要在关闭流之后。
8、多个文件上传时,没有上传内容的问题
if(fileName==null||"".equals(fileName.trim())){
continue;
}
9、上传进度检测
给ServletFileUpload注册一个进度监听器即可,把上传进度传递给页面去显示
//pBytesRead:当前以读取到的字节数
//pContentLength:文件的长度
//pItems:第几项
public void update(long pBytesRead, long pContentLength,
int pItems) {
System.out.println("已读取:"+pBytesRead+",文件大小:"+
pContentLength+",第几项:"+pItems);
}
例:
--------------------------------------------------------------------------------------------------
public class UploadServlet3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 解决上传文件名的中文编码问题
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
try {
// 设置环境
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置临时存放目录 在webroot下创建一个temp的文件夹 查看的时候在apatch查看
factory.setRepository(new File(getServletContext().getRealPath("/temp")));
// 得到存放上传文件的真实路径
String storePath = getServletContext().getRealPath("/WEB-INF/files");
// 判断一下form是否为enctype=multipart/file-data
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart){
System.out.println("大傻鸟,快去将表单的enctype的值设置为multipart/form-data");
return;
}
// ServletFileUpload核心类
ServletFileUpload upload = new ServletFileUpload(factory);
// 进度监听
upload.setProgressListener(new ProgressListener() {
// 关联源代码(common-fileupload.jar 显示参数)
// pBytesRead: 当前已读取到的字节数 pContentLength: 文件的长度 pItem: 第几项,从1开始
public void update(long pBytesRead, long pContentLength,int pItems) {
System.out.println("已读取:"+pBytesRead+",文件大小:"+pContentLength+
",第几项:"+pItems);
}
});
// 设置单个上传文件的大小不能超过8M
// upload.setFileSizeMax(8*1024*1024);
// 设置上传的总文件的大小不能超过15M
// upload.setSizeMax(15*1024*1024);
// 解析
List<FileItem> items = upload.parseRequest(request);
for(FileItem item : items){
if(item.isFormField()){
// 是普通字段
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8"); // 解决form表单请求参数的中文编码问题
System.out.println(fieldName+"="+fieldValue);
}else{
// 得到上传文件的MIME类型
// String mimeType = item.getContentType();
// 限制上传文件的类型: 只允许为图片类型
// if(mimeType.startsWith("image")){
// 是上传字段
InputStream in = item.getInputStream();
// 上传的文件名
String fileName = item.getName(); // 注: 有的浏览器为: c:\Documents and Settings\wzhting\妗岄潰\a.txt 有的为 a.txt
// 如果上传的文件为null
if(fileName==null||"".equals(fileName.trim())){
continue;
}
fileName = fileName.substring(fileName.lastIndexOf("\\")+1); // 得到文件名: a.txt
fileName = UUID.randomUUID()+"_"+fileName; // 保证新的文件名不重复,避免了上传的文件的重名覆盖问题
// 打散存储目录
String newStorePath = makeStorePath(storePath,fileName); // 根据WEB-INF/files和文件名,创建一个新的存储路径: /WEB-INF/files/1/13/文件名
String storeFile = newStorePath + "\\" + fileName; // WEN-INF/files/1/13/文件名
// 构件输出流
OutputStream out = new FileOutputStream(storeFile);
byte[] b = new byte[1024];
int len = 0;
while((len=in.read(b))!=-1){
out.write(b, 0, len);
}
out.close();
in.close();
// 删除临时文件 注:一定要在关闭流之后 临时文件在上传完毕后会调用该方法自动删除
item.delete();
}
// }
}
} catch (org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException e){
// 该异常为: 单个文件超出大小时的异常 注: 异常的捕获: 子类异常必须在父类异常的前面捕获
pw.write("单个文件的大小不能超过8M");
} catch (org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException e){
// 该异常为: 上传的总文件超出大小时的异常
pw.write("上传的总文件的大小不能超过15M");
}catch (FileUploadException e) {
e.printStackTrace();
}
}
// 根据/WEN-INF/files和文件名 创建一个新的存储路径: /WEB-INF/files/1/13
private String makeStorePath(String storePath, String fileName) {
int hashCode = fileName.hashCode();
int dir1 = hashCode&0xf; // 0000~1111 整数0~15 共16个
int dir2 = (hashCode&0xf0)>>4; // 0000~1111 整数0~15 共16个
String path = storePath+"\\"+dir1+"\\"+dir2; // WEB-INF/files/1/13
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
return path;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
--------------------------------------------------------------------------------------------------