JAVAWEB开发之文件的上传与下载(开源组件commons-fileupload的详细使用)

来源:互联网 发布:彩虹六号配件知乎 编辑:程序博客网 时间:2024/06/07 00:28

文件上传简介

什么是文件上传?为什么使用文件上传?
文件上传就是将客户端资源,通过网路传递到服务器端。
因为文件数据比较大,必须通过文件上传才可以完成将数据保存到数据库端的操作。
文件上传的本质就是IO流操作。
演示:文件上传应该如何操作?
浏览器端:
1.method=post 只有post才可以携带大数据
2.必须使用<input type='file' name='f'>要有name属性
3.encType="multipart/form-data"
服务器端:
request对象是用于获取请求信息。
它有一个方法  getInputStream(); 可以获取一个字节输入流,通过这个流,可以读取到
所有的请求正文信息.
文件上传原理:
浏览器端注意上述三件事,在服务器端通过流将数据读取到,在对数据进行解析.
将上传文件内容得到,保存在服务器端,就完成了文件上传。
注意:在实际开发中,不需要我们进行数据解析,完成文件上传。因为我们会使用文件上传的工具,它们已经封装好的,提供API,只要调用它们的API就可以完成文件上传操作.我们使用的commons-fileupload,它是apache提供的一套开源免费的文件上传工具。

代码演示文件上传的原理:
在WebRoot下新建upload1.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 'index.jsp' starting page</title></head><body>  <!-- encType 默认是application/x-www-form-urlencoded --><form action="${pageContext.request.contextPath }/upload1"method="POST" enctype="multipart/form-data"><input type="text" name="content"><br><input type="file" name="f"><br> <input type="submit"value="上传"></form></body></html>
新建Upload1Servlet  路径:/upload1
package cn.itcast.web.servlet;import java.io.IOException;import java.io.InputStream;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class Upload1Servlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// System.out.println("upload1 servlet......");// 通过request获取一个字节输入流,将所有的请求正文信息读取到,打印到控制台InputStream is = request.getInputStream();byte[] b = new byte[1024];int len = -1;while ((len = is.read(b)) != -1) {System.out.println(new String(b, 0, len));}is.close();}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}
在浏览器端访问信息如下:



后台打印正文信息如下:

文件上传概述

实现web开发中的文件上传功能,需要完成如下二步操作:
  • 在web页面中添加上传输入项。
  • 在Servlet中读取上传文件的数据,并保存在服务器硬盘中。
如何在web页面中添加上传输入项?
<input  type="file">标签用于在web页面中添加文件上传输入项,设置文件上传输入项时注意:
  • 1、必须设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。
  • 2、必须把form的encType属性设为multipart/form-data 设置该值后,浏览器在上传文件时,并把文件数据附带在http请求消息体内,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。
  • 3、表单的提交方式要设置为post。
如何在Servlet中读取文件上传数据,并保存到本地硬盘中?
  • Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作,示例。
  • 为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
  • 使用Commons-fileupload组件实现文件上传,需要导入该组件相应支撑jar包:Commons-fileupload和commons-io。commo-io不属于文件上传组件的开发jar文件,但Commons-fileupload组件从1.1版本开始,它工作时需要commons-io包的支持。

fileupload组件工作流程和文件上传步骤:

fileupload组件工作流程:

fileupload组件上传步骤:
1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录。
2、使用DiskFileItemFactory对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3、调用ServletFileUpload.parseRequest方法解析request对象,得到了一个保存了所有上传内容的List对象。
4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件。
     4.1 true为普通表单字段,则调用getFiledName、getString方法得到字段名称和字段值。
     4.2 false为上传组件,则调用getInputStream方法得到数据输入流,从而读取上传数据。

fileupload快速入门:

1.创建upload2.jsp页面
<form action="${pageContext.request.contextPath}/upload2" method="post" encType="multipart/form-data">
<input type="file" name="f"><br>
<input type="submit" value="上传">
</form>
2.创建Upload2Servlet
     2.1创建一个DiskFileItemFactory
        DiskFileItemFactory  factory = new DiskFileItemFactory();
     2.2创建ServletFileUpload类
        ServletFileUpload  upload = new ServletFileUpload(factory);
     2.3解析所有上传数据
       List<FileItem> items = upload.parseRequest(request);
3.遍历items集合,集合中的每一项,就是一个上传数据
     3.1 isFormField();
     3.2 getFieldName
           返回值为String,得到组件名称 <input name=" ">
     3.3 getName();
           返回值为String,得到的是上传文件的名称。
           注意:浏览器不同,它们得到的效果就不一样。
                  3.3.1 包含全路径名称 例如:C:\Users\xxxx\Desktop\a.txt
                  3.3.2 只包含上传文件名称 例如:a.txt
     3.4 getString
            这个方法可以获取非上传组件的内容,相当于 getParameter方法的作用
            如果是上传组件,上传的文件时文本文件,仍然可以获取到文件内容。
            但如果不是文本文件,而是二进制文件 例如 一张图片 就会获取到一堆卡哇伊的符号或内容。
     3.5 获取上传文件的内容,保存到服务器端
           item.getInputStream(); 它是用于读取上传文件内容的输入流。
           使用文件复制操作就可以完成文件上传。
           IOUtils.copy(item.getInputStream(),fos);

