企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] # 页面注意 设置文件上传输入项时须注意: * 必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。 * 必须把form的enctype属值设为`multipart/form-data`。设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。 现在我们设计一个这样的文件上传页面——upload.jsp ~~~ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="${pageContext.request.contextPath }/UploadServlet" enctype="multipart/form-data" method="post"> 上传用户:<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> </body> </html> ~~~ 此时表单的提交方式应该为POST,因为请求方式为POST方式,则可以在请求的实体内容中向服务器发送数据,即文件数据将附带在http请求消息体中。 # Servlet处理请求 接下来如何在Servlet中读取文件上传数据,并保存到本地硬盘中呢? 答案应该是这样的:Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作。 ~~~ public class UploadServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InputStream in = request.getInputStream(); int len = 0; byte[] buffer = new byte[1024]; while ((len=in.read(buffer)) > 0) { System.out.println(new String(buffer, 0, len)); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } ~~~ 这时我们再次输入上传用户名和两个文件,Eclipse的控制台输出如下: ~~~ -----------------------------24566388929294 Content-Disposition: form-data; name="username" aaa -----------------------------24566388929294 Content-Disposition: form-data; name="file1"; filename="涓婁紶鏂囦欢1.txt" Content-Type: text/plain aaaaaaaaaa -----------------------------24566388929294 Content-Disposition: form-data; name="file2"; filename="涓婁紶鏂囦欢2.txt" Content-Type: text/plain bbbbbbbbb -----------------------------24566388929294-- ~~~ 这时我们在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作。为方便用户处理文件上传数据,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。 # 导入Commons-fileupload 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io。commons-io不属于文件上传组件的开发jar文件,但Commons-fileupload组件从1.1版本开始,它工作时需要commons-io包的支持。 我们从网上下载了这两个jar包 * commons-fileupload.jar * commons-io-2.5.jar # fileupload组件工作流程 fileupload组件工作流程如图所示: ![](https://box.kancloud.cn/a5da7b6a677966e14b143a574075aa10_766x348.png) ## 核心API—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):构造函数 ## 核心API—ServletFileUpload 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) ## 中文乱码 上传文件的中文乱码问题: * 解决上传文件的中文乱码:ServletFileUpload.setHeaderEncoding(“UTF-8”) * 解决普通输入项的中文乱码(注意,表单类型为multipart/form-data的时候,设置request的编码是无效的):FileItem.setString("UTF-8") ## 注意事项 **在处理表单之前须判断提交表单的类型** 如果处理文件上传的UploadServlet一上来不管三七二十一就劈头盖脸地用解析器进行解析数据,而不管表单类型是不是multipart/form-data。 显然这样做是不合理的。合理的做法应该是:在处理表单之前,要记得调用ServletFileUpload.isMultipartContent方法判断提交表单的类型,如果该方法返回true,则按上传方式处理,否则按照传统方式处理表单即可 **当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件** DiskFileItemFactory是创建FileItem对象的工厂,其内存缓冲区的大小默认为10K,当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。比方说我们要上传一部电影,电影通常都在几百兆左右,这么大的一个文件显然就已经超出了内存缓冲区的大小,那这个时候我们可以使用临时文件来缓存上传文件。 我们在项目的WebRoot根目录下新建一个保存临时文件的目录——temp。 我们可以设置DiskFileItemFactory的内存缓冲区大小为1M,即内存缓冲区开辟1M大小的空间。如果上传文件没有超过1M,那么fileupload组件将使用内存缓冲区缓存上传文件;如果上传文件超过1M,那么fileupload组件将使用临时文件缓存上传文件。那么代码就应该是这样的: ~~~ DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024*1024); ~~~ 然后还要指定临时文件的目录,那代码就应该是这样的: ~~~ factory.setRepository(new File(this.getServletContext().getRealPath("/temp"))); ~~~ **上传文件的保存目录** 大家在做上传文件时,总该会想我们上传的文件会保存在哪儿吧?绝大部分的程序员可能会在WebRoot根目录下新建一个upload目录,在此目录下保存上传的文件 **判断文件扩展名** 我们在做文件上传系统的时候,可以限制上传文件的类型,如我这个文件上传系统只支持XXX.jpg、XXX.gif、XXX.avi、XXX.txt等格式的文件上传,其他格式的文件我是拒绝的。要做到这点其实不难,只要在处理上传文件时,判断上传文件的后缀名是不是允许的即可 **限制上传文件的大小** 我们也可以设置单个上传文件的大小,只要超出我们设置的值,就不让其上传,并给用户以友好提示。 有些人还是要以身试险的,如我调用解析器的setFileSizeMax方法设置上传文件的最大值为5M,即ServletFileUpload.setFileSizeMax(1024*1024*5);,只要有人上传的文件大于5M,试想解析器在解析这份文件时,应如何做呢?可以想见,解析器就会抛出一个异常,翻阅Commons-fileupload组件的文档,就能找到这样一个异常:FileUploadBase.FileSizeLimitExceededException,不错,我们就是要抛它,然后程序员通过是否抓到这个异常,进而就可以给用户友好提示 **为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名** 我们在上传文件系统的时候,要考虑到多用户上传相同文件名的文件,而导致文件覆盖的情况发生的问题。为了避免上传文件的覆盖,程序在保存上传文件时,要为每一个文件生成一个唯一的文件名 ## 例子 最后我们要编写处理文件上传的Servlet。我们可以按照下面的步骤将它撸出来。 1. 创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录,也即创建解析工厂。 2. 使用DiskFileItemFactory对象创建ServletFileUpload对象,并设置上传文件的大小限制,也即创建解析器。 3. 调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。 4. 对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件。 1. 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值。 2. 为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。 ~~~ public class UploadServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // List<String> types = Arrays.asList(".jpg",".gif",".avi",".txt"); List<String> types = Arrays.asList("jpg","gif","avi","txt"); // request.setCharacterEncoding("UTF-8"); try { DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024*1024); factory.setRepository(new File(this.getServletContext().getRealPath("/temp"))); ServletFileUpload upload = new ServletFileUpload(factory); upload.setProgressListener(new ProgressListener() { // update方法一定要由解析器来调用 @Override public void update(long pBytesRead, long pContentLength, int pItems) { System.out.println("当前已解析:" + pBytesRead); } }); // 限制上传文件的大小 upload.setFileSizeMax(1024*1024*5); // 只要超出5M,for循环在解析的时候就会抛异常 // 提交的表单类型不是multipart/form-data,没必要用解析器进行解析数据,按照传统方式获取表单数据 if (!upload.isMultipartContent(request)) { // 按照传统方式获取表单数据 request.getParameter("username"); blabla...... return; } // 解决上传文件的中文乱码问题,设置解析器的编码,到底设什么编码,也不能瞎写,一定要看表单的数据是以什么编码提交的! upload.setHeaderEncoding("UTF-8"); List<FileItem> list = upload.parseRequest(request); for (FileItem item : list) { if (item.isFormField()) { // 为普通输入项的数据 String inputName = item.getFieldName(); String inputValue = item.getString("UTF-8"); // inputValue = new String(inputValue.getBytes("ISO8859-1"), "UTF-8"); System.out.println(inputName + "=" + inputValue); } else { // 代表当前处理的item里面封装的是上传文件 String fileName = item.getName().substring(item.getName().lastIndexOf("\\")+1); if (fileName == null || fileName.trim().equals("")) { continue; } // 拿到文件的扩展名 String ext = fileName.substring(fileName.lastIndexOf(".")+1); if (!types.contains(ext)) { request.setAttribute("message", "本系统不支持" + ext + "这种类型"); request.getRequestDispatcher("/message.jsp").forward(request, response); return; } InputStream in = item.getInputStream(); int len = 0; byte[] buffer = new byte[1024]; // 得到保存在服务器中唯一的文件名 String saveFileName = generateFileName(fileName); // 产生文件的保存目录 String savePath = generateSavePath(this.getServletContext().getRealPath("/WEB-INF/upload"), saveFileName); FileOutputStream out = new FileOutputStream(savePath + File.separator + saveFileName); while ((len=in.read(buffer)) > 0) { out.write(buffer, 0, len); } in.close(); out.close(); /* * 上传文件完了之后,删除临时文件, * 千万注意:这句代码一定要放在流关闭之后,否则,还有流和它相关联,那就删除不掉临时文件, * 为了确保流关闭、删除掉临时文件,最好把这些代码放到finally代码块中。 */ item.delete(); } } } catch (FileUploadBase.FileSizeLimitExceededException e) { e.printStackTrace(); request.setAttribute("message", "文件大小不能超过5M"); request.getRequestDispatcher("/message.jsp").forward(request, response); return; } catch (Exception e) { throw new RuntimeException(e); } request.setAttribute("message", "上传成功!!!"); request.getRequestDispatcher("/message.jsp").forward(request, response); } public String generateSavePath(String path, String fileName) { int hashCode = fileName.hashCode(); // 得到字符串在内存中的地址,如121221 int dir1 = hashCode & 15; // int dir1 = hashCode & 0xf; 代表一级目录 int dir2 = (hashCode >> 4) & 0xf; // 代表二级目录 // 用户第一次上传文件,此savePath目录在服务器的硬盘里原本是没有的,所以应将其创建出来 String savePath = path + File.separator + dir1 + File.separator + dir2; File file = new File(savePath); if (!file.exists()) { file.mkdirs(); // 创建多级目录用mkdirs()方法 } return savePath; } public String generateFileName(String fileName) { // 4de727e8-2579-477f-a4ae-658355f107fe return UUID.randomUUID().toString() + "_" + fileName; } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } ~~~