一、文件上传原理
1、在TCP/IP中,最早出现的文件上传机制是FTP ,它是将文件由客户端发送到服务器的标准机制;但是在jsp使用过程中不能使用FTP方法上传文件,这是由jsp运行机制所决定。
通过为表单元素设置 method="post" enctype="multipart/form-data" 属性 ,让表单提交的数据以二进制编码的方式提交,在接受此请求的Servlet中用二进制流来获取内容,就可以取得上传文件的内容,从而实现文件的上传。
2、表单enctype属相
1>application/x-www-form-urlencoded
默认编码方式,只处理表单域里的value属性值,采用这种编码方式的表单域中的值处理成URL编码方式。
2>multipart/form-data
这种编码方式的表单会以二进制流的方式处理表单数据, 这种编码方式会把文件域指定的文件内容也封装到请求参数中。
3>text/plain
这种方式主要适用于直接通过表单发送邮件的方式。
二、SmartUpload上传组件的使用
SmartUpload组件使用简单,可以轻松的实现上传文件类型的限制,也可以轻易地取得上传文件的名称、后缀、大小。 使用该组件需要导入jar包,我用的是 jspsmartupload.jar。
1、上传单个文件
upload.htm
文件上传请选择文件:
upload.jsp
文件上传2SmartUpload smart=new SmartUpload();
smart.initialize(pageContext);
smart.upload();
smart.save("upload");
%>
这里要注意在jsp页面引入导入jar包中的类文件:上面是com.jspsmart.upload.*,为什么这样写,可以用360压缩打开jar包文件如下:
upload表示上传文件的保存文件夹,该文件夹要在跟目录手动建立。上传结果如下:
2、混合表单
如果要上传文件,表单必须封装,但是表单使用了enctype 封装后其他非文件类的表单控件无法通过request内置对象取得,此时必须通过SmartUpload类的getRequest()方法取得全部请求参数。
upload2.htm
文件上传姓名:
照片:
upload2.jsp
文件上传2SmartUpload smart=new SmartUpload(); //实例化SmartUpload上传组件
smart.initialize(pageContext); //初始化上传操作
smart.upload(); //上传准备
String name=smart.getRequest().getParameter("uname"); //接受请求参数
smart.save("upload"); //将上传文件保存在upload文件夹中
%>
姓名:
request=:
3、为上传文件自动命名
主要为了防止重名情况下发生覆盖文件。以下例子中文件命名采用 IP地址+时间戳+三位随机数
upload3.htm
文件上传姓名:
照片:
upload3.jsp
文件上传3request.setCharacterEncoding("utf-8");
//实例化SmartUpload上传组件
SmartUpload smart=new SmartUpload();
//初始化上传操作
smart.initialize(pageContext);
//上传准备
smart.upload();
//接受请求参数
String name=smart.getRequest().getParameter("uname");
IPTimeStamp its=new IPTimeStamp(request.getRemoteAddr());
//取得文件后缀名
String ext=smart.getFiles().getFile(0).getFileExt();
//拼凑文件名称
String fileName=its.getIPTimeRand()+"."+ext;
System.out.println("文件名称及后缀==>"+fileName);
//保存文件
smart.getFiles().getFile(0).saveAs(getServletContext().getRealPath("/")
+"upload"+java.io.File.separator+fileName);
//./表示当前目录;../表示源文件所在目录的上一级目录;../../表示源文件所在目录的上上级目录,以此类推
%>
姓名:
IPTimeStamp.java
package kk.upload3;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class IPTimeStamp{
private SimpleDateFormat sdf=null;
private String ip=null;
public IPTimeStamp(){
}
public IPTimeStamp(String ip){
this.ip=ip;
//System.out.println("ip="+ip);
}
//得到IP地址+时间戳+三位随机数
public String getIPTimeRand(){
StringBuffer buf=new StringBuffer();
if(this.ip!=null){
/*
* 进行拆分操作
* 在java中 \代表转义字符 \n \t 等,而 \\ 代表一个反斜杠 而.代表一个元字符
* 要表示一个.就需要用 要用\.
* 所以"\\." 在实际编译中 就代表 .
* */
String s[]=this.ip.split("\\.");
for(int i=0;i
//不够三位数字补0
buf.append(this.addZero(s[i],3));
}
}
//取得时间戳
buf.append(this.getTimeStamp());
Random r=new Random();
for(int i=0;i<3;i++){
buf.append(r.nextInt(10));
}
System.out.println("文件前缀==>"+buf.toString());
return buf.toString();
}
private String addZero(String str,int len){
StringBuffer s=new StringBuffer();
s.append(str);
while(s.length()
s.insert(0,"0");
}
//System.out.println(s);
return s.toString();
}
public String getDate(){
this.sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return this.sdf.format(new Date());
}
public String getTimeStamp(){
this.sdf=new SimpleDateFormat("yyyyMMddHHmmssSSS");
return this.sdf.format(new Date());
}
}
运行结果:
问题:限制文件上传类型?
比如验证上传文件后缀的合法性:
//表示只允许后缀为jpg/gif的文件上传。
if(smart.getFiles().getFile(0).getFileName().matches("^\\w+\\.(jpg|gif)$")){
}
4、批量上传
对upload3.jsp文件进行如下更改,其他的都不用改变,
文件上传3request.setCharacterEncoding("utf-8");
SmartUpload smart=new SmartUpload();
smart.initialize(pageContext);
smart.upload();
String name=smart.getRequest().getParameter("uname");
IPTimeStamp its=new IPTimeStamp(request.getRemoteAddr());
for(int x=0;x
String ext=smart.getFiles().getFile(x).getFileExt();
String fileName=its.getIPTimeRand()+"."+ext;
smart.getFiles().getFile(x).saveAs(getServletContext().getRealPath("/")
+"upload"+java.io.File.separator+fileName);
}
%>
获得上传文件数量:smart.getFiles().getCount()。
三、FileUpload上传组件的使用
1、接收上传内容
FileUpload的具体上传操作与SmartUpload相比有着很高的复杂度;FileUpload上传基本步骤如下:
<1>创建磁盘工厂:DiskFileItemFactory factory=new DiskFileItemFactory();
<2>创建处理工具:ServletFileUpload upload=new ServletFileUpload(factory);
<3>设置上传文件大小:upload.setFileSizeMax(3145728);
<4>接收全部内容:List items=upload.parseRequest(request);
由于FileUpload会将所有上传的内容(包括文件和普通参数)一起接收,所以要依次判断每一次上传的内容是文件还是普通文本。在使用FileUpload接收时所有提交的内容都会通过upload.parseRequest()方法返回,然后在使用Iterator依次取出每一个提交内容。
fileUpload.htm
文件上传姓名:
照片:
fileUpload.jsp
文件上传2DiskFileItemFactory factory=new DiskFileItemFactory(); //创建磁盘工厂
ServletFileUpload upload=new ServletFileUpload(factory); //创建处理工具
upload.setFileSizeMax(3145728); //设置最大上传大小为3MB
upload.setHeaderEncoding("UTF-8"); //解决上传文件名的中文乱码
List items=upload.parseRequest(request); //接受全部内容
Iterator iter=items.iterator(); //将全部的内容变成Iterator实例
while(iter.hasNext()){
FileItem item=iter.next(); //取出每一个上传的文件
String fieldName=item.getFieldName(); //取得表单控件的名称
%>
表单控件名:-->
if(!item.isFormField()){ //不是普通的文本文件,是上传文件
String fileName=item.getName(); //获取上传文件的文件名
String contentType=item.getContentType(); //获得文件类型
long sizeInBytes=item.getSize(); //获得文件的大小
%>
上传文件名:上传文件类型:上传文件大小:}else{
String value=item.getString();
%>
普通参数:}
%>
}
%>
使用FileUpload组件接收完全部的数据后,所有的文件都保存在List集合中,所以需要Iterator来取出每一个数据,但由于其中包含普通的文本数据和上传的文件,每一个上传的内容都是用FileItem类对象表示,所以当时用迭代器取出每一个FileItem对象时就可以使用FileItem类中的isFormField()方法来判断当前操作的内容是普通的文本还是上传的文件,如果是上传的文件则将文件的内容依次取出;如果是普通的文本则直接通过getString()方法取得具体信息; 程序运行结果如下图:
问题:乱码,我使用的是utf-8编码,保存jsp文件的时候也是将文件另存为utf-8编码,但是姓名如果输入中文就会显示乱码;
2、以上所有的文件上传都是htm+jsp完成的,所有的Java代码都是写在jsp文件中,下面将使用jsp+Servlet完成文件上传。
fileUpload2.jsp
pageEncoding="UTF-8"%>
/p>
"http://www.w3.org/TR/html4/loose.dtd">
文件上传文件上传实例
上传用户:
上传文件1:
上传文件2:
fileMessage2.jsp
pageEncoding="UTF-8"%>
/p>
"http://www.w3.org/TR/html4/loose.dtd">
文件上传结果${message}
UploadServlet.java文件
package kk.fileUpload;
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.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;
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(savePath);
//判断上传文件的保存目录是否存在
if (!file.exists() && !file.isDirectory()) {
System.out.println(savePath+"目录不存在,需要创建");
//创建目录
file.mkdir();
}
//消息提示
String message = "";
try {
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(request)){
//按照传统方式获取数据
return;
}
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List集合,每一个FileItem对应一个Form表单的输入项
List list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封装的是普通输入项的数据
if(item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{
//如果fileitem中封装的是上传文件
//得到上传的文件名称,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//获取item中的上传文件的输入流
InputStream in = item.getInputStream();
//创建一个文件输出流
FileOutputStream out = new FileOutputStream(savePath + "\\" + filename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流
in.close();
//关闭输出流
out.close();
//删除处理文件上传时生成的临时文件
item.delete();
message = "文件上传成功!";
}
}
}catch (Exception e) {
message= "文件上传失败!";
e.printStackTrace();
}
request.setAttribute("message",message);
request.getRequestDispatcher("/fileMessage2.jsp").forward(request, response);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
web.xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
Welcome to Tomcat
Uploadf
kk.fileUpload.UploadServlet
Uploadf
/FileUpload/UploadServlet
问题:从Java Web(一)开始一直使用手工在Tomcat安装目录webapps中创建,没有实用工具,所以在编写上面UploadServlet.java文件的时候提示找不到相应的jar包(commons-fileupload-1.3.2.jar/commons-io-2.5.jar/servlet-api.jar)。
解决办法:
classpath:.;%CATALINA_HOME%/lib/servlet-api.jar;%CATALINA_HOME%/lib/commons-io-2.5.jar;%CATALINA_HOME%/lib/commons-fileupload-1.3.2.jar;.;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/lib/tools.jar
经过以上配置成功编译class类文件,然后将class文件放在classes文件夹中(注意按照打包的层级存放)。
运行结果如下:
四、文件上传小结
上述的代码虽然可以成功将文件上传到服务器上面的指定目录当中,但是文件上传功能有许多需要注意的小细节问题:
1、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
2、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
3、为防止一个目录下面出现太多文件,要使用hash算法打散存储。
4、要限制上传文件的最大值。
5、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
将java文件按照以上要求更改如下:
package kk.upload3;
import javax.servlet.http.HttpServlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
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.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadHandleServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
//上传时生成的临时文件保存目录
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
File tmpFile = new File(tempPath);
if (!tmpFile.exists()) {
//创建临时目录
tmpFile.mkdir();
}
//消息提示
String message = "";
try{
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
/*
* 设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
* 设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB
* */
factory.setSizeThreshold(1024*100);
//设置上传时生成的临时文件的保存目录
factory.setRepository(tmpFile);
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//监听文件上传进度
upload.setProgressListener(new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int arg2) {
System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead);
/**
* 文件大小为:14608,当前已处理:4096
* 文件大小为:14608,当前已处理:7367
* 文件大小为:14608,当前已处理:11419
* 文件大小为:14608,当前已处理:14608
*/
}
});
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(request)){
//按照传统方式获取数据
return;
}
//设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB
upload.setFileSizeMax(1024*1024);
//设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB
upload.setSizeMax(1024*1024*10);
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List集合,每一个FileItem对应一个Form表单的输入项
List list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封装的是普通输入项的数据
if(item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{
//如果fileitem中封装的是上传文件
//得到上传的文件名称,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//得到上传文件的扩展名
String fileExtName = filename.substring(filename.lastIndexOf(".")+1);
//如果需要限制上传的文件类型,那么可以通过文件的扩展名来判断上传的文件类型是否合法
System.out.println("上传的文件的扩展名是:"+fileExtName);
//获取item中的上传文件的输入流
InputStream in = item.getInputStream();
//得到文件保存的名称
String saveFilename = makeFileName(filename);
//得到文件的保存目录
String realSavePath = makePath(saveFilename, savePath);
//创建一个文件输出流
FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFilename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流
in.close();
//关闭输出流
out.close();
//删除处理文件上传时生成的临时文件
//item.delete();
message = "文件上传成功!";
}
}
}catch (FileUploadBase.FileSizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message", "单个文件超出最大值!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}catch (FileUploadBase.SizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message", "上传文件的总的大小超出限制的最大值!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}catch (Exception e) {
message= "文件上传失败!";
e.printStackTrace();
}
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
/**
* @Method: makeFileName
* @Description: 生成上传文件的文件名,文件名以:uuid+"_"+文件的原始名称
* @Anthor:kk
* @param filename 文件的原始名称
* @return uuid+"_"+文件的原始名称
*/
private String makeFileName(String filename){ //2.jpg
//为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
return UUID.randomUUID().toString() + "_" + filename;
}
/**
* 为防止一个目录下面出现太多文件,要使用hash算法打散存储
* @Method: makePath
* @Description:
* @Anthor:kk
*
* @param filename 文件名,要根据文件名生成存储目录
* @param savePath 文件存储路径
* @return 新的存储目录
*/
private String makePath(String filename,String savePath){
//得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
int hashcode = filename.hashCode();
int dir1 = hashcode&0xf; //0--15
int dir2 = (hashcode&0xf0)>>4; //0-15
//构造新的保存目录
String dir = savePath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5
//File既可以代表文件也可以代表目录
File file = new File(dir);
//如果目录不存在
if(!file.exists()){
//创建目录
file.mkdirs();
}
return dir;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
运行结果如下:
6、开发FileUpload组件的专属操作类
经过以上FileUpload组件的基本操作学习,可以发现仍有诸多不便;
1>无法像使用request.getParameter()方法那样准确地取得提交的参数;
2>无法像使用request.getParameterValues()方法那样准确地取得一组提交的参数;
3>所有的上传文件都要依次判断,才能分别保存,不能一次性批量保存;
如要解决以上问题则需要自己进行代码的扩充,编写一个FileUpload操作的工具类----FileUploadTools(会用即可)。
五、文件下载