首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Commons FileUpload 文件上傳功能實作與模態視窗整合

     編輯:Java 相關知識

專案場景

圖書館後台管理系統需實現書籍封面圖片上傳功能:預設顯示「暫無圖片」佔位圖,點擊「上傳圖片」按鈕彈出模態視窗,選擇圖片上傳後自動更新預覽圖並回填隱藏欄位供表單提交。

架構設計

  • 前端:JSP + JavaScript (showModalDialog 模態視窗)
  • 後端:Servlet + Commons FileUpload
  • 儲存:伺服器本地磁碟 (生產環境建議對象儲存)
  • 資料流:上傳成功 → 回傳相對路徑 → 父視窗更新 img src 與 hidden value

核心代碼實作

1. 書籍新增頁面 (bookAdd.jsp)

<%@ page language="java" pageEncoding="UTF-8"%>
<% String root = request.getContextPath(); %>
<!DOCTYPE html>
<html>
<head>
    <title>新增書籍</title>
    <meta charset="UTF-8">
    <script>
        // 開啟上傳模態視窗
        function openUploadDialog() {
            // showModalDialog 僅 IE 支援,現代瀏覽器建議改用 window.open + postMessage 或 Bootstrap Modal
            var returnVal = window.showModalDialog(
                "<%=root%>/admin/bookUpload.jsp", 
                "", 
                "dialogWidth:400px;dialogHeight:300px;scroll:no;status:no"
            );
            
            if (returnVal != null) {
                // 回填隱藏欄位 (供表單提交存入資料庫)
                document.getElementById("photoPath").value = returnVal;
                // 更新預覽圖
                document.getElementById("previewImg").src = "<%=root%>/" + returnVal;
            }
        }
    </script>
</head>
<body>
    <h4>新增書籍資料</h4>
    <hr/>
    <form action="<%=root%>/BookServlet?type=add" method="post">
        <div>
            <label>書名:<input type="text" name="bookName" required/></label>
        </div>
        <div>
            <label>作者:<input type="text" name="author" /></label>
        </div>
        <div>
            <label>封面圖片:</label>
            <input type="hidden" id="photoPath" name="coverPath" value="images/noimg.png"/>
            <input type="button" value="上傳圖片" onclick="openUploadDialog()"/>
            <br/>
            <img id="previewImg" src="<%=root%>/images/noimg.png" width="200" height="200" style="border:1px solid #ccc;"/>
        </div>
        <div>
            <input type="submit" value="儲存書籍"/>
        </div>
    </form>
</body>
</html>

2. 上傳模態視窗頁面 (bookUpload.jsp)

<%@ page language="java" pageEncoding="UTF-8"%>
<% String root = request.getContextPath(); %>
<!DOCTYPE html>
<html>
<head>
    <title>圖片上傳</title>
    <meta charset="UTF-8">
    <base target="_self"> <!-- 關鍵:確保表單提交在模態視窗內進行 -->
</head>
<body>
    <h5>上傳書籍封面</h5>
    <hr/>
    <% if (request.getAttribute("errorMsg") != null) { %>
        <p style="color:red;">${requestScope.errorMsg}</p>
    <% } %>
    <form action="<%=root%>/BookServlet?type=bookUpload" method="post" enctype="multipart/form-data">
        <div>注意:圖片大小不得超過 3MB!</div>
        <div><input type="file" name="fileUpload" accept="image/*" required/></div>
        <div><input type="submit" value="上傳"/></div>
    </form>
</body>
</html>

3. 核心處理 Servlet (BookServlet.java 關鍵片段)

package com.library.servlet;

