Web筆記-上傳下載

文件的上傳
  1. 文件上傳的條件和原理

文件上傳頁面書寫必須符合以下條件:

  • Form表單的請(qǐng)求方式必須為post
  • Form表單中提供type="file"類型的上傳輸入框
  • Form表單enctype屬性必須是multipart/form-data,enctype默認(rèn)值為application/x-www-urlencoded

文件上傳原理:
使用request.getInputStream();通過流來取得用戶上傳的數(shù)據(jù),關(guān)鍵問題是流的解析


圖1
圖2

2、借助三方組件實(shí)現(xiàn)文件上傳
使用Apache提供的commons-fileupload組件進(jìn)行上傳功能開發(fā)(內(nèi)部核心封裝的為文件流解析方法)
commons-io為fileupload組件在1.1開始就依賴的jar包,用于輔助文件的io操作

示例代碼:

package com.gaojinze.web.upload.servlet;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
/**
 * 使用第三方組件進(jìn)行上傳
 * apache的
 * commons-fileupload.jar:核心內(nèi)容解析請(qǐng)求正文實(shí)現(xiàn)上傳
 * commons-io.jar:是apache對(duì)jdk對(duì)java.io.*對(duì)擴(kuò)展和增強(qiáng),fileupload從1.1就依賴該包
 * @author gaopengfei
 *
 */
public class UploadServlet3 extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    public UploadServlet3() {
        super();
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        //檢驗(yàn)表單enctype屬性
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
        if (!isMultipartContent) {
            throw new RuntimeException("Please check your form enctype attribute,the value msut be multipart/form-data");
        }
        
        //創(chuàng)建核心解析器實(shí)例
        DiskFileItemFactory factory = new DiskFileItemFactory();//創(chuàng)建工廠實(shí)例,該工廠可以創(chuàng)建FileItem對(duì)象
        ServletFileUpload upload = new ServletFileUpload(factory);//利用工廠創(chuàng)建解析器實(shí)例
        upload.setFileSizeMax(4 * 1024 * 1024);//設(shè)置單個(gè)文件的上傳大小為4M
        upload.setSizeMax(8 * 1024 * 1024);//設(shè)置總上傳文件的大小為8M
        
