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

相关推荐
雪碧5131 小时前
基于afsim的训练多个智能体算法控制(持续关注和收藏后续会同步整个源码包)
后端
保加利亚的风1 小时前
Docker 学习文档(Mac + Docker Desktop 版)
前端·后端
带刺的坐椅1 小时前
Solon I18n:三种解析器,零样板代码
java·国际化·i18n·solon·多语言
刘名喜1 小时前
第30篇-Spring-Security-7核心概念
后端·kotlin·springboot
掉鱼的猫1 小时前
Solon I18n:三种解析器,零样板代码
java
明月_清风2 小时前
🚀 AI Agent 完全入门指南:从 LLM 到生产落地,新手必懂的 34 个核心概念
前端·后端·ai编程
程序员阿明2 小时前
spring boot3访问resources下的文件,使得在浏览器url中可以直接访问
java·spring boot·后端
掘金者阿豪2 小时前
MongoDB迁移不想重写代码?一次国产数据库替换踩坑记录
后端