FileUpload

就是将本地的文件上传到服务器上面去。就是利用请求报文,将文件的二进制数据放在里面,然后传输到服务器,服务器进行解析,获取里面的资源

二进制数据是在哪里?请求体

哪个API是获取请求体的?

request.getInputStream()

文件上传准备

1.form表单 method = post

2.input type=file

Content-Length: 37

该请求头代表了什么意思?

Content-Length: 11

遇到的第一个问题

仅上传文件名,不会上传文件的数据

解决方案:form表单需要设置enctype=multipart/form-data

处理文件的逻辑

  1. package com.cskaoyan.upload;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.ServletInputStream;
  4. import javax.servlet.annotation.WebServlet;
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.io.File;
  9. import java.io.FileOutputStream;
  10. import java.io.IOException;
  11. @WebServlet("/upload")
  12. public class UploadServlet extends HttpServlet {
  13. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  14. //准备工作就绪之后,点击form表单的提交,浏览器会自动帮我们将文件的信息写入到请求体中
  15. //服务器拿到请求报文,会帮我们进行解析处理,将请求体封装到指定的API中,我们只需要去调用对应的API即可
  16. ServletInputStream inputStream = request.getInputStream();
  17. String realPath = getServletContext().getRealPath("upload/1.png");
  18. File file = new File(realPath);
  19. if(!file.getParentFile().exists()){
  20. //当前file文件的父级目录如果不存在
  21. file.getParentFile().mkdirs();
  22. }
  23. FileOutputStream outputStream = new FileOutputStream(file);
  24. int length = 0;
  25. byte[] bytes = new byte[1024];
  26. while ((length = inputStream.read(bytes)) != -1){
  27. outputStream.write(bytes, 0, length);
  28. }
  29. outputStream.close();
  30. inputStream.close();
  31. }
  32. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  33. }
  34. }

FileUpload - 图1

文件经过上传之后,多出了很多内容;如果是文本文件,就会多出了很多东西;如果是二进制数据,这些内容进入到文件内部,会直接导致文件的损坏。

这些东西哪来的?

面临的问题二

是因为如果我们需要进行文件上传,那么就必须要使用enctype=multipart/form-data,但是使用了该enctype之后,请求体里面会多出来很多的字符。对于二进制数据来说,直接导致文件损坏,对于文本数据来说,多出很多分隔符

面临的问题三

之前可以正常获取请求参数的API也不再适用了,无法获取到请求参数,请求参数会和文件一起处理到文件中。

原因在于获取请求参数的API只能够获取key=value&key=value型数据,如果数据结构发生了变化,那么就无法获取到了。

FileUpload - 图2

总结:

目前面临的诸多问题,均和引入了enctype有关,但是又不能不引入,因为如果不引入,那么只会上传文件的名称,不会上传文件的内容;需要手动地将这些分割字符剔除掉。

不要重复造轮子。

commons-fileupload jar包

引入组件之前的这些操作以及遇到的问题,是需要大家掌握的。

文件上传组件的引入(了解使用)

