1.文件上传
1.1介绍
1.在很多业务场景,用户都需要上传头像、证件照以及其他文件.
2.文件上传就是用户通过浏览器选择需要上传的文件并将文件传输至网站服务器上的一个过程
文件上传就是用浏览器把文件数据跟着请求报文一起发送到服务器。服务器把请求报文里的文件部分的内容取出来,并且还原成文件的过程。
1.2使用工具
文件上传需要对请求中传的的文件流进行解析,一般会选择工具来对文件流进行解析
文件上传相关的jar包:
commons-fileupload-1.3.3.jar
commons-io-2.5.jar
2.代码
新建项目day7_upload
新建web.myUpload.html
页面:
<head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 上传文件,要有form,并且method必须是post --> <form action="/day7/upload" method="post"> <input type="text" name="username"><br> <input type="file" name="myfile"><br> <input type="submit"> </form> </body> </html>
新建src.com.javasm.controller.UploadServlet
@WebServlet("/upload") public class UploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String myfile = req.getParameter("myfile"); System.out.println(username); System.out.println(myfile); } }
测试,重新部署,运行:
访问:http://localhost:8080/day7/myUpload.html
点击选择文件,选择一张照片。点击提交
开发者工具network,看到username=&myfile=%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.png
只能当做一个普通键值对去传。后台只会接收到文件名。
发送数据,请求部分有:
Content-Type: application/x-www-form-urlencoded
是参数拼接的url格式。使用这种格式传文件数据时,传不过去。需要改变它。
<body> <!-- 上传文件,要有form,并且method必须是post 另外还要用enctype属性改变传输格式。它的默认值就是application/x-www-form-urlencoded 想要发送文件,就得改成multipart/form-data --> <form action="/day7/upload" method="post" enctype="multipart/form-data"> <input type="text" name="username"><br> <input type="file" name="myfile"><br> <input type="submit"> </form> </body>
修改以后,在network的Preview上看不到参数了,且后台输出两个null。
请求部分格式那块变成了:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuajmTVXWf3eBBtJG
发送文件数据时,浏览器并没有区分是普通的键值对字段还是文件字段,若使用multipart/form-data之后,数据都会变成二进制数据,也就是流里要发送的字节数据。实际上发送的所有数据都打成了boundary=----WebKitFormBoundaryuajmTVXWf3eBBtJG这样一个包。
后台通过getParameter接收数据是因为Tomcat把发送过来的键值对格式的数据拆分,放到一个Map里,从Map里取值。现在改变传输格式,Tomcat默认的形式就读不出来了。
所有的文件都在流里,应该获取请求正文部分的输入流,根据输入流把里面上传的文件原封不动的拆出来。然后在本地建一些文件,把流里读出的文件放到本地建好的文件里。
所以,上传文件的过程中,浏览器只需要做一些设置。服务端这边则是一堆IO的读写,读请求里的输入流,本地再创建一个输出流,通过本地的输出流把内容写入到本地文件里。
作为发送文件来讲,浏览器不管发什么文件都是这个流程,服务器读请求,写入本地。上传不同文件,只是本地创建的文件不同(目录不同),文件名不同。这种模式化的东西有对应的工具,两个jar包:
commons-io-2.5.jar IO增强包
commons-fileupload-1.3.3.jar 上传文件,解析数据用的包
(第二个包基于第一个包,但主要使用第二个包,故第一个包也要用)
jar包还是放在web.WEB-INF.lib里,再给服务器配置一下。
通过这两个工具,服务端的1和3这两步都可以做到。
public class UploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String myfile = req.getParameter("myfile"); System.out.println(username); System.out.println(myfile); //commons-fileupload-1.3.3.jar下的方法,主要用到ServletFileUpload类 //isMultipartContent()返回boolean值,主要检测这次请求的数据Content-Type是不是multipart/form-data //如果是该格式,才做文件的解析,不是就用普通字符串做接收。参数是请求对象 boolean multipartContent = ServletFileUpload.isMultipartContent(req); System.out.println(multipartContent); //DiskFileItemFactory解析数据,并封装成一个个FileItem对象 DiskFileItemFactory dff = new DiskFileItemFactory(); //上传文件核心对象,包含对请求解析的方法 ServletFileUpload servletFileUpload = new ServletFileUpload(dff); try { //解析请求,返回一个列表,列表里装的是FileItem对象 List<FileItem> list = servletFileUpload.parseRequest(req); System.out.println(list); } catch (FileUploadException e) { e.printStackTrace(); } } }
以上就完成了第一步解析的工作。
重新部署,运行,访问:http://localhost:8080/day7/myUpload.html
后台控制台打印出的结果为:
null null true
[name=null, StoreLocation=D:\tomcat\apache-tomcat-8.5.34\temp\upload7b0117f0_180760fb0097fa4_00000000.tmp, size=0bytes, isFormField=true, FieldName=username,
name=文件上传.png, StoreLocation=D:\tomcat\apache-tomcat-8.5.34\temp\upload7b0117f0_180760fb0097fa4_00000001.tmp, size=26989bytes, isFormField=false, FieldName=myfile]
FieldName就是起的name属性。(文件上传时通常要给form里的表单元素起name属性,上传的键值对在英文里叫FormFiled.)
isFormField,如果是普通的上传文件字段就是true,如果是文件字段就是false
size,在第一条很小,因为是普通的字段。第二条比较大,故就是图片。
StoreLocation是Tomcat接收到文件,生成出来的临时文件。之前是什么文件也不会帮助还原出来,仅仅是把数据分成不同的临时文件存储,并且在这里写上了生成临时文件的地址。
name就是文件名。
第二步需要自己去做。创建的目录需要url能访问到。
1.只有自己的服务器,则存在当前服务器下
2.Tomcat作为服务器程序,可以启动多个,每次启动的时候换一个端口即可。如果可以启动不同的服务器,也可单独取一个服务器读取图片用。故也可存在其他服务器下。
能通过url访问必须在如下图片目录下,他是运行目录。在web下新建myupload目录,不过这里建的目录是源代码所在的目录,不是Tomcat运行目录,故建好目录一定要检查运行目录里有没有新建的目录。(右键,show in Explorer是源代码的目录)
但运行目录里没有。因为idea建立了空目录,不会打包过去。故在myupload目录下新建一个空文件,这里新建nouse.text
重新部署,再看运行目录。就会出现该目录。
之后,相应的代码:
@WebServlet("/upload") public class UploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String myfile = req.getParameter("myfile"); System.out.println(username); System.out.println(myfile); boolean multipartContent = ServletFileUpload.isMultipartContent(req); System.out.println(multipartContent); String realPath = req.getServletContext().getRealPath("/"); //获得Tomcat运行代码的真实路径,也是通过url //可以访问到的真实根目录。也是本地IO存储的真实目录 System.out.println(realPath); //D:\workspace\two\javaEE\out\artifacts\day7_upload_war_exploded //windows系统,本地文件使用路径时用右斜杠,但url里使用左斜杠。 /通用性比较强,不管是url,linux系统,windows系统都可 //识别出来 为了防止混乱,目录都用/ String myFolderPath = "myupload/"; //要存的目录 目录和文件名中间也有/ DiskFileItemFactory dff = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(dff); try { List<FileItem> list = servletFileUpload.parseRequest(req); System.out.println(list); for (FileItem item:list){ if (!item.isFormField()){ //如果不是普通的文件字段 //创建文件,参数是路径。前两部分已有,还差文件名。 //生成文件名:1.使用uuid 2.使用原文件名(在name上) File file = new File(realPath + myFolderPath + item.getName()); } } } catch (FileUploadException e) { e.printStackTrace(); } } }
第三步:
@WebServlet("/upload") public class UploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String myfile = req.getParameter("myfile"); System.out.println(username); System.out.println(myfile); boolean multipartContent = ServletFileUpload.isMultipartContent(req); System.out.println(multipartContent); String realPath = req.getServletContext().getRealPath("/"); System.out.println(realPath); String myFolderPath = "myupload/"; DiskFileItemFactory dff = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(dff); try { List<FileItem> list = servletFileUpload.parseRequest(req); System.out.println(list); for (FileItem item:list){ if (!item.isFormField()){ File file = new File(realPath + myFolderPath + item.getName()); //把临时文件的内容,写入到指定的文件中 item.write(file); } } } catch (FileUploadException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }
重新部署,运行,访问:
http://localhost:8080/day7/myUpload.html
后台生成临时文件,打开运行目录:E:\workspace\two\javaEE\out\artifacts\day7_upload_war_exploded\myupload
会看到上传的图片。
再测试通过url访问: http://localhost:8080/day7/myupload/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.png (web目录下的myupload,后面是上传的文件名 上传文件.png)
3.文件上传注意点
1.分清java源代码目录和Tomcat运行代码目录
源代码目录:
运行目录:
上传文件最终会上传到myupload目录里。url访问的目录也是这个目录。
2.服务器每次重新部署或者重启时,idea会根据源码往服务器打包。
重新打包:Build,Build Atifacts,找到指定的服务器,可执行Rebuild。
3.普通字段部分怎么处理(假如要用到它)
@WebServlet("/upload") public class UploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); //告诉Tomcat解析时用utf-8解析 String username = req.getParameter("username"); String myfile = req.getParameter("myfile"); System.out.println(username); System.out.println(myfile); boolean multipartContent = ServletFileUpload.isMultipartContent(req); System.out.println(multipartContent); String realPath = req.getServletContext().getRealPath("/"); System.out.println(realPath); String myFolderPath = "myupload/"; DiskFileItemFactory dff = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(dff); try { List<FileItem> list = servletFileUpload.parseRequest(req); System.out.println(list); for (FileItem item:list){ if (!item.isFormField()){ File file = new File(realPath + myFolderPath + item.getName()); item.write(file); }else { if ("username".equals(item.getFieldName())){ //找到对应的key String string = item.getString(); //获得key对应的值的部分 //如果输入汉字,会出现乱码。因为现在是fileupload工具包解析的数据,和Tomcat没关系 //System.out.println(string); 该工具数据阿帕奇,默认ISO-8859-1编码,纯英文编码。 //string.getBytes("ISO-8859-1")先拆成Byte数组 System.out.println(new String(string.getBytes("ISO-8859-1"),"utf-8")); //这套工具对中文支持比较差,故文件名是中文也要自己解析 } } } } catch (FileUploadException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }
使用工具之后,文件处理简单,但普通字段处理麻烦,因为使用了和文件处理相同的方式。
如果有个注册页面,上传头像和其他很多的字段,就很麻烦。
主流做法:使用Ajax把文件上传,将图片存的地址回传。注册时把地址写入数据库,访问时直接从数据库读地址。
4.Ajax上传文件(异步文件上传)
新建web.js,将jquery-3.6.0.js放入。
@WebServlet("/ajaxupload") public class AjaxUploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String realPath = req.getServletContext().getRealPath("/"); System.out.println(realPath); String myFolderPath = "myupload/"; String retPath = ""; DiskFileItemFactory dff = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(dff); try { List<FileItem> list = servletFileUpload.parseRequest(req); System.out.println(list); for (FileItem item:list){ if (!item.isFormField()){ File file = new File(realPath + myFolderPath + item.getName()); item.write(file); //要返回给页面的路径 存入数据库 retPath = myFolderPath + item.getName(); } } } catch (Exception e) { e.printStackTrace(); } resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("{\"imgsrc\":\""+retPath+"\"}"); writer.flush(); writer.close(); } }
<head> <meta charset="UTF-8"> <title>Title</title> <script src="/day7/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <style> .imgcls{ width: 80px; height: 80px; } </style> </head> <body> <form id="myform" > <input type="text" name="username"><br> <input type="text" name="userpwd"><br> <input type="text" name="userphone"><br> <input type="text" name="usermail"><br> <input type="text" name="useraddr"><br> <input type="hidden" id="imgsrc" name="imgsrc"> <input id="myfile" type="file" name="myfile"><br/> <img id="mifileimg" class="imgcls" src="/day7/img/upready.png"/> <br> <!--<input id="testFileBtn" type="button" value="测试">--> <input id="mySubmitBtn" type="button" value="保存数据"> </form> </body> <script> //文件框 选择的文件改变了 $("#myfile").change(function(){ console.log($("#myfile")[0].files[0]) //js取文件对象 console.log(document.getElementById("myfile").files); //js创建form var myformdata = new FormData(); //在form中创建键值对 myformdata.append("myajaxfile",$("#myfile")[0].files[0]); $.ajax({ url:"/day7/ajaxupload", //请求地址 type:"post", //请求方式 data:myformdata, //发送的数据 可以使用json格式 也可以使用url拼接格式 contentType:false, //使用formData的默认格式 processData:false, //是否把格式转为键值对拼接 dataType:"json", //返回的数据格式 success:function(data){ //成功时的 回调函数 console.log(data); //配合后续流程 把图片路径 写入隐藏框中 $("#imgsrc").val(data.imgsrc); //展示上传数据预览 $("#mifileimg").prop("src","/day7/"+data.imgsrc) } }) }) $("#mySubmitBtn").click(function(){ var myData = $("#myform").serialize(); console.log(myData); $.post("/day7/regist",myData,function(data){},"json"); }) </script> </html>