项目从 Spring Boot 2.2.6 升级到 3.4.5 后,aj-captcha 1.3.0 验证码接口返回
repCode: 6113,提示"底图未初始化成功,请检查路径"。本文记录排查过程与解决方案。
一、问题现象
调用验证码接口:
POST /api/public/safety/get
{"captchaType":"blockPuzzle","ts":1785983564105}
返回结果:
json
{
"code": 200,
"message": "操作成功",
"data": {
"repCode": "6113",
"repMsg": "底图未初始化成功,请检查路径",
"repData": null,
"success": false
}
}

图片资源确实存在于 resources/images/jigsaw/ 下,配置也写了 jigsaw: classpath:images/jigsaw,但底图缓存为空。
二、根因分析
2.1 aj-captcha 加载底图的两条路径
反编译 ImageUtils.cacheImage() 发现,它根据配置路径是否为空走不同逻辑:
| 条置 | 加载方式 | 说明 |
|---|---|---|
| 路径为空 | getResourcesImagesFile("defaultImages/jigsaw/original") |
从 jar 内加载默认图片 |
| 路径非空 | getImagesFile(path + "/original") |
按文件系统 加载 new File(path) |
项目配置了 classpath:images/jigsaw,路径非空,走第二条。而 getImagesFile 内部执行 new File("classpath:images/jigsaw/original") ------ 把 classpath: 前缀当成了物理目录名,文件不存在,返回空 Map,缓存为空。
2.2 starter 原本的补救机制
在 Spring Boot 2 下,starter 的 AjCaptchaServiceAutoConfiguration 会自动装配,其中有一段关键逻辑 initializeBaseMap():
java
// starter 原始流程(Spring Boot 2 自动装配)
if (jigsaw.startsWith("classpath:")) {
config.put("captcha.init.original", "true");
initializeBaseMap(jigsaw, picClick); // ← 关键:预加载 classpath 资源
}
CaptchaServiceFactory.getInstance(config);
initializeBaseMap 使用 Spring 的 PathMatchingResourcePatternResolver 按通配符 classpath:images/jigsaw/original/*.png 加载图片,调用 ImageUtils.cacheBootImage() 预填充缓存。之后即使 cacheImage 文件系统加载失败(空 Map),putAll(emptyMap) 也不会清空已有数据。
2.3 升级后自动装配失效
Spring Boot 3 废弃了 spring.factories 自动装配机制,改用 AutoConfiguration.imports。aj-captcha 1.3.0 未适配 Spring Boot 3,自动装配不生效。
开发者手动创建了 AjCaptchaConfig,但只复制了属性转换(toProperties),遗漏了 initializeBaseMap 预加载步骤:
starter 原始流程: initializeBaseMap() → cacheBootImage() → getInstance()
手动配置流程: (缺失) → (缺失) → getInstance()
缓存始终为空,接口报 6113。
三、解决方案
在 AjCaptchaConfig.captchaService() 中,CaptchaServiceFactory.getInstance() 之前补全 initializeBaseMap 逻辑:
java
@Bean
public CaptchaService captchaService(AjCaptchaProperties properties, StringRedisTemplate stringRedisTemplate) {
CaptchaCacheServiceRedisImpl.setRedisTemplate(stringRedisTemplate);
// 补全:classpath 资源预加载(复刻 starter 的 initializeBaseMap 逻辑)
if (isClasspathResource(properties.getJigsaw()) || isClasspathResource(properties.getPicClick())) {
initializeBaseMap(properties.getJigsaw(), properties.getPicClick());
}
return CaptchaServiceFactory.getInstance(toProperties(properties));
}
/**
* 预加载 classpath 底图到 ImageUtils 缓存
* <p>复刻 starter 的 AjCaptchaServiceAutoConfiguration.initializeBaseMap 逻辑</p>
*/
private void initializeBaseMap(String jigsaw, String picClick) {
try {
Map<String, String> originalMap = loadClasspathImages(jigsaw + "/original/*.png");
Map<String, String> slidingBlockMap = loadClasspathImages(jigsaw + "/slidingBlock/*.png");
Map<String, String> picClickMap = loadClasspathImages(picClick + "/*.png");
ImageUtils.cacheBootImage(originalMap, slidingBlockMap, picClickMap);
} catch (Exception e) {
log.error("验证码底图预加载失败", e);
throw new RuntimeException("验证码底图预加载失败", e);
}
}
/**
* 使用 PathMatchingResourcePatternResolver 加载 classpath 通配符图片资源
*
* @param pattern 资源路径通配符,如 classpath:images/jigsaw/original/*.png
* @return 文件名 → Base64 编码的 Map
*/
private Map<String, String> loadClasspathImages(String pattern) throws Exception {
Map<String, String> result = new HashMap<>();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(pattern);
for (Resource resource : resources) {
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
String base64 = Base64Utils.encodeToString(bytes);
result.put(resource.getFilename(), base64);
}
return result;
}
四、总结
| 维度 | 说明 |
|---|---|
| 根本原因 | Spring Boot 3 废弃 spring.factories,aj-captcha 1.3.0 未适配,自动装配失效 |
| 直接原因 | 手动配置遗漏了 initializeBaseMap 预加载步骤,classpath 资源未被加载 |
| 隐蔽点 | cacheImage 对 classpath 路径走文件系统加载,静默返回空 Map,不报错 |
| 修复方式 | 在创建 CaptchaService 前补全 PathMatchingResourcePatternResolver 预加载逻辑 |
教训 :升级框架大版本时,第三方 starter 若未适配新版本,手动补全配置一定要对照原始自动装配类的完整逻辑,不能只复制属性转换部分,容易遗漏关键的初始化步骤。