看着API文档,来解决你的实际问题即可。

  1. package com.cskaoyan.upload;
  2. import org.apache.commons.fileupload.FileItem;
  3. import org.apache.commons.fileupload.FileUploadException;
  4. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  5. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  6. import javax.servlet.ServletException;
  7. import javax.servlet.ServletInputStream;
  8. import javax.servlet.annotation.WebServlet;
  9. import javax.servlet.http.HttpServlet;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.File;
  13. import java.io.FileOutputStream;
  14. import java.io.IOException;
  15. import java.util.Iterator;
  16. import java.util.List;
  17. @WebServlet("/upload2")
  18. public class UploadServlet2 extends HttpServlet {
  19. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  20. //看着API文档,一步一步完成我们的功能即可
  21. //这行代码的含义是判断当前的请求是否是multipart/form-data
  22. response.setContentType("text/html;charset=utf-8");
  23. boolean multipartContent = ServletFileUpload.isMultipartContent(request);
  24. if(!multipartContent){
  25. response.getWriter().println("当前请求不含上传的文件");
  26. return;
  27. }
  28. DiskFileItemFactory factory = new DiskFileItemFactory();
  29. File repository = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
  30. factory.setRepository(repository);
  31. ServletFileUpload upload = new ServletFileUpload(factory);
  32. try {
  33. //items其实就是对于前端提交过来啊input的封装
  34. //提交过来多少个input,那么这边items就有多少个
  35. List<FileItem> items = upload.parseRequest(request);
  36. Iterator<FileItem> iterator = items.iterator();
  37. while (iterator.hasNext()){
  38. FileItem item = iterator.next();
  39. if(item.isFormField()){
  40. //当前item是常规的form表单
  41. processFormField(item);
  42. }else {
  43. //当前item是上传的文件
  44. processUploadedFile(item);
  45. }
  46. }
  47. } catch (FileUploadException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. /**
  52. * 处理上传的文件逻辑
  53. * @param item
  54. */
  55. private void processUploadedFile(FileItem item) {
  56. String fieldName = item.getFieldName();
  57. String fileName = item.getName();
  58. // String contentType = item.getContentType();
  59. // boolean isInMemory = item.isInMemory();
  60. // long sizeInBytes = item.getSize();
  61. // System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);
  62. String realPath = getServletContext().getRealPath("upload/" + fileName);
  63. File file = new File(realPath);
  64. if(!file.getParentFile().exists()){
  65. file.getParentFile().mkdirs();
  66. }
  67. try {
  68. item.write(file);
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. }
  72. }
  73. /**
  74. * 处理常规form表单的逻辑
  75. * @param item
  76. */
  77. private void processFormField(FileItem item) {
  78. String name = item.getFieldName();
  79. String value = item.getString();
  80. System.out.println("formField " + name + ":" + value);
  81. }
  82. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  83. }
  84. }

中文乱码问题

普通form表单数据乱码

  1. //这行代码针对文件上传时form表单数据乱码也失效了,也不再适用了
  2. request.setCharacterEncoding("utf-8");
  1. String getString(String encoding)
  2. throws UnsupportedEncodingException

Returns the contents of the file item as a String, using the specified encoding. This method uses get() to retrieve the contents of the item.

  • Parameters:
    encoding - The character encoding to use.
  • Returns:
    The contents of the item, as a string.
  • Throws:
    UnsupportedEncodingException - if the requested character encoding is not available.

上传的文件名有没有可能乱码

  1. //上传的文件名如果有中文的乱码问题
  2. upload.setHeaderEncoding("utf-8");

