FileUpload
就是将本地的文件上传到服务器上面去。就是利用请求报文,将文件的二进制数据放在里面,然后传输到服务器,服务器进行解析,获取里面的资源
二进制数据是在哪里?请求体
哪个API是获取请求体的?
request.getInputStream()
文件上传准备
1.form表单 method = post
2.input type=file
Content-Length: 37
该请求头代表了什么意思?
Content-Length: 11
遇到的第一个问题
仅上传文件名,不会上传文件的数据
解决方案:form表单需要设置enctype=multipart/form-data
处理文件的逻辑
package com.cskaoyan.upload;import javax.servlet.ServletException;import javax.servlet.ServletInputStream;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;@WebServlet("/upload")public class UploadServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//准备工作就绪之后,点击form表单的提交,浏览器会自动帮我们将文件的信息写入到请求体中//服务器拿到请求报文,会帮我们进行解析处理,将请求体封装到指定的API中,我们只需要去调用对应的API即可ServletInputStream inputStream = request.getInputStream();String realPath = getServletContext().getRealPath("upload/1.png");File file = new File(realPath);if(!file.getParentFile().exists()){//当前file文件的父级目录如果不存在file.getParentFile().mkdirs();}FileOutputStream outputStream = new FileOutputStream(file);int length = 0;byte[] bytes = new byte[1024];while ((length = inputStream.read(bytes)) != -1){outputStream.write(bytes, 0, length);}outputStream.close();inputStream.close();}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}}

文件经过上传之后,多出了很多内容;如果是文本文件,就会多出了很多东西;如果是二进制数据,这些内容进入到文件内部,会直接导致文件的损坏。
这些东西哪来的?
面临的问题二
是因为如果我们需要进行文件上传,那么就必须要使用enctype=multipart/form-data,但是使用了该enctype之后,请求体里面会多出来很多的字符。对于二进制数据来说,直接导致文件损坏,对于文本数据来说,多出很多分隔符
面临的问题三
之前可以正常获取请求参数的API也不再适用了,无法获取到请求参数,请求参数会和文件一起处理到文件中。
原因在于获取请求参数的API只能够获取key=value&key=value型数据,如果数据结构发生了变化,那么就无法获取到了。

