告别踩坑!若依图片验证码实现与改造指南

 若依(RuoYi)框架中图片验证码的完整实现逻辑、核心代码和定制化方法,我会从实现原理、核心代码拆解、关键配置、定制化改造四个维度讲解,覆盖验证码生成、存储、校验全流程,让你既能理解原理,也能按需修改验证码样式 / 规则。

若依最新前后端分离源码下载:

https://gitee.com/ruoyieleadmin/ruoyi-ele-admin

一、图片验证码核心实现原理

若依框架的图片验证码基于 Java 图形绘制 + Redis 存储 + 前端交互 实现,核心流程如下:


核心关键点:

二、核心代码拆解(若依源码级解析)

若依的验证码功能集中在 ruoyi-common 模块的 captcha 包下,核心类如下:

1. 验证码生成核心类(CaptchaUtil.java)

负责生成随机字符、绘制验证码图片,是核心工具类:

java

package com.ruoyi.common.utils.captcha;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * 若依框架验证码生成工具类
 */
public class CaptchaUtil {
    // 验证码字符集(排除易混淆字符:0/O、1/I等)
    private static final char[] CODE_CHAR = { '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
            'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
    // 验证码宽度/高度
    private static final int WIDTH = 110;
    private static final int HEIGHT = 38;
    // 验证码字符个数
    private static final int CODE_COUNT = 4;
    // 干扰线数量
    private static final int LINE_COUNT = 150;

    /**
     * 生成验证码图片+字符
     * @return CaptchaResult:包含验证码字符、图片BufferedImage
     */
    public static CaptchaResult generate() {
        // 1. 创建图片缓冲区
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        
        // 2. 设置背景色(白色)
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, WIDTH, HEIGHT);
        // 设置字体(加粗、倾斜,增加识别难度)
        g.setFont(new Font("Arial", Font.BOLD | Font.ITALIC, 24));

        // 3. 生成随机验证码字符
        Random random = new Random();
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < CODE_COUNT; i++) {
            char c = CODE_CHAR[random.nextInt(CODE_CHAR.length)];
            code.append(c);
            // 绘制字符(随机颜色、位置、旋转角度)
            g.setColor(new Color(random.nextInt(200), random.nextInt(200), random.nextInt(200)));
            // 字符旋转(-30° ~ 30°)
            g.rotate(Math.toRadians(random.nextInt(60) - 30), 25 * i + 10, HEIGHT / 2);
            g.drawString(String.valueOf(c), 25 * i + 10, HEIGHT / 2);
            // 旋转复位
            g.rotate(-Math.toRadians(random.nextInt(60) - 30), 25 * i + 10, HEIGHT / 2);
        }

        // 4. 绘制干扰线(随机颜色、位置)
        for (int i = 0; i < LINE_COUNT; i++) {
            g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g.drawLine(random.nextInt(WIDTH), random.nextInt(HEIGHT),
                       random.nextInt(WIDTH), random.nextInt(HEIGHT));
        }

        // 5. 绘制噪点(增加干扰)
        for (int i = 0; i < WIDTH * HEIGHT / 20; i++) {
            g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g.fillRect(random.nextInt(WIDTH), random.nextInt(HEIGHT), 1, 1);
        }

        // 6. 释放资源
        g.dispose();

        // 返回结果:验证码字符、图片
        return new CaptchaResult(code.toString(), image);
    }

    /**
     * 验证码结果封装类
     */
    public static class CaptchaResult {
        private String code; // 验证码字符
        private BufferedImage image; // 验证码图片

        // 构造器、getter/setter
        public CaptchaResult(String code, BufferedImage image) {
            this.code = code;
            this.image = image;
        }
        // ... 省略getter/setter
    }
} 

2. 验证码接口实现(CaptchaController.java)

提供前端访问的验证码接口,生成图片并存储到 Redis:

java

package com.ruoyi.web.controller.common;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.captcha.CaptchaUtil;
import com.ruoyi.common.utils.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 若依验证码接口控制器
 */
@RestController
public class CaptchaController {
    @Autowired
    private RedisCache redisCache;