封装数据到Bean(掌握)

  1. package com.cskaoyan.upload;
  2. import org.apache.commons.fileupload.FileItem;
  3. import org.apache.commons.fileupload.FileUploadException;
  4. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  5. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  6. import javax.servlet.ServletException;
  7. import javax.servlet.annotation.WebServlet;
  8. import javax.servlet.http.HttpServlet;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletResponse;
  11. import java.io.File;
  12. import java.io.IOException;
  13. import java.io.UnsupportedEncodingException;
  14. import java.util.Iterator;
  15. import java.util.List;
  16. @WebServlet("/upload3")
  17. public class UploadServlet3 extends HttpServlet {
  18. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  19. //看着API文档,一步一步完成我们的功能即可
  20. //这行代码的含义是判断当前的请求是否是multipart/form-data
  21. request.setCharacterEncoding("utf-8");
  22. response.setContentType("text/html;charset=utf-8");
  23. boolean multipartContent = ServletFileUpload.isMultipartContent(request);
  24. if(!multipartContent){
  25. response.getWriter().println("当前请求不含上传的文件");
  26. return;
  27. }
  28. DiskFileItemFactory factory = new DiskFileItemFactory();
  29. File repository = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
  30. factory.setRepository(repository);
  31. ServletFileUpload upload = new ServletFileUpload(factory);
  32. //上传的文件名如果有中文的乱码问题
  33. upload.setHeaderEncoding("utf-8");
  34. // 1024 bytes
  35. //upload.setSizeMax(1024);
  36. try {
  37. //items其实就是对于前端提交过来啊input的封装
  38. //提交过来多少个input,那么这边items就有多少个
  39. List<FileItem> items = upload.parseRequest(request);
  40. Iterator<FileItem> iterator = items.iterator();
  41. while (iterator.hasNext()){
  42. FileItem item = iterator.next();
  43. if(item.isFormField()){
  44. //当前item是常规的form表单
  45. processFormField(item);
  46. }else {
  47. //当前item是上传的文件
  48. processUploadedFile(item);
  49. }
  50. }
  51. } catch (FileUploadException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. /**
  56. * 处理上传的文件逻辑
  57. * ReflectionUtils.toBean(Object o, String key, String value);
  58. * (user, username, admin)
  59. * (user, password, admin123)
  60. * (user, image, upload/1.jpg)
  61. * 有兴趣的同学可以课后实现它
  62. * 反射的代码有难度,能不能反射这部分代码交给BeanUtils来做呢
  63. * BeanUtils.populate(user, map);
  64. * @param item
  65. */
  66. private void processUploadedFile(FileItem item) {
  67. String fieldName = item.getFieldName();
  68. String fileName = item.getName();
  69. // String contentType = item.getContentType();
  70. // boolean isInMemory = item.isInMemory();
  71. // long sizeInBytes = item.getSize();
  72. // System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);
  73. String realPath = getServletContext().getRealPath("upload/" + fileName);
  74. File file = new File(realPath);
  75. if(!file.getParentFile().exists()){
  76. file.getParentFile().mkdirs();
  77. }
  78. try {
  79. item.write(file);
  80. } catch (Exception e) {
  81. e.printStackTrace();
  82. }
  83. }
  84. /**
  85. * 处理常规form表单的逻辑
  86. * @param item
  87. */
  88. private void processFormField(FileItem item) {
  89. //返回的就是input的name属性
  90. //根据不同的name属性,判断,根据不同的name属性值,调用不同的set方法,完成赋值,这个方法比较臃肿一些
  91. String name = item.getFieldName();
  92. String value = null;
  93. try {
  94. value = item.getString("utf-8");
  95. } catch (UnsupportedEncodingException e) {
  96. e.printStackTrace();
  97. }
  98. System.out.println("formField " + name + ":" + value);
  99. }
  100. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  101. }
  102. }

