Day41
文件的上传
浏览器底层是通过输入流读取文件,通过输出流传输到服务器,服务器通过输入流读取数据,通过输出流将文件保存在本地。注意:浏览器的表单不许用post请求,get请求会将数据显示在地址栏里。
上传头像
场景:在一个注册页面上传头像。
register.jsp:
<%-- Created by IntelliJ IDEA. User: Gu Date: 2024-06-13 Time: 18:43 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; font-family: Arial, sans-serif; background: #f0f0f0; } </style> </head> <body> <form action="RegisterServlet" method="post" enctype="multipart/form-data"> <h2>欢迎来到注册页面</h2> <br/> 账号:<input type="text" name="username" /><br /> 密码:<input type="password" name="password" /><br /> 姓名:<input type="text" name="name" /><br /> 年龄:<input type="text" name="age" /><br /> 性别: <input type="radio" name="sex" value="man" checked="checked"/>男 <input type="radio" name="sex" value="woman"/>女 <br /> 爱好: <input type="checkbox" name="hobbies" value="football" />足球 <input type="checkbox" name="hobbies" value="basketball" />篮球 <input type="checkbox" name="hobbies" value="shop" />购物 <br /> <input type="submit" value="注册" /><br/> <input type="file" name="photo"/><br/> <button type="button" οnclick="fun01()">返回</button> </form> <script type="text/javascript"> function fun01(){ window.location = "register.jsp"; } </script> </body> </html>
注意:1.使用post请求,设置enctype=“multipart/form-data”(以二进制流的形式上传数据,效率比较低,不设置的话以默认的纯文本数据上传。)。2.input type=“file”。
版本一:原生文件方式处理文件
待优化点:1.相同的文件名会覆盖文件;2.存储路径绝对,其他系统无法存储。
RegisterServlet:
package com.qf.servlet; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; 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.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; @WebServlet("/RegisterServlet") public class RegisterServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); //创建文件上传类对象的工厂对象 DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); //利用工厂对象创建文件上传类对象 ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); try { //解析请求对象 List<FileItem> list = servletFileUpload.parseRequest(request); //遍历获取的集合 for(FileItem fileItem:list){ //如果是文本数据 if(fileItem.isFormField()){ String fieldName = fileItem.getFieldName();//属性名 String value = fileItem.getString("UTF-8");//值 System.out.println(fieldName + "--" + value); }else {//二进制数据 String name = fileItem.getName();//用户输入的文件的名 System.out.println(name); InputStream inputStream = fileItem.getInputStream(); String path="F:\\text\\头像01.jpg"; FileOutputStream fileOutputStream = new FileOutputStream(path); byte[] bs = new byte[1024]; int len; while((len=inputStream.read(bs))!=-1){ fileOutputStream.write(bs); } inputStream.close(); fileOutputStream.close(); } } } catch (FileUploadException e) { throw new RuntimeException(e); } } }
注:不能使用request.getparameter()获取数据了,用request.getInputStream()。
版本二:使用到发布路径 + 文件名
待优化点:文件名重复仍会覆盖。
注意:1.使用IOUtil工具类2.编辑发布路径,将文件保存到项目的发布路径里,需要将IDEA里的路径改为服务器(Tomcat)里的webapp下自己创建的文件名里,如:\apache-tomcat-8.0.49\webapps\Day41_upload_war_exploded
public void method02(HttpServletRequest request,HttpServletResponse response) throws UnsupportedEncodingException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); //创建文件上传类工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //创建文件上传类 ServletFileUpload upload = new ServletFileUpload(factory); try { //解析数据 List<FileItem> list = upload.parseRequest(request); for(FileItem file:list){ if(file.isFormField()){ //是文本数据 System.out.println(file.getFieldName()+"--"+file.getString("UTF-8")); } else { //处理二进制数据 String path = this.getServletContext().getRealPath("upload");//获取系统的发布路径 //创建文件夹 File file1 = new File(path); if(!file1.exists()){ file1.mkdirs(); } //设置文件路径 String name = file.getName(); String outPath = path+File.separator+name; //上传文件 InputStream in = file.getInputStream(); FileOutputStream out = new FileOutputStream(outPath); IOUtils.copy(in,out); in.close(); out.close(); } } } catch (FileUploadException | IOException e) { throw new RuntimeException(e); } }
版本三:使用UUID解决文件名重复问题
缺点:改变了文件名
public void method03(HttpServletRequest request,HttpServletResponse response) throws UnsupportedEncodingException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); try { List<FileItem> list = upload.parseRequest(request); for(FileItem fileItem:list){ if (fileItem.isFormField()){ System.out.println(fileItem.getFieldName()+"--"+fileItem.getString("UTF-8")); }else { //获取项目发布路径 String realPath = this.getServletContext().getRealPath("upload"); File file = new File(realPath); if(!file.exists()){ file.mkdirs(); } String name = fileItem.getName(); UUID uuid = UUID.randomUUID(); String fileName = uuid + name; String filePath = realPath +File.separator+ fileName; InputStream in = fileItem.getInputStream(); FileOutputStream out = new FileOutputStream(filePath); IOUtils.copy(in,out); in.close(); out.close(); } } } catch (FileUploadException | IOException e) { throw new RuntimeException(e); } }
最终版:
思路:先自定义一个parseRequest()方法。通过上传文件工厂创建上传文件对象,再解析用户在注册页面填写的所有数据,其中对每一个数据项进行判别,如果是文本数据则直接用ConcurrentHashmap存储数据,其中需要注意的点为遇到多选的时候需要判断当前的键(属性名)是否已经存在于map中,如果存在就添加在后面。如果是二进制数据则需要先获取发布路径(利用File类创建文件夹),然后通过发布路径和文件名创造二进制数据下载的绝对路径,最后用map把属性名和绝对路径后面部分(自定义即可)存储起来。同时还需要创建一个用来存储输入输出流的类CopyIo,因为最终要将用户上传的文件写到发布路径里面,所以用请求创建输入流(建立客户端输入的渠道),并用绝对路径创建输出流(建立输出到发布路径的渠道),并将两个流封装到CopyIo中,以便直接使用。
接下来的思路和之前的一样,通过自定义的parseRequest()方法设置好ConcurrentHashmap和CopyIo对象。然后用自带的BeanUtils类的populate方法将数据封装到Student类中,利用这个类对象往数据库中查询,没有账号则可以注册,即先往数据库中添加数据,再用CopyIo对象中的输入输出流进行数据写入和发布输出;如果有账号则用请求存储错误信息,返回注册页面。注意:别忘了关闭资源(输入流、输出流)。
public void parseRequest(HttpServletRequest request, Student student , CopyIO copyIO){ ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(); DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); String username=null; try { List<FileItem> list = upload.parseRequest(request); for(FileItem fileItem:list){ if(fileItem.isFormField()){ String fieldName = fileItem.getFieldName(); String value = URLEncoder.encode(fileItem.getString(),"UTF-8"); if("username".equals(fieldName)){ username=value; } String s = map.get(fieldName); if(s==null){ //第一次添加 map.put(fieldName,value); } else{ //多选的情况 s = s+","+value; map.put(fieldName,s); } }else{ //二进制数据 String realPath = this.getServletContext().getRealPath("upload"+File.separator+"student"+File.separator+username); File file = new File(realPath); if(!file.exists()){ file.mkdirs(); } String fileName = fileItem.getName(); String path = realPath + File.separator + fileName; copyIO.setIn(fileItem.getInputStream()); copyIO.setOut(new FileOutputStream(path)); map.put(fileItem.getFieldName(),"upload"+File.separator+"student"+File.separator+username+File.separator+fileName); } } BeanUtils.populate(student,map); } catch (FileUploadException | IOException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } } public void method04(HttpServletRequest request,HttpServletResponse response) throws UnsupportedEncodingException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); Student student = new Student(); CopyIO copyIO = new CopyIO(); parseRequest(request,student,copyIO); System.out.println(student); try { Student sqlStu = DBUtil.commonQueryObj(Student.class, "select * from student where username=?", student.getUsername()); System.out.println(sqlStu); if(sqlStu==null){ //允许注册 String sql = "insert into student(username,password,name,sex,age,hobbies,photo) values (?,?,?,?,?,?,?)"; DBUtil.commonInsert(sql,student.getUsername(),student.getPassword(),student.getName(),student.getSex(),student.getAge(),student.getHobbies(),student.getPhoto()); //上传头像 InputStream in = copyIO.getIn(); OutputStream out = copyIO.getOut(); int len; byte[] bs = new byte[1024]; while((len=in.read(bs))!=-1){ out.write(bs); } in.close(); out.close(); }else{ //不允许注册 // copyIO.getIn().close(); // copyIO.getOut().close(); request.setAttribute("msg","账号已存在,注册失败"); request.getRequestDispatcher("register.jsp").forward(request,response); } } catch (SQLException | InstantiationException | IllegalAccessException | IOException | ServletException e) { throw new RuntimeException(e); } }
文件的下载
下载图片和压缩包
在web文件夹中设置一个download文件夹用来存放下载资源。
页面:超链接中href="download/xxx.zip"下载压缩包
href="download/xxx.jpg"下载图片
注:因为浏览器无法识别zip,所以可以直接下载,而jpg可以识别,所以会直接展示。如果想直接下载,要自己写一个servlet去实现图片下载功能。
更改发布路径,获取需要下载的文件路径,从发布路径中截取到文件名,设置编码格式,设置响应头信息即告诉浏览器以附件的形式下载而不是展示,用输入输出流读取和输出(输出流用响应获取)。
DownloadServlet:
package com.qf.servlet; import org.apache.commons.io.IOUtils; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.FileInputStream; import java.io.IOException; import java.net.URLEncoder; @WebServlet("/DownloadServlet") public class DownloadServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); //获取需要下载的文件路径 String realPath = this.getServletContext().getRealPath("download\\aaa.jpg"); //从下载路径中截取文件名 String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1); //设置编码格式 fileName = URLEncoder.encode(fileName, "UTF-8"); //设置响应头,使浏览器只能以附件形式下载 response.setHeader("Content-disposition","attachment;fileName="+fileName); FileInputStream fileInputStream = new FileInputStream(realPath); ServletOutputStream outputStream = response.getOutputStream(); IOUtils.copy(fileInputStream,outputStream); fileInputStream.close(); fileInputStream.close(); } }
经验:下载图片用servlet,将下载的文件放在一个地方。
MVC设计模式
M-model-模型层:biz/service-服务层:做业务;dao/mapper -数据持久层:操作数据库。
V-view-视图层:页面(HTML、CSS、JS、JSP)。
C-Controller-控制器层:控制页面的跳转。
服务端分层思想:controller、biz、dao
优缺点:
缺点:使用MVC不能减少代码量, 增加系统结构和实现的复杂性
优点:整个项目结构清晰,业务逻辑清晰,降低了代码的耦合性,代码的重用性高
MVC编写顺序:先写模型层,再写视图层和控制器层