day18_文件的上传和下载学习笔记

摘要:
通过文件上传,浏览器中的数据可以直接保存到服务器。C、 提供inputtype=“file”类的上传输入字段。F、 临时文件DiskFileItemFactory:函数:生成FileItem对象//若要上载文件,请首先确定表单是否支持文件上载。

1、文件上传的原理分析

什么是文件上传?
    要将客户端(浏览器)数据存储到服务器端,而不将数据直接存储到数据库中,而是要将数据存储到服务器所在的磁盘上,这就要使用文件上传。
为什么使用文件上传?
    通过文件上传,可以将浏览器端的数据直接保存到服务器端。不将数据保存到数据库中,而是保存到服务器磁盘上,这样减少了数据库服务器的压力,对数据的操作更加灵活。

1.1、文件上传的必要前提

  • a、提供form表单,method必须是post提交方式。
  • b、form表单必须设置为enctype="multipart/form-data"
  • c、提供input type="file"类的上传输入域。

1.2、enctype属性

作用:告知服务器请求正文的MIME类型(文件类型)。(与请求消息头中:Content-Type作用是一致的)
可选值
application/x-www-form-urlencoded(默认)
  请求消息正文:name=tom&photo=a.txt
  服务器获取数据:String name = request.getParameter("name");

day18_文件的上传和下载学习笔记第1张
浏览器效果如下:
day18_文件的上传和下载学习笔记第2张
示例代码如下:
day18_文件的上传和下载学习笔记第3张
multipart/form-data
  请求消息正文:
  服务器获取数据:request.getParameter(String) 方法获取指定的表单字段字符内容,但文件上传表单已经不再是字符内容,而是字节内容,所以失效,所以需要字节流的方式。
day18_文件的上传和下载学习笔记第4张
浏览器效果如下:
day18_文件的上传和下载学习笔记第5张
示例代码如下:
day18_文件的上传和下载学习笔记第6张
文件上传解析请求正文的每部分的内容。

2、借助第三方的上传组件实现文件上传

2.1、fileupload概述

fileupload是由apachecommons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()
使用步骤,导入:
commons-fileupload相关jar包
commons-fileupload.jar,核心包。
commons-io.jar,依赖包。

2.2、fileupload的核心类有

DiskFileItemFactory、ServletFileUpload、FileItem。
a、解析原理

day18_文件的上传和下载学习笔记第7张

2.3、fileupload简单应用

使用fileupload组件的步骤如下:
1、创建工厂类DiskFileItemFactory对象
  DiskFileItemFactory factory = new DiskFileItemFactory();
2、使用工厂创建解析器对象
  ServletFileUpload fileUpload = new ServletFileUpload(factory);
3、使用解析器来解析request对象
  List< FileItem > list = fileUpload.parseRequest(request);

FileItem对象对应一个`表单项(表单字段)`。可以是`文件字段``普通字段`

FileItem接口的方法:
boolean isFormField():判断当前表单字段是否为`普通文本字段`,如果返回false,则说明是`文件字段`
String getFieldName():获取`字段名称`,例如:< input type="text" name="username" />,返回的是username。
String getString():获取`字段的内容`,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件。
String getName():获取`文件字段的文件名称`(如:a.txt)。
String getContentType():获取上传的`文件的MIME类型`,例如:text/plain、image/pjpeg。
int getSize():获取上传`文件的大小`
InputStream getInputStream():获取上传文件对应的`输入流`
void write(File file):把上传的文件`保存`到指定文件中。
void delete();

3、文件上传时要考虑的几个问题(`经验分享`

a、保证服务器的安全

把保存上传文件的目录放在用户直接访问不到的地方。

day18_文件的上传和下载学习笔记第8张

b、避免文件被覆盖

让文件名唯一即可。

day18_文件的上传和下载学习笔记第9张

c、避免同一个文件夹中的文件过多

方案一:按照日期进行打散存储目录

day18_文件的上传和下载学习笔记第10张
方案二:用文件名的hashCode计算打散的存储目录:二级目录
day18_文件的上传和下载学习笔记第11张

d、限制文件的大小:web方式不适合上传大的文件

设置单个文件大小
  ServletFileUpload.setFileSizeMax(字节);
设置总文件大小:(多文件上传时)
  ServletFileUpload.setSizeMax(字节);

e、上传字段用户没有上传的问题

  通过判断文件名是否为空即可。

f、临时文件的问题

DiskFileItemFactory:作用:产生FileItem对象。
  DiskFileItemFactory内部有一个缓存,缓存大小默认是10Kb。如果上传的文件超过10Kb,就用磁盘作为缓存。
  存放缓存文件的目录在哪里?:默认是系统的临时目录。
FileItem.delete();
  FileItem.delete(); 如果自己用IO流实现的文件上传,则要在流关闭后,清理临时文件。
FileItem.write(File file);
  把上传的文件保存到指定文件,该方式会自动删除临时文件,注意:实际操作不能够自动删除临时文件(即:使用 FileItem自带的方法上传文件)。
完整的示例代码如下:

public class UploadServlet2 extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response
            throws ServletException, IOException 
{

        // request.setCharacterEncoding("UTF-8"); // 解决服务器接收请求的编码问题,该方式的优先级不高,不好!

        // 要执行文件上传的操作
        // 首先判断表单是否支持文件上传。即:判断 enctype="multipart/form-data"
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
        if (!isMultipartContent) {
            throw new RuntimeException("your form is not multipart/form-data");
        }

        // 创建一个DiskFileItemfactory工厂类对象
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setRepository(new File("e:\")); // 指定临时文件的存储目录

        // 使用工厂创建解析器ServletFileUpload核心对象
        ServletFileUpload sfu = new ServletFileUpload(factory);

        // 解决上传文件表单项出现乱码问题
        sfu.setHeaderEncoding("UTF-8");

        // 使用解析器来解析request对象 ,并得到一个表单项的List集合
        try {
            // 限制上传文件的大小
            // sfu.setFileSizeMax(1024 * 1024 * 3); // 表示3M大小的文件
            // sfu.setSizeMax(1024 * 1024 * 6); // 多个文件上传时
            List<FileItem> fileItems = sfu.parseRequest(request);

            // 遍历表单项数据
            for (FileItem fileitem : fileItems) {
                if (fileitem.isFormField()) {
                    // 文本表单项,普通文本字段(字符串)
                    processFormField(fileitem);
                } else {
                    // 文件表单项,文件字段(使用字节流读取)
                    processUploadField(fileitem);
                }
            }
        } catch (FileUploadBase.FileSizeLimitExceededException e) {
            // throw new RuntimeException("文件过大,不能超过3M");
            System.out.println("文件过大,不能超过3M"); // 自定义异常
        } catch (FileUploadBase.SizeLimitExceededException e) {
            System.out.println("总文件大小不能超过6M"); // 自定义异常
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
{
        doGet(request, response);
    }

    // 文本表单项,普通文本字段(字符串)
    private void processFormField(FileItem fileitem{
        try {
            String fieldname = fileitem.getFieldName(); // 获取字段名
            String fieldvalue = fileitem.getString("UTF-8"); // 获取字段值,并解决上传普通文本表单出的乱码问题

            // 解决上传普通文本表单项出现的乱码问题
            // fieldvalue = new String(fieldvalue.getBytes("iso-8859-1"), "utf-8");

            System.out.println(fieldname + "=" + fieldvalue);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    // 文件表单项,文件字段(使用字节流读取)
    private void processUploadField(FileItem fileitem{
        try {
            // 得到文件输入流
            InputStream is = fileitem.getInputStream();

            // 创建一个文件存盘的目录
            String directoryRealPath = this.getServletContext().getRealPath("/WEB-INF/upload");
            File storeDirectory = new File(directoryRealPath); // 既代表文件又代表目录
            if (!storeDirectory.exists()) {
                storeDirectory.mkdirs(); // 就创建一个指定的目录
            }

            // 获取文件字段的文件名称
            String filename = fileitem.getName(); // 文件项框中的值 F:图片素材小清新43.jpg 或者 43.jpg

            // 处理文件名问题 F:apache-tomcat-7.0.52webappsday18_00_upload uploadF:图片素材小清新43.jpg
            if (filename != null) {
                // filename = filename.substring(filename.lastIndexOf(File.separator) + 1);
                filename = FilenameUtils.getName(filename); // 效果同上
            }

            // 解决文件同名的问题
            filename = UUID.randomUUID() + "_" + filename;

            // 法一:按日期打散(创建)目录
            // String childDirectory = makeChildDirectory(storeDirectory); // 2015-10-19
            // 法二:用文件名的hashCode计算打散的存储目录:即二级目录
            String childDirectory = makeChildDirectory(storeDirectory, filename); // a/b

            // 在storeDirectory目录下,创建完整目录下的文件
            File file = new File(storeDirectory, childDirectory + File.separator + filename); // 绝对目录/日期目录/文件名
            // 通过文件输出流将上传的文件保存到服务器的磁盘
            FileOutputStream fos = new FileOutputStream(file);
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = is.read(b)) != -1) {
                fos.write(b, 0, len);
            }
            fos.close();
            is.close();
            fileitem.delete(); // 如果自己用IO流实现的文件上传,则要在流关闭后,清除临时文件

            // 把上传的文件保存到指定文件(使用 FileItem自带的方法上传文件)
            // fileitem.write(new File(storeDirectory, childDirectory + File.separator + filename));
            // fileitem.delete();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 用文件名的hashCode计算打散的存储目录:二级目录
    private String makeChildDirectory(File storeDirectory, String filename{
        int hashcode = filename.hashCode(); // 返回字符串转换的32位hashcode码
        System.out.println(hashcode);

        String code = Integer.toHexString(hashcode); // 把hashcode码转换为16进制的字符                    
        System.out.println(code); // 例如:abdsaf2131safsd

        String childDirectory = code.charAt(0) + File.separator + code.charAt(1); // 取前两位,作为二级目录,例如:a/b

        // 创建指定目录
        File file = new File(storeDirectory, childDirectory);
        if (!file.exists()) {
            file.mkdirs();
        }

        return childDirectory;
    }

    /*
    // 按日期打散目录
    private String makeChildDirectory(File storeDirectory) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String dateDirectory = sdf.format(new Date());

        // 创建指定目录
        File file = new File(storeDirectory, dateDirectory);
        if (!file.exists()) {
            file.mkdirs();
        }

        return dateDirectory;
    }*/

}

一次上传一个文件的form表单代码upload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form enctype="multipart/form-data"
        action="${pageContext.request.contextPath }/servlet/uploadServlet2"
        method="post">


        <input type="text" name="name" /><br /> 
        <input type="file" name="photo" /><br /> 
        <input type="submit" value="上传" /><br />
    </form>
</body>
</html>

浏览器效果如下:

day18_文件的上传和下载学习笔记第12张
一次上传两个文件的form表单代码upload2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form enctype="multipart/form-data"
        action="${pageContext.request.contextPath }/servlet/uploadServlet2"
        method="post">


        <input type="text" name="name" /><br /> 
        <input type="file" name="photo" /><br /> 
        <input type="file" name="photo" /><br /> 
        <input type="submit" value="上传" /><br />
    </form>
</body>
</html>

浏览器效果如下:

day18_文件的上传和下载学习笔记第13张
动态添加上传按钮上传多个文件upload3.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<script type="text/javascript">
    function addFile() {
        // 得到div容器
        var div1 = document.getElementById("div1");
        div1.innerHTML += "<div><input type='file' name='photo'/><input type='button' value='删除' onclick='delFile(this)'/></div>";
    }

    function delFile(input{
        // 使用input对象的爷爷删除他的爸爸
        input.parentNode.parentNode.removeChild(input.parentNode);
    }
</script>
<body>
    <form enctype="multipart/form-data"
        action="${pageContext.request.contextPath }/servlet/uploadServlet2"
        method="post">


        <input type="text" name="name" /><br />
            <div id="div1">
                <div>
                    <input type="file" name="photo"/>
                    <input type="button" value="添加" onclick="addFile()"/><br />
                </div>
            </div>
        <input type="submit" value="上传" /><br />
    </form>
</body>
</html>

浏览器效果如下:

day18_文件的上传和下载学习笔记第14张

4、文件的下载

注意:在web开发中,不适合大的数据下载,通过浏览器进行大的数据下载,不合适,此时需要借助下载软件进行下载,比如:迅雷、电驴、百度网盘等等。
web开发中,文件下载的应用场景是:从数据库表里面查找数据,动态的生成所需的文件。
文件下载的示例代码如下:

public class DownloadServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置一个要下载的文件
        String filename = "销售榜单.csv"// csv文件可以用excle打开

        // 设置文件名的编码
        if (request.getHeader("user-agent").toLowerCase().contains("msie")) {
            filename = URLEncoder.encode(filename, "UTF-8"); // 将不安全的文件名改为UTF-8格式
        } else {
            filename = new String(filename.getBytes("UTF-8"), "iso-8859-1"); // 火狐浏览器
        }

        // 告知浏览器要下载的文件
        response.setHeader("content-disposition""attachment;filename=" + filename);
        // response.setHeader("content-type", "image/jpeg");
        response.setContentType(this.getServletContext().getMimeType(filename)); // 告知浏览器根据文件名的后缀自动获得文件类型

        response.setCharacterEncoding("UTF-8"); // 设置服务器使用什么编码,去向外输出下面文件输出流的内容
        // 创建一个文件输出流
        PrintWriter out = response.getWriter();
        out.write("电视机, 20 "); // 以后连接数据库即可
        out.write("洗衣机, 10 ");
        out.write("冰箱, 8 ");
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

重构添加图书信息的示例代码:

public class AddBookServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 创建一个DiskFileItemFactory工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 创建一个ServletFileUpload对象
        ServletFileUpload sfu = new ServletFileUpload(factory);
        // 解决上传文件的乱码
        sfu.setHeaderEncoding("UTF-8"); 
        // 解析request对象,返回所有表单项
        List<FileItem> fileItems = new ArrayList<FileItem>(0);
        // 用于封装普通表单项的数据
        Map<String, String[]> map = new HashMap<String, String[]>();
        try {
            fileItems = sfu.parseRequest(request);

            // 迭代遍历fileItems表单项
            for (FileItem fileItem : fileItems) {
                if (fileItem.isFormField()) {
                    // 普通表单项
                    String name = fileItem.getFieldName(); // 得到字段名
                    String value = fileItem.getString("UTF-8"); // 得到字段值
                    map.put(name, new String[] { value }); // 向map中赋值
                } else {
                    // 文件表单项
                    InputStream inputStream = fileItem.getInputStream();
                    String filename = fileItem.getName(); // 得到上传的文件名
                    String extension = FilenameUtils.getExtension(filename);
                    if (!("jsp".equals(extension) || "exe".equals(extension))) { // 上传的文件不能是jsp、exe
                        // 创建一个文件存盘的目录
                        File storeDirectory = new File(this.getServletContext().getRealPath("/upload"));
                        if (!storeDirectory.exists()) {
                            storeDirectory.mkdirs(); // 如果该目录不存在,就创建
                        }
                        // 处理文件名问题
                        if (filename != null) {
                            filename = FilenameUtils.getName(filename);
                        }
                        // 目录打散
                        String childDirectory = makeChildDirectory(storeDirectory, filename); // a/b
                        // 把上传的文件保存到指定文件(使用 FileItem自带的方法上传文件)
                        filename = childDirectory + File.separator + filename;
                        fileItem.write(new File(storeDirectory, filename));
                        fileItem.delete(); // 删除临时文件
                    }
                    map.put(fileItem.getFieldName(), new String[] { filename }); // 将图片表单项的name和value保存到map中
                }
            }

            Book book = new Book();
            BeanUtils.populate(book, map);
            book.setId(UUIDUtil.getUUID()); // 设置图书编号

            // 调用业务逻辑
            BookServiceImpl bs = new BookServiceImpl();
            bs.addBook(book);

            // 分发转向
            // 不写/代表相对路径,相对于本类的路径
            request.getRequestDispatcher("bookListServlet").forward(request, response);
        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

//        request.setCharacterEncoding("UTF-8"); // 获取表单数据 
//        Book book = new Book();
//        try {
//            BeanUtils.populate(book, request.getParameterMap());
//            book.setId(UUIDUtil.getUUID()); // 手动生成一个随机ID
//        } catch (Exception e) {
//            e.printStackTrace();
//        } 
//    
//        // 调用业务逻辑 
//        BookServiceImpl bs = newBookServiceImpl();
//        bs.addBook(book);
//
//        // 分发转向 
//        // 不写/代表相对路径,相对于本类的路径
//        request.getRequestDispatcher("bookListServlet").forward(request, response);

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    // 目录打散
    private String makeChildDirectory(File storeDirectory, String filename) {
        int hashcode = filename.hashCode(); // 返回字符转换的32位hashcode码
        System.out.println(hashcode);
        String code = Integer.toHexString(hashcode); // 把hashcode转换为16进制的字符                                         
        System.out.println(code); // abdsaf2131safsd
        String childDirectory = code.charAt(0) + File.separator + code.charAt(1); // a/b
        // 创建指定目录
        File file = new File(storeDirectory, childDirectory);
        if (!file.exists()) {
            file.mkdirs();
        }
        return childDirectory;
    }

}

product_list.jsp

<div class="divbookpic">
    <p>
        <a href="#"><img src="${pageContext.request.contextPath}/upload/${b.img_url}" width="115" height="129" border="0" /> </a>
    </p>
</div>

免责声明:文章转载自《day18_文件的上传和下载学习笔记》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用nvmw解决windows下多版本node共存的问题svn 提交 commit慢下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

eltable数据遍历结合elform校验

需要实现的效果 最新遇到一个需求,数据在table中遍历展示,且需要校验每一项数据的格式,而且表头数据需要增加必填项*标示。 这里的校验和平时的校验不一样的是此处的数据是循环遍历展示的,因此要注意 prop的值为`bindList[${scope.$index}].nickName` :model="bindForm"中bindForm的数据类型,...

Linux Tomcat部署常用命令

Linux Tomcat部署常用命令 1、连接服务器 2、进入webapps目录: cd /usr/local/tomcat8080/webapps/ 3、上传文件(war包等):rz 4、删除文件夹及文件:rm -rf aa.war 5、回上级目录:cd../ 6、查看当前目录 ls 或者ll 7、查看tomcat控制台:tail -f /usr/loc...

linux(centos7) 常用命令和快捷键 持续验证更新中...

1、文件和目录cd 进入目录示例:cd /home 进入home目录    cd..    返回上一级目录cd../..    返回上两级目录cd -    返回上次所在目录cd ~    返回根目录cpcp file1 file1    文件复制cp-acp-a dir1 dir2    复制一个目录cp -a /tmp/dir1 .     复制一个目...

在 Vue 项目中(vue-cli2,vue-cli3)使用 pug 简化 HTML 的编写

使用 pug 的原因: 使得 HTML 写起了来更加清晰和快捷 用法: Vue 的用法没有变化: <template lang="pug"> transition(name="sider") div.hello h3 {{msg}} p(:style="{color:'#000'}", :htmlData="...

VSCode编辑器使用技巧:快捷输入HTML代码(转)

原文:https://www.cnblogs.com/kharvey/p/12990787.html VSCode中有一些快捷编辑HTML的方法,能大大提高工作效率,在这记录一些。 1.输入html:5,然后按tab键或enter键,效果如下: 1 <!-- 输入html或者html:5生成页面模板 --> 2 <!DOCTYPE h...

apache配置禁止访问某些文件/目录

 我们来看俩段通常对上传目录设置无权限的列子,配置如下: 代码如下: ? 1 2 3 4 5 6 <Directory "/var/www/upload"> <FilesMatch ".php"> Order Allow,Deny Deny from all </FilesMatch> </Dir...