改进

  1. package com.cskaoyan.upload;
  2. import org.apache.commons.beanutils.BeanUtils;
  3. import org.apache.commons.fileupload.FileItem;
  4. import org.apache.commons.fileupload.FileUploadException;
  5. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  6. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.annotation.WebServlet;
  9. import javax.servlet.http.HttpServlet;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.io.UnsupportedEncodingException;
  15. import java.lang.reflect.InvocationTargetException;
  16. import java.util.HashMap;
  17. import java.util.Iterator;
  18. import java.util.List;
  19. import java.util.Map;
  20. @WebServlet("/upload3")
  21. public class UploadServlet3 extends HttpServlet {
  22. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  23. //看着API文档,一步一步完成我们的功能即可
  24. //这行代码的含义是判断当前的请求是否是multipart/form-data
  25. request.setCharacterEncoding("utf-8");
  26. response.setContentType("text/html;charset=utf-8");
  27. boolean multipartContent = ServletFileUpload.isMultipartContent(request);
  28. if(!multipartContent){
  29. response.getWriter().println("当前请求不含上传的文件");
  30. return;
  31. }
  32. DiskFileItemFactory factory = new DiskFileItemFactory();
  33. File repository = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
  34. factory.setRepository(repository);
  35. ServletFileUpload upload = new ServletFileUpload(factory);
  36. //上传的文件名如果有中文的乱码问题
  37. upload.setHeaderEncoding("utf-8");
  38. // 1024 bytes
  39. //upload.setSizeMax(1024);
  40. User user = new User();
  41. Map<String, Object> map = new HashMap<>();
  42. try {
  43. //items其实就是对于前端提交过来啊input的封装
  44. //提交过来多少个input,那么这边items就有多少个
  45. List<FileItem> items = upload.parseRequest(request);
  46. Iterator<FileItem> iterator = items.iterator();
  47. while (iterator.hasNext()){
  48. FileItem item = iterator.next();
  49. if(item.isFormField()){
  50. //当前item是常规的form表单
  51. processFormField(item, map);
  52. }else {
  53. //当前item是上传的文件
  54. processUploadedFile(item, map);
  55. }
  56. }
  57. BeanUtils.populate(user, map);
  58. System.out.println(user);
  59. } catch (FileUploadException e) {
  60. e.printStackTrace();
  61. } catch (IllegalAccessException e) {
  62. e.printStackTrace();
  63. } catch (InvocationTargetException e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. /**
  68. * 处理上传的文件逻辑
  69. * ReflectionUtils.toBean(Object o, String key, String value);
  70. * (user, username, admin)
  71. * (user, password, admin123)
  72. * (user, image, upload/1.jpg)
  73. * 有兴趣的同学可以课后实现它
  74. * 反射的代码有难度,能不能反射这部分代码交给BeanUtils来做呢
  75. * BeanUtils.populate(user, map);
  76. * @param item
  77. * @param map
  78. */
  79. private void processUploadedFile(FileItem item, Map<String, Object> map) {
  80. String fieldName = item.getFieldName();
  81. String fileName = item.getName();
  82. // String contentType = item.getContentType();
  83. // boolean isInMemory = item.isInMemory();
  84. // long sizeInBytes = item.getSize();
  85. // System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);
  86. String uploadPath = "upload/" + fileName;
  87. String realPath = getServletContext().getRealPath(uploadPath);
  88. File file = new File(realPath);
  89. if(!file.getParentFile().exists()){
  90. file.getParentFile().mkdirs();
  91. }
  92. try {
  93. item.write(file);
  94. map.put(fieldName, uploadPath);
  95. } catch (Exception e) {
  96. e.printStackTrace();
  97. }
  98. }
  99. /**
  100. * 处理常规form表单的逻辑
  101. * @param item
  102. * @param map
  103. */
  104. private void processFormField(FileItem item, Map<String, Object> map) {
  105. //返回的就是input的name属性
  106. String name = item.getFieldName();
  107. String value = null;
  108. try {
  109. value = item.getString("utf-8");
  110. map.put(name, value);
  111. } catch (UnsupportedEncodingException e) {
  112. e.printStackTrace();
  113. }
  114. System.out.println("formField " + name + ":" + value);
  115. }
  116. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  117. }
  118. }

FileUpload - 图3

进一步优化

假如今后我们还需要进行商品发布、用户注册等其他行为,这个时候也需要使用文件上传。需要写在其他servlet中,再写一遍把

