使用Spring Boot快速构建一个简单的文件处理工具

 更新时间:2025年06月03日 08:56:43   作者:码农阿豪@新空间  
在现代Web应用中,文件上传与处理是常见的需求,本文将通过一个实际案例,详细介绍如何使用Spring Boot构建一个文件处理工具,感兴趣的小伙伴可以参考一下

引言

在现代Web应用中,文件上传与处理是常见的需求。本文将通过一个实际案例,详细介绍如何使用Spring Boot构建一个文件处理工具,实现以下功能:

  • 接收用户上传的ZIP压缩包
  • 解压ZIP文件并提取其中的图片
  • 使用OCR技术识别图片中的关键信息
  • 将识别结果导出为Excel文件
  • 同时保存结果到服务器本地

我们将从项目搭建、核心功能实现到错误处理等方面进行全面讲解,并提供完整的代码示例。

一、项目概述与搭建

1.1 功能需求

用户通过网页上传ZIP文件

后端解压ZIP,提取图片文件

对每张图片进行OCR识别(如快递单号、手机号)

将识别结果生成Excel并提供下载

在服务器resources/output目录保留结果副本

1.2 技术栈

后端:Spring Boot 2.7+

模板引擎:Thymeleaf(前后端不分离)

文件处理:Apache Commons Compress(ZIP解压)

OCR识别:百度OCR API(或其他OCR服务)

Excel操作:Apache POI

1.3 初始化项目

使用Spring Initializr创建项目,添加依赖:

<dependencies>
    <!-- Web支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    <!-- Excel操作 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
    
    <!-- 文件上传 -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

二、核心功能实现

2.1 文件上传接口

2.2 ZIP解压实现

private List<File> unzipFile(MultipartFile file) throws IOException {
    List<File> extractedFiles = new ArrayList<>();
    Path tempDir = Files.createTempDirectory("unzip_");
    
    try (ZipInputStream zipIn = new ZipInputStream(file.getInputStream())) {
        ZipEntry entry;
        while ((entry = zipIn.getNextEntry()) != null) {
            Path filePath = tempDir.resolve(entry.getName());
            
            // 防止ZIP滑动攻击
            if (!filePath.normalize().startsWith(tempDir)) {
                throw new SecurityException("非法文件路径");
            }
            
            if (!entry.isDirectory() && isImageFile(entry.getName())) {
                Files.copy(zipIn, filePath);
                extractedFiles.add(filePath.toFile());
            }
            zipIn.closeEntry();
        }
    }
    return extractedFiles;
}
​​​​​​​private boolean isImageFile(String filename) {
    String[] extensions = {".jpg", ".png", ".jpeg"};
    return Arrays.stream(extensions).anyMatch(filename::endsWith);
}

2.3 OCR信息识别

public OrderInfo getPicInfo(String imagePath) {
    OrderInfo info = new OrderInfo();
    try {
        String base64Image = imageToBase64(imagePath);
        String ocrResult = callOcrApi(base64Image); // 调用OCR API
        info.setExpressNumber(extractExpressNo(ocrResult));
        info.setPhoneNumber(extractPhoneNo(ocrResult));
    } catch (Exception e) {
        info.setError(true);
    }
    return info;
}

2.4 生成Excel并保存

private void saveToResourcesOutput(Workbook workbook) throws IOException {
    Path outputDir = Paths.get("src/main/resources/output");
    if (!Files.exists(outputDir)) {
        Files.createDirectories(outputDir);
    }
    
    String filename = "result_" + System.currentTimeMillis() + ".xlsx";
    try (FileOutputStream out = new FileOutputStream(outputDir.resolve(filename).toFile())) {
        workbook.write(out);
    }
}

三、前端页面实现

Thymeleaf模板(index.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>图片处理工具</title>
</head>
<body>
    <h1>上传ZIP压缩包</h1>
    <form method="post" action="/upload" enctype="multipart/form-data">
        <input type="file" name="file" accept=".zip" required>
        <button type="submit">提交</button>
    </form>
    
    <div th:if="${message}" th:text="${message}"></div>
</body>
</html>

四、错误处理与优化

4.1 常见错误解决

问题1:Thymeleaf模板找不到

Error resolving template [index], template might not exist

解决方案:

  • 确认index.html位于resources/templates/目录
  • 检查是否添加了Thymeleaf依赖
  • 确保控制器返回的视图名称匹配

问题2:ZIP解压失败

增强健壮性:

try {
    unzipFile(file);
} catch (IOException e) {
    log.error("解压失败", e);
    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "无效的ZIP文件");
}