        List<FileItem> fileItems = new ArrayList<>();
        try {
            fileItems = upload.parseRequest(request);
        } catch (FileUploadBase.FileSizeLimitExceededException e) {
            response.getWriter().write("單個(gè)文件上傳大小不得超過4M");
        }catch (FileUploadBase.SizeLimitExceededException e) {
            
            response.getWriter().write("多文件上傳總大小不得超過8M");
        }catch (FileUploadException e) {
            e.printStackTrace();
        }
        for (FileItem fileItem : fileItems) {
            
            if (fileItem.isFormField()) {
                //非上傳字段
                processFormField(fileItem);
            }else {
                //上傳字段
                processUploadField(fileItem);
            }
        }
        response.getWriter().write("上傳成功!");
        response.setHeader("refresh", "1;url=" + request.getContextPath() + "/upload.jsp");
//      response.sendRedirect(request.getContextPath() + "/upload.jsp");
    }
    /**
     * 處理非上傳字段
     * @param fileItem
     */
    private void processFormField(FileItem fileItem) {
    
        String fieldName = fileItem.getFieldName();
        String fieldValue = null;
        try {
            fieldValue = fileItem.getString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(fieldName + ":" + fieldValue);
    }
    /**
     * 處理上傳文件
     * @param fileItem
     */
    private void processUploadField(FileItem fileItem) {
        
        try {
            
            //獲取文件名(因?yàn)闉g覽器不同導(dǎo)致在選擇完文件后的name也不同,如IE選擇后,文件名為:D:\XXX\a.txt)
            String fileName = fileItem.getName();
            //判斷是否有未選擇的空項(xiàng)
            if ("".equals(fileName) || null == fileName) {
                return;
            }
            //通過擴(kuò)展名來限制上傳文件類型
            String extension = FilenameUtils.getExtension(fileName);
            //獲取上傳文件的MIME類型
            String contentType = fileItem.getContentType();
            if (!extension.equalsIgnoreCase("jpg"))
                return;
            if (!contentType.startsWith("image")) {
                return;
            }
            fileName = FilenameUtils.getName(fileName);
            String uuidFileName = UUID.randomUUID().toString() + "_" + fileName;
            //得到存放文件的真實(shí)路徑
            String uploadBasePath = getServletContext().getRealPath("/WEB-INF/files");
//          String childUploadDir = getChildUploadDir(uploadBasePath);
            String childUploadDir = getChildUploadDir2(uploadBasePath, uuidFileName);
            //保存文件,并清理緩存
            fileItem.write(new File(uploadBasePath + File.separator +childUploadDir, uuidFileName));
        } catch (Exception e) {
            throw new RuntimeException("上傳失敗!");
        }
    }
    /**
     * 按照日期進(jìn)行子目錄創(chuàng)建
     * @param uploadPath
     * @return
     */
    private String getChildUploadDir(String uploadBasePath) {
        
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String childUploadDir = dateFormat.format(date);
        File childDir = new File(uploadBasePath, childUploadDir);
        if (!childDir.exists()) {
            //若目錄不存在則創(chuàng)建
            childDir.mkdirs();
        }
        return childUploadDir;
    }
    
    /**
     * 通過uuid的hascode分散目錄
     * @param uploadBasePath
     * @param uuidFileName
     * @return
     */
    private String getChildUploadDir2(String uploadBasePath, String uuidFileName){
        
        int hashCode = uuidFileName.hashCode();
        //作為一級(jí)目錄名
        int dir1 = hashCode&0xf;
        //作為二級(jí)目錄名
        int dir2 = (hashCode&0xf0)>>4;
        String childUploadDir = dir1 + File.separator + dir2;
        File childDir = new File(uploadBasePath, childUploadDir);
        if (!childDir.exists()) {
            //若目錄不存在則創(chuàng)建
            childDir.mkdirs();
        }
        return childUploadDir;
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

3、三方組件核心類介紹以及實(shí)際開發(fā)中需要注意的細(xì)節(jié)

  • 如何保證服務(wù)器安全?
    將文件上傳目錄放置在WEB-INF目錄下即可
  • 如何避免文件重名而導(dǎo)致上傳后文件覆蓋?
    創(chuàng)建唯一文件名,使用UUID.randomUUID().toString() + "_" + fileName;即可
  • 如何避免同一個(gè)目錄文件過多而查找難?
    解決方法:
    a)以日期分類目錄
/**
     * 按照日期進(jìn)行子目錄創(chuàng)建
     * @param uploadPath
     * @return
     */
    private String getChildUploadDir(String uploadBasePath) {
        
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String childUploadDir = dateFormat.format(date);
        File childDir = new File(uploadBasePath, childUploadDir);
        if (!childDir.exists()) {
            //若目錄不存在則創(chuàng)建
            childDir.mkdirs();
        }
        return childUploadDir;
    }

b)利用UUID文件名的hash碼進(jìn)行分散存儲(chǔ)

/**
 * 通過uuid的hascode分散目錄
 * @param uploadBasePath
 * @param uuidFileName
 * @return
 */
private String getChildUploadDir2(String uploadBasePath, String uuidFileName){
        
    int hashCode = uuidFileName.hashCode();
    //作為一級(jí)目錄名
    int dir1 = hashCode&0xf;
    //作為二級(jí)目錄名
    int dir2 = (hashCode&0xf0)>>4;
    String childUploadDir = dir1 + File.separator + dir2;
    File childDir = new File(uploadBasePath, childUploadDir);
    if (!childDir.exists()) {
        //若目錄不存在則創(chuàng)建
        childDir.mkdirs();
    }
    return childUploadDir;
}
hashcode
  1. 文件的限制
  • 限制文件上傳大小
    Web上傳不適合上傳太大的文件,一般為2M
