文章目录
前言
上一章讲了验证码的获取,我们知道了前端如何实现验证码获取的调用,以及后端如何处理这个验证码的请求。最终结果是:后端传给前端一个这样的返回结果。
前端根据这个返回结果进行处理(代码解读参见 https://blog.csdn.net/niTaoTaoa/article/details/122463708)
getCode() {
getCodeImg().then(res => {
this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
if (this.captchaOnOff) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
}
最终实现的就是这个效果
现在,来看一下输入验证码后会发生怎样精彩的联动吧。
1. 前端输入验证码
假设我已经输入了正确的账号和牧马,以及验证码的结果 3
并点击了登录按钮
1.看看人家的登录按钮怎么写的
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
代码解读
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin"
>
<!-- 绑定一个 loading参数,默认是false,意思是 是不是加载中呗
type 类型 string primary / success / warning / danger / info / text
@click.native.prevent
1.给vue组件绑定事件时候,必须加上native ,否则会认为监听的是来自Item组件自定义的事件,
2.prevent 是用来阻止默认的 ,相当于原生的event.preventDefault()
注意:preventDefault() 方法阻止元素发生默认的行为(例如,当点击提交按钮时阻止对表单的提交)。
显然,这个按钮会触发一个 handleLogin 的方法
-->
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
<!-- 这里就是根据 loading 的值动态显示要显示的内容 -->
</el-button>
2.看看 handleLogin()方法
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
}
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
if (this.captchaOnOff) {
this.getCode();
}
});
}
});
}
解读一下
handleLogin() {
this.$refs.loginForm.validate(valid => {
/**
* 以前经常用 this.refs,只知道这样用,不知道是干啥的,今天刚好看看了
* 参考 https://blog.csdn.net/qq_38128179/article/details/88876060
* 在JavaScript中需要通过document.querySelector("#demo")来获取dom节点,然后再获取这个节点的值。
* 在Vue中,我们不用获取dom节点,元素绑定ref之后,直接通过this.$refs即可调用,这样可以减少获取dom节点的消耗。
*
* 咦,在哪绑定的ref
* 哦,在第三行
* <template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">若依后台管理系统</h3>
通俗的讲,ref特性就是为元素或子组件赋予一个ID引用,通过this.$refs.refName来访问元素或子组件的实例
this.$refs是一个对象,持有当前组件中注册过 ref特性的所有 DOM 元素和子组件实例
且只有在组件渲染完成后才填充,在初始渲染的时候不能访问它们,并且它是非响应式的,因此不能用它在模板中做数据绑定
validate 根据自定义的规则进行校验,符合规则返回true,否则false
*/
if (valid) {
this.loading = true; //表示现在开始登录
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
}
this.$store.dispatch("Login", this.loginForm).then(() => {
//this.$store.dispatch(‘Login’, this.loginForm)来调取store里的user.js的Login方法,从而要更新。
//参考 https://blog.csdn.net/longzhoufeng/article/details/103658726
//dispatch:含有异步操作,数据提交至 actions ,可用于向后台提交数据
// this.$store.dispatch('isLogin', true);
// commit:同步操作,数据提交至 mutations ,可用于读取用户信息写到缓存里
// this.$store.commit('loginStatus', 1);
//如果登录成功,路由转发到首页
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
if (this.captchaOnOff) {
this.getCode();
}
});
}
});
}
表单提交后,跳到了 store的user.js的login方法
3.看看store的user.js的Login方法
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
let data = res.data
setToken(data.access_token)
commit('SET_TOKEN', data.access_token)
setExpiresIn(data.expires_in)
commit('SET_EXPIRES_IN', data.expires_in)
resolve()
}).catch(error => {
reject(error)
})
})
},
解读
// 登录
Login({ commit }, userInfo) {
/**参考 https://vuex.vuejs.org/zh/guide/actions.html#%E5%88%86%E5%8F%91-action
* Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
Action 通过 store.dispatch 方法触发:
store.dispatch('increment')
乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,
还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作
*/
// 获取表单里的数据,涛涛表示有手就行
const username = userInfo.username.trim() //两边去空格
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
// ES6---new Promise()使用方法 参考 https://blog.csdn.net/qq_29483485/article/details/86605396
/**
*Promise 是一个对象,也是一个构造函数。是js对异步操作的一种解决方案,为异步操作提供了统一的接口
Promise的构造函数接收一个参数,是函数
并且传入两个参数:resolve,reject,
分别表示 异步操作执行成功后的回调函数
和 异步操作执行失败后的回调函数。
其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,
resolve是将Promise的状态置为fullfiled,
reject是将Promise的状态置为rejected。
参考 https://www.cnblogs.com/mg6666/p/14508322.html
.then() 和 .catch():
Promise构造器接受一个函数作为参数,这个函数有两个参数:resolve,reject,分别代表这个Promise实例成功之后的回调函数和失败之后的回调函数。
Promise.all() 和 Promise.race():
all方法的效果实际就是 谁跑的慢,那么就以谁为准来执行回调,而race则相反,谁跑的快,就以谁为准来执行回调。
*/
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
//这些就是后端返回来后的要看的了,暂且不提,先看这个code经历了什么
//此处调用了login方法,传来四个参数 username, password, code, uuid
// import { login, logout, getInfo, refreshToken } from '@/api/login'
// 这个方法是从/api/login导入的,进去看看
let data = res.data
setToken(data.access_token)
commit('SET_TOKEN', data.access_token)
setExpiresIn(data.expires_in)
commit('SET_EXPIRES_IN', data.expires_in)
resolve()
}).catch(error => {
reject(error)
})
})
},
4.看看login()方法
// 登录方法 这里没啥好说的,涛涛表示有手就行
export function login(username, password, code, uuid) {
return request({
url: '/auth/login',
headers: {
isToken: false
},
method: 'post',
data: { username, password, code, uuid }
})
}
这里访问了 /auth/login 接口,开始看后端!
2.后端校验验证码
1.ValidateCodeFilter类
先进的是网关的过滤器
@Override
public GatewayFilter apply(Object config)
{
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
}
try
{
String rspStr = resolveBodyFromRequest(request);
JSONObject obj = JSONObject.parseObject(rspStr);
validateCodeService.checkCapcha(obj.getString(CODE), obj.getString(UUID));
}
catch (Exception e)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
代码解读
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
// 获取接收的请求
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) {
return chain.filter(exchange);
}
//这次是登录请求,不被过滤
try {
//从请求中解析出请求体
String rspStr = resolveBodyFromRequest(request);
System.out.println(rspStr);
// {"username":"admin","password":"admin123","code":"3","uuid":"9d4832c10ca94bedbd1559926afa21b2"}
//请求体转为对象
JSONObject obj = JSONObject.parseObject(rspStr);
// 先根据UUID验证验证码
validateCodeService.checkCapcha(obj.getString(CODE), obj.getString(UUID));
} catch (Exception e) {
//只要有异常出现,就说明有问题,不放行
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
// 如果验证码通过了,就放行
return chain.filter(exchange);
};
}
这里我们可以看到中间有个
validateCodeService.checkCapcha(obj.getString(CODE), obj.getString(UUID));
这行就是验证码校验
2.checkCapcha()方法
源代码
/**
* 校验验证码
*/
@Override
public void checkCapcha(String code, String uuid) throws CaptchaException {
if (StringUtils.isEmpty(code)) {
throw new CaptchaException("验证码不能为空");
}
if (StringUtils.isEmpty(uuid)) {
throw new CaptchaException("验证码已失效");
}
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisService.getCacheObject(verifyKey);
redisService.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha)) {
throw new CaptchaException("验证码错误");
}
}
解读,xdm坚持住,马上结束了!!!
/**
* 校验验证码
*/
@Override
public void checkCapcha(String code, String uuid) throws CaptchaException {
if (StringUtils.isEmpty(code)) {
throw new CaptchaException("验证码不能为空");
}
if (StringUtils.isEmpty(uuid)) {
throw new CaptchaException("验证码已失效");
}
//上述内容涛涛表示有手就行
//根据传来的UUID,拼接上 验证码存储在Redis中的键的前缀,得到这个用户 在 redis 中 验证码 对应的键
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
// 根据这个键,在Redis中获取对应的存储的值
String captcha = redisService.getCacheObject(verifyKey);
//Redis 删除这个记录
redisService.deleteObject(verifyKey);
//如果传来的验证码,和后端根据UUID拼接,在Redis中存储的验证码,在忽略大小写后,不一样
//说明验证码错误,抛出自定义异常 验证码错误
//否则不做处理,上个方法没有捕捉到内部方法有异常抛出,正常执行,即放行。
if (!code.equalsIgnoreCase(captcha)) {
throw new CaptchaException("验证码错误");
}
}
不报错,验证码就没问题,验证码校验即通过。
至此,验证码这章基本结束了