代码演示如下:
   创建upload2.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 'index.jsp' starting page</title></head><body>  <!-- encType 默认是application/x-www-form-urlencoded --><form action="${pageContext.request.contextPath }/upload2"method="POST" enctype="multipart/form-data"><input type="text" name="content"><br><input type="file" name="f"><br> <input type="submit"value="上传"></form></body></html>
创建Upload2Servlet  路径是 /upload2
package cn.itcast.web.servlet;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.FileUploadException;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;import org.apache.commons.io.IOUtils;public class Upload2Servlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 1.创建DiskFileItemFactoryDiskFileItemFactory factory = new DiskFileItemFactory();// 2.创建ServletFileUpload upload = new ServletFileUpload(factory);try {// 底层通过request获取数据,进行解析,将解析的数据封装到List<FileItem>List<FileItem> items = upload.parseRequest(request);// 3.遍历集合for (FileItem item : items) {if (item.isFormField()) {// 是表单组件 就得到了 <input type="text" name="content"> 这样的组件// String fieldName = item.getFieldName();// System.out.println(fieldName);// 上传文件的名称// String name = item.getName();// System.out.println(name);// String string = item.getString();// System.out.println(string);} else {// 不是表单组件 这就得到了<input type="file" name="f"> 这样的组件// String fieldName = item.getFieldName();// System.out.println("上传组件的名称: " + fieldName);// 上传文件的名称String name = item.getName();name = name.substring(name.lastIndexOf("\\") + 1);// System.out.println(name);//// String string = item.getString();// System.out.println(string);// 获取上传文件内容,完成文件上传操作// InputStream is = item.getInputStream();// byte[] b = new byte[1024];// int len = -1;FileOutputStream fos = new FileOutputStream("C:/Users/lx/Desktop/upload/" + name);// while ((len = is.read(b)) != -1) {// // System.out.println(new String(b, 0, len));// fos.write(b, 0, len);// }// fos.close();// is.close();IOUtils.copy(item.getInputStream(), fos);}}} catch (FileUploadException e) {e.printStackTrace();}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}
deploy 访问upload2.jsp 上传文件 运行结果如下图所示:


在浏览器端查捕捉请求正文信息如下:


FileUpload核心API

DiskFileItemFactory

DiskFileItemFactory是创建FileItem对象的工厂

  作用:可以设置缓存大小以及临时文件保存位置。
  默认缓存大小是10240(10k)
  临时文件默认存储在系统的临时文件目录下(可以在环境变量中查看)
1. new DiskFileItemFactory();  缓存大小与临时文件存储位置使用默认的
2.new DiskFileItemFactory(int sizeThreshould, File repository);
          sizeThreshold :缓存大小
          repository:临时文件存储位置
    注意:对于无参数构造,也可以设置缓存大小以及临时文件存储位置
public void setSizeThreshold(int sizeThreshold) 
      设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。
public void setRepository(java.io.File repository) 
    指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").

ServletFileUpload

ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中。常用的方法有:
(1)ServletFileUpload  upload=new ServletFileUpload(factory);
        创建一个上传工具,指定使用缓存区与临时文件存储位置
(2)List parseRequest(HttpServletRequest request)
        List<FileItem> items = upload.parseRequest(request);
        它是用于解析request对象,得到所有上传项,每一个FileItem就相当于一个上传项。
(3)boolean flag=upload.isMultipartContent(request);
用于判断是否是上传.
可以简单理解,就是判断encType="multipart/form-data";
(4)设置上传文件大小
void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小 
void  setSizeMax(long sizeMax) 设置总文件上传大小 
(5)解决上传文件中文名称乱码
void  setHeaderEncoding("utf-8");
注意:如果使用reqeust.setCharacterEncoding("utf-8")也可以,但不建议使用。
(6)setProgressListener(ProgressListener pListener)
        实时监听文件上传状态      

FileItem

FileItem用来表示文件上传表单中的一个上传文件对象或者普通表单对象。
(1)isFormField
用于判断是否是上传组件.
如果是<input type="file">返回的就是false,否则返回true.
(2)getFieldName();
返回值String,得到组件名称  <input name="">
(3)getName();
返回值是String,得到的是上传文件的名称.
注意:浏览器不同,它们得到的效果不一样。
1.包含全路径名称  例如: C:\Users\Administrator\Desktop\a.txt
2.只包含上传文件名称 例如:a.txt
(4)getString();
这个方法可以获取非上传组件的内容,相当于  getParameter方法作用。
问题:如果信息是中文,会出现乱码,解决方案  getString("utf-8");
如果是上传组件,上传的文件是文本文件,可以获取到文件文件的内容。
但是如果不是文件文件,例如:是一张图片,是二进制文件 就不合适了。
(5)获取上传文件的内容,保存到服务器端.
item.getInputStream();它是用于读取上传文件内容的输入流.
使用文件复制操作就可以完成文件上传。
(6)删除临时文件
item.delete();
示例代码如下:
在WebRoot下新建临时文件夹temp 用来存储缓存(上传中产生的临时文件)
新建upload3.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 'index.jsp' starting page</title></head><body>  <!-- encType 默认是application/x-www-form-urlencoded --><form action="${pageContext.request.contextPath }/upload3"method="POST" enctype="multipart/form-data"><input type="text" name="content"><br><input type="file" name="f"><br> <input type="submit"value="上传"></form></body></html>

新建Upload3Servlet  访问路径:/upload3
package cn.itcast.web.servlet;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;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.IOUtils;//commons-fileupload api详解@SuppressWarnings("all")// 去除所有警告public class Upload3Servlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");// 1.创建DiskFileItemFactory// DiskFileItemFactory factory = new DiskFileItemFactory(); // 使用默认的File file = new File(this.getServletContext().getRealPath("/temp"));// 获取temp目录部署到tomcat后的绝对磁盘路径DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 100, file);// 2.创建ServletFileUploadServletFileUpload upload = new ServletFileUpload(factory);// 判断是否是上传操作(即检测encType是否为"multipart/form-data")boolean flag = upload.isMultipartContent(request);if (flag) {// 解决上传文件中文名称乱码// 注意:也可以使用request.setCharacterEncoding("utf-8"); 不建议使用upload.setHeaderEncoding("utf-8");// 设置上传文件大小//upload.setSizeMax(1024*1024*10); // 设置文件总大小为10Mtry {List<FileItem> items = upload.parseRequest(request);// 解析request,得到所有上传项FileItem// 3.得到所有上传项for (FileItem item : items) {if (item.isFormField()) { // 判断是否是上传组件 如果是上传组件就返回false// 非上传组件System.out.println("组件名称:" + item.getFieldName());System.out.println("内容:" + item.getString("utf-8"));} else {// 上传组件System.out.println("非上传组件:" + item.getFieldName());System.out.println("上传文件名:" + item.getName());String name = item.getName(); // 上传文件名name = name.substring(name.lastIndexOf("\\") + 1);IOUtils.copy(item.getInputStream(),new FileOutputStream("c:/Users/lx/Desktop/upload/" + name));// 删除临时文件item.delete();}}} catch (FileUploadException e) {// e.printStackTrace();response.getWriter().write(e.getMessage());}} else {response.getWriter().write("不是上传操作");return;}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

总结:关于文件上传时的乱码问题:
(1)上传文件名乱码  ServletFileUpload.setHeaderEncoding("utf-8");
(2)非上传组件内容乱码 FileItem.getString("utf-8");
  注意:上传文件信息一定不会乱码,不需要解决,因为我们在上传时,使用的是字节流来进行的复制。

多文件上传时的JS编码

技巧:
每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的div中,并对删除按纽的onclick事件进行响应,使之删除删除按纽所在的div。
如:this.parentNode.parentNode.removeChild(this.parentNode);
代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>多文件上传</title><script type="text/javascript">function addFile() {var div = document.getElementById("content");div.innerHTML += "<div><input type='file' name='f'><input type='button' value='remove file' onclick='removeFile(this)'></div>";}function removeFile(btn) {document.getElementById("content").removeChild(btn.parentNode);}</script></head><body><input type="button" value="add File" onclick="addFile();"><br><form action="${pageContext.request.contextPath }/upload3"method="POST" enctype="multipart/form-data"><input type="file" name="f"><br><div id="content"></div><input type="submit" value="上传"></form></body></html>
运行结果如下:


关于文件上传的注意事项:

事项一:上传文件在服务器端保存位置问题

(1)保存在可以被浏览器直接访问的位置
     例如:商城中的商品图片
    保存在工程的WebRoot下的路径(但不包含META-INF以及WEB_INF目录及其子目录)
(2)保存在不能被浏览器直接访问的位置
     例如:付费的视频
     保存在工程中 META-INF  WEB_INF目录及其子目录下
     或者保存到不在工程中的服务器的磁盘目录下

事项二:上传文件在同一目录重名问题

 可以在开发中为上传文件起一个随机名称
  •  使用当前的毫秒值
  •  或使用UUID工具类生成随机字符串

事项三:同一目录下文件过多

需要进行目录分离,因为同一文件夹下的文件过多会影响查询速率
目录分离的几种方案:
(1)按照上传时间进行目录分离(月、周)
(2)按照上传用户进行目录分离(为每个用户建立单独的目录)
(3)按照文件固定数量进行分离(假设每个目录只能存放3000个文件,每当一个目录存满3000个文件后,创建一个       新的目录)
(4)按照文件名的hashcode进行目录分离
public static String generateRandomDir(String uuidFileName) {
// 获得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
// 获得一级目录
int d1 = hashcode & 0xf;       
// 获得二级目录
int d2 = (hashcode >>> 4) & 0xf;
        return "/" + d2 + "/" + d1;// 共有256目录l
}
几种目录分离的方法优缺点:
按照时间分离的话 不稳定 比如淘宝双十一访问量最多 按照时间会造成某一时期文件数量过多。
按照用户目录进行分离也不是太好,因为有的用户上传文件数量多 有的少。
按照文件固定数量进行分离固然好,但是需要判断文件夹内文件的数量 相当繁琐。
最好按照文件名的hashcode进行分离 

实例演示:按照文件名哈希值进行目录分离
还使用多文件上传的upload4.jsp 进行上传
首先新建一个FileUploadUtils工具类
package cn.itcast.utils;import java.io.File;import java.util.UUID;public class FileUploadUtils {// 得到上传文件真实名称 c:\a.txt a.txt// 如果参数为c:\a.txt 得到a.txt 如果为a.txt index=-1+1=0; 仍然正确public static String getRealName(String filename) {int index = filename.lastIndexOf("\\") + 1;return filename.substring(index);}// 获取随机名称public static String getUUIDFileName(String filename) {int index = filename.lastIndexOf(".");if (index != -1) {return UUID.randomUUID() + filename.substring(index);} else {return UUID.randomUUID().toString();}}// 目录分离算法public static String getRandomDirectory(String filename) {// 方式一:int hashcode = filename.hashCode();System.out.println(hashcode);// int数据类型在内存中占32位字节,转换成16进制数(4位),就得到8个16进制数String hex = Integer.toHexString(hashcode);System.out.println(hex);return "/" + hex.charAt(0) + "/" + hex.charAt(1);/* * 方式二: int hashcode = filename.hashCode(); *  * System.out.println(Integer.toBinaryString(hashcode)); *  * int a = hashcode & 0xf; *  * hashcode = hashcode >>> 4; *  * int b = hashcode & 0xf; *  * return "/" + a + "/" + b; */}/* * public static void main(String[] args) { String path = * getRandomDirectory("a.txt"); *  * File file = new File("C:/Users/lx/Desktop/upload"); File directory = new * File(file, path); if (!directory.exists()) { directory.mkdirs(); } } */}


新建UploadServlet4
package cn.itcast.web.servlet;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;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.IOUtils;import cn.itcast.utils.FileUploadUtils;public class Upload4Servlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");// 1.创建DiskFileItemFactoryFile file = new File(this.getServletContext().getRealPath("/temp"));// 获取temp目录部署到tomcat后的绝对磁盘路径DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 100, file);// 2.创建ServletFileUploadServletFileUpload upload = new ServletFileUpload(factory);// 判断是否是上传操作(即检测encType是否为"multipart/form-data")boolean flag = upload.isMultipartContent(request);if (flag) {// 解决上传文件中文名称乱码// 注意:也可以使用request.setCharacterEncoding("utf-8"); 不建议使用upload.setHeaderEncoding("utf-8");// 设置上传文件大小// upload.setSizeMax(1024*1024*10); // 设置文件总大小为10Mtry {List<FileItem> items = upload.parseRequest(request);// 解析request,得到所有上传项FileItem// 3.得到所有上传项for (FileItem item : items) {if (!item.isFormField()) {String name = item.getName(); // 上传文件名// 得到上传文件真实名称String filename = FileUploadUtils.getRealName(name);// 得到随机名称String uuidname = FileUploadUtils.getUUIDFileName(filename);// 得到随机目录String randomDirectory = FileUploadUtils.getRandomDirectory(filename);// 注意:随机目录可能不存在,需要创建File rd = new File("C:/Users/lx/Desktop/upload/",randomDirectory);if (!rd.exists()) {rd.mkdirs();}IOUtils.copy(item.getInputStream(),new FileOutputStream(new File(rd, uuidname)));// 删除临时文件item.delete();}}} catch (FileUploadException e) {// e.printStackTrace();response.getWriter().write(e.getMessage());}} else {response.getWriter().write("不是上传操作");return;}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}
运行如下:

文件下载

 (1)超连接下载

download1.jsp
<a href='${pageContext.request.contextPath}/upload/a.jpg'>a.jpg</a><br>
<a href='${pageContext.request.contextPath}/upload/a.docx'>a.docx</a><br>
<a href='${pageContext.request.contextPath}/upload/a.txt'>a.txt</a><br>
<a href='${pageContext.request.contextPath}/upload/就算没有如果.flac'>就算没有如果.flac</a><br>
注意:如果文件可以直接被浏览器解析,那么会在浏览器中直接打开,不能被浏览器直接解析,就是下载操作。
直接打开的要想下载 ,右键另存为。
超连接下载,要求下载 的资源,必须是可以直接被浏览器直接访问的。
客户端访问服务器静态资源文件时,静态资源文件是通过 缺省Servlet返回的,
在tomcat配置文件conf/web.xml 找到 --- org.apache.catalina.servlets.DefaultServlet

(2)在服务器端编程完成下载.

1.创建download2.jsp
<a href='${pageContext.request.contextPath}/download?filename=a.jpg'>a.jpg</a><br>
<a href='${pageContext.request.contextPath}/download?filename=a.docx'>a.docx</a><br>
<a href='${pageContext.request.contextPath}/download?filename=a.txt'>a.txt</a><br>
<a href='${pageContext.request.contextPath}/download?filename=就算没有如果.flac'>就算没有如果.flac</a><br>

2.创建DownloadServlet
// 1.得到要下载 的文件名称
String filename = request.getParameter("filename");

//2.判断文件是否存在
File file = new File("d:/upload/" + filename);
if (file.exists())
        //3.进行下载 
原理:就是通过response获取一个输出流,将要下载的文件内容写回到浏览器端就可以了.

注意:要想通过编程的方式,实现文件下载,
1.要设置mimetype类型
resposne.setContextType(String mimeType);
问题:怎样可以得到要下载文件的mimeType类型?
ServletContext.getMimeType(String filename);
如果设置了mimeType,浏览器能解析的就直接展示了,不能解析的,直接下载.
2.设置一个响应头,设置后的效果,就是无论返回的是否可以被浏览器解析,就是下载 。
response.setHeader("content-disposition","attachment;filename=下载文件名称");
总结:服务器端编程下载:
1.将下载的文件通过resposne.getOutputStream()流写回到浏览器端。
2.设置mimeType  response.setContentType(getServletContext.getMimeType(String filename));
3.设置响应头,目的是永远是下载操作
response.setHeader("content-disposition","attachment;filename=下载文件名称");

(3)文件下载时的乱码问题:

下载乱码一:关于下载时中文名称资源查找不到

原因:<a href='${pageContext.request.contextPath}/download?filename=就算没有如果.flac'>就算没有如果.flac</a>
这是get请求。
 在服务器端需要做如下处理:
String filename = request.getParameter("filename");
解决: new String(filename.getBytes("iso8859-1"),"utf-8"); 

下载乱码二:下载文件显示时的中文乱码问题

response.setHeader("content-disposition", "attachment;filename="+filename);
IE:要求filename必须是utf-8码
firefox:要求filename必须是base64编码.
问题:怎样判断浏览器?
String agent=request.getHeader("user-agent");
        if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"
+ base64Encoder.encode(filename.getBytes("utf-8"))
+ "?=";
}else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}

代码示例:浏览器端超链接下载

在WebRoot目录下新建名称为upload的文件夹 拖进去几个文件如下所示:
 新建download1.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <title>My JSP 'index.jsp' starting page</title>  </head>    <body>    <a href='${pageContext.request.contextPath}/upload/a.jpg'>a.png</a><br><a href='${pageContext.request.contextPath}/upload/a.docx'>a.docx</a><br><a href='${pageContext.request.contextPath}/upload/a.txt'>a.txt</a><br><a href='${pageContext.request.contextPath}/upload/就算没有如果.flac'>就算没有如果.flac</a><br>  </body></html>



发现浏览器能解析的就直接显示在页面中了 而不是直接下载

代码示例:服务器端下载

在桌面新建upload文件夹  在文件夹内将上面几个资源文件拖进去
新建download2.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <title>My JSP 'index.jsp' starting page</title>  </head>    <body>    <a href='${pageContext.request.contextPath}/download?filename=a.jpg'>a.jpg</a><br><a href='${pageContext.request.contextPath}/download?filename=a.docx'>a.docx</a><br><a href='${pageContext.request.contextPath}/download?filename=a.txt'>a.txt</a><br><a href='${pageContext.request.contextPath}/download?filename=就算没有如果.flac'>就算没有如果.flac</a><br>  </body></html>
新建DownloadServlet
package cn.liuxun.web.servlet;import java.io.File;import java.io.FileInputStream;import java.io.IOException;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 sun.misc.BASE64Encoder;public class DownloadServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 1.得到要下载的文件名称String filename = request.getParameter("filename");filename = new String(filename.getBytes("iso8859-1"), "utf-8"); // 解决中文乱码// 2.在c:/Users/lx/Desktop/upload目录下查找这个文件是否存在File file = new File("c:/Users/lx/Desktop/upload", filename);if (file.exists()) {// 文件存在,完成下载// 下载注意事项1--设置下载文件的mimeTypeString mimeType = this.getServletContext().getMimeType(filename);response.setContentType(mimeType);String agent = request.getHeader("user-agent");if (agent.contains("MSIE")) {// IE浏览器filename = URLEncoder.encode(filename, "utf-8");} else if (agent.contains("Firefox")) {// 火狐浏览器BASE64Encoder base64Encoder = new BASE64Encoder();filename = "=?utf-8?B?"+ base64Encoder.encode(filename.getBytes("utf-8"))+ "?=";} else {// 其它浏览器filename = URLEncoder.encode(filename, "utf-8");}// 下载注意事项2--永远是下载 设置以附件的形式进行打开下载response.setHeader("content-disposition", "attachment;filename="+ filename);FileInputStream fis = new FileInputStream(file); // 读取要下载文件的内容OutputStream os = response.getOutputStream();// 将要下载的文件内容通过输出流写回到浏览器int len = -1;byte[] b = new byte[1024 * 100];while ((len = fis.read(b)) != -1) {os.write(b, 0, len);os.flush();}os.close();fis.close();} else {throw new RuntimeException("下载资源不存在");}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}


