##文件上传
1.进行文件上传时, 表单需要做的准备:
1). 请求方式为 POST: <form action="uploadServlet" method="post" ... >
2). 使用 file 的表单域: <input type="file" name="file"/>
3). 使用 multipart/form-data 的请求编码方式: <form action="uploadServlet" method="post" enctype="multipart/form-data">
1 2 3 4 5 6 <form action="uploadServlet" method="post" enctype="multipart/form-data"> File: <input type="file" name="file"/> <input type="submit" value="Submit"/> </form>
4). 关于 enctype:
> application/x-www-form-urlencoded:表单 enctype 属性的默认值。这种编码方案使用有限的字符集,当使用了非字母和数字时,
必须用”%HH”代替(H 代表十六进制数字)。对于大容量的二进制数据或包含非 ASCII 字符的文本来说,这种编码不能满足要求。
> multipart/form-data:form 设定了enctype=“multipart/form-data”属性后,表示表单以二进制传输数据
2.服务端:
1). 不能再使用 request.getParameter() 等方式获取请求信息. 获取不到, 因为请求的编码方式已经改为 multipart/form-data, 以 二进制的方式来提交请求信息.
2). 可以使用输入流的方式来获取. 但不建议这样做.
3). 具体使用 commons-fileupload 组件来完成文件的上传操作.
3.搭建环境: 加入 commons-fileupload-1.2.1.jar commons-io-2.0.jar
II. 基本思想:
> commons-fileupload 可以解析请求, 得到一个 FileItem 对象组成的 List
> commons-fileupload 把所有的请求信息都解析为 FileItem 对象, 无论是一个一般的文本域还是一个文件域.
> 可以调用 FileItem 的 isFormField() 方法来判断是一个 表单域 或不是表单域(则是一个文件域)
> 再来进一步获取信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (item.isFormField()) { String name = item.getFieldName(); String value = item.getString(); } if (!item.isFormField()) { String fieldName = item.getFieldName(); String fileName = item.getName(); String contentType = item.getContentType(); boolean isInMemory = item.isInMemory(); long sizeInBytes = item.getSize(); InputStream uploadedStream = item.getInputStream(); uploadedStream.close(); }
III. 如何得到 List 对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 简单的方式 // Create a factory for disk-based file items FileItemFactory factory = new DiskFileItemFactory(); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); // Parse the request List /* FileItem */ items = upload.parseRequest(request); > 复杂的方式: 可以为文件的上传加入一些限制条件和其他的属性 // Create a factory for disk-based file items DiskFileItemFactory factory = new DiskFileItemFactory(); //设置内存中最多可以存放的上传文件的大小, 若超出则把文件写到一个临时文件夹中. 以 byte 为单位 factory.setSizeThreshold(yourMaxMemorySize); //设置那个临时文件夹 factory.setRepository(yourTempDirectory); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); //设置上传文件的总的大小. 也可以设置单个文件的大小. upload.setSizeMax(yourMaxRequestSize); // Parse the request List /* FileItem */ items = upload.parseRequest(request);
这里 InputStream in = req.getInputStream(); 的流只能读一次,
参考代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @WebServlet(name = "upLoadServlet", urlPatterns = {"/upLoadServlet"}) public class UpLoadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //这里参考下面的常规方法。。。。。。 // 这里开始使用upload 组件中的方法了,这里省去了我们自己来解析流,获得参数,文件内容等操作了。 DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setRepository(new File("/tmp")); ServletFileUpload upload = new ServletFileUpload(factory); upload.setSizeMax(1024 * 1024 * 5); try { // 得到FileItem对象集合// 如果上面获取过流读取过流,这里的items集合是空的,这里要特别注意 List<FileItem> items = upload.parseRequest(req); //这里调用解析请求的方法,获得一个FileItem 列表 System.out.println("==" + items); //遍历, 判断是否是表单域,还是文件域 for (FileItem item : items) { if (item.isFormField()) { // 这里判断出来这个item是一个普通的域里面的参数 String name = item.getFieldName(); String value = item.getString(); System.out.println("=fileitem=" + name + " " + value); // 表单里面如果是有多选的话应该怎么办呢? } else { // 这里判断出来参数是一个文件,也就是我们要上传给服务器的哪个文件 String fieldName = item.getFieldName(); String fileName = item.getName(); String contentType = item.getContentType(); boolean isInMemory = item.isInMemory(); long sizeInBytes = item.getSize(); System.out.println("fieldName = " + fieldName); System.out.println("fileName = " + fileName); System.out.println("contentType = " + contentType); System.out.println("isInMemory = " + isInMemory); System.out.println("sizeInBytes = " + sizeInBytes); //这里就可以写上读取流,并把这个流在存储在服务器上硬盘中的某个文件啦。 // 参考下面的代码 }//end if }//end for //若是文件域就把文件保存到某个目录下面 } catch (FileUploadException e) { e.printStackTrace(); } } }
这里是按照常规方法取获取请求中参数,这个时候是获取不到的参数内容的, 这里可以获取请求中的一个InputStream流,通过这个流可以倒是可以获取请求中的参数,不过比较麻烦需要取解析的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 String desc = req.getParameter("desc"); String file = req.getParameter("file"); System.out.println(desc); System.out.println(file); InputStream in = req.getInputStream(); //这里的流只能读一次,下面的upload就读不到了 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String str = null; while ((str = reader.readLine()) != null) { System.out.println("==raw data ===" + str); } //这里不支持reset方法,应为没有重新父类的reset //in.reset();
这里我们读取FileItem中的输入流,把文件保存到服务器上面的/tmp目录, 这里我指定的是linux服务器上面的/tmp这样的一个临时目录。
1 2 3 4 5 6 7 8 9 10 //这里加上文件输入输出的操作来保存上传的文件到一个目录下面。 InputStream in = item.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; OutputStream out = new FileOutputStream("/tmp/" + fileName); while ((len = in.read(buffer)) != -1) { out.write(buffer); } in.close(); out.close();
表单里面如果是有多选的话应该怎么办呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <input type="checkbox" name="interesting" value="reading" />reading <input type="checkbox" name="interesting" value="party" />party <input type="checkbox" name="interesting" value="sports" />sports <input type="checkbox" name="interesting" value="shopping" />shopping for (FileItem item : items) { if (item.isFormField()) { // 这里判断出来这个item是一个普通的域里面的参数 String name = item.getFieldName(); String value = item.getString(); System.out.println("=fileitem=" + name + " " + value); // 表单里面如果是有多选的话应该怎么办呢? // 每一个都对应一个FileItem 对象,其中的getFieldName()都是interesting,可以直接拼出一个数组。 }//end if() }//end for()
如何修改工具类,框架的源代码? 1)原则:能不修改就不修改。 2)修改方法 >修改源代码,替换jar包中对应的class文件。 >在本地新建相同的包,和类,在这个类里面修改即可。
1). 需求:
I. 上传
> 在 upload.jsp 页面上使用 jQuery 实现 "新增一个附件", "删除附件". 但至少需要保留一个.
> 对文件的扩展名和文件的大小进行验证. 以下的规则是可配置的. 而不是写死在程序中的.
>> 文件的扩展名必须为 .pptx, docx, doc
>> 每个文件的大小不能超过 1 M
>> 总的文件大小不能超过 5 M.
> 若验证失败, 则在 upload.jsp 页面上显示错误消息:
>> 若某一个文件不符合要求: xxx 文件扩展名不合法 或 xxx 文件大小超过 1 M
>> 总的文件大小不能超过 5 M.
> 若验证通过, 则进行文件的上传操作
>> 文件上传, 并给一个不能和其他文件重复的名字, 但扩展名不变
>> 在对应的数据表中添加一条记录.
id file_name file_path file_desc
首先来使用jquery来把界面做出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <head> <title>上传</title> <!-- 这里是引用jquery的路径,这里使用斜杠, --> <script type="text/javascript" src="/scripts/jquery-1.7.2.js"></script> <script type="text/javascript"> $(function () { var i = 2; // 1. 获取#addFile,并为其添加click响应函数,这里首先完成添加的功能 $("#addFile").click(function () { //注意这里的br标签加了个 id,不然这里的$("#br")好像获取不到 $("#br").before( '文件File ' + i + ': <input type="file" name="file' + i + '"/>' + '<br/>' + '描述Desc ' + i + ': <input type="text" name="desc' + i + '"/>' + '<br/>' + '<button id="delete">删除</button>' ); // 这里别忘了 i 自增一下。 i ++ ; // 需要注意的一个地方是这里的click方法要返回一个false, // 不然这个按钮在form表单中被点击的时候会触发提交表单的动作 return false; }); // 2. 利用jquery来生成新的,注意数字的变化,加到一个br标签前面, // 文件File 1: <input type="file" name="file1"/> // <br/> // 描述Desc 1: <input type="text" name="desc1"/> // <br/> // <button id="delete">删除当前的file和desc的input节点</button> }); </script> </head>
下面是表单form中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <form action="/upLoadServlet" method="post" enctype="multipart/form-data"> <input type="hidden" id="fileNum" name="fileNum" value="1"> <br/><br/> 文件File: <input type="file" name="file"/> <br/><br/> Desc: <input type="text" name="desc"/> <button id="delete">删除当前的file和desc的input节点</button> <br/><br/> <!-- 注意这里的br标签加了个 id,不然上面的jquery好像获取不到,--> <br id="br"> <br/><br/> <input type="submit" name="submit" value="submit"/> <!-- 按钮 需要js来支持 --> <button id="addFile">新增一个附件</button> </form>
这里增加了一个删除的功能。用一个div把那些内容都包含进去,删除的时候直接删除这个div标签。 不过这个删除之后 带数字的地方都没有重新排序,下面会把它重新排序一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script type="text/javascript"> $(function () { var i = 2; //获取#addFile,并为其添加click响应函数 $("#addFile").click(function () { $("#br").before( '<div>' + '文件File ' + i + ': <input type="file" name="file' + i + '"/>' + '<br/>' + '描述Desc ' + i + ': <input type="text" name="desc' + i + '"/>' + '<button id="delete">删除</button></div>' ).prev("div").find("button").click(function () { $(this).parent("div").remove(); i--; return false; }); i++; return false; }); }); </script>
下面对代码进行优化,上面的代码input,button都是松散的,这里我们可以他们一起放到一个tr里面,这样就能很好的整体操作他们了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script type="text/javascript"> $(function () { var i = 2; $("#addFile").click(function () { $(this).parent().parent().before( '<tr><td>File' + i + ':</td><td><input type="file" name="file' + i + '"/></td></tr>' + '<tr><td>Desc' + i + ':</td><td><input type="text" name="desc' + i + '"/><button id="delete' + i + '">删除</button></td></tr>' ); i++; //获取新添加的删除按钮 $("#delete" + (i - 1)).click(function () { var $tr = $(this).parent().parent(); $tr.prev("tr").remove(); $tr.remove(); i--; }); return false; }); }); </script>
1 2 3 4 5 <table> <tr> <td>File1:</td> <td><input type="file" name="file"/></td> </tr> <tr> <td>Desc1:</td> <td><input type="text" name="desc"/></td> </tr> <tr> <td> <input type="submit" name="submit" value="submit"/> </td> <td> <!-- 按钮 需要js来支持 --> <button id="addFile">新增一个附件</button> </td> </tr> </table>
删除功能基本上可以,但是还缺少一个对删除后的tr进行重新排序的功能,下面加上排序的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <!-- 下面是js的代码 --> <script type="text/javascript"> $(function () { var i = 2; $("#addFile").click(function () { $(this).parent().parent().before( '<tr class="file"><td>File' + i + ':</td><td><input type="file" name="file' + i + '"/></td></tr>' + '<tr class="desc"><td>Desc' + i + ':</td><td><input type="text" name="desc' + i + '"/><button id="delete' + i + '">删除</button></td></tr>' ); i++; //获取新添加的删除按钮 $("#delete" + (i - 1)).click(function () { var $tr = $(this).parent().parent(); $tr.prev("tr").remove(); $tr.remove(); // 对i进行重新排序,这个就是删除了中间的某一个的时候,这个数字还是按照顺序的 // 下面的代码是进行重新排序的,也就是不管原来是什么顺序,什么数字,这里就是按照0,1,2,3,这样的顺序遍历,重新把数字设置上而已。 $(".file").each(function (index) { var n = index + 1; $(this).find("td:first").text("File" + (n)); $(this).find("td:last input").attr("name", "file" + (n)); }); $(".desc").each(function (index) { var n = index + 1; $(this).find("td:first").text("Desc" + (n)); $(this).find("td:last input").attr("name", "desc" + (n)); }); i--; }); return false; }); }); </script> <!-- 上面是js的代码 -->
1 2 3 4 5 <table> <!-- 这里把之前的input,button等标签都放到一个表格table中 --> <tr class="file"><td>File1 : </td><td><input type="file" name="file"/></td> </tr> <tr class="desc"><td>Desc1 : </td> <td><input type="text" name="desc"/></td></tr> <tr><td><input type="submit" name="submit" value="submit"/></td><td> <!-- 按钮 需要js来支持 --> <button id="addFile">新增一个附件</button></td></tr> </table>
下面我们来做个prop文件来配置之前需求中提到的几个配置,这些配置可以写到.prop属性配置文件中,这个比较简单, 复杂的可以写到xml配置文件中。这里我们采取简单的办法,写到一个upload.prop 属性文件中
> 对文件的扩展名和文件的大小进行验证. 以下的规则是可配置的. 而不是写死在程序中的.
>> 文件的扩展名必须为 .pptx, docx, doc
>> 每个文件的大小不能超过 1 M
>> 总的文件大小不能超过 5 M.
uoload.prop 文件的内容
1 2 3 4 5 6 7 8 9 10 11 # 支持的文件的扩展名 exts=pptx,docx,doc # 一个文件的大小 file.max.size=1048576 # 总的大小 total.max.size=5242880 # 这个要在servletlistener中读取,初始化
这个属性文件要在启动web应用的时候进行初始化,而且只需要初始化一次就够了。这时候我们应该能够想到 ServletContextListener的作用了。所有我们定义一个FileUploadAppListener的类来做这个web应用的初始化相关的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class FileUploadAppListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { //这里获取属性文件的输入流 InputStream in = getClass().getClassLoader().getResourceAsStream("/upload.prop"); Properties properties = new Properties(); // new出来一个Properties对象,下面就会调用其load()方法了。 try { properties.load(in);//通过上面获得的输入流,调用属性对象的load()方法 for (Map.Entry<Object, Object> prop : properties.entrySet()) { // 遍历所有属性,并把他们存到一个FileUploadAppProperties里里面,这个类采取了一个单例模式, // 这个类里面定义了一个map对象,也就是把属性名字,属性值都存在一个字典中。 FileUploadAppProperties.getInstance().addProperty( (String) prop.getKey(), (String) prop.getValue() );//add prop } } catch (IOException e) { e.printStackTrace(); } System.out.println("FileUploadAppListener: "+ FileUploadAppProperties.getInstance().getProperties()); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { //未使用的 } }
上面的listener代码写好之后,千万别忘记在web.xml中配置注册这个listener。
1 2 3 <listener> <listener-class>com.test.mvcapp.listener.FileUploadAppListener</listener-class> </listener>
下面我们看这个存储属性的那个类,这个类很简单,里面定义了一个map用来存储属性。 这个类同时采用了单例设计模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class FileUploadAppProperties { private Map<String, String> properties = new HashMap<>(); private FileUploadAppProperties() { } private static FileUploadAppProperties instance = new FileUploadAppProperties(); public static FileUploadAppProperties getInstance() { return instance; } public void addProperty(String key, String value) { properties.put(key, value); } public String getProperty(String key) { return properties.get(key); } public Map<String, String> getProperties() { return properties; } }
属性可配置这个解决了,下面我们就来新建一个servlet,upLoadServlet 注意这里我们采用了注解的方式来配置这个servlet类和url的映射的。 首先我们拿到之前解析到的属性的配置的内容,先尝试的这打印出看看值对不对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @WebServlet(name = "upLoadServlet", urlPatterns = {"/upLoadServlet"}) public class FileUploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String exts = FileUploadAppProperties.getInstance().getProperty("exts"); String fileMaxSize = FileUploadAppProperties.getInstance().getProperty("file.max.size"); String totalMaxSize = FileUploadAppProperties.getInstance().getProperty("total.max.size"); System.out.println("exts = " + exts); System.out.println("fileMaxSize = " + fileMaxSize); System.out.println("totalMaxSize = " + totalMaxSize); System.out.println("============================================================="); DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024*500); factory.setRepository(new File("/tmp")); ServletFileUpload upload = new ServletFileUpload(factory); upload.setFileSizeMax(Integer.parseInt(fileMaxSize)); upload.setSizeMax(Integer.parseInt(totalMaxSize)); // 后续操作文件上传的代码 try { // 得到FileItem对象集合 List<FileItem> items = upload.parseRequest(req); //...... } catch (FileUploadException e) { e.printStackTrace(); } }//end doPost }
这里把相关的好多代码放到一个单独的方法里面,这样doPost()方法看着会更简洁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @WebServlet(name = "upLoadServlet", urlPatterns = {"/upLoadServlet"}) public class FileUploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 这里直接把相关的好多代码放到一个单独的方面里面,这样doPost()方法看着会更简洁。 ServletFileUpload upload = getServletFileUpload(); // 后续操作文件上传的代码 try { // 得到FileItem对象集合 List<FileItem> items = upload.parseRequest(req); //...... } catch (FileUploadException e) { e.printStackTrace(); } }//end doPost private ServletFileUpload getServletFileUpload() { //在idea中选中相应的代码块,然后 选择 Refactor | Extract | Method 或者 按 Ctrl+Alt+M String exts = FileUploadAppProperties.getInstance().getProperty("exts"); String fileMaxSize = FileUploadAppProperties.getInstance().getProperty("file.max.size"); String totalMaxSize = FileUploadAppProperties.getInstance().getProperty("total.max.size"); DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 500); factory.setRepository(new File("/tmp")); ServletFileUpload upload = new ServletFileUpload(factory); upload.setFileSizeMax(Integer.parseInt(fileMaxSize)); upload.setSizeMax(Integer.parseInt(totalMaxSize)); return upload; } }
下面我们来完成上传文件的相关的代码,这个代码可以参考之前上面的那个例子,很大部分代码是重复的。 这里 上传文件相关的 有文件名称,文件描述,还有文件路径,文件id等,这个时候我们需要考虑写一个 关于文件信息的javabean类了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class FileUploadInfo implements Serializable { //文件的信息相关的一个javabean类 private int id; private String fileName; private String filePath; private String fileDesc; public FileUploadInfo() {//无参数的构造器,必须的。为了反射使用的 } public FileUploadInfo(String fileName, String filePath, String fileDesc) { this.fileName = fileName; this.filePath = filePath; this.fileDesc = fileDesc; } // 下面是一些getter,setter方法,这些方法使用idea都会自动生成的。 public int getId() {} public void setId(int id) {} public String getFileName() {} public void setFileName(String fileName) {} public String getFilePath() {} public void setFilePath(String filePath) {} public String getFileDesc() {} public void setFileDesc(String fileDesc) {} public boolean equals(Object o) {} public int hashCode() { } public String toString() { } }
我们来完成上传文件的try catch里面的一些步骤,把每一个小步骤都定义为一个简单的独立的方法,这样try语句块就看着比较简单,清晰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 try { // 得到FileItem对象集合 List<FileItem> items = upload.parseRequest(req); //填充FileUploadInfo的集合,同时填充这个集合uploadFiles Map<String, FileItem> fileUploadMap = new HashMap<>(); List<FileUploadInfo> fileUploadInfoList = new ArrayList<>(); //填充bean集合 fillBeans(items, fileUploadInfoList, fileUploadMap); //校验文件扩展名 vaidateExtName(fileUploadInfoList); //校验文件大小,这个不需要直接写,这个在解析时候已经校验了,如果大小不符合会抛出异常的 //以上都通过就进行文件的上传操作 uploadFile(fileUploadMap); //把上传成功的文件的信息保存到数据库中 saveBeans(fileUploadInfoList); } catch (FileUploadException e) { e.printStackTrace(); }
抽象出来的方法,这里我们还没有具体的代码,稍后会补上具体的代码的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void saveBeans(List<FileUploadInfo> beans) { } private void uploadFile(Map<String, FileItem> uploadFiles) { } private void vaidateExtName(List<FileUploadInfo> beans) { } private void fillBeans(List<FileItem> items, List<FileUploadInfo> fileUploadInfoList, Map<String, FileItem> uploadFilesMap) { // 这个方法就是用来填充bean的两个结合的,通过遍历items然后分别把相应的内容填充到bean集合中去。 }
下面我们来完成这个fillBeans()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private void fillBeans(List<FileItem> items, List<FileUploadInfo> list, Map<String, FileItem> map) { //1. 遍历items集合,先得到desc的map,其中键是 FieldName 是表单域中的desc1,desc2等 Map<String, String> descMap = new HashMap<>(); for (FileItem item : items) { if (item.isFormField()) descMap.put(item.getFieldName(), item.getString()); } for (FileItem item : items) { if (!item.isFormField()) { //文件的这个表单域,file1,file2,file3,..... String fieldName = item.getFieldName(); //下面取到最后一个,最后一个是一个索引,一个数字,这个和desc是一一对应的 String index = fieldName.replace("file", ""); String fileName = item.getName(); String desc = descMap.get("desc" + index); String filePath = getFilePath(fileName); FileUploadInfo info = new FileUploadInfo(fileName, filePath, desc); list.add(info); map.put(filePath, item); } } }//end fillBeans() private String getFilePath(String fileName) { //根据给定的文件名构建一个随机的文件名称,文件扩展名保持和原来的一样。 String extName = fileName.substring(fileName.lastIndexOf("."));//获取文件的扩展名 Random random = new Random(); int randomNum = random.nextInt(1000000); String filePath = getServletContext().getRealPath(ROOT_PATH) + "/" + System.currentTimeMillis() + randomNum + extName; return filePath; }
下面我们来完成这个uploadFile()方法,这个方法用来上传文件,保存文件到一个目录下面。 就是读取输入流,写入到输出流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void uploadFile(Map<String, FileItem> map) { for (Map.Entry<String, FileItem> entry : map.entrySet()) { String filePath = entry.getKey(); FileItem fileItem = entry.getValue(); try { InputStream inputStream = fileItem.getInputStream(); OutputStream outputStream = new FileOutputStream(filePath); byte[] buffer = new byte[10240]; int len = 0; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer); } inputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } }//end for() }
下面完成扩展名验证的方法,判断扩展名不合法就抛出一个InvalidExtNameException异常,这个异常需要我们自己定义一个异常类。 只要有一个不符合就都不允许上传,这里抛出一个InvalidExtNameException异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 private void vaidateExtName(List<FileUploadInfo> list) { String exts = FileUploadAppProperties.getInstance().getProperty("exts"); List<String> extList = Arrays.asList(exts.split(",")); for (FileUploadInfo fileUploadInfo : list) { String fileName = fileUploadInfo.getFileName(); String extName = fileName.substring(fileName.lastIndexOf(".") + 1); if (!extList.contains(extName)) { //只要有一个不符合就都不允许上传,这里抛出一个异常 throw new InvalidExtNameException("文件的扩展名不合法:" + fileName); } } }
我们自己定义一个异常InvalidExtNameException类。 异常类一般继承RuntimeException这个父类。
1 2 3 4 5 6 7 8 9 public class InvalidExtNameException extends RuntimeException { public InvalidExtNameException() { } public InvalidExtNameException(String message) { super(message); } }
这样在doPost() 方法里面我们就要取捕获这个异常了。 这里我们定义一个转发的path,上传成功转发到一个页面,失败的话还回到upload.jsp面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletFileUpload upload = getServletFileUpload(); String path; try { // 得到FileItem对象集合 List<FileItem> items = upload.parseRequest(req); //.......省略其他代码 //校验文件扩展名 vaidateExtName(fileUploadInfoList); //.......省略其他代码 path = "/updown/success.jsp"; } catch (Exception e) { e.printStackTrace(); path = "/updown/upload.jsp"; req.setAttribute("message", e.getMessage()); } req.getRequestDispatcher(path).forward(req, resp); }//end doPost
接下来完成保存上传文件信息到数据库saveBeans() 这个方法。 代码很简单直接调用DAO对象里面的saveFiles方法。这里说到关于DAO相关的类,用法可以参考稍后的总结。 这个个DAO是一个类成员变量,是FileUploadInfoDAO类型的对象。
1 2 3 4 5 private FileUploadInfoDAO dao = new FileUploadInfoDAOImpl(); //.... 省略中间的部分代码 private void saveBeans(List<FileUploadInfo> list) { dao.saveFiles(list); }
关于FileUploadInfoDAO这个接口的定义:
1 2 3 4 5 public interface FileUploadInfoDAO { List<FileUploadInfo> getFiles(); void saveFiles(List<FileUploadInfo> list); void saveFile(FileUploadInfo fileUploadInfo); }
然后是这个接口的一个具体实现类,也就是我们上面new出来的那个对象的类 这个类实现了FileUploadInfoDAO接口,并且继承了一个DAO的父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class FileUploadInfoDAOImpl extends DAO<FileUploadInfo> implements FileUploadInfoDAO { @Override public List<FileUploadInfo> getFiles() { String sql = "SELECT id, file_name fileName, file_path filePath, " + "file_desc fileDesc FROM upload_files"; return getForList(sql); } @Override public void saveFiles(List<FileUploadInfo> list) { for (FileUploadInfo file : list) { saveFile(file); } } @Override public void saveFile(FileUploadInfo file) { String sql = "INSERT INTO upload_files (file_name, file_path, file_desc) VALUES (?, ?, ?)"; update(sql, file.getFileName(), file.getFilePath(), file.getFileDesc()); } }
这里是DAO这个父类里面代码,这个里面主要就是关于数据库的方法,常规的增删改查这些方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class DAO<T> { private QueryRunner queryRunner = new QueryRunner(); private Class<T> clazz; public DAO() { Type superClass = getClass().getGenericSuperclass(); if (superClass instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) superClass; Type[] typeArgs = parameterizedType.getActualTypeArguments(); if (typeArgs != null && typeArgs.length > 0) { if (typeArgs[0] instanceof Class) { clazz = (Class<T>) typeArgs[0]; } } } } public <E> E getForValue(String sql, Object... args) { //获取一个的 Connection connection = null; try { connection = JdbcUtils.getConnection(); return (E) queryRunner.query(connection, sql, new ScalarHandler(), args); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.releaseConnection(connection); } return null; } public List<T> getForList(String sql, Object... args) { //获取一个的 Connection connection = null; try { connection = JdbcUtils.getConnection(); return queryRunner.query(connection, sql, new BeanListHandler<>(clazz), args ); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.releaseConnection(connection); } return null; } public T get(String sql, Object... args) { System.out.println(clazz); //获取一个的 Connection connection = null; try { connection = JdbcUtils.getConnection(); return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), args); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.releaseConnection(connection); } return null; } public void update(String sql, Object... args) { Connection connection = null; try { connection = JdbcUtils.getConnection(); queryRunner.update(connection, sql, args); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.releaseConnection(connection); } } }
这里DAO类里面数据库的链接等使用到了一个jdbcutil这个类里面的static方法。获取数据库链接,释放数据库链接都是调用的JdbcUtils类里面的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class JdbcUtils { private static DataSource dataSource = null; static { dataSource = new ComboPooledDataSource("mvcapp"); } public static void releaseConnection(Connection connection) { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }
这其中ComboPooledDataSource类是c3p0包里面的,这里使用到了c3p0数据库相关的包,这里有个c3p0相关的xml配置文件配置文件主要就是定义了数据库名称,数据库账户密码等。 如果出现中文编码的问题可以把url那里改为 <property name="jdbcUrl">jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF8 </property>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <c3p0-config> <!-- This app is massive! --> <named-config name="mvcapp"> <property name="user">test</property> <property name="password">123456</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///test</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">10</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">10</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> <user-overrides user="master-of-the-universe"> <property name="acquireIncrement">1</property> <property name="initialPoolSize">1</property> <property name="minPoolSize">1</property> <property name="maxPoolSize">5</property> <property name="maxStatementsPerConnection">50</property> </user-overrides> </named-config> </c3p0-config>
##文件下载
1.设置 contentType 响应头: 设置响应的类型是什么 ? 通知浏览器是个下载的文件
1 response.setContentType("application/x-msdownload");
2.设置 Content-Disposition 响应头: 通知浏览器不再由浏览器来自行处理(或打开)要下载的文件, 而由用户手工处理
1 response.setHeader("Content-Disposition", "attachment;filename=abc.txt");
2.具体的文件: 可以调用 response.getOutputStream 的方式, 以 IO 流的方式发送给客户端.
1 2 3 4 5 6 7 8 9 10 11 12 13 OutputStream out = response.getOutputStream(); String pptFileName = "/tmp/JavaWEB_监听器.pptx"; InputStream in = new FileInputStream(pptFileName); byte [] buffer = new byte[1024]; int len = 0; while((len = in.read(buffer)) != -1){ out.write(buffer, 0, len); } in.close();