    /**
     * 获取验证码接口
     * 前端访问:/captchaImage
     */
    @GetMapping("/captchaImage")
    public AjaxResult getCaptchaImage() {
        // 1. 生成验证码字符+图片
        CaptchaUtil.CaptchaResult captchaResult = CaptchaUtil.generate();
        String captchaCode = captchaResult.getCode();
        // 2. 生成验证码UUID(作为Redis的key)
        String uuid = UUID.randomUUID().toString().replace("-", "");
        // 3. 存入Redis,设置过期时间(2分钟)
        redisCache.setCacheObject("captcha_codes:" + uuid, captchaCode, 2, TimeUnit.MINUTES);
        
        // 4. 将图片转为Base64编码(返回前端展示)
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            ImageIO.write(captchaResult.getImage(), "jpg", outputStream);
            String imageBase64 = Base64.getEncoder().encodeToString(outputStream.toByteArray());
            // 5. 返回结果:UUID、Base64图片(前端用<img src="data:image/jpeg;base64,...">展示)
            return AjaxResult.success()
                    .put("uuid", uuid)
                    .put("img", imageBase64);
        } catch (Exception e) {
            return AjaxResult.error("验证码生成失败");
        }
    }
} 

3. 验证码校验逻辑(登录接口示例)

用户登录时校验验证码,核心代码在 LoginController.java

java

/**
 * 登录接口验证码校验
 */
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {
    AjaxResult ajax = AjaxResult.success();
    // 1. 获取前端传入的UUID和验证码
    String uuid = loginBody.getUuid();
    String code = loginBody.getCode();
    
    // 2. 从Redis获取正确的验证码
    String captchaCode = redisCache.getCacheObject("captcha_codes:" + uuid);
    // 3. 校验验证码(忽略大小写)
    if (captchaCode == null || !captchaCode.equalsIgnoreCase(code)) {
        return AjaxResult.error("验证码错误");
    }
    
    // 4. 校验通过,删除Redis中的验证码(避免复用)
    redisCache.deleteObject("captcha_codes:" + uuid);
    
    // 5. 执行后续登录逻辑...
    return ajax;
} 

三、关键配置与注意事项

1. 核心配置(无需额外配置,若依默认支持)

2. 安全性注意事项

四、定制化改造(按需调整)

1. 修改验证码样式

2. 切换验证码类型(如算术验证码、滑块验证码)

若依默认是字符验证码,可扩展为算术验证码(适合低安全场景):

java

// 算术验证码生成逻辑(替换CaptchaUtil.generate()中的字符生成部分)
String num1 = String.valueOf(random.nextInt(100));
String num2 = String.valueOf(random.nextInt(100));
String operator = "+"; // 可随机+、-、×
int result = Integer.parseInt(num1) + Integer.parseInt(num2);
String code = num1 + operator + num2 + "=?"; // 验证码显示:99+88=?
// 存储到Redis的是结果:187
redisCache.setCacheObject("captcha_codes:" + uuid, String.valueOf(result), 2, TimeUnit.MINUTES); 

3. 对接第三方验证码(如极验、阿里云验证码)

若需更高安全级别的验证码(滑块、点选),可替换原生实现:

  1. 引入第三方验证码 SDK(如极验:geetest-sdk-java);
  2. 修改 CaptchaController,调用第三方接口生成验证码参数;
  3. 登录接口中调用第三方校验接口,替换 Redis 校验逻辑。

五、常见问题及解决方法

1. 验证码图片无法显示

2. 验证码校验一直失败

3. 验证码生成性能低

总结

若依框架图片验证码的核心实现逻辑:

  1. 生成:通过 Java 图形 API 绘制带干扰的字符图片,生成随机字符;
  2. 存储:字符存入 Redis(UUID 为 key,设置短过期时间),图片转 Base64 返回前端;
  3. 校验:登录时通过 UUID 从 Redis 读取字符,与用户输入对比,校验后删除;
  4. 定制:可调整样式、切换验证码类型,或对接第三方高安全验证码。

核心优化点:生产环境需添加接口限流、防刷新机制,高并发场景可考虑异步生成或对接第三方验证码服务。

若依最新前后端分离源码下载:

https://gitee.com/ruoyieleadmin/ruoyi-ele-admin