4.2 性能优化建议

大文件处理:使用SXSSFWorkbook流式写入Excel

并发处理:对多图片采用线程池并行OCR识别

临时文件清理:

@Scheduled(fixedRate = 86400000) // 每天清理
public void cleanTempFiles() {
    // 删除超过1天的临时文件
}

五、问题分析与解决方案

问题描述

用户在使用Spring Boot开发一个文件处理应用时,遇到以下两个主要问题:

  • 中文文件名处理失败:上传包含中文文件名的ZIP文件时,报错MALFORMED。
  • JAR包无法运行:使用java -jar启动时提示no main manifest attribute,手动指定主类后,又报NoClassDefFoundError。

环境信息

Spring Boot版本:2.6.13

JDK版本:1.8

构建工具:Maven

问题场景:文件上传解析 + 生成Excel

问题分析

1.中文文件名处理失败(MALFORMED错误)

问题原因

  • 在解压ZIP文件时,ZipInputStream默认使用的编码可能与ZIP文件的实际编码不一致,导致中文文件名解析错误。
  • 路径安全检查不完善,可能引发安全漏洞(如ZIP路径遍历攻击)。

解决方案

优化unzipFile方法,正确处理编码和路径安全:

private List<File> unzipFile(MultipartFile file) throws IOException {
    List<File> extractedFiles = new ArrayList<>();
    File tempDir = Files.createTempDirectory("unzip_").toFile();
    tempDir.deleteOnExit();

    // 尝试GBK编码(常见于Windows生成的ZIP)
    try (ZipInputStream zipIn = new ZipInputStream(file.getInputStream(), Charset.forName("GBK"))) {
        ZipEntry entry;
        while ((entry = zipIn.getNextEntry()) != null) {
            String entryName = entry.getName();
            File destFile = new File(tempDir, entryName);
            
            // 安全检查:防止ZIP路径遍历
            String canonicalPath = destFile.getCanonicalPath();
            if (!canonicalPath.startsWith(tempDir.getCanonicalPath() + File.separator)) {
                throw new SecurityException("ZIP文件包含非法路径: " + entryName);
            }

            if (!entry.isDirectory() && isImageFile(entryName)) {
                Files.createDirectories(destFile.getParentFile().toPath());
                Files.copy(zipIn, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                extractedFiles.add(destFile);
            }
            zipIn.closeEntry();
        }
    } catch (Exception e) {
        // 如果GBK失败,回退到UTF-8
        try (ZipInputStream zipIn = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) {
            // 重复解压逻辑...
        }
    }
    return extractedFiles;
}

关键改进点

多编码支持:优先尝试GBK(常见中文编码),失败后回退到UTF-8。

路径安全:使用getCanonicalPath检查路径合法性,防止恶意ZIP文件攻击。

代码健壮性:使用Files.copy替代手动缓冲读写,更高效可靠。

2.JAR包无法运行(NoClassDefFoundError)

问题原因

打包时未正确生成可执行的Spring Boot JAR,导致:

  • 缺少MANIFEST.MF中的Main-Class。
  • 依赖库未打包进JAR(BOOT-INF/lib缺失)。

解决方案

修正pom.xml,确保正确打包:

<build>
    <plugins>
        <!-- 1. 指定Java编译版本 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>

        <!-- 2. 关键:正确配置Spring Boot Maven插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal> <!-- 生成可执行JAR -->
                    </goals>
                </execution>
            </executions>
            <configuration>
                <mainClass>com.debang.debang_phone_tool.DebangPhoneToolApplication</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

关键改进点

移除<skip>true</skip>:确保插件执行重新打包操作。

明确指定mainClass:避免运行时找不到主类。

验证打包结果

# 检查JAR结构
jar tf target/debang_phone_tool-0.0.1-SNAPSHOT.jar | grep BOOT-INF/lib
# 检查MANIFEST.MF
jar xf target/debang_phone_tool-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF && cat META-INF/MANIFEST.MF

完整解决方案

修正后的项目结构

src/
├── main/
│   ├── java/
│   │   └── com/debang/debang_phone_tool/
│   │       ├── DebangPhoneToolApplication.java  # Spring Boot主类
│   │       ├── controller/                      # 控制器
│   │       └── service/                         # 业务逻辑
│   └── resources/
│       ├── static/                              # 静态文件
│       └── application.yml                      # 配置文件
pom.xml                                         # 修正后的Maven配置

打包与运行命令

# 清理并重新打包
mvn clean package

# 后台运行(Linux)
nohup java -jar target/debang_phone_tool-0.0.1-SNAPSHOT.jar > app.log 2>&1 &

# 查看日志
tail -f app.log

总结

1.编码问题

文件处理:显式指定编码(如GBK/UTF-8),避免依赖平台默认值。

路径安全:使用getCanonicalPath检查路径合法性。

2.打包问题

必须使用spring-boot-maven-plugin:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

验证JAR结构:

  • 确保存在BOOT-INF/lib(依赖库)和META-INF/MANIFEST.MF。
  • 避免<skip>true</skip>:否则会导致依赖未打包。

3.日志与监控

日志重定向:使用2>&1捕获所有输出:

nohup java -jar app.jar > app.log 2>&1 &

进程管理:结合systemd或supervisord实现服务化。

六、总结

本文实现了一个完整的Spring Boot文件处理流程,关键点包括:

  • 使用MultipartFile接收上传文件
  • 安全的ZIP解压与路径校验
  • 第三方OCR服务集成
  • 动态Excel生成与双存储(下载+本地保存)
  • 全面的异常处理机制

扩展方向:

  • 添加用户系统,隔离不同用户的数据
  • 支持更多文件格式(如RAR、7z)
  • 集成更强大的OCR引擎(如Tesseract)

GitHub示例:完整代码可在 https://github.com/example/file-processor 获取

通过这个案例,读者可以掌握Spring Boot中文件处理的核心技术,并快速应用到实际项目中。

以上就是使用Spring Boot快速构建一个简单的文件处理工具的详细内容,更多关于Spring Boot文件处理的资料请关注脚本之家其它相关文章!

相关文章

  • Java BIO,NIO,AIO总结

    Java BIO,NIO,AIO总结

    这篇文章主要介绍了Java BIO,NIO,AIO的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-09-09
  • @Autowired注解在抽象类中失效的原因及解决

    @Autowired注解在抽象类中失效的原因及解决

    这篇文章主要介绍了@Autowired注解在抽象类中失效的原因及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • java 判断字符串是否包含子串的方法

    java 判断字符串是否包含子串的方法

    这篇文章主要介绍了java 判断字符串是否包含子串的方法的相关资料,这里提供了三种方法帮助大家实现这样的功能,需要的朋友可以参考下
    2017-08-08
  • Java异常ClassCastException的解决

    Java异常ClassCastException的解决

    这篇文章主要介绍了Java异常ClassCastException的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • Java如何导出多个excel并打包压缩成.zip文件

    Java如何导出多个excel并打包压缩成.zip文件

    本文介绍了Java如何导出多个excel文件并将这些文件打包压缩成zip格式,首先,需要从数据库中获取数据并导出到指定位置形成excel文件,接着,将这些数据分散到不同的excel文件中,最后,使用相关的Java工具类对这些excel文件进行打包压缩
    2024-09-09
  • JAVA利用HttpClient进行POST请求(HTTPS)实例

    JAVA利用HttpClient进行POST请求(HTTPS)实例

    下面小编就为大家带来一篇JAVA利用HttpClient进行POST请求(HTTPS)实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起 小编过来看看吧
    2016-11-11
  • Java基础之List内元素的排序性能对比

    Java基础之List内元素的排序性能对比

    这篇文章主要介绍了Java基础之List内元素的排序性能对比,文中有非常详细的代码示例,对正在学习java基础的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • Java非静态成员变量之死循环(详解)

    Java非静态成员变量之死循环(详解)

    下面小编就为大家带来一篇Java非静态成员变量之死循环(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Kotlin基础教程之面向对象

    Kotlin基础教程之面向对象

    这篇文章主要介绍了Kotlin基础教程之面向对象的相关资料,需要的朋友可以参考下
    2017-05-05
  • Java 的访问修饰符public,protected,private(封装、继承)

    Java 的访问修饰符public,protected,private(封装、继承)

    这篇文章主要介绍了Java 的访问修饰符public,protected,private(封装、继承),文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09

最新评论