杰瑞科技汇

JSP Java如何实现图片上传?

Servlet 3.0+Apache Commons FileUpload,Servlet 3.0+ 提供了更简单、更原生的文件上传 API,而 Commons FileUpload 则是一个功能强大且稳定的库,能更好地处理各种边界情况(如大文件、内存限制等)。

JSP Java如何实现图片上传?-图1
(图片来源网络,侵删)

整体思路

  1. 前端 (JSP):创建一个包含 <input type="file"> 的表单,用于用户选择图片,表单的 enctype 必须设置为 multipart/form-data
  2. 后端 (Servlet):创建一个 Servlet 来接收前端的请求。
  3. 处理上传
    • 使用 commons-fileupload 解析 multipart 请求,从中提取出上传的文件。
    • 验证文件类型(确保是图片)和文件大小。
    • 为文件生成一个唯一的文件名,防止文件名冲突。
    • 将文件保存到服务器的指定目录中。
  4. 响应:根据处理结果,向前端返回成功或失败的信息。

第一步:项目环境准备

  1. 创建 Web 项目:在你的 IDE(如 Eclipse, IntelliJ IDEA)中创建一个 Dynamic Web Project。

  2. 添加依赖库:你需要将以下 JAR 包添加到项目的 WEB-INF/lib 目录中。

    • commons-fileupload-x.x.jar (commons-fileupload-1.4.jar)
    • commons-io-x.x.jar (commons-io-2.11.jar),FileUpload 依赖它来简化 IO 操作。

    你可以从 Maven 中央仓库下载它们:


第二步:前端页面 (index.jsp)

创建一个 JSP 页面,让用户可以选择图片并提交。

JSP Java如何实现图片上传?-图2
(图片来源网络,侵删)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">图片上传示例</title>
<style>
    body { font-family: Arial, sans-serif; margin: 20px; }
    .container { max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
    h2 { text-align: center; }
    input[type="file"] { width: 100%; padding: 10px; margin-bottom: 10px; }
    input[type="submit"] { background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
    input[type="submit"]:hover { background-color: #45a049; }
    .message { margin-top: 20px; padding: 10px; border-radius: 4px; }
    .success { background-color: #dff0d8; color: #3c763d; }
    .error { background-color: #f2dede; color: #a94442; }
</style>
</head>
<body>
    <div class="container">
        <h2>上传图片</h2>
        <form action="upload" method="post" enctype="multipart/form-data">
            <label for="imageFile">选择图片文件:</label>
            <input type="file" id="imageFile" name="imageFile" accept="image/*" required>
            <input type="submit" value="上传">
        </form>
        <%-- 用于显示上传结果 --%>
        <div id="message-container"></div>
    </div>
    <script>
        // 使用 JavaScript 来显示服务器返回的消息
        window.onload = function() {
            const urlParams = new URLSearchParams(window.location.search);
            const message = urlParams.get('message');
            const messageType = urlParams.get('type');
            if (message) {
                const messageContainer = document.getElementById('message-container');
                const messageDiv = document.createElement('div');
                messageDiv.className = `message ${messageType}`;
                messageDiv.textContent = message;
                messageContainer.appendChild(messageDiv);
            }
        };
    </script>
</body>
</html>

关键点说明:

  • <form> 标签的 enctype="multipart/form-data"必须的,它告诉浏览器要以二进制流的方式发送表单数据,而不是以 application/x-www-form-urlencoded 的方式。
  • <input type="file"> 创建了文件选择控件。
  • name="imageFile":这个 name 属性非常重要,后端 Servlet 将通过这个 name 来获取上传的文件。
  • accept="image/*":这是一个前端提示,告诉浏览器只允许选择图片文件,但不能完全依赖它进行安全验证,后端必须再次验证。

第三步:后端 Servlet (UploadServlet.java)

这是处理上传的核心逻辑。

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.apache.commons.io.FilenameUtils;
@WebServlet("/upload")
@MultipartConfig // 必须注解,以支持 Servlet 3.0 的文件上传功能
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    // 定义上传文件存储的目录
    private static final String UPLOAD_DIR = "uploads";
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 检查上传目录是否存在,如果不存在则创建
        // getServletContext().getRealPath() 获取 Web 应用在服务器上的真实路径
        String applicationPath = getServletContext().getRealPath("");
        String uploadPath = applicationPath + File.separator + UPLOAD_DIR;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }
        try {
            // 2. 获取上传的 Part 对象 (Servlet 3.0+ API)
            Part filePart = request.getPart("imageFile");
            if (filePart == null) {
                // 如果没有文件被选择
                response.sendRedirect("index.jsp?message=请选择一个文件&type=error");
                return;
            }
            // 3. 获取文件名并进行处理
            String fileName = getSubmittedFileName(filePart);
            if (fileName == null || fileName.isEmpty()) {
                response.sendRedirect("index.jsp?message=文件名无效&type=error");
                return;
            }
            // 4. 验证文件类型 (只允许图片)
            String fileExtension = FilenameUtils.getExtension(fileName);
            if (!isImage(fileExtension)) {
                response.sendRedirect("index.jsp?message=只允许上传图片文件 (jpg, png, gif等)&type=error");
                return;
            }
            // 5. 验证文件大小 (限制为 5MB)
            long fileSize = filePart.getSize();
            long maxSize = 5 * 1024 * 1024; // 5MB
            if (fileSize > maxSize) {
                response.sendRedirect("index.jsp?message=文件大小不能超过 5MB&type=error");
                return;
            }
            // 6. 生成唯一文件名,防止覆盖
            String uniqueFileName = System.currentTimeMillis() + "_" + fileName;
            // 7. 将文件保存到服务器
            // 使用 Java NIO 的 Files.copy() 方法,它更高效
            Path uploadFilePath = Paths.get(uploadPath, uniqueFileName);
            // 使用 try-with-resources 确保 InputStream 被正确关闭
            try (InputStream input = filePart.getInputStream()) {
                Files.copy(input, uploadFilePath, StandardCopyOption.REPLACE_EXISTING);
            }
            // 8. 上传成功,重定向到首页并显示成功消息
            // 注意:重定向后,request 中的数据会丢失,所以通过 URL 参数传递消息
            response.sendRedirect("index.jsp?message=文件 " + uniqueFileName + " 上传成功&type=success");
        } catch (Exception e) {
            // 处理异常
            e.printStackTrace();
            response.sendRedirect("index.jsp?message=上传失败: " + e.getMessage() + "&type=error");
        }
    }
    /**
     * 获取提交的文件名,兼容不同浏览器
     */
    private String getSubmittedFileName(Part part) {
        for (String cd : part.getHeader("content-disposition").split(";")) {
            if (cd.trim().startsWith("filename")) {
                String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
                return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1);
            }
        }
        return null;
    }
    /**
     * 检查文件扩展名是否为图片
     */
    private boolean isImage(String extension) {
        if (extension == null || extension.isEmpty()) {
            return false;
        }
        // 转换为小写进行比较
        String ext = extension.toLowerCase();
        return ext.equals("jpg") || ext.equals("jpeg") || ext.equals("png") || ext.equals("gif") || ext.equals("bmp");
    }
}

代码关键点解析:

  1. @WebServlet("/upload"):将 Servlet 映射到 /upload URL。
  2. @MultipartConfig必须添加,这个注解告诉 Servlet 容器,这个 Servlet 将处理 multipart/form-data 请求,它还会配置一些默认的内存和临时文件设置。
  3. request.getPart("imageFile"):这是 Servlet 3.0+ 提供的标准 API,用于获取表单中名为 imageFile 的文件部分。Part 对象代表了上传的一个文件。
  4. 文件名处理getSubmittedFileName() 方法用于从 Part 对象中提取原始文件名,不同浏览器(如 Chrome, IE)提供的文件名格式不同,这个方法做了兼容性处理。
  5. 文件验证
    • 类型验证:通过检查文件扩展名(如 jpg, png)来判断是否为图片。注意:这种方法不是100%安全的,因为恶意用户可以伪造文件扩展名,更严格的验证方式是读取文件的“魔数”(Magic Number),即文件头部的几个字节,来判断其真实类型,但对于大多数应用场景,检查扩展名已经足够。
    • 大小验证:通过 part.getSize() 获取文件大小(以字节为单位),并与预设的最大值进行比较。
  6. 生成唯一文件名:使用 System.currentTimeMillis() 时间戳作为前缀,可以极大地避免因文件名相同而导致的文件覆盖问题。
  7. 文件保存
    • getServletContext().getRealPath("") 获取当前 Web 应用的根目录在服务器上的物理路径。
    • 我们创建了一个 uploads 目录来存放所有上传的文件。
    • Files.copy() 是 Java NIO 提供的高效文件复制方法,比传统的 InputStream + OutputStream 更简洁、更健壮。
  8. 响应:使用 response.sendRedirect() 进行重定向,将用户带回首页,并通过 URL 参数 ?message=...&type=... 传递操作结果,前端 JavaScript 会读取这些参数并显示相应的消息。

第四步:配置 web.xml (可选)

如果你不使用 @WebServlet 注解,或者需要进行更复杂的配置(如过滤器、监听器),你需要在 WEB-INF/web.xml 文件中配置 Servlet。

JSP Java如何实现图片上传?-图3
(图片来源网络,侵删)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>UploadServlet</servlet-name>
        <servlet-class>com.yourpackage.UploadServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>UploadServlet</servlet-name>
        <url-pattern>/upload</url-pattern>
    </servlet-mapping>
</web-app>

第五步:部署和测试

  1. 将项目部署到你的 Web 服务器(如 Apache Tomcat)。
  2. 启动服务器。
  3. 在浏览器中访问 http://localhost:8080/你的项目名/index.jsp
  4. 选择一张图片文件,点击“上传”。
  5. 查看结果:
    • 如果成功,页面会显示“上传成功”的消息。
    • 如果失败(不是图片文件或文件太大),页面会显示相应的错误消息。
  6. 检查你的项目部署目录下(Tomcat/webapps/你的项目名/),是否多了一个 uploads 文件夹,里面是否存放了你上传的图片。

高级与生产环境考虑

  1. 数据库存储:在生产环境中,通常不会将图片文件本身存入数据库,而是将图片的路径URL存入数据库,数据库中可以有一个表,如 images,包含字段 id, file_name, file_path, upload_date 等。
  2. 文件服务器:为了提高性能和可扩展性,上传的文件最好存储在专门的文件服务器(如 MinIO, AWS S3, 阿里云 OSS)上,而不是应用服务器的本地磁盘,这样可以将应用服务器和存储服务器解耦。
  3. 安全性
    • 路径遍历攻击:在上传文件时,要小心用户上传的文件名可能包含 这样的路径,可能导致文件被保存到 uploads 目录之外,一个简单的做法是只保留文件名,去掉所有路径信息。
    • 病毒扫描:对上传的文件进行病毒扫描,防止恶意文件上传。
    • 权限控制:确保 uploads 目录对 Web 服务器进程有写入权限,但对外部访问者没有读取权限(如果需要通过 URL 访问,则另当别论)。
  4. 性能:对于非常大的文件,Servlet 3.0 的默认配置可能不够用,你可以在 @MultipartConfig 中自定义配置,
    @MultipartConfig(
        fileSizeThreshold = 1024 * 1024,      // 1 MB 内存阈值,超过则写入临时文件
        maxFileSize = 1024 * 1024 * 10,       // 10 MB 最大单个文件大小
        maxRequestSize = 1024 * 1024 * 15     // 15 MB 最大请求大小 (包含所有文件和表单数据)
    )
分享:
扫描分享到社交APP
上一篇
下一篇