pdf生成排查记录与解决方案

pdf生成排查记录与解决方案

第一次出错:ClassNotFound异常

错误信息

复制代码
Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/http/client/ClientHttpRequestFactory

排查步骤

1. 验证类路径是否存在
java 复制代码
package com.tgerp.workaffairs.service.impl;

public class ClassPathTest {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("org.springframework.http.client.ClientHttpRequestFactory");
            System.out.println("Class found: " + clazz);
        } catch (ClassNotFoundException e) {
            System.out.println("Class not found in classpath");
        }
    }
}

结果:类存在,排除基础依赖缺失。

2. 依赖分析
  • 问题spring-web依赖缺失或版本不匹配
  • 检查方法
    • IDEA依赖分析工具
    • Maven依赖树检查
3. 解决方案

Maven项目:

xml 复制代码
<!-- 非Spring Boot项目 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>与你的Spring核心版本一致</version>
</dependency>

<!-- Spring Boot项目 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Gradle项目:

groovy 复制代码
implementation 'org.springframework:spring-web:版本号'
// 或Spring Boot项目
implementation 'org.springframework.boot:spring-boot-starter-web'
4. 依赖冲突排查
bash 复制代码
# Maven项目
mvn dependency:tree

# Gradle项目
gradle dependencies
5. 构建清理
bash 复制代码
# Maven
mvn clean install

# Gradle
./gradlew clean build

# IDE缓存清理
# IntelliJ: File → Invalidate Caches and Restart
# Eclipse: Project → Clean
6. 依赖完整性检查
bash 复制代码
# Linux/Mac
find . -name "*.jar" | xargs grep -l "ClientHttpRequestFactory"

# Windows PowerShell
Get-ChildItem -Recurse -Filter "*.jar" | ForEach-Object {
    if (Select-String -Path $_.FullName -Pattern "ClientHttpRequestFactory" -Quiet) {
        $_.FullName
    }
}

第二次出错:连接被拒绝

错误信息

复制代码
org.springframework.web.client.ResourceAccessException: 
I/O error on POST request for "https://xxxxxx:8443/xxxx": 
Connection refused: connect; 
nested exception is java.net.ConnectException: Connection refused: connect

HTTP/HTTPS连接过程

  1. 发起连接:当你的浏览器(或任何客户端程序)想要访问 http://www.example.com 时,它知道需要连接服务器的 80 端口(HTTP默认端口)或 443 端口(HTTPS默认端口)。

  2. 请求操作系统:应用程序向操作系统发起一个网络连接请求(例如,调用 socket() 和 connect() 系统调用)。

  3. 操作系统分配端口:你的操作系统会从它的 "本地临时端口范围" 中,挑选一个当前未被使用的端口。

    • 在Linux/Unix/Windows系统中,这个范围通常是 1024 到 65535。

    • 端口 0-1023 被称为"知名端口",通常需要管理员权限才能绑定,用于HTTP、FTP、SSH等标准服务。

  4. 建立连接:操作系统将这个随机选择的端口(例如 54321)作为 源端口,将目标服务器的IP地址和端口(例如 93.184.216.34:80)作为 目标地址,发起TCP三次握手。

  5. 端口绑定:一旦连接建立,这个 本地IP:54321 到 服务器IP:80 的"套接字"组合在整个通信会话期间(直到你关闭网页)都会保持不变。

通信过程如下:

• 你的请求包:[源IP:你的IP, 源端口:54321] -> [目标IP:服务器IP, 目标端口:80]

• 服务器的响应包:[源IP:服务器IP, 源端口:80] -> [目标IP:你的IP, 目标端口:54321]
失败
成功
需要
不需要
客户端
TCP三次握手
是否成功?
Connection refused

TCP连接问题
TLS/SSL握手
是否需要客户端证书?
双向认证

需提供客户端证书
单向认证

服务器证书验证
HTTPS加密通信

排查步骤

1. 网络连通性检查
bash 复制代码
# 检查TCP连接(替换实际地址)
telnet xxxxxx 8443
# 或
nc -zv xxxxxx 8443

# Windows PowerShell
Test-NetConnection xxxxxx -Port 8443
2. 防火墙检查
bash 复制代码
# Linux检查防火墙
sudo iptables -L
sudo firewall-cmd --list-all

# Windows检查防火墙
netsh advfirewall show allprofiles
3. SSL证书验证
java 复制代码
// 临时绕过SSL验证(仅用于测试,生产环境不推荐)
@Configuration
public class SSLConfig {
    
    @PostConstruct
    public void disableSSLValidation() throws Exception {
        // 仅用于测试环境
        TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) { }
                public void checkServerTrusted(X509Certificate[] certs, String authType) { }
            }
        };
        
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}
4. 连接超时设置
java 复制代码
@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(5000); // 5秒连接超时
        factory.setReadTimeout(10000);    // 10秒读取超时
        return new RestTemplate(factory);
    }
}

根本原因

