若依前后端分离框架——生成数字验证码功能源码学习

使用版本:Ruo-Vue 3.8.0

项目地址:https://gitee.com/y_project/RuoYi-Vue

0.1:验证码生成流程图

流程图

0.2:验证码生成时序图

时序图

0.3:验证码的配置项

代码位置:com.google.code.kaptcha.Constants

public class Constants
{
  // session key
  public final static String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
  // session date
  public final static String KAPTCHA_SESSION_DATE = "KAPTCHA_SESSION_DATE";
  
  public final static String KAPTCHA_SESSION_CONFIG_KEY = "kaptcha.session.key";

  public final static String KAPTCHA_SESSION_CONFIG_DATE = "kaptcha.session.date";
  // 是否有边框 默认为true 我们可以自己设置yes,no
  public final static String KAPTCHA_BORDER = "kaptcha.border";
  // 边框颜色 默认为Color.BLACK
  public final static String KAPTCHA_BORDER_COLOR = "kaptcha.border.color";
  // 边框宽度
  public final static String KAPTCHA_BORDER_THICKNESS = "kaptcha.border.thickness";
  // 验证码噪点颜色 默认为Color.BLACK
  public final static String KAPTCHA_NOISE_COLOR = "kaptcha.noise.color";
  // 干扰实现类
  public final static String KAPTCHA_NOISE_IMPL = "kaptcha.noise.impl";
  // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
  public final static String KAPTCHA_OBSCURIFICATOR_IMPL = "kaptcha.obscurificator.impl";
  // 图片实现类
  public final static String KAPTCHA_PRODUCER_IMPL = "kaptcha.producer.impl";
  // 验证码文本生成器
  public final static String KAPTCHA_TEXTPRODUCER_IMPL = "kaptcha.textproducer.impl";
  // 文本集合,验证码值从此集合中获取
  public final static String KAPTCHA_TEXTPRODUCER_CHAR_STRING = "kaptcha.textproducer.char.string";
  // 验证码文本字符长度 默认为5
  public final static String KAPTCHA_TEXTPRODUCER_CHAR_LENGTH = "kaptcha.textproducer.char.length";
  // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
  public final static String KAPTCHA_TEXTPRODUCER_FONT_NAMES = "kaptcha.textproducer.font.names";
  // 验证码文本字符颜色 默认为Color.BLACK
  public final static String KAPTCHA_TEXTPRODUCER_FONT_COLOR = "kaptcha.textproducer.font.color";
  // 验证码文本字符大小 默认为40
  public final static String KAPTCHA_TEXTPRODUCER_FONT_SIZE = "kaptcha.textproducer.font.size";
  // 验证码文本字符间距 默认为2
  public final static String KAPTCHA_TEXTPRODUCER_CHAR_SPACE = "kaptcha.textproducer.char.space";
  // 文字渲染
  public final static String KAPTCHA_WORDRENDERER_IMPL = "kaptcha.word.impl";
  // 背景实现类
  public final static String KAPTCHA_BACKGROUND_IMPL = "kaptcha.background.impl";
  // 背景颜色渐变,开始颜色
  public static final String KAPTCHA_BACKGROUND_CLR_FROM = "kaptcha.background.clear.from";
  // 背景颜色渐变,结束颜色
  public static final String KAPTCHA_BACKGROUND_CLR_TO = "kaptcha.background.clear.to";
  // 验证码图片宽度 默认为200
  public static final String KAPTCHA_IMAGE_WIDTH = "kaptcha.image.width";
  // 验证码图片高度 默认为50
  public static final String KAPTCHA_IMAGE_HEIGHT = "kaptcha.image.height";
}

1:获取验证码的入口

代码位置:com.ruoyi.web.controller.common.CaptchaController#getCode

@Resource(name = "captchaProducer")
private Producer captchaProducer;

@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;

@Autowired
private RedisCache redisCache;

