RuoYi-Vue源码阅读(一):验证码模块

1 验证码模块实现思路

  1. 首先检查是否启用了验证码功能,如果没有启用,则直接返回一个成功的AjaxResult对象;
  2. 生成一个唯一的UUID,并将其与验证码关联起来,以便后续验证;
  3. 根据系统配置的验证码类型(数学表达式或字符),生成相应的验证码和图片;
  4. 如果验证码类型是数学表达式,后端会随机生成一个固定的表达式比如(2*2=?@4),通过字符串符分割的方式,获得对应的题目和答案,后端会通过该题目生成对应的验证码图, 并将答案存放到redis中;
  5. 如果验证码类型是字符,后端会随机生成一个字符验证码文本,通过该文本生成对应的验证码图,并将验证码存放到redis中;
  6. 将生成的验证码图片转换为Base64编码的字符串,并将其放入AjaxResult对象中;
  7. 将UUID和Base64编码的验证码图片字符串返回给客户端;
  8. 前端通过调用后端api传入用户输入值和之前的key值,最终这个api查询redis,判断redis中的value是否和用户输入的值一样,最终实现验证码。

验证码模块逻辑流程图

2 后端的实现步骤

验证码操作处理代码位于com.ruoyi.web.controller.common包下的CaptchaCotroller.java内

验证码操作处理
生成验证码的方法如下:

生成验证码

2.1 是否开启验证码模块

首先创建一个返回成功消息的AjaxResult对象;再检查是否启用验证码功能,将验证码启用状态添加到AjaxResult对象中;如果验证码功能未启用,则直接返回AjaxResult对象。

AjaxResult ajax = AjaxResult.success(); // 创建一个成功的AjaxResult对象
boolean captchaEnabled = configService.selectCaptchaEnabled();  // 检查是否启用了验证码功能
ajax.put("captchaEnabled", captchaEnabled); // 将验证码启用状态添加到AjaxResult对象中
if (!captchaEnabled)
{
     return ajax;    // 如果验证码功能未启用,则直接返回AjaxResult对象
}

按Ctrl键再点入selectCaptchaEnabled()函数,该函数的作用是根据配置来决定是否启用验证码功能。

    /**
     * 获取验证码开关
     * 
     * @return true开启,false关闭
     */
    @Override
    public boolean selectCaptchaEnabled()
    {
        String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled");
        if (StringUtils.isEmpty(captchaEnabled))
        {
            return true;
        }
        return Convert.toBool(captchaEnabled);
    }

其代码逻辑如下:

  • 首先,它尝试从配置中获取一个名为"sys.account.captchaEnabled"的键值对。

  • 如果这个键在配置中不存在(即StringUtils.isEmpty(captchaEnabled)返回true),那么默认启用验证码功能,方法返回true。

  • 如果这个键在配置中存在,那么将获取到的字符串转换为布尔值并返回。这里使用了Convert.toBool方法,这个方法的具体实现没有给出,但从方法名来看,它可能是用来将字符串转换为布尔值的。

按Ctrl键再点入selectConfigByKey()函数,该方法的目的是为了从缓存或数据库中获取配置信息,并将其存入缓存以便下次使用。如果配置信息不存在,则返回一个空字符串。

    /**
     * 根据键名查询参数配置信息
     * 
     * @param configKey 参数key
     * @return 参数键值
     */
    @Override
    public String selectConfigByKey(String configKey)
    {
        String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
        if (StringUtils.isNotEmpty(configValue))
        {
            return configValue;
        }
        SysConfig config = new SysConfig();
        config.setConfigKey(configKey);
        SysConfig retConfig = configMapper.selectConfig(config);
        if (StringUtils.isNotNull(retConfig))
        {
            redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
            return retConfig.getConfigValue();
        }
        return StringUtils.EMPTY;
    }

其代码逻辑如下:

  • 首先,它尝试从Redis缓存中获取名为configKey的配置信息。getCacheKey(configKey)方法用于生成缓存键,redisCache.getCacheObject(getCacheKey(configKey))从Redis缓存中获取对应的值。

  • 如果缓存中存在配置信息,则将其转换为字符串并返回。StringUtils.isNotEmpty(configValue)检查转换后的字符串是否不为空。

  • 如果缓存中不存在配置信息,则从数据库中查询。configMapper.selectConfig(config)方法执行数据库查询,并返回一个SysConfig对象。

  • 如果数据库查询成功,且返回的SysConfig对象不为null,则将其配置值存入Redis缓存,并返回配置值。redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue())将配置值存入Redis缓存。

  • 如果数据库中也不存在配置信息,则返回一个空字符串。StringUtils.EMPTY是一个表示空字符串的常量。

