• Java web文件上传下载



    [版权申明:本文系作者原创,转载请注明出处]
    文章出处:http://blog.csdn.net/sdksdk0/article/details/52048666
    作者:朱培 ID:sdksdk0 邮箱: zhupei@tianfang1314.cn


    本文主要从javaweb上传文件到服务器中,并且在服务器端进行数据文件存储,主要分享了文件上传原理、使用第三方开源工具进行上传以及一些文件上传时需要注意的地方,文件的优化处理,还有简易分享了从我们刚才上传进去的文件进行下载。需要掌握基本的开发流程,底层实现原理等。

    一、文件上传原理

    • 提供form表单,method必须是post
    • form表单的enctype必须是multipart/form-data
    • 提供input type=”file”

    Enctype属性

    告知服务器请求正文的MIME类型。
    这里写图片描述
    application/x-www-form-urlencoded(默认):
    正文:name=aa&password=123
    服务器获取数据:request.getParameter(“name”);

    文件上传原理:

    解析请求正文的每部分的内容。

    基于html form表单上传的数据都是以类似—————————–7da3c8e180752{0x130x10}这样的分割符来标记一块数据的起止。
    这里写图片描述

    文件上传的Content-Type为multipart/form-data; boundary=—-WebKitFormBoundaryhQslmBE7nbTLTJzD,而普通的form表单的Content-Type为application/x-www-form-urlencoded。因此,我们可以利用HttpServletRequest的request.getHeaderNames()方法和request.getHeaders(headName)方法得到请求头Headers中的Content-Type数据,然后根据Content-Type数据中是否包含multipart/form-data来区分请求是否为文件上传请求。其中boundary为文件数据的分隔符,用于区分上传多个文件。

    二、使用第三方工具实现文件上传

    fileupload组件工作流程:

    这里写图片描述

    开发步骤

    导入commons-fileupload.jar、commons-io.jar包。
    1、界面
    我们就是需要一个form表单,为其添加enctype属性和post方法:

         <form action="${pageContext.request.contextPath}/servlet/UploadServlet2"  method="post"  enctype="multipart/form-data" >
            姓名: <input  type="text"  name="name" /><br />
            照片: <input  type="file"  name="photo" /><br />
    
                <input type="submit"  value="提交" />
        </form>

    2、逻辑处理
    我们在一个servlet中进行处理:

    request.setCharacterEncoding("UTF-8");
            //判断用户的请求内容是不是multipart/form-data
            boolean isMultipart=ServletFileUpload.isMultipartContent(request);
            if(!isMultipart){
                throw new RuntimeException("error!");
            }
    
    

    //创建DiskFileItemFactory对象
    DiskFileItemFactory factory=new DiskFileItemFactory();

    
            //创建核心解析类ServlertFileUpload
            ServletFileUpload  sfu=new ServletFileUpload(factory);
    
            //解析请求对象
            List<FileItem> items=new ArrayList<FileItem>(0);
    
            try {
                items=sfu.parseRequest(request);
            } catch (FileUploadException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for(FileItem item:items){
                if(item.isFormField()){
                    processFormField(item);
                }else{
                    processUploadField(item);
                }
            }
            response.getWriter().write("sucess!");
    

    接下来就是分为两种情况了,一种是对普通的表单元素进行处理,另一种是对文件类的数据进行处理,对于第一种情况的话就比较简单,我们直接获取名字就可以了,基本上不用过多的处理。

    private void processFormField(FileItem item) {
            String fieldName=item.getFieldName();
            String fieldValue=item.getString();
            System.out.println(fieldValue+"="+fieldName);
    
        }

    对于第二种情况,需要我们直接对上传文件进行一系列的处理了,我们首先需要在服务器上找一个存放文件的地方,然后截取上传的文件名、构建输出流、关闭流等操作。

    private void processUploadField(FileItem item) {
    
            try {
                InputStream in=item.getInputStream();
                String filename=item.getName();
    
                //在服务器上找一个存放文件的地方
                String storeDirectoryRealPath=getServletContext().getRealPath("/WEB-INF/files");
                File storeDirectory=new File(storeDirectoryRealPath);
    
                if(!storeDirectory.exists()){
                    storeDirectory.mkdirs();
                }
    
                //截取上传的文件名
                //filename=filename.substring(filename.lastIndexOf(File.separator)+1);
    
                if(filename!=null){
                    filename=FilenameUtils.getName(filename);
                }
    
                String guidFilename=GUIDUtil.generateGUID()+"_"+filename;
    
    
                //按日期来区分存储目录
            //  String childDirectory=makeChileDirectory(storeDirectory);
    
                String childDirectory=makeChildDirectory(storeDirectory,guidFilename);
    
                //构建输出流
                OutputStream  out=new FileOutputStream(new File(storeDirectory,childDirectory+File.separator+guidFilename));
    
                int len = -1;
                byte buf[] = new byte[1024];
                while((len=in.read(buf))!=-1){
                    out.write(buf, 0, len);
                }
                in.close();
                out.close();
    
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    三、文件上传优化处理

    1、把保存的文件放在用户无法直接访问到的地方:例如放在:在WEB-INF/files目录中。

       String storeDirectoryRealPath=getServletContext().getRealPath("/WEB-INF/files");

    2、让文件名唯一。

        String guidFilename=GUIDUtil.generateGUID()+"_"+filename;
                //构建输出流
                OutputStream  out=new FileOutputStream(new File(storeDirectory,guidFilename));

    3、避免同一个文件夹中的文件过多。
    3.1按照日期进行存储。

        String childDirectory=makeChileDirectory(storeDirectory);
    
        private String makeChileDirectory(File storeDirectory) {
            Date now=new Date();
            DateFormat df=new SimpleDateFormat("yyyy-MM-dd");
            String sdate=df.format(now);
            File f=new File(storeDirectory,sdate);
            if(!f.exists()){
                f.mkdirs();
            }
            return sdate;
        }

    3.2用文件名的hashCode计算需要进行存储的目录,二级目录。

        private String makeChildDirectory(File storeDirectory, String guidFilename) {
            int hashCode = guidFilename.hashCode();
            int dir1 = hashCode&0xf;//  0~15
            int dir2 = (hashCode&0xf0)>>4;//0~15
    
            String s = dir1+File.separator+dir2;
    
            File f = new File(storeDirectory,s);
            if(!f.exists()){
                f.mkdirs();
            }
            return s;
        }

    4、限制文件的大小。web方式不适合上传大的文件。
    4.1单个文件大小:

    ServletFileUpload  sfu=new ServletFileUpload(factory);
            sfu.setFileSizeMax(4*1024*1024);//限制不超过4M

    4.2总文件大小:多文件上传

        ServletFileUpload  sfu=new ServletFileUpload(factory);
        sfu.setSizeMax(8*1024*1024);//总文件大小

    5、限制文件的上传类型。
    5.1通过文件扩展名来进行限制。

      String extensionName=FilenameUtils.getExtension(filename);

    5.2通过文件MIME类型来限制。

    
        String mimeType=item.getContentType();

    6、空文件上传解决方案。
    判断文件名是否为空,当文件名为空时return。

    7、临时文件
    DiskFileItemFactory的作用是产生FileItem对象。其内部有一个缓存,默认大写拾10kb,如果上传文件超过10kb,则用磁盘作为缓存。存放缓存的目录默认是系统的临时目录。

    DiskFileItemFactory factory=new DiskFileItemFactory();
            //更改临时文件的存放目录
            factory.setRepository(new File("D:/"));

    如果是自己用IO流实现的文件上传,则需要在流关闭后,清理临时文件。

        FileItem.delete();

    可以使用FileItem.write(File f)实现文件上传的保存。

    8、中文编码

        request.setCharacterEncoding("UTF-8");
    
        //该编码要和jsp页面保持一致
        String fieldValue=item.getString("UTF-8");

    9、动态js控制上传框

       <form action="${pageContext.request.contextPath}/servlet/UploadServlet3" method="post" enctype="multipart/form-data">
            name:<input type="text" name="name"/><br/>
            <div id="d1">
                <div>
                photo:<input type="file" name="photo"/><input type="button" value="继续上传" onclick="addFile()"/>
                </div>
            </div>
            <input type="submit" value="上传"/>
        </form>
        <script type="text/javascript">
            function addFile(){
                var d1 = document.getElementById("d1");
                var oldInnerHtml = d1.innerHTML;
                d1.innerHTML=oldInnerHtml+"<div>photo:<input type='file' name='photo'/><input type='button' value='删除' onclick='deleteOne(this)'/></div>";
            }
            function deleteOne(delBtn){
                delBtn.parentNode.parentNode.removeChild(delBtn.parentNode);
            }
        </script>

    这里写图片描述

    四、文件的下载

    首先我们来看一下页面的处理:
    新建一个list.jsp文件:

      全部资源如下:<br />
    
       <c:forEach items="${map}" var="me">
            <c:url value="/servlet/DownLoadServlet" var="url">
                <c:param name="filename" value="${me.key}"></c:param>
            </c:url>
            ${me.value}&nbsp;&nbsp;<a href="${url}">下载</a><br/>
        </c:forEach>

    要记得引入jstl的核心包 。

    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

    接下做界面显示的servlet。
    我们之前上传的文件是用GUID做过相应处理的,是一个拼接好的文件名,用来防止文件同名的情况发生,这里用户浏览到的文件当然要和上传的时候的文件名 相同,所以这里我们要进行截取,把GUID拼接的前缀去掉,以“_”分开。一个是在服务器中防同名的文件名以及显示出来的截取后的文件名。这里以前面说过的,防同名文件方法2:用文件名的hashCode计算需要进行存储的目录,二级目录。这里也就需要使用hashCode找到做过文件的路径。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //key:GUID文件名  value:old文件名
            Map<String, String> map = new HashMap<String, String>();
            //获取/WEB-INF/files的真实路径
            String rootDirectoryRealPath = getServletContext().getRealPath("/WEB-INF/files");
            //递归遍历找出所有的文件
            System.out.println(rootDirectoryRealPath);
            File rootDirectory = new File(rootDirectoryRealPath);
            treeWalk(rootDirectory,map);
            //存到请求范围中,转发给jsp显示
            request.setAttribute("map", map);
            request.getRequestDispatcher("/list.jsp").forward(request, response);
        }
        //递归遍历找出所有的文件,把文件名高出来
        public void treeWalk(File file, Map<String, String> map) {
            if(file.isFile()){
                String guidFileName = file.getName();
                String oldFileName = guidFileName.substring(guidFileName.indexOf("_")+1);
                map.put(guidFileName, oldFileName);
            }else{
                //目录
                File[] childFiles = file.listFiles();
                for(File f:childFiles){
                    treeWalk(f, map);
                }
            }
        }

    接下来就是下载的处理了。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            String guidFilename = request.getParameter("filename");//get方式提交的
            guidFilename = new String(guidFilename.getBytes("ISO-8859-1"),"UTF-8");
            //计算存放路径
            File storeDirectory = new File(getServletContext().getRealPath("/WEB-INF/files"));
            String childDirectory = makeChildDirecotry(storeDirectory, guidFilename);//  13/1
            //构建输入流
            InputStream in = new FileInputStream(new File(storeDirectory,childDirectory+File.separator+guidFilename));
    
            //用响应对象的输出流输出:下载的方式
            String oldFileName = guidFilename.substring(guidFilename.indexOf("_")+1);
            response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(oldFileName,"UTF-8"));//不适用火狐
            response.setContentType("application/octet-stream");
            OutputStream out = response.getOutputStream();
    
            int len = -1;
            byte buf[] = new byte[1024];
            while((len=in.read(buf))!=-1){
                out.write(buf, 0, len);
            }
            in.close();
    
    
        }
        private String makeChildDirecotry(File storeDirectory, String guidFilename) {
    
            int hashCode = guidFilename.hashCode();
            int dir1 = hashCode&0xf;
            int dir2 = (hashCode&0xf0)>>4;
    
            String s = dir1+File.separator+dir2;
    
            File f = new File(storeDirectory,s);
            if(!f.exists()){
                f.mkdirs();
            }
            return s;
        }

    通过这个网址进行访问

    http://localhost:8080/UploadDemo/servlet/ShowAllFilesServlet

    这里写图片描述

    源码下载:https://github.com/sdksdk0/UploadDemo

  • 相关阅读:
    暑假集训单切赛第二场 UVA 10982 Troublemakers
    暑假集训单切赛第一场 POJ 2309 BST(找规律的题)
    暑假集训单切赛第一场 CF 191A Dynasty Puzzles
    暑假集训单切赛第一场 CF 266E More Queries to Array(线段树+二项式展开式)
    暑假集训单切赛第一场 UVA 1737 Mnemonics and Palindromes 3
    大一暑假集训第六周第一场单切赛
    POJ 1486 Sorting Slides(寻找必须边)
    【机器学习】梯度下降法的相关介绍
    Linux下使用Tmux提高终端环境下的效率
    Fedora23安装以后要做的优化配置
  • 原文地址:https://www.cnblogs.com/sdksdk0/p/6060035.html
Copyright © 2020-2023  润新知