文件的上传和下载的本质就是文本复制的过程,实现web层面的文件上传功能,需要完成如下两步的操作:
1. 在web页面中添加上传输入项
2. 在servlet中读取上传文件的数据,并保存到本地硬盘中
因此在技术层面上,在Java中一定会用到IO操作,主要以二进制方式读写。
那么如何在Web页面中添加上传输入项?
1.传统方式下,在前端页面中将input标签的type属性设置为file即<input type = “file”>,但是对于上传文件字段不同的浏览器有着不同的解析方式,例如:
IE6:upfile=c:\aa\bb\a.JPG
非IE6:
upfile=a.JPG
可以看出IE6下包含文件的绝对路径但是非IE6下不能识别出文件的相对路径所以可能识别不出文件,因此这个方式不行
2.把前端页面中form的enctype属值设为multipart/form-data.设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。在servlet中request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作,解析方式为
InputStream is = request.getInputStream();
OutputStream os = response.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = is.read(buf))>0){
os.write(buf, 0, len);
}
is.close();
os.close();
以上两种方式都比较麻烦,因此为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件(commons-fileupload),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用commons-fileupload组件实现。
使用commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:commons-fileupload和commons-io。commons-io 不属于文件上传组件的开发jar文件,但commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持,可以在此处下载:http://download.csdn.net/detail/it_tingge/9169347
本文中文件的上传下载功能可以实现控制上传文件的数量,实现对文件的大小和文件类型的控制,可以检测用户是否上传文件。
本文所使用的工程(完整源代码下载地址在博文后面)目录为:
一、前端页面
前端页面主要实现的功能如下图所示
1.上传页面主要是一个form表单
<form
action="${pageContext.request.contextPath}/UploadServlet"
method="POST"
enctype="multipart/form-data">
<table border="1" align="center">
<caption>文件上传</caption>
<tr>
<th>上传用户</th>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<th></th>
<td>
<div id="outDiv">
<div>
<input type="file" name="upfile"/>
<input type="button" value="删除"/>
</div>
</div>
</td>
</tr>
<tr>
<th></th>
<td>
<input
type="button"
value="添加上传文件"
οnclick="addLine(this)"
/>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="上传"/>
<a href="${pageContext.request.contextPath}/ListFileServlet">
显示下载文件
</a>
</td>
</tr>
</table>
</form>
为了实现对上传文件数量的控制,需要通过JS进行控制,代码实现如下所示
<script type="text/javascript">
//全局变量
var time = 0;
function addLine(addButton){
//创建内部div对象
var divElement = document.createElement("div");
//创建input对象[file类型]
var inputElement1 = document.createElement("input");
inputElement1.type="file";
inputElement1.name="upfile";
//创建input对象[button类型]
var inputElement2 = document.createElement("input");
inputElement2.type="button";
inputElement2.value="删除";
//对删除按钮添加事件监听
inputElement2.οnclick=function(){
//取得该按钮所在行的直接父元素
var divElement = this.parentNode.parentNode;
//通过父元素删除直接子元素
divElement.removeChild(this.parentNode);
time--;
if(time < 5){
//按钮生效
addButton.disabled=false;
//addButton.style.visibility="visible";
}
}
//依次将file类型和button类型的input对象加入到内部div对象中
divElement.appendChild(inputElement1);
divElement.appendChild(inputElement2);
//再次内部div对象加入到外部div对象
var outDivElement = document.getElementById("outDiv");
outDivElement.appendChild(divElement);
time++;
if(time == 5){
//将按钮失效
addButton.disabled=true;
//addButton.style.visibility="hidden";
}
}
</script>
2.下载页面
下载页面主要是一个表格列出上传的文件并提供下载功能,其代码实现为
<table border="1" align="center">
<caption>下载文件列表</caption>
<tr>
<th>文件名</th>
<th>操作</th>
</tr>
<c:forEach var="entry" items="${requestScope.map}">
<tr>
<td>${entry.value}</td>
<td>
<c:url var="myURL" value="/DownloadServlet">
<c:param name="uuidFileName" value="${entry.key }"></c:param>
</c:url>
<a href="${myURL}"
style="text-decoration:none">
下载
</a>
</td>
</tr>
</c:forEach>
</table>
二、后台功能实现
Fileupload组件工作流程为
其核心API有
1.DiskFileItemFactory
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) 构造函数
2.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)
设置编码格式,解决上传中文名文件的问题数
3.实现步骤
创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容FileItem的List对象。
对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
为上传文件,则调用getName 、 getInputStream方法得到数据输入流,从而读取上传数据。
编码实现文件上传
4.中文文件名乱码问题
文件名中文乱码问题,可调用ServletFileUpload的setHeaderEncoding方法,或者设置request的setCharacterEncoding属性
5临时文件的删除问题
由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小时,commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件。
delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。
6文件存放名字
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
7.文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储到不同的目录 .文件上传到关键代码为
package com.uploaddownload.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.uploaddownload.domain.User;
import com.uploaddownload.exception.NoUpfileException;
public final class UploadUtil {
// 取的上传文件使用的临时目录
public static final String tempPath = "/WEB-INF/temp";
// 取的上传文件使用的真实目录
public static final String uploadPath = "/WEB-INF/upload";
//取的真实文件名
public static String getRealFileName(String realFileName){
int index = realFileName.lastIndexOf("\\");
if(index>=0){
//IE6浏览器
realFileName = realFileName.substring(index+1);
}
return realFileName;
}
//取的uuid文件名
public static String makeUuidFilePath(String uuidFileName, String uploadPath) {
String uuidFilePath = null;
int code = uuidFileName.hashCode();
int dir1 = code & 0xF;
int dir2 = code >> 4 & 0xF;
File file = new File(uploadPath+"/"+dir1+"/"+dir2);
if(!file.exists()){
//如果该目录未存在
//一次性创建N层目录
file.mkdirs();
}
uuidFilePath = file.getPath();
return uuidFilePath;
}
//去的upload/目录下的分散目录
public static String makeUuidFileName(String realFileName) {
return UUID.randomUUID().toString()+"_"+realFileName;
}
//文件复制
public static void doSave(InputStream is ,String uuidFilePath,String uuidFileName){
//取的文件输出流
OutputStream os = null;
try {
os = new FileOutputStream(uuidFilePath+"/"+uuidFileName);
byte[] buf = new byte[1024];
int len = 0;
while((len = is.read(buf))>0){
os.write(buf, 0, len);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(is!=null){
try {
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//将上传文件封装成JavaBean对象中
@SuppressWarnings({ "static-access", "unchecked" })
public static User doUpload(HttpServletRequest request) throws Exception{
User user = new User();
//取的上传文件使用的临时目录
String tempPath = request.getSession().getServletContext().getRealPath("/WEB-INF/temp");
//创建上传文件工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置内存中缓存区的大小,默认10K
factory.setSizeThreshold(10*1024);
//设置上传文件临时存放的目录
factory.setRepository(new File(tempPath));
//创建上传文件对象[核心]
ServletFileUpload upload = new ServletFileUpload(factory);
//设置上传文件的中文编码方式
upload.setHeaderEncoding("UTF-8");
//客户端上传文件是否使用MIME协议
boolean flag = upload.isMultipartContent(request);
if(!flag){
//不是以MIME协议上传文件
throw new ServletException();
}else{
//是以MIME协议上传文件,解析request中的所有上传内容
//将每个内容封装成一个对象FileItem,
//FileItem代表普通字段和上传字段两类
List<FileItem> fileItemList = upload.parseRequest(request);
for(FileItem fileItem:fileItemList){
if(fileItem.isFormField()){
//必定是普通字段
String fieldName = fileItem.getFieldName();
//解决中文乱码问题
String fieldValue = fileItem.getString("UTF-8");
user.setUsername(fieldValue);
System.out.println(fieldName+":"+fieldValue);
}else {
//必定是上传字段
if(fileItem.getSize()==0){
throw new NoUpfileException();
}
/*String realFileName = UploadUtil.getRealFileName(fileItem.getName());
//只能上传JPG文件
if(!realFileName.endsWith("JPG")){
throw new UpfileTypeException();
}
//只有上传<=200K的文件
if(fileItem.getSize() > 200 * 1024){
throw new UpfileSizeException();
}*/
//封装到JavaBean
user.getUpfileList().add(fileItem);
}
}
}
return user;
}
public static void doSave(User user, String uploadPath) throws Exception {
List<FileItem> fileItemList = user.getUpfileList();
for(FileItem fileItem:fileItemList){
//取得输入流
InputStream is = fileItem.getInputStream();
//取得真实文件名
String realFileName = fileItem.getName();
realFileName = UploadUtil.getRealFileName(realFileName);
//取的UUID文件名
String uuidFileName = UploadUtil.makeUuidFileName(realFileName);
//取的UUID文件路径
String uuidFilePath = UploadUtil.makeUuidFilePath(uuidFileName, uploadPath);
//保存
UploadUtil.doSave(is, uuidFilePath, uuidFileName);
fileItem.delete();
}
}
}
列出上传文件的关键代码为
package com.uploaddownload.download;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.uploaddownload.util.UploadUtil;
/**
* 显示下载文件
* @author LiChunting
*
*/
public class ListFileServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//定位下载文件的目录
String uploadPath = this.getServletContext().getRealPath(UploadUtil.uploadPath);
//创建Map<UUID文件名,真实文件名>
Map<String,String> map = new HashMap<String,String>();
//取的下载文件的相关信息
getFiles(uploadPath,map);
//转发到list.jsp显示可供下载的文件
request.setAttribute("map", map);
request.getRequestDispatcher("/WEB-INF/list.jsp").forward(request, response);
}
//递归寻找所有可供下载的文件
private void getFiles(String uploadPath,Map<String,String> map){
File file = new File(uploadPath);
//如果file表示文件
if(file.isFile()){//出口
//取的文件名,即UUID文件名
String uuidFileName = file.getName();
int index = uuidFileName.indexOf("_");
String realFileName = uuidFileName.substring(index+1);
//存放到Map集合中
map.put( uuidFileName,realFileName);
}else{
//必定是目录
//取的该目录下的所有内容
File[] files = file.listFiles();
for(File f:files){
//递归调自己
getFiles(f.getPath(),map);
}
}
}
}
下载
超链接直接指向下载资源
程序实现下载需设置两个响应头:
设置Content-Type 的值为:application/x-msdownload。Web 服务器需要告诉浏览器其所输出的内容的类型不是普通的文本文件或 HTML 文件,而是一个要保存到本地的下载文件。
Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。在设置 Content-Dispostion 之前一定要指定 Content-Type.
下载的关键代码为
package com.uploaddownload.download;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.uploaddownload.util.UploadUtil;
public class DownloadServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String uuidFileName = request.getParameter("uuidFileName");
byte[] buf = uuidFileName.getBytes("ISO8859-1");
uuidFileName = new String(buf,"UTF-8");
int index = uuidFileName.lastIndexOf("_");
String realFileName = uuidFileName.substring(index+1);
response.setHeader("content-disposition","attachment;filename="+URLEncoder.encode(realFileName,"UTF-8"));
String uploadPath = this.getServletContext().getRealPath(UploadUtil.uploadPath);
String uuidFilePath = UploadUtil.makeUuidFilePath(uuidFileName, uploadPath);
InputStream is = new FileInputStream(uuidFilePath+"/"+uuidFileName);
OutputStream os = response.getOutputStream();
buf = new byte[1024];
int len = 0;
while((len = is.read(buf))>0){
os.write(buf,0,len);
}
is.close();
os.close();
}
}
到这,文件的上传下载功能都实现了,完整的代码可以在http://download.csdn.net/detail/it_tingge/9169905下载。
谢谢您的阅读!