/創(chuàng)建核心解析器實(shí)例
DiskFileItemFactory factory = new DiskFileItemFactory();//創(chuàng)建工廠實(shí)例,該工廠可以創(chuàng)建FileItem對(duì)象
ServletFileUpload upload = new ServletFileUpload(factory);//利用工廠創(chuàng)建解析器實(shí)例
upload.setFileSizeMax(4 * 1024 * 1024);//設(shè)置單個(gè)文件的上傳大小為4M
upload.setSizeMax(8 * 1024 * 1024);//設(shè)置總上傳文件的大小為8M
  • 限制上傳文件的類型
    通過MIME類型
    操作系統(tǒng)是根據(jù)擴(kuò)展名來區(qū)分文件類型的
    信息傳輸?shù)臄?shù)據(jù)通過MIME來區(qū)分類型,詳細(xì)的對(duì)應(yīng)關(guān)系,可以查看tomcat目錄下的web.xml有記錄
//通過擴(kuò)展名來限制上傳文件類型,該方式不靠譜
String extension = FilenameUtils.getExtension(fileName);
if (!extension.equalsIgnoreCase("jpg")) {
    return;
}
------------------------------------------------------------
//獲取上傳文件的MIME類型
String contentType = fileItem.getContentType();
if (!extension.equalsIgnoreCase("jpg"))
    return;