端口访问权限问题 :应用程序所在服务器无法访问目标服务器的8443端口。
白名单问题:应用程序所在服务器未加入到对方的白名单。

解决方案

  1. 联系网络管理员,开通服务器之间的端口访问权限
  2. 检查目标服务是否正常运行在8443端口
  3. 验证网络策略和防火墙规则

第三次出错:文件路径格式错误

错误信息

复制代码
java.io.FileNotFoundException: 
file:\\D:\\Program%20Files\\tgerp-cloud-server\\tgerp-modules-workaffairs.jar!\\BOOT-INF\\classes!\\template\\stamp.svg 
(文件名、目录名或卷标语法不正确。)

问题分析

错误路径格式

复制代码
file:\\D:\\Program%20Files\\tgerp-cloud-server\\tgerp-modules-workaffairs.jar!\\BOOT-INF\\classes!\\template\\stamp.svg

问题

  1. 双重"!"分隔符jar!\\BOOT-INF\\classes!\\template\\stamp.svg不正确
  2. URL编码问题%20未正确解码为空格
  3. 协议格式错误 :应该是jar:file:///格式

正确路径格式

复制代码
jar:file:///D:/Program%20Files/tgerp-cloud-server/tgerp-modules-workaffairs.jar!/BOOT-INF/classes/template/stamp.svg

解决方案

方案1:使用ClassPathResource(推荐)
java 复制代码
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.InputStream;

// 读取JAR包内的资源
Resource resource = new ClassPathResource("template/stamp.svg");
InputStream inputStream = resource.getInputStream();

// 如果不在类路径根目录,使用相对路径
Resource resource2 = new ClassPathResource("static/template/stamp.svg");
方案2:使用类加载器
java 复制代码
// 获取资源流
InputStream inputStream = getClass().getClassLoader()
    .getResourceAsStream("template/stamp.svg");

// 获取资源URL
URL resourceUrl = getClass().getClassLoader()
    .getResource("template/stamp.svg");
方案3:修复文件路径(如果必须使用文件系统路径)
java 复制代码
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

// 解码URL编码的路径
String decodedPath = URLDecoder.decode("D:\\Program%20Files\\tgerp-cloud-server", 
    StandardCharsets.UTF_8.name());

// 使用正确的路径分隔符
String correctPath = decodedPath.replace("\\", "/") + 
    "/tgerp-modules-workaffairs.jar";

// 构建正确的JAR URL
String jarUrl = "jar:file:///" + correctPath + "!/BOOT-INF/classes/template/stamp.svg";
方案4:Spring Boot资源加载最佳实践
java 复制代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;

@Component
public class ResourceLoaderService {
    
    @Value("classpath:template/stamp.svg")
    private Resource stampResource;
    
    @PostConstruct
    public void loadResource() throws IOException {
        try (InputStream is = stampResource.getInputStream()) {
            // 处理资源
            // ...
        }
    }
    
    // 或者使用ResourceLoader
    @Autowired
    private ResourceLoader resourceLoader;
    
    public void loadResourceDynamic(String path) throws IOException {
        Resource resource = resourceLoader.getResource("classpath:" + path);
        // 使用资源...
    }
}

预防措施

  1. 统一资源加载方式 :在Spring Boot项目中统一使用ClassPathResourceResourceLoader
  2. 路径标准化 :使用Paths.get()toUri()方法处理路径
  3. 编码处理:始终对路径进行URL编码/解码处理
  4. 错误处理:添加适当的异常处理机制
java 复制代码
public class ResourceUtils {
    
    public static InputStream loadResource(String classpath) throws IOException {
        Resource resource = new ClassPathResource(classpath);
        if (!resource.exists()) {
            throw new FileNotFoundException("Resource not found: " + classpath);
        }
        return resource.getInputStream();
    }
    
    public static String getResourcePath(String classpath) {
        try {
            URL url = ResourceUtils.class.getClassLoader().getResource(classpath);
            return url != null ? url.getPath() : null;
        } catch (Exception e) {
            return null;
        }
    }
}

总结

问题 原因 解决方案
NoClassDefFoundError 依赖缺失或版本冲突 添加正确依赖,检查依赖树
Connection refused 网络连接问题 检查网络、防火墙、端口访问权限
FileNotFoundException JAR内资源路径格式错误 使用ClassPathResource或类加载器读取资源

最佳实践建议

  1. 使用Maven/Gradle依赖管理,避免版本冲突
  2. 网络连接配置适当的超时和重试机制
  3. 资源加载统一使用Spring的Resource API
  4. 完善的异常处理和日志记录
相关推荐
侠客行031710 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪10 小时前
深入浅出LangChain4J
java·langchain·llm
老毛肚12 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎12 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Yvonne爱编码12 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚12 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂12 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
fuquxiaoguang12 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
琹箐13 小时前
最大堆和最小堆 实现思路
java·开发语言·算法
__WanG13 小时前
JavaTuples 库分析
java