一、文件上传
1.文件上传的原理分析
1.1文件上传的必要前提:
a.表单的method必须是post
b.表单的enctype属性必须是multipart/form-data类型的
作用:告知服务器,请求正文的MIME类型
urlencoded的正文的格式为:
String name = request.getParameter("name"); 这种获取参数的方法,仅适用于该种类型
1.2将表单设置为enctype = multipart/form-data类型之后,内容的展现类型如下所示
文件头和正文之间用空行隔开,上面是指定的分界符号,用来区分不同的表单提交内容。
1.3文件上传的原理:
解析请求正文的内容
c.表单中提供type="file"类型的上传组件
2.借助第三方组件实现文件上传
借助第三方组件,Commons-fileupload组件来实现文件的上传。
package com.itheima.servlet;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.List;
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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import com.sun.corba.se.impl.orb.ParserTable;
//借助commons-fileupload 实现文件上传的简单案例
public class UploadServlet2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置响应正文的编码格式,防止中文乱码
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//判断用户提交的正文类型是不是multipart/form-data类型
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart){
throw new RuntimeException("请检查您的表单的enctype属性,确定是multipart/form-data");
}
//创建DiskFileItemFactory
DiskFileItemFactory dfif = new DiskFileItemFactory();
ServletFileUpload parser = new ServletFileUpload(dfif);
List<FileItem> items = null ;
//解析内容
try {
items = parser.parseRequest(request);
} catch (FileUploadException e) {
throw new RuntimeException("解析上传内容失败,请重新试一下");
}
//处理请求内容
if(items != null){
for(FileItem item:items){
if(item.isFormField()){
//普通表单字段
processFormField(item);
}
else{
//上传的文件字段
processUploadField(item);
}
}
out.write("上传成功");
}
}
//上传字段
private void processUploadField(FileItem item) {
try {
// InputStream in = item.getInputStream(); //输入流
String fileName = item.getName(); //上传文件的名称 C:\Users\zhangjianbo\Desktop\a.txt a.txt
//截取文件名
// fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
if(fileName!=null){
fileName = FilenameUtils.getName(fileName);
}
//把文件存到服务器的某个目录中
String storeDirectoryPath = getServletContext().getRealPath("/files");
File storeDirectory = new File(storeDirectoryPath);
if(!storeDirectory.exists()){
storeDirectory.mkdirs();
}
// //构建输出流,输出流需要指定文件写出的路径
// OutputStream out = new FileOutputStream(storeDirectoryPath +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(); //传递大文件的时候,会自动产生临时文件
item.write(new File(storeDirectoryPath +File.separator+ fileName));// File Item中存在write方法
} catch (Exception e) {
throw new RuntimeException("上传失败,请重试");
}
}
//普通表单字段
private void processFormField(FileItem item) {
String filedName = item.getFieldName(); //字段名
String filedValue = item.getString(); //字段值
System.out.println(filedName + "=" + filedValue);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
3.文件上传详解
该类主要是对磁盘文件的操作,对于上传文件分为两部分:
1)如果文件的大小小于10KB,那么默认存放到缓存中。
2)如果文件的大小大于10KB,那么该文件将存放到临时文件的存放目录中,默认是当前登录用户的临时文件目录。
public void setRepository(File repository):设置临时文件的存放目录
public void setSizeThreshold(int sizeThreshold):设置缓存的大小
专题:关于临时文件
虚拟内存的概念:当内存中的空间不够用的时候,这个时候,会在磁盘上划分一部分区域作为虚拟内存,将内存中不经常使用的文件先保存到虚拟内存中。所以虚拟内存是磁盘上的一部分存储空间。
为什么启用缓存呢?减少与硬盘的交互次数,最终还是要存放到磁盘上的 ,存放到内存中,速度更快。 所以上传的文件首先是存放到内存中的缓存中的,
如果是一个100M大小的文件,那么每次获取10Kb,到内存中,然后存放到临时文件中,当所有的100M都存放到临时文件中后,然后再把临时文件中的文件全部放到最终文件中,最后把临时文件进行删除。
临时文件需要在代码中指定来实现,调用item.delete()来删除。 FileItem item = new DiskFileItemFactory()
临时文件的删除问题:
文件上传时,自己用IO流处理的文件,一定要在流关闭的时候删除临时文件,FileItem.delete()
建议使用:FileItem.writer(File f)会自动删除临时文件
3.2乱码问题
1) 普通字段乱码
filedValue = item.getString("UTF-8"); 如果提交的是输出框的值,那么需要通过FileItem.getString(String charset);编码需要和客户端一致
2)文件名乱码
可以通过requset.setCharacterEncoding("UTF-8");仅能够解决报文中的请求头的问题,不能解决正文的编码问题
3)解决文件名重名的问题
通过UUID来命名文件名fileName = UUID.randomUUID()+"_"+ FilenameUtils.getName(fileName);
4)保证服务器的安全
为了防止用户上传的文件,通过路径可以直接访问到该文件,造成服务器的漏洞,bat文件等,需要将用户上传的文件存放到用户不能直接访问的地方,如:
String storeDirectoryPath = getServletContext().getRealPath("/WEB-INF/files"); 将文件放到WEB-INF目录下面。
5)避免一个文件夹中的文件过多
解决方法:分目录存储
参考实现:
1)按照日期分目录存储
2)通过文件名的HashCode来设定文件夹
//上传字段
private void processUploadField(FileItem item) {
try {
String fileName = item.getName(); //上传文件的名称 C:\Users\zhangjianbo\Desktop\a.txt a.txt
//截取文件名
if(fileName!=null){
fileName = UUID.randomUUID()+"_"+ FilenameUtils.getName(fileName);
}
// //分目录存储:日期解决方案
// Date now = new Date();
// DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
// String time = df.format(now);
//通过文件名的hashCode来实现
String childDiretory = makeChildDirectory(getServletContext().getRealPath("/WEB-INF/files/"),fileName);
//把文件存到服务器的某个目录中
String storeDirectoryPath = getServletContext().getRealPath("/WEB-INF/files/"+childDiretory);
File storeDirectory = new File(storeDirectoryPath);
if(!storeDirectory.exists()){
storeDirectory.mkdirs();
}
item.delete();//表示删除临时文件
item.write(new File(storeDirectoryPath +File.separator+ fileName));// File Item中存在write方法
} catch (Exception e) {
throw new RuntimeException("上传失败,请重试");
}
}
private String makeChildDirectory(String realPath, String fileName) {
int hashCode = fileName.hashCode();
int dir1 = hashCode&0xf;//取文件名的二进制的1~4位
int dir2 = (hashCode&0xf0)>>4; //获取5~8
String directory = "" + dir1 + File.separator +dir2;
File file = new File(realPath,directory);
if (!file.exists()){
file.mkdirs();
}
return directory;
}
3.6 限制文件上传的大小,并给出友好提示
Web方式不适合传输较大的文件
1)单文件大小(一般限制4M)
//创建DiskFileItemFactory
DiskFileItemFactory dfif = new DiskFileItemFactory();
ServletFileUpload parser = new ServletFileUpload(dfif);
parser.setFileSizeMax(4*1024*1024); //设置单个文件上传的大小
parser.setSizeMax(6*1024*1024); //多文件上传时的总大小限制
List<FileItem> items = null ;
//解析内容
try {
items = parser.parseRequest(request);
}catch(FileUploadBase.FileSizeLimitExceededException e){ //该异常为一个静态内部类
out.write("上传的文件超出了4M");
return;
}
catch(FileUploadBase.SizeLimitExceededException e){ //该异常为一个静态内部类
out.write("总文件大小超出了6M");
return;
}
catch (FileUploadException e) {
throw new RuntimeException("解析上传内容失败,请重新试一下");
}
2)总文件大小(多文件上传)
parser.setSizeMax(6*1024*1024); //多文件上传时的总大小限制
3.7 限制上传文件的类型
通过扩展名+ 文件的MIME类型来进行限制
// 扩展名
String extension = FilenameUtils.getExtension(fileName);
// MIME类型 -- 判断MIME类型仅限于IE浏览器
String contentType = item.getContentType();
if (contentType.startsWith("image/")) {
MIME类型:Web请求文件中的Content-Type类型,就表示MIME类型
由大类型/小类型组成,我们只需要判断大类型即可。
4.文件的下载
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String uuidfilename = request.getParameter("filename");//get方式提交的
uuidfilename = new String(uuidfilename.getBytes("ISO-8895-1"),"UTF-8");//UUID的文件名
String storeDirectory = getServletContext().getRealPath("/WEB-INF/files");
//得到存放的子目录
String childDirecotry = makeChildDirectory(storeDirectory,uuidfilename);
//构建输出流
InputStream in = new FileInputStream(storeDirectory + File.separator + childDirecotry + File.separator + uuidfilename);
//下载
String oldfilename = uuidfilename.substring(uuidfilename.indexOf("_") + 1);
//通知客户端以下载的方式打开
response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(oldfilename,"UTF-8"));
OutputStream out = response.getOutputStream();
int len = -1;
byte b[] = new byte[1024];
while((len = in.read(b))!= - 1){
out.write(b,0,len);
}
in.close();
out.close();
}
private String makeChildDirectory(String realPath, String fileName) {
int hashCode = fileName.hashCode();
int dir1 = hashCode & 0xf;// 取文件名的二进制的1~4位
int dir2 = (hashCode & 0xf0) >> 4; // 获取5~8
String directory = "" + dir1 + File.separator + dir2;
File file = new File(realPath, directory);
if (!file.exists()) {
file.mkdirs();
}
return directory;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
=================jstl url ==================
<h1>jstl中的url的编码方式,使用url编码,因为路径中有中文</h1>
<c:forEach items="${map}" var = "me">
<c:url value = "/servlet/DownLoadServlet" var = "url">
<c:param name="filename" value="${me.key}">
</c:param>
</c:url>
${me.value} <a href="${url}"></a>下载<br/>
==============showallfiles=============
public class ShowAllFilesServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String storeDirectory = getServletContext().getRealPath("/WEB-INF/files");
File root = new File(storeDirectory);
//使用Map保存递归的文件名,key:UUID文件名 value:老文件名
Map<String,String> map = new HashMap<String, String>();
treeWalker(root,map);
request.setAttribute("map", map);
request.getRequestDispatcher("/listFiles.jsp").forward(request, response);
}
//递归,将文件名存放到Map中
private void treeWalker(File root, Map<String, String> map) {
if(root.isFile()){
String fileName = root.getName(); //获取当前的文件名 234322_a_a.bmp
String oldFileName = fileName.substring(fileName.indexOf("_") + 1);
map.put(fileName, oldFileName);
}
else{
File fs[] = root.listFiles();
for(File file:fs){
treeWalker(file, map);
}
}
}
二、Servlet规范中的监听器
1.观察者设计模式
2.Servlet规范中提供的八个监听器
3.监听器案例:查看在线登录的人数,踢人