@Autowired
private ISysConfigService configService;
/**
 * 生成验证码
 */
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException
{
    // 是否需要生成验证码
    AjaxResult ajax = AjaxResult.success();
    // 
    boolean captchaOnOff = configService.selectCaptchaOnOff();
    ajax.put("captchaOnOff", captchaOnOff);
    if (!captchaOnOff)
    {
        return ajax;
    }

    String uuid = IdUtils.simpleUUID();
    String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

    String capStr = null, code = null;
    BufferedImage image = null;

    // 生成验证码
    String captchaType = RuoYiConfig.getCaptchaType();
    if ("math".equals(captchaType))
    {
        String capText = captchaProducerMath.createText();
        capStr = capText.substring(0, capText.lastIndexOf("@"));
        code = capText.substring(capText.lastIndexOf("@") + 1);
        image = captchaProducerMath.createImage(capStr);
    }
    else if ("char".equals(captchaType))
    {
        capStr = code = captchaProducer.createText();
        image = captchaProducer.createImage(capStr);
    }
    // 保存验证码到redis
    redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
    // 构造需要返回的数据
    FastByteArrayOutputStream os = new FastByteArrayOutputStream();
    try
    {
        ImageIO.write(image, "jpg", os);
    }
    catch (IOException e)
    {
        return AjaxResult.error(e.getMessage());
    }

    ajax.put("uuid", uuid);
    ajax.put("img", Base64.encode(os.toByteArray()));
    return ajax;
}

这里重点研究如何生成验证码

String captchaType = RuoYiConfig.getCaptchaType();
if ("math".equals(captchaType))
{
    // 在这里若依框架使用了自定义的类来生成验证码
    String capText = captchaProducerMath.createText();
    capStr = capText.substring(0, capText.lastIndexOf("@"));
    code = capText.substring(capText.lastIndexOf("@") + 1);
    image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
    // 在这里若依框架使用了自定义的类来生成验证码
    capStr = code = captchaProducer.createText();
    image = captchaProducer.createImage(capStr);
}

以下是对自定义生成验证码类进行分析

1.1:自定义captchaProducerMath

代码位置:com.ruoyi.framework.config.CaptchaConfig#getKaptchaBeanMath

@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath()
{
    DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
    Properties properties = new Properties();
    // 是否有边框 默认为true 我们可以自己设置yes,no
    properties.setProperty(KAPTCHA_BORDER, "yes");
    // 边框颜色 默认为Color.BLACK
    properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
    // 验证码文本字符颜色 默认为Color.BLACK
    properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
    // 验证码图片宽度 默认为200
    properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
    // 验证码图片高度 默认为50
    properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
    // 验证码文本字符大小 默认为40
    properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
    // KAPTCHA_SESSION_KEY
    properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
    // 验证码文本生成器【1】
    properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");
    // 验证码文本字符间距 默认为2
    properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
    // 验证码文本字符长度 默认为5
    properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
    // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
    properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
    // 验证码噪点颜色 默认为Color.BLACK
    properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
    // 干扰实现类
    properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
    // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
    properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
    Config config = new Config(properties);
    //【2】
    defaultKaptcha.setConfig(config);
    return defaultKaptcha;
}
  • 【1】这里使用了自定义的验证码文本生成器,以下为自定义的验证码文本生成器的源码
  • 【2】在这里使用反射的方式将自定义的文本生成器反射到DefaultKaptcha里

1.2:自定义的文本生成器

代码位置:com.ruoyi.framework.config.KaptchaTextCreator

/**
 * 验证码文本生成器
 * 
 * @author ruoyi
 */
 // 【1】
public class KaptchaTextCreator extends DefaultTextCreator
{
    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");

    @Override
    public String getText()
    {
        Integer result = 0;
        Random random = new Random();
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        StringBuilder suChinese = new StringBuilder();
        // 【2】
        int randomoperands = (int) Math.round(Math.random() * 2);
        if (randomoperands == 0)
        {
            // 【3】
            result = x * y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("*");
            suChinese.append(CNUMBERS[y]);
        }
        else if (randomoperands == 1)
        {
            // 【4】
            if (!(x == 0) && y % x == 0)
            {
                // 【5】
                result = y / x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("/");
                suChinese.append(CNUMBERS[x]);
            }
            else
            {
                // 【6】
                result = x + y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("+");
                suChinese.append(CNUMBERS[y]);
            }
        }
        else if (randomoperands == 2)
        {
            // 【7】
            if (x >= y)
            {
                // 【8】
                result = x - y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[y]);
            }
            else
            {
                // 【9】
                result = y - x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[x]);
            }
        }
        else
        {
            // 【10】
            result = x + y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("+");
            suChinese.append(CNUMBERS[y]);
        }
        suChinese.append("=?@" + result);
        return suChinese.toString();
    }
}
  • 【1】:需要继承DefaultTextCreator类
  • 【2】:Math.round会取到最接近的整数,所以Math.round(Math.random() * 2)的结果只会是0、1、2
  • 【3】【4】【7】【10】:做了一个小测试,这四个分支的的占比大约是25:50:25:0
  • 【5】【6】:这两个分支的占比大约是16:34
  • 【8】【9】:这两个分支的占比大约是14:11
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leo的心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值