2.2 保存验证码信息

通过id生成器工具类生成一个唯一的UUID,并将UUID与验证码关联起来,这里生成的UUID就是Redis里的key

        // 保存验证码信息
        String uuid = IdUtils.simpleUUID(); // 通过id生成器工具类生成一个唯一的UUID
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;  // 将UUID与验证码关联起来

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

2.3 生成验证码

  1. 首先通过获取getCaptchaType()方法获取系统配置的验证码类型;
  2. 如果验证码类型为数学表达式,通过Google的验证码生成工具kaptcha的createText()方法生成数学表达式的验证码文本,该文本是一个类似于(2*2=?@4)的字符串,@符前面是题目,@符后面是对应的答案,通过字符串符分割的方式,获得对应的题目和答案,通过Google的验证码生成工具kaptcha的createImage()方法生成数学表达式的验证码图片;
  3. 如果验证码类型为字符,通过Google的验证码生成工具kaptcha的createText()方法生成字符验证码文本,再通过kaptcha的createImage()方法生成字符验证码图片;
  4. 将生成的验证码存入Redis缓存中,设置过期时间为Constants.CAPTCHA_EXPIRATION,时间单位是TimeUnit.MINUTES。
        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);    // 生成字符验证码图片
        }

        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); 

2.4 转换成流写出信息

  1. 首先创建字节输出流FastByteArrayOutputStream,通过Java标准库里的ImageIO类的write()方法将验证码图片转换成字节流;
  2. 如果转换过程中出现异常,则返回一个包含错误信息的AjaxResult对象;
  3. 如果没有出现异常,将UUID和经过Base64编码的验证码图片字符串信息添加到AjaxResult对象并返回该对象。
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
     ImageIO.write(image, "jpg", os);    // 将验证码图片转换为字节流
}
catch (IOException e)
{
      return AjaxResult.error(e.getMessage());    // 如果转换过程中出现异常,则返回一个包含错误信息的AjaxResult对象
}

ajax.put("uuid", uuid); // 将UUID添加到AjaxResult对象中
ajax.put("img", Base64.encode(os.toByteArray()));   // 将Base64编码的验证码图片字符串添加到AjaxResult对象中
return ajax;    // 返回AjaxResult对象

3 前端的实现步骤

在页面初始化时调用自定义方法 getCode():

getCode方法
点击进入getCode()方法,该方法的主要作用是获取验证码图片,并将其显示在前端界面上,同时保存对应的key即UUID以便后续验证码的验证。

    getCode() {
      getCodeImg().then(res => {
        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    }

其代码逻辑如下:

  1. 调用getCodeImg方法,这个方法返回一个Promise,因此可以使用.then()来处理异步结果;
  2. 检查返回的响应中是否包含captchaEnabled字段,如果没有定义则默认为true;
  3. 如果验证码功能已启用,将返回的base64编码的图片数据转换为可以直接在前端显示的格式,将返回的uuid保存在组件的状态中,这个uuid可能用于后续的验证码验证.

自定义的getCodeImage的方法如下:

getCodeImage方法
在该方法中调用了自定义方法request,该方法就是使用axios实现ajax


在该request.js中设置的了前端请求的共部分 VUE_APP_BASE_API, 这里该值为:


我们后续可以根据需求设置为上线环境:


上线环境的配置如下:


因为Request方法中设置了BaseURL所以对应的getCodeImg的请求路径为下:


我们通过观察可以发现前端地址是 localhost:80, 而我们的后端接口却是localhost:8080,存在跨域的问题,这种跨域问题前端和后端都有方法解决,这里通过前端解决此问题。

前端通过反向代理的方法解决,使用vue自带的反向代理服务器。


其中在进行反向代理的时候会将/dev-api重写成空,将localhost:80代理成localhost:8080,最终实现反向代理

最终前端实现验证码的效果。

4 参考链接

  1. 若依使用及源码解析(前后端分离版):https://blog.csdn.net/Ostkakah/article/details/132984838
  2. 若依框架学习(前后端分离)——(登录代码学习篇):http://t.csdnimg.cn/TAUyq
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值