总结:
目前面临的诸多问题,均和引入了enctype有关,但是又不能不引入,因为如果不引入,那么只会上传文件的名称,不会上传文件的内容;需要手动地将这些分割字符剔除掉。
不要重复造轮子。
commons-fileupload jar包
引入组件之前的这些操作以及遇到的问题,是需要大家掌握的。
文件上传组件的引入(了解使用)
看着API文档,来解决你的实际问题即可。
package com.cskaoyan.upload;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 javax.servlet.ServletException;import javax.servlet.ServletInputStream;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.util.Iterator;import java.util.List;@WebServlet("/upload2")public class UploadServlet2 extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//看着API文档,一步一步完成我们的功能即可//这行代码的含义是判断当前的请求是否是multipart/form-dataresponse.setContentType("text/html;charset=utf-8");boolean multipartContent = ServletFileUpload.isMultipartContent(request);if(!multipartContent){response.getWriter().println("当前请求不含上传的文件");return;}DiskFileItemFactory factory = new DiskFileItemFactory();File repository = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");factory.setRepository(repository);ServletFileUpload upload = new ServletFileUpload(factory);try {//items其实就是对于前端提交过来啊input的封装//提交过来多少个input,那么这边items就有多少个List<FileItem> items = upload.parseRequest(request);Iterator<FileItem> iterator = items.iterator();while (iterator.hasNext()){FileItem item = iterator.next();if(item.isFormField()){//当前item是常规的form表单processFormField(item);}else {//当前item是上传的文件processUploadedFile(item);}}} catch (FileUploadException e) {e.printStackTrace();}}/*** 处理上传的文件逻辑* @param item*/private void processUploadedFile(FileItem item) {String fieldName = item.getFieldName();String fileName = item.getName();// String contentType = item.getContentType();// boolean isInMemory = item.isInMemory();// long sizeInBytes = item.getSize();// System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);String realPath = getServletContext().getRealPath("upload/" + fileName);File file = new File(realPath);if(!file.getParentFile().exists()){file.getParentFile().mkdirs();}try {item.write(file);} catch (Exception e) {e.printStackTrace();}}/*** 处理常规form表单的逻辑* @param item*/private void processFormField(FileItem item) {String name = item.getFieldName();String value = item.getString();System.out.println("formField " + name + ":" + value);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}}
中文乱码问题
普通form表单数据乱码
//这行代码针对文件上传时form表单数据乱码也失效了,也不再适用了request.setCharacterEncoding("utf-8");
String getString(String encoding)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.
上传的文件名有没有可能乱码
//上传的文件名如果有中文的乱码问题upload.setHeaderEncoding("utf-8");
封装数据到Bean(掌握)
package com.cskaoyan.upload;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 javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.File;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.util.Iterator;import java.util.List;@WebServlet("/upload3")public class UploadServlet3 extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//看着API文档,一步一步完成我们的功能即可//这行代码的含义是判断当前的请求是否是multipart/form-datarequest.setCharacterEncoding("utf-8");response.setContentType("text/html;charset=utf-8");boolean multipartContent = ServletFileUpload.isMultipartContent(request);if(!multipartContent){response.getWriter().println("当前请求不含上传的文件");return;}DiskFileItemFactory factory = new DiskFileItemFactory();File repository = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");factory.setRepository(repository);ServletFileUpload upload = new ServletFileUpload(factory);//上传的文件名如果有中文的乱码问题upload.setHeaderEncoding("utf-8");// 1024 bytes//upload.setSizeMax(1024);try {//items其实就是对于前端提交过来啊input的封装//提交过来多少个input,那么这边items就有多少个List<FileItem> items = upload.parseRequest(request);Iterator<FileItem> iterator = items.iterator();while (iterator.hasNext()){FileItem item = iterator.next();if(item.isFormField()){//当前item是常规的form表单processFormField(item);}else {//当前item是上传的文件processUploadedFile(item);}}} catch (FileUploadException e) {e.printStackTrace();}}/*** 处理上传的文件逻辑* ReflectionUtils.toBean(Object o, String key, String value);* (user, username, admin)* (user, password, admin123)* (user, image, upload/1.jpg)* 有兴趣的同学可以课后实现它* 反射的代码有难度,能不能反射这部分代码交给BeanUtils来做呢* BeanUtils.populate(user, map);* @param item*/private void processUploadedFile(FileItem item) {String fieldName = item.getFieldName();String fileName = item.getName();// String contentType = item.getContentType();// boolean isInMemory = item.isInMemory();// long sizeInBytes = item.getSize();// System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);String realPath = getServletContext().getRealPath("upload/" + fileName);File file = new File(realPath);if(!file.getParentFile().exists()){file.getParentFile().mkdirs();}try {item.write(file);} catch (Exception e) {e.printStackTrace();}}/*** 处理常规form表单的逻辑* @param item*/private void processFormField(FileItem item) {//返回的就是input的name属性//根据不同的name属性,判断,根据不同的name属性值,调用不同的set方法,完成赋值,这个方法比较臃肿一些String name = item.getFieldName();String value = null;try {value = item.getString("utf-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}System.out.println("formField " + name + ":" + value);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}}
改进
package com.cskaoyan.upload;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 javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.File;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;@WebServlet("/upload3")public class UploadServlet3 extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//看着API文档,一步一步完成我们的功能即可//这行代码的含义是判断当前的请求是否是multipart/form-datarequest.setCharacterEncoding("utf-8");response.setContentType("text/html;charset=utf-8");boolean multipartContent = ServletFileUpload.isMultipartContent(request);if(!multipartContent){response.getWriter().println("当前请求不含上传的文件");return;}DiskFileItemFactory factory = new DiskFileItemFactory();File repository = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");factory.setRepository(repository);ServletFileUpload upload = new ServletFileUpload(factory);//上传的文件名如果有中文的乱码问题upload.setHeaderEncoding("utf-8");// 1024 bytes//upload.setSizeMax(1024);User user = new User();Map<String, Object> map = new HashMap<>();try {//items其实就是对于前端提交过来啊input的封装//提交过来多少个input,那么这边items就有多少个List<FileItem> items = upload.parseRequest(request);Iterator<FileItem> iterator = items.iterator();while (iterator.hasNext()){FileItem item = iterator.next();if(item.isFormField()){//当前item是常规的form表单processFormField(item, map);}else {//当前item是上传的文件processUploadedFile(item, map);}}BeanUtils.populate(user, map);System.out.println(user);} catch (FileUploadException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}/*** 处理上传的文件逻辑* ReflectionUtils.toBean(Object o, String key, String value);* (user, username, admin)* (user, password, admin123)* (user, image, upload/1.jpg)* 有兴趣的同学可以课后实现它* 反射的代码有难度,能不能反射这部分代码交给BeanUtils来做呢* BeanUtils.populate(user, map);* @param item* @param map*/private void processUploadedFile(FileItem item, Map<String, Object> map) {String fieldName = item.getFieldName();String fileName = item.getName();// String contentType = item.getContentType();// boolean isInMemory = item.isInMemory();// long sizeInBytes = item.getSize();// System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);String uploadPath = "upload/" + fileName;String realPath = getServletContext().getRealPath(uploadPath);File file = new File(realPath);if(!file.getParentFile().exists()){file.getParentFile().mkdirs();}try {item.write(file);map.put(fieldName, uploadPath);} catch (Exception e) {e.printStackTrace();}}/*** 处理常规form表单的逻辑* @param item* @param map*/private void processFormField(FileItem item, Map<String, Object> map) {//返回的就是input的name属性String name = item.getFieldName();String value = null;try {value = item.getString("utf-8");map.put(name, value);} catch (UnsupportedEncodingException e) {e.printStackTrace();}System.out.println("formField " + name + ":" + value);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}}

