【JavaWeb学习 | 第22篇】文件上传下载与 Excel 导入导出

【JavaWeb学习 | 第22篇】文件上传下载与 Excel 导入导出

恭喜你学到这里!前面我们已经掌握了 Servlet + MVCFilterEL+JSTLAJAX+JSON 等核心技术。今天这篇是 JavaWeb 开发中非常实用且高频的内容:文件上传与下载,以及企业开发中几乎必备的 Excel 导入导出(使用 Apache POI)。

这两个功能直接关系到“用户能不能方便地传输文件”和“系统能不能高效地处理批量数据”。


一、文件上传(File Upload)

1. 前端表单要求(必须掌握)

<%-- upload.jsp --%>
<form action="${pageContext.request.contextPath}/upload" method="post" 
      enctype="multipart/form-data">

    选择文件:<input type="file" name="uploadFile" /><br><br>
    描述:<input type="text" name="description" /><br><br>

    <input type="submit" value="上传" />
</form>

关键点enctype="multipart/form-data"(必须!否则后端无法接收文件)

2. 后端实现方式对比

推荐方式(2026年视角)

  • Servlet 3.0+ 原生方式@MultipartConfig + Part API):无需额外jar,简单。
  • Apache Commons FileUpload:功能更强大,支持大文件、进度条等(传统教程常用)。

我们以 Commons FileUpload 为例(兼容性好,学习价值高)。

所需jar包(放入 WEB-INF/lib):

  • commons-fileupload-1.5.jar
  • commons-io-2.16.1.jar(依赖)

3. UploadServlet 完整代码

// com.example.web.UploadServlet.java
@WebServlet("/upload")
@MultipartConfig(
    fileSizeThreshold = 1024*1024*2,      // 2MB 临时文件阈值
    maxFileSize = 1024*1024*10,           // 单个文件最大10MB
    maxRequestSize = 1024*1024*50         // 整个请求最大50MB
)
public class UploadServlet extends HttpServlet {

    private static final String UPLOAD_DIR = "uploads";   // 相对路径

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {

        // 获取上传目录的绝对路径(推荐放在 WEB-INF 外或自定义路径)
        String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIR;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) uploadDir.mkdirs();

        try {
            // 使用 Commons FileUpload
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);

            // 解析请求
            List<FileItem> items = upload.parseRequest(req);

            for (FileItem item : items) {
                if (!item.isFormField()) {   // 是文件
                    String fileName = new File(item.getName()).getName();  // 防止路径穿越
                    String filePath = uploadPath + File.separator + fileName;

                    // 保存文件
                    item.write(new File(filePath));

                    req.setAttribute("message", "文件上传成功!文件名:" + fileName);
                } else {   // 是普通表单字段
                    String fieldName = item.getFieldName();
                    String value = item.getString("UTF-8");
                    System.out.println(fieldName + " = " + value);
                }
            }
        } catch (Exception e) {
            req.setAttribute("message", "上传失败:" + e.getMessage());
            e.printStackTrace();
        }

        req.getRequestDispatcher("/upload.jsp").forward(req, resp);
    }
}

安全建议

  • 文件名使用 UUID 重命名(防止覆盖和路径穿越)
  • 限制文件类型(图片/文档等)
  • 上传目录不要放在 Web 根目录下(防止直接访问)

二、文件下载(File Download)

1. DownloadServlet 代码

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {

        String fileName = req.getParameter("filename");   // 如:测试文档.docx

        // 文件真实路径
        String filePath = getServletContext().getRealPath("") + "/uploads/" + fileName;
        File file = new File(filePath);

        if (!file.exists()) {
            resp.getWriter().write("文件不存在!");
            return;
        }

        // 设置响应头(关键!)
        resp.setContentType("application/octet-stream");
        resp.setContentLength((int) file.length());

        // 处理中文文件名乱码(通用方案)
        String userAgent = req.getHeader("User-Agent");
        String encodedFileName;
        if (userAgent.contains("Firefox")) {
            encodedFileName = "=?utf-8?B?" + 
                Base64.getEncoder().encodeToString(fileName.getBytes("utf-8")) + "?=";
        } else {
            encodedFileName = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
        }

        resp.setHeader("Content-Disposition", 
            "attachment; filename=\"" + encodedFileName + "\"");

        // 流传输
        try (InputStream in = new FileInputStream(file);
             OutputStream out = resp.getOutputStream()) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            out.flush();
        }
    }
}

