一、登录流程
1- 进入登录页面,调用 com.ruoyi.web.controller.common.CaptchaController 类中的
captchaImage 方法,生成base64的图片 以及 UUID
2- 提交 登录信息 + 验证码 + uuid 比对
错误:返回错误信息,删除缓存的验证码
成功: 拿到token,加入缓存
二、验证码生成
1-前台代码配置
前端login 页面中,图片如下:
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
methods 函数中,captchaEnabled 是验证码是否开启的属性,以及注册功能开关,定义在data中如下:
data() { // 验证码开关 captchaEnabled: true, // 注册开关 register: false, redirect: undefined }; },
在页面 created 时候 ,调用getCode 生成验证码,返回页面
created() {
this.getCode();
this.getCookie();
}
methods: {
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.data.captchaEnabled === undefined ? true : res.data.captchaEnabled;
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.data.img;
this.loginForm.uuid = res.data.uuid;
}
});
},
....................
src\api\login.js 中,请求后台
// 获取验证码
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}
返回如下:
{
"code": 200,
"msg": "操作成功",
"data": {
"img": "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",
"captchaEnabled": true,
"uuid": "2f329cd71b96426cac682791a058a3a6"
}
}
2-后台代码配置
系参数配置 菜单中,sys.account.captchaEnabled 设置了关于验证码是否开启的配置
后台接口: com.ruoyi.web.controller.common.CaptchaController 类中,逻辑如下:
1- 判断验证码功能是否启用
redis缓存中获取 sys.account.captchaEnabled 判断是否开启验证码 ,配置如上截图
2- 生成uuid
这样根据uuid 就能在缓存中找到验证码
String uuid = IdUtil.simpleUUID(); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;3- 生成验证码
CaptchaType captchaType = captchaProperties.getType();
CaptchaType :ruoyi-admin中yml配置 (验证码类型、干扰线等等配置)captcha: # 页面 <参数设置> 可开启关闭 验证码校验 # 验证码类型 math 数组计算 char 字符验证 type: MATH # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰 category: CIRCLE # 数字验证码位数 numberLength: 1 # 字符验证码长度 charLength: 4
4- spel表达式 (spel 很强大 可以自己看看)
如果是数字,计算结果
if (isMath) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(StringUtils.remove(code, "=")); code = exp.getValue(String.class); }5- 保存到缓存中,设置过期时间 RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));详细代码如下:
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public R<Map<String, Object>> getCode() {
Map<String, Object> ajax = new HashMap<>();
//redis缓存中获取 sys.account.captchaEnabled
boolean captchaEnabled = configService.selectCaptchaEnabled();
ajax.put("captchaEnabled", captchaEnabled);
//是否需要验证码
if (!captchaEnabled) {
return R.ok(ajax);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
ajax.put("uuid", uuid);
ajax.put("img", captcha.getImageBase64());
return R.ok(ajax);
}
三、手机验证、短信验证
com.ruoyi.web.controller.common.CaptchaController 类中还有其他验证方法:
smsCaptcha 短信验证码captchaEmail 邮箱验证码都是保存到redis中,然后通过uuid拿出来校验,和上门逻辑一样
/** * 短信验证码 * * @param phonenumber 用户手机号 */ @GetMapping("/captchaSms") public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber; String code = RandomUtil.randomNumbers(4); RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); // 验证码模板id 自行处理 (查数据库或写死均可) String templateId = ""; LinkedHashMap<String, String> map = new LinkedHashMap<>(1); map.put("code", code); SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA); SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map); if (!"OK".equals(smsResponse.getCode())) { log.error("验证码短信发送异常 => {}", smsResponse); return R.fail(smsResponse.getMessage()); } return R.ok(); } /** * 邮箱验证码 * * @param email 邮箱 */ @GetMapping("/captchaEmail") public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) { if (!mailProperties.getEnabled()) { return R.fail("当前系统没有开启邮箱功能!"); } String key = CacheConstants.CAPTCHA_CODE_KEY + email; String code = RandomUtil.randomNumbers(4); RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); try { MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。"); } catch (Exception e) { log.error("验证码短信发送异常 => {}", e.getMessage()); return R.fail(e.getMessage()); } return R.ok(); }