Spring Boot 3 升级踩坑:aj-captcha 行为验证码底图加载失效

项目从 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 若未适配新版本,手动补全配置一定要对照原始自动装配类的完整逻辑,不能只复制属性转换部分,容易遗漏关键的初始化步骤。

相关推荐
Zane19946 小时前
自定义异常该继承 Exception 还是 RuntimeException,就看这一个问题
java·后端
西峰u6 小时前
Java多线程初阶完整总结|线程、锁、volatile、等待通知、常见案例
java·开发语言·jvm
风曳丷7 小时前
10|攻击如何跨越 Context、阶段与时间
后端
tyung7 小时前
znet 数据编解码:字节流怎么变成消息
后端·网络协议·go
爱喝可乐的中登7 小时前
SpringBoot 云边协同|智慧地铁 ISCS 改造实战第 10 篇:全网权限与数据隔离重构|线路‑车站‑专业三级 RBAC、边缘数据权限下沉
java·spring boot·重构
落魄实习生7 小时前
Spring AI Alibaba入门-生态集成
java·人工智能·spring
码视野7 小时前
基于 Spring Boot + Vue3 的【高校化学实验室安全准入考试与危化品配伍排查系统】设计与实现(含PRD/三端高保真源码/大屏)
前端·人工智能·spring boot·后端·安全·vue3
重生之我是Java开发战士7 小时前
【Java EE】认识Linux与项目部署
java·linux·java-ee
LXMXHJ7 小时前
springboot项目测试
java·spring boot·后端·测试
程序员雪球7 小时前
本地IDEA打断点debug容器
java·开发语言