当前位置: 首页 > news >正文

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关闭*/@Overridepublic 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 参数键值*/@Overridepublic 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生成器工具类生成一个唯一的UUIDString 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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Kylin系列(二)使用
  • 缓存常见问题优化
  • 树莓派边缘计算网关搭建:集成MQTT、SQLite与Flask的完整解决方案
  • 数据结构初阶最终讲:排序
  • 使用python-pptx代码添加幻灯片:向PPT中插入新的幻灯片页面
  • Openwrt配置ZeroTier,实现公网访问内网中服务器
  • Windows下,C# 通过FastDDS高效通信
  • 碳化硅陶瓷膜过滤设备优异的过滤性能
  • 前端技术 -- 动画效果之GSAP作用与使用示例
  • Apex - Annotation#AuraEnabled
  • go的工厂模式
  • Oracle Flashback Recyclebin从回收站中恢复被删除的对象
  • 使用RabbitMQ死信交换机实现延迟消息
  • MySQL Galera Cluster 部署与介绍
  • 天津教育杂志天津教育杂志社天津教育编辑部2024年第24期目录
  • [NodeJS] 关于Buffer
  • 【个人向】《HTTP图解》阅后小结
  • 11111111
  • Apache Spark Streaming 使用实例
  • C# 免费离线人脸识别 2.0 Demo
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • export和import的用法总结
  • Javascript Math对象和Date对象常用方法详解
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • session共享问题解决方案
  • spring boot下thymeleaf全局静态变量配置
  • vue的全局变量和全局拦截请求器
  • 安装python包到指定虚拟环境
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 初探 Vue 生命周期和钩子函数
  • 番外篇1:在Windows环境下安装JDK
  • 诡异!React stopPropagation失灵
  • 浏览器缓存机制分析
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前言-如何学习区块链
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 数据结构java版之冒泡排序及优化
  • 我感觉这是史上最牛的防sql注入方法类
  • 我是如何设计 Upload 上传组件的
  • 学习Vue.js的五个小例子
  • MyCAT水平分库
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​2021半年盘点,不想你错过的重磅新书
  • ​Spring Boot 分片上传文件
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • ​香农与信息论三大定律
  • #Z0458. 树的中心2
  • #图像处理
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (一)基于IDEA的JAVA基础12
  • (转)平衡树