楼层: 首页/ 软件技术/ JavaEE / Jakarta EE/ 文件上传与下载
08

文件上传与下载

File Upload · Part · Multipart Config · Download

用户传头像、传附件,是 Web 应用绕不开的功能。Servlet 3.0(现代 Jakarta EE)起提供了 Part 接口,不用再装第三方 commons-fileupload。注意:上传表单的 enctype 必须是 multipart/form-data,少一个字都收不到文件。

浏览器上传文件的表单有个"潜规则":默认的表单编码只发普通键值对,文件这种二进制内容必须换 multipart/form-data 编码,它会把文件切成一块一块标好边界发过来。Servlet 3.0+ 的 Part 接口就是替你拆开这些边界。这也是为什么少写一个属性就收不到文件的根本原因。

上传 Servlet:接收文件并保存到磁盘

@WebServlet("/upload")
@MultipartConfig(maxFileSize = 10 * 1024 * 1024,   // 单文件最大 10MB
                 maxRequestSize = 50 * 1024 * 1024)
public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        Part filePart = req.getPart("avatar");     // 对应表单里 input name="avatar"
        String submittedName = filePart.getSubmittedFileName();

        // 用 UUID 重命名,防止两个用户都叫 a.jpg 互相覆盖
        String ext = submittedName.substring(submittedName.lastIndexOf("."));
        String savedName = java.util.UUID.randomUUID() + ext;

        filePart.write("/data/uploads/" + savedName);   // 落盘
        resp.getWriter().write("上传成功:" + savedName);
    }
}

对应的上传表单(HTML 页面里)

<form action="upload" method="post" enctype="multipart/form-data">
  <input type="file" name="avatar" accept="image/*">
  <button type="submit">上传</button>
</form>

文件下载:把服务器上的文件发给浏览器

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String fileName = req.getParameter("name");
        Path file = Path.of("/data/uploads/", fileName);

        // 关键:告诉浏览器"这是要下载的附件",并给出建议保存的文件名
        resp.setContentType("application/octet-stream");
        resp.setHeader("Content-Disposition",
            "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));

        // try-with-resources 把文件复制到响应输出流
        try (OutputStream out = resp.getOutputStream()) {
            Files.copy(file, out);
        }
    }
}

论上传这条路上容易忘的三件事

① 表单 enctype="multipart/form-data" 忘写,getPart("avatar") 直接拿不到东西,半天找不到原因。② 类上要加 @MultipartConfig(或在 web.xml 配),不然容器不认这是文件上传。③ 上传文件会先写到临时目录,part.write() 移动走;不处理的话临时文件越攒越多。

文件上传安全三连

① 别用用户上传的原始文件名直接落盘——他可以传 ../../webapps/ROOT/evil.jsp 来覆盖你的程序(目录穿越攻击)。一定自己重命名(UUID)。
② 校验扩展名和 MIME 类型,别让人传 .jsp / .exe。
③ 上传目录放在 Web 可访问路径之外,或配成"只当静态资源下载、绝不执行"。传上来的东西永远不可信。

章末面试 · 文件上传下载(3 题)

1.(排错题)上传表单收不到文件,最可能漏了什么?

查看答案

答案:表单 enctype="multipart/form-data" 忘写。默认编码只发键值对,文件这种二进制必须换 multipart 编码。

2.(安全题)为什么上传文件要用 UUID 重命名?

查看答案

答案:防目录穿越攻击。用户传 ../../webapps/evil.jsp 能覆盖你的程序。自己用 UUID 重命名,再校验扩展名。

3.(代码题)文件下载时怎么让浏览器弹出"另存为"?

查看答案

答案:设响应头 Content-Disposition: attachment;filename=xxx,Content-Type 设为 application/octet-stream。文件名要 URLEncoder.encode 防中文乱码。