前端调用示例

<a href="${pageContext.request.contextPath}/download?filename=报告.docx">下载报告</a>

三、Excel 导入导出(Apache POI)

推荐使用 POI(目前最成熟):

  • .xls → HSSF
  • .xlsx → XSSF(推荐)

所需jar包(Maven 或直接下载):

  • poi-5.3.0.jar
  • poi-ooxml-5.3.0.jar
  • poi-ooxml-lite-5.3.0.jar
  • xmlbeans-5.2.1.jar 等(根据版本配套)

1. Excel 导出(最常用)

// ExportExcelServlet.java
@WebServlet("/exportExcel")
public class ExportExcelServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {

        // 模拟数据(实际从数据库查询)
        List<User> userList = UserService.findAll();

        // 创建工作簿
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("用户列表");

        // 创建标题行
        String[] headers = {"ID", "用户名", "年龄", "邮箱"};
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }

        // 填充数据
        int rowNum = 1;
        for (User user : userList) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(user.getId());
            row.createCell(1).setCellValue(user.getUsername());
            row.createCell(2).setCellValue(user.getAge());
            row.createCell(3).setCellValue(user.getEmail());
        }

        // 设置响应头
        resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        String fileName = "用户列表_" + new SimpleDateFormat("yyyyMMdd").format(new Date()) + ".xlsx";

        String encodedName = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
        resp.setHeader("Content-Disposition", "attachment; filename=\"" + encodedName + "\"");

        // 输出
        workbook.write(resp.getOutputStream());
        workbook.close();
    }
}

2. Excel 导入(读取上传的Excel)

结合文件上传,先上传 .xlsx,然后在 Servlet 中解析:

// 在上传成功后或单独的 ImportServlet 中
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(excelFile));
XSSFSheet sheet = workbook.getSheetAt(0);

for (int i = 1; i <= sheet.getLastRowNum(); i++) {   // 从第2行开始(跳过标题)
    Row row = sheet.getRow(i);
    String username = row.getCell(0).getStringCellValue();
    int age = (int) row.getCell(1).getNumericCellValue();
    // ... 保存到数据库
}
workbook.close();

四、实际开发注意事项与最佳实践

  1. 文件上传安全
  • 校验文件类型(白名单:jpg/png/xlsx 等)
  • 重命名文件(UUID + 后缀)
  • 大小限制 + 病毒扫描(生产环境)
  • 上传目录定期清理或使用云存储(OSS)
  1. 下载中文文件名:必须处理 User-Agent,不同浏览器差异大,上面代码已给出通用方案。
  2. POI 性能
  • 大数据量导出推荐 SXSSFWorkbook(流式写入,节省内存)
  • 导入时注意空行、空单元格处理
  1. 结合 AJAX:上传可以用 FormData + AJAX 实现无刷新上传 + 进度条。
  2. 推荐工具类:实际项目中会把导出/导入封装成工具类(ExcelUtil),便于复用。

五、练习建议(强烈推荐动手完成)

  1. 实现单文件上传 + 下载(支持图片和文档)。
  2. 实现用户列表 Excel 导出功能(包含日期格式化)。
  3. 实现 Excel 导入用户数据(读取后存入数据库或List)。
  4. 进阶:支持多文件上传 + 上传进度显示(结合 AJAX)。
  5. 挑战:实现带模板的 Excel 导出(使用 POI 或 jxls)。

完成这些后,你已经具备了开发“后台管理系统”中核心 CRUD + 文件操作的能力!


系列文章导航(持续更新中):

  • 第20篇:EL表达式与JSTL标签
  • 第21篇:AJAX与JSON详解
  • 第22篇:文件上传下载与 Excel 导入导出(本文)
  • 第23篇:Listener监听器(在线人数统计、Session监听、应用启动/销毁监听等)

下一篇文章预告:我们将学习 Listener(监听器),它能监听应用、会话、请求的生命周期,是实现“在线人数统计”、“网站访问量统计”、“Session超时处理”等功能的必备技术。

有任何疑问(比如想看完整多文件上传代码、POI设置样式、或 SXSSFWorkbook 大数据导出示例),欢迎在评论区留言,我会及时补充更多细节和完整 Demo!

继续加油!掌握文件操作和 Excel 处理后,你的 JavaWeb 项目实用性将大幅提升,已经非常接近能独立完成中小型企业级 Web 应用了!💪

下一篇文章见~ 🚀

文章已创建 5295

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部