if (!contentType.startsWith("image")) {
    return;
}
------------------------------------------------------------
//判斷是否有未選擇的空項(xiàng)
if ("".equals(fileName) || null == fileName) {
    return;
}
  1. 上傳時(shí)的臨時(shí)文件處理
    DiskFileItemFactory創(chuàng)建FileItem對(duì)象時(shí)會(huì)使用緩存,默認(rèn)10kb,若上傳文件大小超過10kb,則會(huì)采用磁盤進(jìn)行緩存(磁盤緩存即為垃圾文件
//創(chuàng)建核心解析器實(shí)例
DiskFileItemFactory factory = new DiskFileItemFactory();//創(chuàng)建工廠實(shí)例,該工廠可以創(chuàng)建FileItem對(duì)象
//設(shè)置緩存文件大小
factory.setSizeThreshold(100 * 1024);
//設(shè)置緩存文件的存放目錄,默認(rèn)是系統(tǒng)的臨時(shí)文件目錄
factory.setRepository(new File("f:/"));

若使用原生io流進(jìn)行文件讀寫存儲(chǔ),則在關(guān)閉流后使用fileItem.delete();方法進(jìn)行緩存清理
若使用fileItem.write();進(jìn)行讀寫存儲(chǔ),則無需處理,其內(nèi)部已經(jīng)進(jìn)行流緩存清理

  1. 中文亂碼問題
  • 普通字段中文亂碼解決
    String fieldValue = fileItem.getString("UTF-8");
  • 中文文件名亂碼解決
    request.setCharacterEncoding("UTF-8");

文件的下載
  • FilelistServlet(文件列表獲?。?/li>
package com.gaojinze.web.upload.servlet.download;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 文件列表Servlet
 * @author gaopengfei
 */
public class FilelistServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    public FilelistServlet() {
        super();
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        //創(chuàng)建存儲(chǔ)key為文件在服務(wù)器中的名稱,value為用戶上傳文件名
        Map<String, String> fileMap = new HashMap<>();
        
        String fileBasePath = getServletContext().getRealPath("/WEB-INF/files");
        File uploadRootDir = new File(fileBasePath);
        findAllFiles(uploadRootDir, fileMap);
        request.setAttribute("fileMap", fileMap);
        request.getRequestDispatcher("/filelist.jsp").forward(request, response);
    }
    /**
     * 遍歷服務(wù)器所有文件并將文件信息存儲(chǔ)到map中
     * @param file
     * @param fileMap
     */
    private void findAllFiles(File file, Map<String, String> fileMap) {
        
        if (file.isFile()) {
            
            String fileUUIDName = file.getName();
            String fileRealName = fileUUIDName.substring(fileUUIDName.indexOf("_") + 1);
            fileMap.put(fileUUIDName, fileRealName);
        }else {
            
            //如果是目錄則遞歸進(jìn)行遍歷
            File[] listFiles = file.listFiles();
            if (listFiles == null || listFiles.length == 0) {
                return;
            }
            for (File f : listFiles) {
                findAllFiles(f, fileMap);
            }
        }
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
  • filelist.jsp文件列表展示頁
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!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>文件列表</title>
</head>
<body>
    <h3 align="center">文件列表</h3>
    <table align="center" border="1">
        <tr>
            <th>文件名</th>
            <th>操作</th>
        </tr>
        <c:forEach items="${ fileMap }" var="mk">
            <!-- 以下標(biāo)簽用于構(gòu)建一個(gè)URL /Web-Upload/servlet/Download?filename=經(jīng)過url編碼的中文 -->
            <c:url var="url" value="/servlet/DownloadServlet">
                <c:param name="filename" value="${ mk.key }"></c:param>
            </c:url>
            <tr>
                <td>${ mk.value }</td>
                <!-- 中文名跟在URL后需要進(jìn)行URL編碼 -->
                <!-- ${pageContext.request.contextPath }"/servlet/DownloadServlet?filename=${ mk.key } -->
                <td><a href="${ url }">下載</a></td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>
  • DownloadServlet(文件下載)
package com.gaoshiyi.web.upload.servlet.download;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 文件下載
 * @author gaopengfei
 *
 */
public class DownloadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public DownloadServlet() {
        super();
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        String uuidName = request.getParameter("filename");
        String realName = uuidName.substring(uuidName.indexOf("_") + 1);
        // 上傳文件跟目錄
        String uploadRootPath = getServletContext().getRealPath("/WEB-INF/files");
        // 獲取對(duì)應(yīng)uuidname文件的子目錄
        String childDirPath = getChildDirPathByUUIDName(uploadRootPath, uuidName);
        // 構(gòu)建文件讀取流
        InputStream in = new FileInputStream(new File(uploadRootPath + File.separator + childDirPath, uuidName));
        //處理下載時(shí)的文件名稱中文問題
        String userAgent = request.getHeader("User-Agent");
        if (userAgent.contains("Firefox")) {
            //單獨(dú)處理火狐瀏覽器
            realName = new String(realName.getBytes("UTF-8"), "ISO-8859-1");
        }else {
            //其他瀏覽器
            realName = URLEncoder.encode(realName, "UTF-8");
        }
        // 告知瀏覽器以下載方式下載
        response.setHeader("Content-Disposition", "attachment;filename=" + realName);
        // 設(shè)置頭信息告知客戶端文件大小 
        response.setContentLength(in.available());
        // 設(shè)置頭信息告知客戶端文件MIME類型 
        response.setHeader("Content-Type", "application/octet-stream");
        // 輸出
        OutputStream out = response.getOutputStream();
        int len = 0;
        byte[] buff = new byte[1024];
        while ((len = in.read(buff)) != 1) {
            out.write(buff, 0, len);
        }
        in.close();
        out.close();
    }
    /**
     * 獲取子目錄
     * 
     * @param uploadRootPath
     * @param uuidName
     * @return
     */
    private String getChildDirPathByUUIDName(String uploadRootPath, String uuidName) {
        int hashCode = uuidName.hashCode();
        int dir1 = hashCode&0xf;
        int dir2 = (hashCode&0xf0) >> 4;
        String cDir = dir1 + File.separator + dir2;
        File childDir = new File(uploadRootPath, cDir);
        if (!childDir.exists()) {
            childDir.mkdirs();
        }
        return cDir;
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文包括:1、文件上傳概述2、利用 Commons-fileupload 組件實(shí)現(xiàn)文件上傳3、核心API——Dis...
    廖少少閱讀 12,743評(píng)論 5 91
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 一、文件上傳概述 實(shí)現(xiàn)web開發(fā)中的文件上傳功能,需完成如下二步操作在web頁面中添加上傳輸入項(xiàng)在servlet中...
    yjaal閱讀 3,004評(píng)論 0 22
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,893評(píng)論 25 709
  • 浣溪沙·鳳棲花 霜夜聆香興味稠,癡心盡被陸花收。蒼山泠水意悠悠。 一指靈犀寰內(nèi)懼,雙棲百萼世外游。得友若此幾世修?...
    薄小荷閱讀 962評(píng)論 26 28

友情鏈接更多精彩內(nèi)容