进一步优化
假如今后我们还需要进行商品发布、用户注册等其他行为,这个时候也需要使用文件上传。需要写在其他servlet中,再写一遍把
能不呢将一些代码抽提出来,形成一个工具类,后面的话可以复用。
package com.cskaoyan.upload.utils;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 javax.servlet.http.HttpServletRequest;import java.io.File;import java.io.UnsupportedEncodingException;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;public class FileUploadUtils {public static Map<String, Object> parseRequest(HttpServletRequest request){DiskFileItemFactory factory = new DiskFileItemFactory();File repository = (File) request.getServletContext().getAttribute("javax.servlet.context.tempdir");factory.setRepository(repository);Map<String, Object> map = new HashMap<>();ServletFileUpload upload = new ServletFileUpload(factory);//上传的文件名如果有中文的乱码问题upload.setHeaderEncoding("utf-8");// 1024 bytes//upload.setSizeMax(1024);try {//items其实就是对于前端提交过来啊input的封装//提交过来多少个input,那么这边items就有多少个List<FileItem> items = upload.parseRequest(request);Iterator<FileItem> iterator = items.iterator();while (iterator.hasNext()){FileItem item = iterator.next();if(item.isFormField()){//当前item是常规的form表单processFormField(item, map);}else {//当前item是上传的文件processUploadedFile(item, map, request);}}} catch (FileUploadException e) {e.printStackTrace();}return map;}private static void processUploadedFile(FileItem item, Map<String, Object> map, HttpServletRequest request) {String fieldName = item.getFieldName();String fileName = item.getName();// String contentType = item.getContentType();// boolean isInMemory = item.isInMemory();// long sizeInBytes = item.getSize();// System.out.println("File " + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);String uploadPath = "upload/" + fileName;String realPath = request.getServletContext().getRealPath(uploadPath);File file = new File(realPath);if(!file.getParentFile().exists()){file.getParentFile().mkdirs();}try {item.write(file);map.put(fieldName, uploadPath);} catch (Exception e) {e.printStackTrace();}}/*** 处理常规form表单的逻辑* @param item* @param map*/private static void processFormField(FileItem item, Map<String, Object> map) {//返回的就是input的name属性String name = item.getFieldName();String value = null;try {value = item.getString("utf-8");map.put(name, value);} catch (UnsupportedEncodingException e) {e.printStackTrace();}System.out.println("formField " + name + ":" + value);}}
package com.cskaoyan.upload;import com.cskaoyan.upload.utils.FileUploadUtils;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 javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.File;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;@WebServlet("/upload4")public class UploadServlet4 extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");boolean multipartContent = ServletFileUpload.isMultipartContent(request);if(!multipartContent){response.getWriter().println("当前请求不含上传的文件");return;}User user = new User();Map<String, Object> map = FileUploadUtils.parseRequest(request);try {BeanUtils.populate(user, map);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}}
文件重名
文件改名问题。 wxid
利用时间戳 + username + 文件名
随机字符串重命名
String uuid = UUID.randomUUID().toString();String fileName = item.getName();//文件有后缀名的fileName = uuid + fileName;
目录内文件数过多
hash散列
hashcode:尽可能散列的开,均匀。
文件名———hashcode———-int 数字——-16进制 8位 0x 1 2 3 4 5 6 a f
利用时间来划分—-年度、月份、日期,概率问题,如果出现某些大促,很可能需要进一步细分。
微信头像加国旗。去年 70周年
加上国旗的头像上传——-服务器
自己看自己的头像时改成功的,但是别人看起来,依然是之前的头像。经过了半天左右,头像终于同步了,别人看到你的头像也是新的了。
案例
写一个注册页面,包含图片上传,然后将上传的资源进行回显,回显图片
