Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
问题背景
在一个基于 Spring Cloud Gateway + WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code
,使用函数式路由(RouterFunction
)和 Hutool 的 CircleCaptcha
生成验证码图片。然而在部署上线后,访问该接口始终返回 404
,而其他网关转发接口均正常。
该接口定义如下:
@Configuration
public class RouterFunctionConfiguration {
@Autowired
private ValidateCodeHandler validateCodeHandler;
@Bean
public RouterFunction<?> routerFunction() {
return RouterFunctions.route(
RequestPredicates.GET("/code")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
validateCodeHandler);
}
}
初步排查思路
起初怀疑为以下常见问题:
Accept: text/plain
请求头不匹配;- 路由被
gateway.routes
中/code/**
路由转发覆盖; RouterFunction
Bean 未生效或未注入;ValidateCodeHandler
写法错误;- 实际监听端口与预期不符(注册到 Nacos 的端口为 8080)。
经多项验证后发现,这些都不是直接原因。
真正的原因:JVM 图形字体系统缺失导致验证码生成失败
查看日志后,发现关键错误如下:
java.lang.NoClassDefFoundError: Could not initialize class sun.font.SunFontManager
at sun.font.FontDesignMetrics.getMetrics(FontDesignMetrics.java:264)
...
at cn.hutool.captcha.CircleCaptcha.createImage(...)
Hutool 的 CircleCaptcha
使用 Java AWT 图形库渲染验证码,而 AWT 依赖系统字体和图形环境支持。在当前使用的 Docker 镜像 openjdk:8-jre-slim
中,字体系统缺失,导致:
- JVM 无法初始化字体管理器;
Captcha.createImage()
方法抛出NoClassDefFoundError
;- WebFlux 返回 500,但表现为
/code
接口 404(无默认异常处理器); - 外部看起来像是“接口路由失效”。
解决方案
方案一:更换基础镜像(推荐)
将 Dockerfile 中基础镜像改为完整版本:
FROM openjdk:8-jre # 或 openjdk:8-jdk
该版本包含 AWT 和字体支持,开箱即用。
方案二:在 slim 镜像中安装字体依赖
如果仍想使用 slim
镜像,可在构建中添加字体:
RUN apt-get update && apt-get install -y fontconfig ttf-dejavu
或者更小:
RUN apt-get update && apt-get install -y fonts-dejavu-core
这会安装 JVM 字体渲染所需的核心字体文件和环境。
方案三:替换验证码实现逻辑
如果无需图形验证码,可以:
- 改用字符验证码;
- 替换为不依赖 AWT 的实现;
- 或直接生成文本验证码。
验证建议
1. 使用 curl
添加请求头进行测试:
curl -H "Accept: text/plain" http://localhost:8001/code
2. 日志开启 Web 路由匹配调试:
logging:
level:
org.springframework.web: DEBUG
总结
问题 | 原因 |
---|---|
/code 接口 404 |
实际是内部抛出 NoClassDefFoundError ,未进入 handler |
报错类 | sun.font.SunFontManager 无法初始化 |
根本原因 | Docker 镜像缺少字体渲染环境 |
解决方式 | 更换镜像或安装字体 |
建议
在使用 Hutool、Captcha、或任何基于 AWT 的组件时,务必检查部署环境是否具备必要图形支持。特别是在 Docker 化部署中,openjdk-slim
镜像虽小,但常缺关键功能,适用场景需慎重评估。