import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class BookServlet extends HttpServlet {
    // 配置常數
    private static final String UPLOAD_DIR = "eshop/upload/";      // 正式儲存目錄
    private static final String TEMP_DIR = "eshop/upload_temp/";   // 暫存目錄
    private static final int MAX_SIZE = 3;                         // MB
    private static final String[] ALLOWED_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".ico"};
    
    private String serverRealPath;

    @Override
    public void init() throws ServletException {
        serverRealPath = getServletContext().getRealPath("/").replace("\\", "/");
        // 初始化建立目錄
        new File(serverRealPath + UPLOAD_DIR).mkdirs();
        new File(serverRealPath + TEMP_DIR).mkdirs();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        String type = request.getParameter("type");
        if (!"bookUpload".equals(type)) {
            // 其他業務邏輯...
            return;
        }

        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        // 1. 建立工廠與解析器
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(5 * 1024); // 5KB 記憶體緩衝
        factory.setRepository(new File(serverRealPath + TEMP_DIR));
        
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setSizeMax(MAX_SIZE * 1024 * 1024); // 3MB
        upload.setHeaderEncoding("UTF-8");

        try {
            List<FileItem> items = upload.parseRequest(request);
            
            for (FileItem item : items) {
                if (!item.isFormField()) { // 處理文件欄位
                    String fileName = item.getName();
                    if (fileName == null || fileName.isEmpty()) {
                        forwardError(request, response, "未選擇檔案");
                        return;
                    }
                    
                    // 檔案類型驗證 (副檔名)
                    String lowerName = fileName.toLowerCase();
                    boolean validExt = false;
                    for (String ext : ALLOWED_EXTS) {
                        if (lowerName.endsWith(ext)) { validExt = true; break; }
                    }
                    if (!validExt) {
                        forwardError(request, response, "僅允許上傳圖片檔案 (JPG/PNG/GIF/BMP/ICO)");
                        return;
                    }
                    
                    // 生成唯一檔名:UUID + 原始副檔名
                    String uuid = UUID.randomUUID().toString();
                    String ext = lowerName.substring(lowerName.lastIndexOf("."));
                    String savedPath = UPLOAD_DIR + uuid + ext;
                    String fullPath = serverRealPath + savedPath;
                    
                    // 寫入檔案 (自動清理暫存)
                    item.write(new File(fullPath));
                    
                    // 回傳成功腳本:alert + 設定 returnValue + 關閉視窗
                    out.write("<script>");
                    out.write("alert('圖片上傳成功!');");
                    out.write("window.returnValue = '" + savedPath + "';");
                    out.write("window.close();");
                    out.write("</script>");
                    out.flush();
                    return; // 結束處理
                }
            }
            
        } catch (FileUploadBase.SizeLimitExceededException e) {
            forwardError(request, response, "檔案大小超過 " + MAX_SIZE + "MB 限制");
        } catch (Exception e) {
            e.printStackTrace();
            forwardError(request, response, "上傳失敗:" + e.getMessage());
        }
    }

    private void forwardError(HttpServletRequest request, HttpServletResponse response, String msg) 
            throws ServletException, IOException {
        request.setAttribute("errorMsg", msg);
        request.getRequestDispatcher("/admin/bookUpload.jsp").forward(request, response);
    }
}

關鍵技術點解析

1. 模態視窗資料回傳機制

  • window.showModalDialog(url, args, features):IE 專用,阻塞父視窗直到關閉
  • 子視窗設定 window.returnValue = "資料" 並 window.close()
  • 父視窗接收回傳值:var ret = showModalDialog(...)
  • 現代替代方案:Bootstrap Modal + AJAX 上傳,或 window.open() + postMessage() 跨視窗通訊

2. 檔案儲存路徑設計

  • 使用 getServletContext().getRealPath("/") 取得 Web 應用絕對路徑
  • 儲存於 WEB-INF 外部目錄,確保可直接透過 URL 存取 (<img src="/contextPath/eshop/upload/xxx.jpg">)
  • 生產環境建議:配置 Nginx/Apache 靜態資源映射,或使用雲端對象儲存 (OSS/S3)

3. 安全性強化建議

// 1. 檢查 Magic Number (防止偽裝副檔名)
public boolean isValidImage(FileItem item) throws IOException {
    byte[] header = new byte[12];
    try (InputStream in = item.getInputStream()) {
        in.read(header);
    }
    // JPEG: FF D8 FF, PNG: 89 50 4E 47 0D 0A 1A 0A, GIF: 47 49 46 38
    return (header[0] == (byte)0xFF && header[1] == (byte)0xD8) ||
           (header[0] == (byte)0x89 && header[1] == (byte)0x50) ||
           (header[0] == (byte)0x47 && header[1] == (byte)0x49);
}

// 2. 限制上傳目錄執行權限 (防止腳本執行)
// Nginx 配置: location /uploads { add_header X-Content-Type-Options nosniff; }

// 3. 檔案大小雙重驗證 (前端 JS + 後端 Servlet)

// 4. 防毒掃描整合 (ClamAV 等)

4. 現代化改造方向

  1. 前端:Vue/React + Element UI/Ant Design Upload 組件
  2. 上傳方式:AJAX (FormData) 非同步上傳,支援進度條、拖曳、預覽
  3. 後端:Spring Boot @RestController + MultipartFile 返回 JSON
  4. 儲存:MinIO / AWS S3 / 阿里云 OSS,返回簽名 URL
  5. 資料庫:僅存相對路徑/Object Key,不存二進位資料

web.xml 完整配置參考

<servlet>
    <servlet-name>BookServlet</servlet-name>
    <servlet-class>com.library.servlet.BookServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>BookServlet</servlet-name>
    <url-pattern>/BookServlet</url-pattern>
</servlet-mapping>

總結

本方案展示了傳統 JSP/Servlet + Commons FileUpload 實現模態視窗圖片上傳的完整流程。雖然 showModalDialog 已被現代瀏覽器棄用,但核心上傳邏輯 (驗證、命名、儲存、回傳) 仍具參考價值。新專案強烈建議採用前後端分離架構,配合現代上傳組件與雲端儲存服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved