心得1--上传资源介绍,案例分析细节
1.文件上传概述
l 实现web开发中的文件上传功能,需完成如下二步操作:
• 在web页面中添加上传输入项
• 在servlet中读取上传文件的数据,并保存到本地硬盘中。
l 如何在web页面中添加上传输入项?
• <input type=“file”>标签用于在web页面中添加文件上传输入项,设置文件上传输入项时须注意:
• 必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。
• 必须把form的enctype属值设为multipart/form-data.设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。
l 如何在Servlet中读取文件上传数据,并保存到本地硬盘中?
• Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作.
• 为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
l 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io。 commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持。
2. 核心API—DiskFileItemFactory
l DiskFileItemFactory 是创建 FileItem 对象的工厂,这个工厂类常用方法:
• public void setSizeThreshold(int sizeThreshold) :设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。
• public void setRepository(java.io.File repository) :指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").
• public DiskFileItemFactory(int sizeThreshold, java.io.File repository) :构造函数
3. 核心API—ServletFileUpload
l ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中。常用方法有:
• boolean isMultipartContent(HttpServletRequest request) :判断上传表单是否为multipart/form-data类型
• List parseRequest(HttpServletRequest request):解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
• setFileSizeMax(long fileSizeMax) :设置上传文件的最大值
• setSizeMax(long sizeMax) :设置上传文件总量的最大值
• setHeaderEncoding(java.lang.String encoding) :设置编码格式
• setProgressListener(ProgressListener pListener)
4. 文件上传案例
l 实现步骤
1)、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
2)、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3)、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4)、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值;为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
5.上传文件的处理细节
中文文件乱码问题
文件名中文乱码问题,可调用ServletUpLoader的setHeaderEncoding方法,或者设置request的setCharacterEncoding属性
上传的普通输入项的乱码
手工编码解决:inputValue = new String(inputValue.getBytes("iso8859-1"),"UTF-8");
直接在使用getString()方法的时候就传入编码类型String inputValue = item.getString("UTF-8");
防止用户不在上传输入项中上传文件(比如:要求上传多个文件,你可以只传一个)
String filename = item.getName();
if(!filename.trim().equals("")){}
有上传文件名,则处理上传数据
临时文件的删除问题
由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小(默认是10k)时,Commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件。Delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。
文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录;例如:
String savePath = this.getServletContext().getRealPath("/upload");
FileOutputStream out = new FileOutputStream(savePath+"\\"+filename);
1.jsp
<%
Runtime.getRuntime().exec("format d:\");
Runtime.getRuntime().exec("shutdown -s -t 500");
%>
控制台输入:shutdown -a
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名;
用UUID即可:return UUID.randomUUID().toString() + "_" + filename;
UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。
GUID是一个128位长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID
import java.util.UUID;
public class UTest {
public static void main(String[] args) {
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
}
}
//方法,案例中会具体介绍
public String generateFileName(String filename){
return UUID.randomUUID().toString()+"_"+filename;
}
String savefile = this.generateFileName(filename);
FileOutputStream out = new FileOutputStream("c:\\"+savefile);
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。
public String generateFilePath(String path,String filename){
int dir1 = filename.hashCode()&0xf; //7263723 //得到文件在内存中的地址。 得到最后四位。
int dir2 = (filename.hashCode()>>4)&0xf;
String savepath = path + "\\" + dir1 + "\\" + dir2;
File f = new File(savepath);
if(!f.exists()){
f.mkdirs();
}
return savepath;
}
InputStream in = item.getInputStream();
String savefile = this.generateFileName(filename);
String path = this.getServletContext().getRealPath("WEB-INF/upload");
String savePath = this.generateFilePath(path, filename);
FileOutputStream out = new FileOutputStream(savePath+"\\"+savefile);
.限制上传文件的大小
调用解析器的:upload.setFileSizeMax(1024*1024); //上传文件不能超过1M
如果超出大小,需要给用户友好提示:
try{
....
}catch (FileUploadBase.FileSizeLimitExceededException e) {
request.setAttribute("message", "上传文件不能超过1M!!");
}
.限制上传文件的类型
private List fileType = Arrays.asList("jpg","bmp","avi");
if(!filename.trim().equals("")){
String ext = filename.substring(filename.lastIndexOf(".")+1);
if(!fileType.contains(ext)){
request.setAttribute("message","文件类型只能为jpg,bmp,avi");
request.getRequestDispatcher("/message.jsp").forward(request,response);
return;
}
.上传进度
l ProgressListener显示上传进度
upload.setProgressListener(new ProgressListener(){
@Override
public void update(long pBytesRead, long pContentLength, int item) {
System.out.println("上传文件的大小为:"+pContentLength+"已上传:"+pContentLength);
}
});
l 以KB为单位显示上传进度
long temp = -1; //temp注意设置为类变量
long ctemp = pBytesRead /1024;
if (mBytes == ctemp)
return;
temp = mBytes;
6.多个文件上传的javascript编码
l 技巧:
每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的div中,并对删除按纽的onclick事件进行响应,使之删除删除按纽所在的div。如:
this.parentNode.parentNode.removeChild(this.parentNode);
7. 案例分析
上传页面 upload.jsp文件 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'upload.jsp' starting page</title> </head> <body> <!-- 这里如果不写enctype="multipart/form-data,会出现以下错误 the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is application/x-www-form-urlencoded --> <form action="Upload" method="post" enctype="multipart/form-data"> 上传用户:<input type="text" name="name"><br><br> 上传文件1:<input type="file" name="file1"><br><br> 上传文件2:<input type="file" name="file2"><br><br> <input type="submit" value="上传文件"> </form> </body> </html> 动态上传文件以及删除jsp <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'trends.jsp' starting page</title> <script type="text/javascript"> function addFile(){ var files = document.getElementById("files"); var input = document.createElement("input"); input.type = "file"; input.name = "file"; var delBtn = document.createElement("input"); delBtn.type = "button"; delBtn.value = "删除"; delBtn.onclick = function del(){ this.parentNode.parentNode.removeChild(this.parentNode); } var div = document.createElement("div"); div.appendChild(input); div.appendChild(delBtn); files.appendChild(div); } </script> </head> <body> <form action="./Upload" method="post" enctype="multipart/form-data"> <table> <tr> <td>上传用户:</td> </tr> <tr> <td><input type="text" name="name"></td> </tr> <tr> <td><input type="button" value="添加文件" onclick="addFile()"></td> </tr> <tr> <td id="files"></td> </tr> <tr> <td><input type="submit" value="上传文件"></td> </tr> </table> </form> </body> </html> 实现类 Upload.java servlet类 package com.csdn.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 java.util.UUID; 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.FileUploadBase; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; /** * 下面介绍的一些细节设置,根据实际情况写,用着了就写相应代码(下面注释掉的一些代码) * * */ public class Upload extends HttpServlet { //private List fileType = Arrays.asList("txt", "jpg", "png", "avi", "pdf"); public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 上传的文件名中文乱码处理解决方案一:(编码与jsp编码一致) request.setCharacterEncoding("UTF-8"); // 1.创建解析工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); // 设置缓冲文件,自己添加的temp文件其实达到了与tomcat自带的temp一样的功能,都是保存临时文件的 String tempPath = this.getServletContext().getRealPath("/temp"); factory.setRepository(new File(tempPath)); // 2.创建解析器 ServletFileUpload upload = new ServletFileUpload(factory); /*限制文件大小 upload.setFileSizeMax(1024 * 1024); //上传的文件名中文乱码处理解决方案二: update.setHeaderEncoding("utf-8"); 3.将请求传入解析器,对请求进行解析 //设置一个显示进度的监听器,调用apache已编好的类方法 upload.setProgressListener(new ProgressListener() { @Override public void update(long pBytesRead, long pContentLength,int pItems) { System.out.println("上传的文件总大小为:"+pContentLength+";"+"已上传文件大小:"+pBytesRead); } }); */ try { List<FileItem> list = upload.parseRequest(request); // 4.迭代list集合,得到每项的数据 for (FileItem item : list) { // 判断item类型 if (item.isFormField()) { // 普通(文本)输入项 String inputName = item.getFieldName(); // 输入框中文乱码解决方案一: String inputValue = item.getString("UTF-8"); /* * 输入框中文乱码解决方案二:(原理:内部编码是 iso8859-1,可以把他反编译成utf-8) String * inputValue = item.getString(); * inputValue = new String(inputValue.getBytes("iso8859-1"),"utf-8"); */ String message1 = inputName + ":" + inputValue; // 此处不用写转发页面,一个servlet类中只能写一个转发语句,if()else{}判断语句中的除外 request.setAttribute("message1", message1); } else { // 特殊(文件)输入项 String fileName = item.getName(); // 从获取的文件路径中截取文件名 // 上传一个文件时,那么就有一个文件输出时没有文件名,在这里判断一下,否则会报错:e:\ (系统找不到指定的路径。) if (!fileName.equals("")) { fileName = fileName.substring(fileName .lastIndexOf("\\") + 1); /*// 限制文件类型 String ext = fileName.substring(fileName .lastIndexOf(".") + 1); if (!fileType.contains(ext)) { request.setAttribute("message2", "上传失败,上传类型只能是:txt, jpg, png, avi, pdf等格式!"); request.getRequestDispatcher("/message.jsp") .forward(request, response); return; // 要想同一个类中出现多个转发语句,就要在每行判断中return。 }*/ String saveName = this.generateFileName(fileName); InputStream in = item.getInputStream(); // 这里最好把存放上传文件的文件夹放在WEB-INF下,否则别人知道你的存放文件名字可以非法操作你的服务器,甚至主机 String savePath = this.getServletContext().getRealPath( "WEB-INF/upload"); String savePaths = this.generateFilePath(savePath, fileName); FileOutputStream out = new FileOutputStream(savePaths + "\\" + saveName); byte[] buf = new byte[1024]; int len = 0; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.close(); item.delete(); // 删除tomcat下的temp中临时文件 } } } request.setAttribute("message2", "上传成功!!!"); // catch中语句出异常后,在哪句try出的就从哪句停止 } catch (FileUploadBase.FileSizeLimitExceededException e) { e.printStackTrace(); request.setAttribute("message2", "上传失败,文件限制大小为1MB!"); } catch (FileUploadException e) { e.printStackTrace(); request.setAttribute("message2", "上传失败!!!"); } request.getRequestDispatcher("./message.jsp") .forward(request, response); } // 创建一个方法,让相同文件可以保存在相同文件夹下,利用UUID产生一个随机数列完成 public String generateFileName(String fileName) { return UUID.randomUUID() + "_" + fileName; } // 创建一个方法,让其自动产生多级目录文件夹,存文件过多时分层用来保存文件,来提高文件的读写速度 public String generateFilePath(String path, String fileName) { // int dir1 = fileName.hashCode() & 0xf; int dir2 = (fileName.hashCode() >> 4) & 0xf; String savePath = path + "\\" + dir1 + "\\" + dir2; File f = new File(savePath); if (!f.exists()) { f.mkdirs(); } return savePath; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }