Java-Web 文件上传和文件下载

一、文件上传

        将信息从个人计算机(本地计算机)传送至中央计算机(远程计算机)系统上,让网络上的人都能看到。

    1.文件上传对页面的要求

        (1)必须使用表单,而不能是超链接;

        (2)表单的method必须是POST,而不能是GET;

        (3)表单的enctype必须是multipart/form-data

        (4)在表单中添加file表单字段,即<input type=”file”…/>

    <form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
    	用户名:<input type="text" name="username"/><br/>
    	文件1:<input type="file" name="file1"/><br/>
    	文件2:<input type="file" name="file2"/><br/>
    	<input type="submit" value="提交"/>
    </form>
    2.文件上传对Servlet的要求

        当提交的表单是文件上传表单时,那么对Servlet也是有要求的。

        首先我们要肯定一点,文件上传表单的数据也是被封装到request对象中的。

        request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不在是字符内容,而是字节内容,所以失效。

        这时可以使用request的getInputStream()方法获取ServletInputStream对象,它是InputStream的子类,这个ServletInputStream对象对应整个表单的正文部分(从第一个分隔线开始,到最后),这说明我们需要的解析流中的数据。当然解析它是很麻烦的一件事情,而Apache已经帮我们提供了解析它的工具:commons-fileupload

二、commons-fileupload

    1.fieupload概述

        fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。

        fileupload组件需要的JAR包有:

                commons-fileupload.jar,核心包;

                commons-io.jar,依赖包。

    2.fieupload的简单应用

        fileupload的核心类有:DiskFileItemFactory、ServletFileUpload、FileItem。

        使用fileupload组件的步骤如下:

            1.创建工厂类DiskFileItemFactory对象:DiskFileItemFactory factory = new DiskFileItemFactory()

            2.使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)

            3.使用解析器来解析request对象:List<FileItem> list = fileUpload.parseRequest(request)


        一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。

        String getName():获取文件字段的文件名称;

        String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;

        String getFieldName():获取字段名称,例如:<input type=”text” name=”username”/>,返回的是username;

        String getContentType():获取上传的文件的类型,例如:text/plain。

        int getSize():获取上传文件的大小;

        boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;

        InputStream getInputStream():获取上传文件对应的输入流;

        void write(File):把上传的文件保存到指定文件中。

文件上传代码简单实现:

shangchuan.jsp

<h1>上传</h1>
<form action="<c:url value='/Upload1Servlet'/>" method="post" enctype="multipart/form-data">
	用户名:<input type="text" name="username"/><br/>
	选择图片:<input type="file" name="image"/><br/>
	<input type="submit" value="上传"/><br/>
</form>
Upload1Servlet.java
package cn.yfy.fileload;

import java.io.File;
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 cn.itcast.commons.CommonUtils;

public class Upload1Servlet extends HttpServlet {

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		// 创建工厂
		DiskFileItemFactory factory = new DiskFileItemFactory();
		// 得到解析器
		ServletFileUpload sfu = new ServletFileUpload(factory);
		// 解析request得到FileItem集合
		try {
			List<FileItem> fileItemList = sfu.parseRequest(request);
			FileItem fi1 = fileItemList.get(0);
			FileItem fi2 = fileItemList.get(1);
			// 得到上传文件的表单项名字和值
			System.out.println("表单项名称为" + fi1.getFieldName());
			System.out.println("表单项的值为" + fi1.getString());
			// 得到上传文件的字节数、大小和文件类型
			System.out.println("长传的字节数" + fi2.getSize());
			System.out.println("上传的文件名称为" + fi2.getName());
			System.out.println("上传的文件类型为" + fi2.getContentType());
			// 得到文件保存的路径
			String root = this.getServletContext()
					.getRealPath("/WEB-INF/files/");
			// 得到用户上传的文件名,处理可能出现的绝对路径问题
			System.out.println("root:"+root);
			String filename = fi2.getName();
			int index = filename.lastIndexOf("\\");
			if (index != -1) {
				filename = filename.substring(index + 1);
			}
			System.out.println("filename"+filename);
			// 得到hashCode
			int hCode = filename.hashCode();
			// 转换成16进制
			String hex = Integer.toHexString(hCode);
			// 获取前两个字符生成二级目录
			File dirFile = new File(root, hex.charAt(0) + "/" + hex.charAt(1));
			// 创建目录
			dirFile.mkdirs();
			// 得到保存时的文件名,加上uuid确保不会重名
			String savename = CommonUtils.uuid() + filename;
			System.out.println(savename);
			File destFile = new File(dirFile, savename);
			try {
				fi2.write(destFile);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (FileUploadException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

三、文件上传之细节

    1.把上传的文件放到WEB-INF目录下

        如果没有把用户上传的文件存放到WEB-INF目录下,那么用户就可以通过浏览器直接访问上传的文件,这是非常危险的。

        假如说用户上传了一个a.jsp文件,然后用户在通过浏览器去访问这个a.jsp文件,那么就会执行a.jsp中的内容,如果在a.jsp中有如下语句:Runtime.getRuntime().exec(“shutdown –s –t 1”);,那么你就会…

        通常我们会在WEB-INF目录下创建一个uploads目录来存放上传的文件,而在Servlet中找到这个目录需要使用ServletContext的getRealPath(String)方法,例如在我的upload1项目中有如下语句:

                ServletContext servletContext = this.getServletContext();

                String savepath = servletContext.getRealPath(“/WEB-INF/uploads”);

        其中savepath为:F:\tomcat6_1\webapps\upload1\WEB-INF\uploads。

    2.文件名称(完整路径)

        使用不同浏览器测试,其中IE6就会返回上传文件的完整路径,这给我们带来了很大的麻烦,就是需要处理这一问题。

        处理这一问题也很简单,无论是否为完整路径,我们都去截取最后一个“\\”后面的内容就可以了。

			String name = file1FileItem.getName();
			int lastIndex = name.lastIndexOf("\\");//获取最后一个“\”的位置
			if(lastIndex != -1) {//注意,如果不是完整路径,那么就不会有“\”的存在。
				name = name.substring(lastIndex + 1);//获取文件名称
			}
			response.getWriter().print(name);
    3.中文乱码问题

        上传文件名称中包含中文:

            当上传的谁的名称中包含中文时,需要设置编码,commons-fileupload组件为我们提供了两种设置编码的方式:

                request.setCharacterEncoding(String):这种方式是我们最为熟悉的方式了;

                fileUpload.setHeaderEncdoing(String):这种方式的优先级高与前一种。

        上传文件的文件内容包含中文:

            通常我们不需关心上传文件的内容,因为我们会把上传文件保存到硬盘上!也就是说,文件原来是什么样子,到服务器这边还是什么样子!

            但是如果你要在控制台显示上传的文件内容,那么你可以使用fileItem.getString(“utf-8”)来处理编码。

            文本文件内容和普通表单项内容使用FileItem类的getString(“utf-8”)来处理编码。

    4.上传文件同名问题

        通常我们会把用户上传的文件保存到uploads目录下,但如果用户上传了同名文件呢?这会出现覆盖的现象。处理这一问题的手段是使用UUID生成唯一名称,然后再使用“_”连接文件上传的原始名称。

        例如用户上传的文件名称是“a.jpg”,在uuid自动生成随机数处理后,文件名称为:“DC15EF727F144376864E99450A82268Da.jpg”,这种手段不会使文件丢失扩展名,并且因为UUID的唯一性,上传的文件同名,但在服务器端是不会出现同名问题的。

    5.一个目录不能存放过多的文件

        我们这里使用hash算法来打散:

                1.获取文件名称的hashCode:int hCode = name.hashCode();;

                2.获取hCode的低4位,然后转换成16进制字符;

                3.获取hCode的5~8位,然后转换成16进制字符;

                4.使用这两个16进制的字符生成目录链。例如低4位字符为“5”

        这种算法的好处是,在uploads目录下最多生成16个目录,而每个目录下最多再生成16个目录,即256个目录,所有上传的文件都放到这256个目录下。如果每个目录上限为1000个文件,那么一共可以保存256000个文件。

    6.上传文件的大小限制

        有时我们需要限制一个请求的大小。也就是说这个请求的最大字节数(所有表单项之和)!实现这一功能也很简单,只需要调用ServletFileUpload类的setSizeMax(long)方法即可。还可以限制文件大小,只需要调用调用ServletFileUpload类的setFileSizeMax(long)方法即可。上面的两个方法必须在解析之前开始调用。

        例如fileUpload.setSizeMax(1024 * 10);,显示整个请求的上限为10KB。当请求大小超出10KB时,ServletFileUpload类的parseRequest()方法会抛出FileUploadBase.SizeLimitExceededException异常。

        例如fileUpload.setFileSizeMax(1024 * 10);,显示文件的上限为10KB。当文件大小超出10KB时,ServletFileUpload类的parseRequest()方法会抛出FileUploadBase.FileSizeLimitExceededException异常。

    7.缓存大小与临时目录

        大家想一想,如果我上传一个蓝光电影,先把电影保存到内存中,然后再通过内存copy到服务器硬盘上,那么你的内存能吃的消么?

        所以fileupload组件不可能把文件都保存在内存中,fileupload会判断文件大小是否超出10KB,如果是那么就把文件保存到硬盘上,如果没有超出,那么就保存在内存中。

  10KB是fileupload默认的值,我们可以来设置它。

  当文件保存到硬盘时,fileupload是把文件保存到系统临时目录,当然你也可以去设置临时目录。

        DiskFileItemFactory dfif = new DiskFileItemFactory(1024*20, new File("F:\\temp"));

                指定缓存大小为20KB,当上传的文件超出了20KB就会保存到临时目录。临时目录为F:\\temp

四、文件下载

    1.通过Servlet下载

        被下载的资源必须放到WEB-INF目录下(只要用户不能通过浏览器直接访问就OK),然后通过Servlet完成下载。

        在jsp页面中给出超链接,链接到DownloadServlet,并提供要下载的文件名称。然后DownloadServlet获取文件的真实路径,然后把文件写入到response.getOutputStream()流中。

        还需要设置两个响应头。

            > Content-Type:你传递给客户端的文件是什么MIME类型,例如:image/pjpeg

                  * 通过文件名称调用ServletContext的getMimeType()方法,得到MIME类型!

            > Content-Disposition:它的默认值为inline,表示在浏览器窗口中打开!attachment;filename=xx

    2.下载框中文乱码问题

        (1)给设置响应头的filename进行如下编码

                filename = new String(filename.getBytes("GBK"), "ISO-8859-1");

response.addHeader("content-disposition", "attachment;filename=" + filename);

        (2)调用下面的方法

package cn.yfy.download;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;

import sun.misc.BASE64Encoder;

public class FilenameEncoding {
	public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {
	String agent = request.getHeader("User-Agent"); //获取浏览器
	if (agent.contains("Firefox")) {
		BASE64Encoder base64Encoder = new BASE64Encoder();
		filename = "=?utf-8?B?"
				+ base64Encoder.encode(filename.getBytes("utf-8"))
				+ "?=";
	} else if(agent.contains("MSIE")) {
		filename = URLEncoder.encode(filename, "utf-8");
	} else {
		filename = URLEncoder.encode(filename, "utf-8");
	}
	return filename;
}
}
    3.代码演示
package cn.yfy.download;

import java.io.FileInputStream;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;

public class DownLoadServlet extends HttpServlet {

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		/*
		 * 两个头一个流 1. Content-Type 2. Content-Disposition 3. 流:下载文件的数据
		 */
		String filename = "F:/追梦赤子心.mp3";

		// 为了使下载框中显示中文文件名称不出乱码!
		// String framename = new String("追梦赤子心.mp3".getBytes("GBK"),
		// "ISO-8859-1");
		String framename = FilenameEncoding.filenameEncoding("追梦赤子心.mp3", req);

		// 通过文件名称获取MIME类型
		String contentType = this.getServletContext().getMimeType(filename);
		String contentDisposition = "attachment;filename=" + framename;
		// 一个流
		FileInputStream input = new FileInputStream(filename);

		// 设置头
		resp.setHeader("Content-Type", contentType);
		resp.setHeader("Content-Disposition", contentDisposition);

		// 获取绑定了响应端的流
		ServletOutputStream output = resp.getOutputStream();
		
		// 把输入流中的数据写入到输出流中。
		IOUtils.copy(input, output);
		
		// 关闭流
		input.close();
	}
}



猜你喜欢

转载自blog.csdn.net/fy_java1995/article/details/80419530