week12_day03_Fileupload
1.FileUpload
1.1.文件上传的准备工作
请求报文。文件的二进制信息应当放在哪?请求体。
form表单 method=post
input type=file
Upload.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app/upload" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="file" name="image"><br>
<input type="submit">
</form>
</body>
</html>
正文长度为12,很明显不正常
此时发现,仅会上传文件名,不会上传真实的文件数据。
解决方案:form表单需要添加enctype=multipart/form-data
这次的正文长度就比较正常了
接下来,编写servlet来接收上传的文件。
UploadServlet:
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 {
//逻辑
//response给我们提供好了现存的响应输出流
//request里面有没有给我们封装好现存的输入流呢?
ServletInputStream inputStream = request.getInputStream();
String realPath = getServletContext().getRealPath("upload/1.txt");
File file = new File(realPath);
//防止父目录不存在,引发异常,如果父目录不存在,则创建
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
int length = 0;
byte[] bytes = new byte[1024];
while ((length = inputStream.read(bytes)) != -1){
fileOutputStream.write(bytes, 0, length);
}
fileOutputStream.close();
//inputStream可以关也可以不关,不关的话tomcat会帮助我们关闭
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
上传完成后,开发目录中没有发现上传的文件,应当到部署目录中去查看上传的文件。
发现最终文件损坏?代码有问题吗????没有问题的。
可以更换一个文件,1.txt来看一下。
上传前:
上传后:
文件平白无故多出了一些WebKitFormBoundary分隔符,
对于一个普通的文本,多出一些内容
如果对于一个二进制文件,图片、音频、视频、exe文件,多出来这一部分会直接导致文件损坏。
1.2.文件上传遇到的第一个问题
1.2.1.会多出来一部分分隔符,导致文件损坏
在文件上传的同时,还想上传form表单数据
会发现获取到的数据都是null
此时上传的文件打开,会发现
普通的form表单数据也进入到文件中了。
1.2.2.无法获取到普通form表单请求参数,且表单数据会进入到上传的文件中
为什么呢?
所有一切的原因都是因为添加了enctype=multipart/form-data。但是如果不添加,就不会上传文件。
请求体中的数据结构发生了变更,之前的数据key=value&key=value拼接的,getParameter通过key可以获取到数据value,那么如果数据结构发生了变更,getParameter理应不能再用了。
为什么整个表单中的数据都会进入文件中呢?
因为这些数据都是位于请求体中的,request.getInputSream()相当于把整个请求体全部放入文件中。同样的,分隔符也是位于请求体中的,所以也会被写入文件中。
1.3.总结
问题的话,主要有两大类,
1.文件会有分隔符,导致文件损坏
2.获取请求参数的API不再适用,无法获取到普通form表单数据
原因都是因为添加了enctype=multipart/form-data,因为添加这个属性,使得请求体的数据结构发生了变更,所以获取请求参数的API不在适用,同时添加之后,会多出很多分隔符,所以文件中会包含有分隔符。
1.4.使用组件来进行文件上传
接下来的内容大家会觉得代码太多,而且不理解,记不住。不用去深究。
以后学习到Spring。Spring会帮助你把这些步骤全部封装好,所以今后啥也不用干。
Commons-FileUpload组件
http://commons.apache.org/proper/commons-fileupload/using.html
这个网站会有Commons-FileUpload的一些使用案例。
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
这句代码的作用就是用来检查request是否包含enctype=multipart/form-data.
实现步骤
1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
True 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
False 为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
编码实现文件上传
Tip:核心API—DiskFileItemFactory
DiskFileItemFactory 是创建 FileItem 对象的工厂,这个工厂类常用方法:
public DiskFileItemFactory(int sizeThreshold, java.io.File repository)
构造函数
public void setSizeThreshold(int sizeThreshold)
设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。
public void setRepository(java.io.File repository)
指定临时文件目录,默认值为System.getProperty(“java.io.tmpdir”).
Tip:核心API—ServletFileUpload
ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中。常用方法有:
boolean isMultipartContent(HttpServletRequest request)
判断上传表单是否为multipart/form-data类型
List parseRequest(HttpServletRequest request)
解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
setFileSizeMax(long fileSizeMax)
设置单个上传文件的最大值bytes
setSizeMax(long sizeMax)
设置上传文件总量的最大值(多个文件同时上传时文件最大值的总和)
setHeaderEncoding(java.lang.String encoding)
设置编码格式,解决上传文件名乱码问题
Tip:核心API—FileItem
FileItem 用来表示文件上传表单中的一个上传文件对象或者普通表单对象
boolean isFormField() 判断FileItem是一个文件上传对象还是普通表单对象
如果判断是一个普通表单对象
String getFieldName() 获得普通表单对象的name属性
String getString(String encoding) 获得普通表单对象的value属性
如果判断是一个文件上传对象
String getName() 获得上传文件的文件名(有些浏览器会携带客户端路径)
InputStream getInputStream() 获得上传文件的输入流
delete() 在关闭FileItem输入流后,删除临时文件
Tip:上传文件的存放问题
文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。
UploadServlet2:
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.ServletContext;
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("/upload2")
public class UploadServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//因为提交的是multipart/form-data,所以响应正文的数据结构发生改变,无法通过以下代码解决乱码问题。
//request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//post里面写代码
//返回的是true表示有Multipart
boolean result = ServletFileUpload.isMultipartContent(request);
if(!result){
response.getWriter().println("不包含上传的文件");
return;
}
//result=true
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletContext servletContext = getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
//如果上传的文件名中文乱码,可以这么解决
//upload.setHeaderEncoding("utf-8");
// 允许上传文件的最大大小为1024 bytes
//upload.setFileSizeMax(1024);
try {
List<FileItem> items = upload.parseRequest(request);
//对items进行迭代
Iterator<FileItem> iterator = items.iterator();
while (iterator.hasNext()){
//每一个item其实就是一个有name属性的input框的封装
FileItem item = iterator.next();
if(item.isFormField()){
//当前是普通的form表单数据
processFormField(item);
}else{
//当前是上传的文件
processUploadedFile(item);
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
/**
* 处理上传的文件逻辑
* @param item
*/
private void processUploadedFile(FileItem item) {
String fieldName = item.getFieldName();
String fileName = item.getName();
System.out.println(fieldName + " === " + fileName);
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表单数据
* 只需要获取name和value值即可
* 上传文件之后,不要再用getParameter去获取请求参数了
* 有没有中文乱码问题呢?
* @param item
*/
private void processFormField(FileItem item) {
String name = item.getFieldName();
String value = null;
try {
//表示value用utf-8编码
value = item.getString("utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(name + " = " + value);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
1.4.1.form表单数据中文乱码
问题:
比如,还是像之前获取form表单数据一样,接下来需要将这些数据封装到一个bean中,然后保存到数据库,怎么操作简便一些???????
作业1: 在之前的注册页面的基础上,增加注册时传头像(可以想一想如何更加简便)
form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--action就表示你需要向哪个地址发起一个请求-->
<!--submit一个表单一个,就构建一个请求报文,由于post请求方式,所以在请求体中,-->
<!--参数的组织形式是这样的:username=xx&password=xx&gender=xx&-->
<!--所以我们就可以通过name取到用户所输入的值-->
<form action="/app/register" enctype="multipart/form-data" method="post">
用户名:<input type="text" name="username"><br>
上传头像:<input type="file" name="img"><br>
密码:<input type="password" name="password"><br>
性别:男<input type="radio" name="gender" value="male">
女<input type="radio" name="gender" value="female"><br>
爱好:java<input type="checkbox" name="hobby" value="java">
c++<input type="checkbox" name="hobby" value="c++">
python<input type="checkbox" name="hobby" value="python"><br>
简介<textarea name="description"></textarea><br>
<input type="submit">
</form>
</body>
</html>
HomeworkServlet01 :
package com.cskaoyan.Homework;
import org.apache.commons.beanutils.BeanUtils;
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.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
/**
* @author shihao
* @create 2020-06-25 16:35
*/
@WebServlet("/register")
public class HomeworkServlet01 extends HttpServlet {
/**
* 需要注意的是,当引入文件上传之后,form表单中的数据通过request.getParameter方法获取不到数据
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map<String, Object> map = FileUploadUtils.parseRequest(request);
User user = new User();
try {
BeanUtils.populate(user,map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//将user保存到数据库中
System.out.println(user);
//hobby里面的数据直接将多个数据以,隔开的形式保存在数据库中即可
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
作业2: 实现多文件上传功能
uploadMultiFile.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app/multi" enctype="multipart/form-data" method="post">
<input type="file" name="headImage"><br>
<input type="file" name="detailImage"><br>
<input type="submit">
</form>
</body>
</html>
FileUploadUtils:
package com.cskaoyan.Homework;
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.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.*;
/**
* @author shihao
* @create 2020-06-25 17:03
*/
public class FileUploadUtils {
private static String UPLOAD_PATH = "/upload";
public static Map<String, Object> parseRequest(HttpServletRequest request) {
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Configure a repository (to ensure a secure temp location is used)
ServletContext servletContext = request.getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
HashMap<String, Object> result = new HashMap<>();
//如果上传的文件名中文乱码,可以这么解决
upload.setHeaderEncoding("utf-8");
// Parse the request
try {
List<FileItem> items = upload.parseRequest(request);
Iterator<FileItem> iterator = items.iterator();
while (iterator.hasNext()) {
FileItem item = iterator.next();
if (item.isFormField()) {
processFormField(item, result);
} else {
processUploadField(item, result, servletContext);
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
/**
* 处理上传文件的具体逻辑
*
* @param item
* @param result 文件上传成功后路径保存在map中
* @param servletContext
*/
private static void processUploadField(FileItem item, HashMap<String, Object> result, ServletContext servletContext) {
String fieldName = item.getFieldName();
String fileName = item.getName();
System.out.println(fieldName + " === " + fileName);
String realPath = servletContext.getRealPath("upload/" + fileName);
File file = new File(realPath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
try {
//保存文件到硬盘中
item.write(file);
} catch (Exception e) {
e.printStackTrace();
}
result.put(fileName, realPath);
}
/**
* 处理普通的表单数据
* 需要考虑到hobby是一个组合,需要考虑到后面覆盖前面的情况
*
* @param item
* @param result 处理表单的数据保存再map中
*/
private static void processFormField(FileItem item, HashMap<String, Object> result) throws UnsupportedEncodingException {
String fieldName = item.getFieldName();
if (result.get(fieldName) == null) {
result.put(fieldName, item.getString("utf-8"));
return;
}
result.put(fieldName, result.get(fieldName) + "," + item.getString("utf-8"));
}
}
HomeworkServlet02:
package com.cskaoyan.Homework;
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.IOException;
import java.util.Map;
/**
* @author shihao
* @create 2020-06-25 17:52
*/
@WebServlet("/multi")
public class HomeworkServlet02 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map<String, Object> map = FileUploadUtils.parseRequest(request);
System.out.println(map);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
上一篇: Vue 的 增 删 查 改
下一篇: centos8 设置运行模式及恢复