能不呢将一些代码抽提出来,形成一个工具类,后面的话可以复用。

  1. package com.cskaoyan.upload.utils;
  2. import org.apache.commons.fileupload.FileItem;
  3. import org.apache.commons.fileupload.FileUploadException;
  4. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  5. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  6. import javax.servlet.http.HttpServletRequest;
  7. import java.io.File;
  8. import java.io.UnsupportedEncodingException;
  9. import java.util.HashMap;
  10. import java.util.Iterator;
  11. import java.util.List;
  12. import java.util.Map;
  13. public class FileUploadUtils {
  14. public static Map<String, Object> parseRequest(HttpServletRequest request){
  15. DiskFileItemFactory factory = new DiskFileItemFactory();
  16. File repository = (File) request.getServletContext().getAttribute("javax.servlet.context.tempdir");
  17. factory.setRepository(repository);
  18. Map<String, Object> map = new HashMap<>();
  19. ServletFileUpload upload = new ServletFileUpload(factory);
  20. //上传的文件名如果有中文的乱码问题
  21. upload.setHeaderEncoding("utf-8");
  22. // 1024 bytes
  23. //upload.setSizeMax(1024);
  24. try {
  25. //items其实就是对于前端提交过来啊input的封装
  26. //提交过来多少个input,那么这边items就有多少个
  27. List<FileItem> items = upload.parseRequest(request);
  28. Iterator<FileItem> iterator = items.iterator();
  29. while (iterator.hasNext()){
  30. FileItem item = iterator.next();
  31. if(item.isFormField()){
  32. //当前item是常规的form表单
  33. processFormField(item, map);
  34. }else {
  35. //当前item是上传的文件
  36. processUploadedFile(item, map, request);
  37. }
  38. }
  39. } catch (FileUploadException e) {
  40. e.printStackTrace();
  41. }
  42. return map;
  43. }
  44. private static void processUploadedFile(FileItem item, Map<String, Object> map, HttpServletRequest request) {
  45. String fieldName = item.getFieldName();
  46. String fileName = item.getName();
  47. // String contentType = item.getContentType();
  48. // boolean isInMemory = item.isInMemory();
  49. // long sizeInBytes = item.getSize();
  50. // System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);
  51. String uploadPath = "upload/" + fileName;
  52. String realPath = request.getServletContext().getRealPath(uploadPath);
  53. File file = new File(realPath);
  54. if(!file.getParentFile().exists()){
  55. file.getParentFile().mkdirs();
  56. }
  57. try {
  58. item.write(file);
  59. map.put(fieldName, uploadPath);
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. /**
  65. * 处理常规form表单的逻辑
  66. * @param item
  67. * @param map
  68. */
  69. private static void processFormField(FileItem item, Map<String, Object> map) {
  70. //返回的就是input的name属性
  71. String name = item.getFieldName();
  72. String value = null;
  73. try {
  74. value = item.getString("utf-8");
  75. map.put(name, value);
  76. } catch (UnsupportedEncodingException e) {
  77. e.printStackTrace();
  78. }
  79. System.out.println("formField " + name + ":" + value);
  80. }
  81. }
  1. package com.cskaoyan.upload;
  2. import com.cskaoyan.upload.utils.FileUploadUtils;
  3. import org.apache.commons.beanutils.BeanUtils;
  4. import org.apache.commons.fileupload.FileItem;
  5. import org.apache.commons.fileupload.FileUploadException;
  6. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  7. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  8. import javax.servlet.ServletException;
  9. import javax.servlet.annotation.WebServlet;
  10. import javax.servlet.http.HttpServlet;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.io.UnsupportedEncodingException;
  16. import java.lang.reflect.InvocationTargetException;
  17. import java.util.HashMap;
  18. import java.util.Iterator;
  19. import java.util.List;
  20. import java.util.Map;
  21. @WebServlet("/upload4")
  22. public class UploadServlet4 extends HttpServlet {
  23. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  24. response.setContentType("text/html;charset=utf-8");
  25. boolean multipartContent = ServletFileUpload.isMultipartContent(request);
  26. if(!multipartContent){
  27. response.getWriter().println("当前请求不含上传的文件");
  28. return;
  29. }
  30. User user = new User();
  31. Map<String, Object> map = FileUploadUtils.parseRequest(request);
  32. try {
  33. BeanUtils.populate(user, map);
  34. } catch (IllegalAccessException e) {
  35. e.printStackTrace();
  36. } catch (InvocationTargetException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  41. }
  42. }

文件重名

文件改名问题。 wxid

利用时间戳 + username + 文件名

随机字符串重命名

  1. String uuid = UUID.randomUUID().toString();
  2. String fileName = item.getName();
  3. //文件有后缀名的
  4. fileName = uuid + fileName;

目录内文件数过多

hash散列

hashcode:尽可能散列的开,均匀。

文件名———hashcode———-int 数字——-16进制 8位 0x 1 2 3 4 5 6 a f

利用时间来划分—-年度、月份、日期,概率问题,如果出现某些大促,很可能需要进一步细分。

微信头像加国旗。去年 70周年

加上国旗的头像上传——-服务器

自己看自己的头像时改成功的,但是别人看起来,依然是之前的头像。经过了半天左右,头像终于同步了,别人看到你的头像也是新的了。

案例

写一个注册页面,包含图片上传,然后将上传的资源进行回显,回显图片