遍历文件夹操作

(1)方式一:递归遍历
在项目中新建如下文件夹

递归遍历如下:
<%@page import="java.io.File"%><%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>使用递归完成下载upload目录下的所有文件</title></head><body><!-- 使用递归操作 --><%!//声明一个方法public void getFile(File file) {if (file.isDirectory()) { //是目录File[] fs = file.listFiles();for (int i = 0; i < fs.length; i++) {getFile(fs[i]); //递归调用}} else if (file.isFile()) { //是文件System.out.println(file.getName()+"  路径:"+file.getPath());}}%><%String path = application.getRealPath("/upload");File uploadDirectory = new File(path);getFile(uploadDirectory);%></body></html>


(2)方式二:使用队列替代递归
队列特点:先进先出.
在jdk中有一个接口Queue 它有一个实现类叫LinkedList它其时就是一个队列。
如果要使用队列,插入 offer  获取使用 poll
使用队列来优化递归操作:是可以解决目录层次过多问题。
  • 因为:递归操作可以理解成是纵向的遍历,如果目录层次比较多,在内存中存储的数据也多,会引起溢出。
  • 使用队列,它是横向遍历,一层一层遍历,可以解决目录层次比较多问题。
  • 因为使用队列,最多时候在内存中只存储了一层的信息
<%@page import="java.io.File"%><%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <title>使用队列来完成下载upload目录下所有文件</title>  </head>    <body>  <!-- 使用队列操作 -->  <%    String path=application.getRealPath("/upload");   File uploadDirectory=new File(path);   //创建一个队列      Queue<File> queue=new LinkedList<File>();      queue.offer(uploadDirectory);      while(!queue.isEmpty()){ //如果队列不为空   File f=queue.poll(); //从队列中获取一个File      if(f.isDirectory()){ //是目录,将目录下所有文件遍历出来,存储在队列中   File[] fs=f.listFiles();     for(int i=0;i<fs.length;i++){     queue.offer(fs[i]);     }   }else{   String absolutePath=f.getAbsolutePath();      String  p=absolutePath.substring(absolutePath.lastIndexOf("\\upload"));      out.println("<a href='"+application.getContextPath()+p+"'>"+f.getName()+"</a><br>");   }      }  %>    </body></html>
访问如下:



网盘系统(实例演示上传和下载)

 
 实现步骤如下:
(1)创建表
                create table resources(  id int primary key auto_increment,  uuidname varchar(100) unique not null,  realname varchar(40) not null,  savepath varchar(100) not null,  uploadtime timestamp ,  description varchar(255));

(2)导入jar包
C3P0、BeanUtils、DbUtils、FileUpload、JSTL、MySQL驱动
C3P0:c3p0-0.9.1.2.jar
BeanUtils:commons-beanutils-1.8.3.jar、commons-logging-1.1.1.jar
DbUtils:commons-dbutils-1.4.jar
FileUpload:commons-fileupload-1.2.1.jar、commons-io-1.4.jar
JSTL:jstl.jar、standard.jar

MySQL驱动:mysql-connector-java-5.1.28-bin.jar

(3)编码实现上传

1.在index.jsp页面添加上传连接
<a href='${pageContext.request.contextPath}/upload.jsp'>上传</a><br>
2.创建upload.jsp页面
上传操作浏览器端三个注意事项:
1.method=post
2.encType="multipart/form-data"
3.要使用<input type="file" name='f'>

<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f"><br>
描述:<input type="text" name="description"><br>
<input type="submit" value="提交">
</form>
3.创建UploadServlet
1.完成上传操作
2.将数据封装,存储到数据库。

1.上传操作
commons-fileupload
1.DiskFileItemFactory 
2.ServletFileUpload
3.FileItem

2.将数据封装,存储到数据库.
问题:怎样将数据封装到javaBean?
手动创建一个Map<String,String[]>将数据封装到map集合,
通过BeanUtils完成数据封装.

(4)下载操作

1.在index.jsp页面,下载连接会访问一个servlet,得到所有可以下载的数据,在页面上展示 .
1.index.jsp页面代码
<a href="${pageContext.request.contextPath}/showDownload">下载</a>
2.创建ShowDownloadServlet
在这个servlet中,查看db,得到所有可以下载的信息.
List<Resource> rs = service.findAll();
3.创建一个download.jsp页面,展示所有可以下载的信息.

2.在download.jsp,点击下载时,传递的是要下载文件的id。
<a href='${pageContext.request.contextPath}/download?id=${r.id}'>下载</a>
1.创建一个DownloadServlet
1.查询数据库,得到要下载的文件的相关信息
2.下载操作


该项目目录图如下:

---

具体代码如下:

 添加数据源配置c3p0-config.xml 

<?xml version="1.0" encoding="UTF-8"?><c3p0-config><default-config><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql:///mydb1</property><property name="user">root</property><property name="password">root</property></default-config></c3p0-config>
新建工具类DataSourceUtils 获取连接池中的数据源

package cn.liuxun.utils;import java.sql.Connection;import java.sql.SQLException;import javax.sql.DataSource;import com.mchange.v2.c3p0.ComboPooledDataSource;public class DataSourceUtils {private static ComboPooledDataSource cpds = new ComboPooledDataSource();public static Connection getConnection() throws SQLException {return cpds.getConnection();}public static DataSource getDataSource() {return cpds;}}
新建工具类FileUploadUtils获取 文件真实名称、随机名称以及 目录分离中的随机目录

package cn.liuxun.utils;import java.util.UUID;public class FileUploadUtils {// 得到上传文件真实名称 c:\a.txt a.txtpublic static String getRealName(String filename) {int index = filename.lastIndexOf("\\") + 1;return filename.substring(index);}// 获取随机名称public static String getUUIDFileName(String filename) {int index = filename.lastIndexOf(".");if (index != -1) {return UUID.randomUUID() + filename.substring(index);} else {return UUID.randomUUID().toString();}}// 目录分离算法public static String getRandomDirectory(String filename) {int hashcode = filename.hashCode();System.out.println(Integer.toBinaryString(hashcode));int a = hashcode & 0xf;hashcode = hashcode >>> 4;int b = hashcode & 0xf;return "/"+a+"/"+b;}}

新建Bean类Resource

package cn.liuxun.domain;import java.sql.Timestamp;public class Resource {// id INT PRIMARY KEY AUTO_INCREMENT,// uuidname VARCHAR(100) UNIQUE NOT NULL,// realname VARCHAR(40) NOT NULL,// savepath VARCHAR(100) NOT NULL,// uploadtime TIMESTAMP ,// description VARCHAR(255)private int id;private String uuidname;private String realname;private String savepath;private Timestamp uploadtime;private String description;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUuidname() {return uuidname;}public void setUuidname(String uuidname) {this.uuidname = uuidname;}public String getRealname() {return realname;}public void setRealname(String realname) {this.realname = realname;}public String getSavepath() {return savepath;}public void setSavepath(String savepath) {this.savepath = savepath;}public Timestamp getUploadtime() {return uploadtime;}public void setUploadtime(Timestamp uploadtime) {this.uploadtime = uploadtime;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}}


新建DAO类ResourceDao

package cn.liuxun.dao;import java.sql.SQLException;import java.util.List;import org.apache.commons.dbutils.QueryRunner;import org.apache.commons.dbutils.handlers.BeanHandler;import org.apache.commons.dbutils.handlers.BeanListHandler;import cn.liuxun.domain.Resource;import cn.liuxun.utils.DataSourceUtils;public class ResourceDao {public List<Resource> findAll() throws SQLException {QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());return runner.query("select * from resources",new BeanListHandler<Resource>(Resource.class));}public void save(Resource r) throws SQLException {String sql = "insert into resources values(null,?,?,?,null,?)";QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());runner.update(sql, r.getUuidname(), r.getRealname(), r.getSavepath(),r.getDescription());}public Resource findById(String id) throws SQLException {QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());return runner.query("select * from resources where id=?",new BeanHandler<Resource>(Resource.class), id);}}


新建service类 ResourceService类

package cn.liuxun.service;import java.sql.SQLException;import java.util.List;import cn.liuxun.dao.ResourceDao;import cn.liuxun.domain.Resource;public class ResourceService {public List<Resource> findAll() throws SQLException { return new ResourceDao().findAll();}public void save(Resource r) throws SQLException {new ResourceDao().save(r);}public Resource findById(String id) throws SQLException {        return new ResourceDao().findById(id);}}

新建Servlet类DownloadServlet  用于下载网盘文件

package cn.liuxun.web.servlet;import java.io.File;import java.io.IOException;import java.net.URLEncoder;import java.sql.SQLException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.io.FileUtils;import sun.misc.BASE64Encoder;import cn.liuxun.domain.Resource;import cn.liuxun.service.ResourceService;public class DownloadServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 1.得到idString id = request.getParameter("id");// 2.调用service,得到Resource对象ResourceService service = new ResourceService();try {Resource r = service.findById(id);File file = new File(r.getSavepath(), r.getUuidname());if (file.exists()) {// 资源存在String filename = r.getRealname();// 下载注意事项1 -- 设置下载文件的mimeTypeString mimeType = this.getServletContext().getMimeType(filename);response.setContentType(mimeType);String agent = request.getHeader("user-agent");if (agent.contains("MSIE")) {// IE浏览器filename = URLEncoder.encode(filename, "utf-8");} else if (agent.contains("Firefox")) {// 火狐浏览器BASE64Encoder base64Encoder = new BASE64Encoder();filename = "=?utf-8?B?"+ base64Encoder.encode(filename.getBytes("utf-8"))+ "?=";} else {// 其它浏览器filename = URLEncoder.encode(filename, "utf-8");}// 下载注意事项2 -- 永远是下载response.setHeader("content-disposition","attachment;filename=" + filename);byte[] b = FileUtils.readFileToByteArray(file);// 将指定文件读取到byte数组中response.getOutputStream().write(b);} else {}} catch (SQLException e) {e.printStackTrace();}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

新建下载列表展示类ShowDownloadServlet

package cn.liuxun.web.servlet;import java.io.IOException;import java.sql.SQLException;import java.util.List;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import cn.liuxun.domain.Resource;import cn.liuxun.service.ResourceService;public class ShowDownloadServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {ResourceService service = new ResourceService();try {List<Resource> rs = service.findAll();request.setAttribute("rs", rs);request.getRequestDispatcher("/download.jsp").forward(request,response);} catch (SQLException e) {e.printStackTrace();}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

新建UploadServlet类 用于上传文件

package cn.liuxun.web.servlet;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.sql.SQLException;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.beanutils.BeanUtils;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.IOUtils;import cn.liuxun.domain.Resource;import cn.liuxun.service.ResourceService;import cn.liuxun.utils.FileUploadUtils;public class UploadServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {Map<String, String[]> map = new HashMap<String, String[]>();// 1.创建DiskFileItemFactoryDiskFileItemFactory factory = new DiskFileItemFactory();// 2.创建ServletFileUploadServletFileUpload upload = new ServletFileUpload(factory);// 设置上传中文名称乱码upload.setHeaderEncoding("utf-8");// upload.isMultipartContent(request);// 3.得到所有的FileItemtry {List<FileItem> items = upload.parseRequest(request);// 遍历items,得到所有的上传信息for (FileItem item : items) {if (item.isFormField()) {// 不是上传组件map.put(item.getFieldName(),new String[] { item.getString("utf-8") });// 封装非上传组件信息} else {// 是上传组件// 得到上传文件名称String filename = item.getName();filename = FileUploadUtils.getRealName(filename);map.put("realname", new String[] { filename });// 封装上传文件真实名称// 得到随机名称String uuidname = FileUploadUtils.getUUIDFileName(filename);map.put("uuidname", new String[] { uuidname });// 封装上传文件随机名称// 得到随机目录String randomDirectory = FileUploadUtils.getRandomDirectory(filename);String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");File parentDirectory = new File(uploadPath, randomDirectory);if (!parentDirectory.exists()) {parentDirectory.mkdirs();}map.put("savepath", new String[] { uploadPath+ randomDirectory });// 封装上传文件保存路径IOUtils.copy(item.getInputStream(), new FileOutputStream(new File(parentDirectory, uuidname)));item.delete();}}// 将数据封装到JavaBeanResource r = new Resource();BeanUtils.populate(r, map);// 调用service完成保存数据到dbResourceService service = new ResourceService();service.save(r);} catch (FileUploadException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

新建JSP页面 

index.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 'index.jsp' starting page</title>  </head>    <body>   <a href="${pageContext.request.contextPath }/upload.jsp">上传</a>   <a href="${pageContext.request.contextPath }/showDownload">下载</a>  </body></html>

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 'index.jsp' starting page</title></head><body><form action="${pageContext.request.contextPath }/upload" method="post"enctype="multipart/form-data"><input type="file" name="f"> <br>描述:<input type="text" name="description"><br>      <input type="submit" value="提交"></form></body></html>

download.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>My JSP 'index.jsp' starting page</title></head><body><table border="1" align="center" width="65%"><tr><td>文件名称</td><td>文件描述</td><td>下载操作</td></tr><c:forEach items="${rs }" var="r"><tr><td>${r.realname }</td><td>${r.description }</td><td><ahref="${pageContext.request.contextPath }/download?id=${r.id}">下载</a></td></tr></c:forEach></table></body></html>


上传几个文件进行测试  下载如下:



查看数据库中的resources表



打开发布后的文件夹


OK 

注意:此网盘文件内容是保存在项目中的 如果重新部署项目 文件就会丢失

在重新部署项目之前需要将upload文件夹进行备份
























1 0
原创粉丝点击