前言
前两节讲了验证码的一整套流程,现在单独讲一下登录。因为是管理系统,就没有注册一说了。
1. 看一下界面
显然,整个登录部分,包括用户名、密码、验证码、记住密码和登录按钮五部分构成。
2. 看看前端代码
1. 登录表单
<template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<!-- 元素绑定loginForm
在Vue中,我们不用获取dom节点,元素绑定ref之后,直接通过this.$refs即可调用,这样可以减少获取dom节点的消耗。
:model="loginForm" 绑定值 loginForm
:rules="loginRules" 绑定校验规则
-->
<h3 class="title">若依后台管理系统</h3>
<el-form-item prop="username">
<!-- prop用法 https://cn.vuejs.org/v2/guide/components-props.html
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,
但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
-->
<el-input
v-model="loginForm.username"
type="text"
auto-complete="off"
placeholder="账号"
>
<!--
auto-complete="off" 关闭代码自动补全,什么意思呢,把鼠标光标放到输入框,不会自动出现下拉框,如果改为on,则会出现近期输入的数据。
autocomplete 属性规定输入字段是否应该启用自动完成功能。
placeholder 属性提供可描述输入字段预期值的提示信息(hint)。
该提示会在输入字段为空时显示,并会在字段获得焦点时消失。
-->
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
<!-- 用户图标
slot 是对组件的扩展,通过slot插槽向组件内部指定位置传递内容,通过slot可以父子传参
-->
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleLogin"
>
<!--
@keyup.enter 是 vue 监听键盘回车事件
如果是封装了组件,要用
@keyup.enter.native
最后效果就是,按下回车键,调用handleLogin方法
-->
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff">
<!-- el开头,是element UI 官网: https://element.eleme.cn/#/zh-CN/component/installation -->
<!-- code 是自己输入的验证码,此处先讲获取验证码
在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。
显然这个code是父组件通过prop传递数据给子组件的
-->
<!-- captchaOnOff 初始值为true,是验证码开关 -->
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter.native="handleLogin"
>
<!-- 此处绑定了loginForm.code,这个code是表单对象里的code,不是上文说的code
auto-complete="off" 关闭代码自动补全,什么意思呢,把鼠标光标放到输入框,不会自动出现下拉框,如果改为on,则会出现近期输入的数据。
autocomplete 属性规定输入字段是否应该启用自动完成功能。默认情况下是启动的,也就是当你点击了input
获取焦点之后浏览器会自动将以前的输入记录作为填入选项显示出来。这个是HTML5中的新属性,在不支持HTML5的浏览器下是没有用的。
注释:autocomplete 属性适用于 <form>,以及下面的 <input> 类型:text, search, url, telephone, email, password, datepickers, range 以及 color。
在一部分浏览器中,autocomplete 属性是默认关闭的,如果需要打开就打开 一下。因为这是input自带的数学,会在自己的div上加了一层div的ul列表。
placeholder 属性提供可描述输入字段预期值的提示信息(hint)。
该提示会在输入字段为空时显示,并会在字段获得焦点时消失。
@keyup.enter 是 vue 监听键盘回车事件
如果是封装了组件,要用
@keyup.enter.native
最后效果就是,按下回车键,调用handleLogin方法
-->
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
<!-- Vue中slot的使用 https://www.cnblogs.com/qq735675958/p/8377761.html
是对组件的扩展,通过slot插槽向组件内部指定位置传递内容,通过slot可以父子传参
即 slot的出现是为了父组件可以堂而皇之地在子组件中加入内容。
<span slot=”header”>hello world</span>
<slot name=”header”>这是拥有命名的slot的默认内容</slot>
显然这个叫 prefix 的slot 就是上文提到过的gif
-->
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
<!-- 根据codeUrl显示验证码图片,点击这个img会调用 getCode方法 -->
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
<!-- 是否记住密码,绑定loginForm.rememberMe
-->
<el-form-item style="width:100%;">
<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>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
<!-- 注册功能 router-link :to="'/register' 组件内跳转 -->
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2021 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
export default {
name: "Login",
data() {
return {
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: ""
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "请输入您的账号" }
],
password: [
{ required: true, trigger: "blur", message: "请输入您的密码" }
],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
/**
* trigger: 'blur'
blur失去焦点
比如输入框里。校验文本框是否为空
trigger: 'change'
change数据改变
比如
下图这些。需要手动选择的东西,用change
*/
},
loading: false,
// 验证码开关
captchaOnOff: true,
// 注册开关
register: false,
redirect: undefined
};
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
/**
* 说明加载登录页面时就会执行handler函数,handler的第一个参数表示to,
所以上述代码的意义就是,加载登录页后,直接获取,登录页地址栏的的redirect参数,赋值给this.redirect
参照 https://blog.csdn.net/qq_35176916/article/details/88744836
*/
}
},
created() {
this.getCode();
this.getCookie();
},
methods: {
getCode() {
getCodeImg().then(res => {
// 会调用 getCodeImg()接口,把返回的res进行处理
this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
//如果返回的captchaOnOff是undefined,估计是没访问出来啥东西,就返回true,否则返回false
// 什么鬼?仔细看看data里的captchaOnOff,嗷嗷,是验证码开关,初始值为true,所以这行的代码就是能不能继续 请求验证码 呗
//如果是true,说明没返回出来东西,肯定就能继续请求,如果是false,那就不能继续请求验证码
//再看一下这个在哪用captchaOnOff,哦,原来是 <el-form-item prop="code" v-if="captchaOnOff">
//显然,如果captchaOnOff为false就不会显示这个组件了
if (this.captchaOnOff) { //如果有验证码
this.codeUrl = "data:image/gif;base64," + res.img; //codeUrl的值即改为 data:image/gif;base64,res.img
//如果你不懂 base64 看看这个 https://blog.csdn.net/weixin_38465623/article/details/80199999
//当然,也可以看我总结的
/**
* base64 data:image/gif;base64," + res.img
* data表示取得数据的协定名称,image/gif 是数据类型名称,base64 是数据的编码方法,逗号后面就是这个image/gif文件base64编码后的数据。
* 目前,Data URI scheme支持的类型有:
data:,文本数据
data:text/plain,文本数据
data:text/html,HTML代码
data:text/html;base64,base64编码的HTML代码
data:text/css,CSS代码
data:text/css;base64,base64编码的CSS代码
data:text/javascript,Javascript代码
data:text/javascript;base64,base64编码的Javascript代码
data:image/gif;base64,base64编码的gif图片数据
data:image/png;base64,base64编码的png图片数据
data:image/jpeg;base64,base64编码的jpeg图片数据
base64简单地说,它把一些 8-bit 数据翻译成标准 ASCII 字符,网上有很多免费的base64 编码和解码的工具,在PHP中可以用函数base64_encode() 进行编码
举个图片的例子:
网页中一张图片可以这样显示:
<img src=“http://www.aimks.com/images/wg.png”/>
也可以这样显示
<img src=“data:image/png;base64,iVBORw0KGgoAAAANSUhEU
gAAAAEAAAAkCAYAAABIdFAMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZS
BJbWFnZVJlYWR5ccllPAAAAHhJREFUeNo8zjsOxCAMBFB/KEAUFFR
0Cbng3nQPw68ArZdAlOZppPFIBhH5EAB8b+Tlt9MYQ6i1BuqFaq1C
KSVcxZ2Acs6406KUgpt5/LCKuVgz5BDCSb13ZO99ZOdcZGvt4mJjz
MVKqcha68iIePB86GAiOv8CDADlIUQBs7MD3wAAAABJRU5ErkJggg%3D%3D”/>
*/
//显然 res.img就是传来的图片,在后端生成图片,base64图片转成字符 传输回前端,再在前端 字符转成图片 显示给用户 妙啊秒啊
this.loginForm.uuid = res.uuid;
//并且表单的uuid设置为res.uuid
}
});
},
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe')
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password),
// RSA解密
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
};
},
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里删除用户名,密码,记住密码
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
// RSA加密 https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108343805
/**
* 前端使用Vue在进行登录时,需要将密码存进cookie中。
为了防止密码明文暴露,前端需要采用加密方式对密码进行加密。
常用加密方式之一就是RSA加密解密。
*/
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
// 否则在cookies里删除用户名,密码,记住密码
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;
//加载中改为false,暴露登录按钮
if (this.captchaOnOff) {
this.getCode();
//重新获取验证码
}
});
}
});
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../assets/images/login-background.jpg");
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 38px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 38px;
}
</style>
就是获取表单数据,然后提交表单数据
然后就是走前端登录的代码
2. 登录方法
// 登录
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) => {
/**
*
* 传值
*{
"username": "admin",
"password": "admin123",
"code": "0",
"uuid": "60cd9a548bd8491a9f67b96f64943a29"
}
接收
{"code":200,
"msg":null,
"data":{
"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6IjViM2I2MjM1LTRkZjAtNDc2My04NDJlLTg3ZjljZmYxNWZmNiIsInVzZXJuYW1lIjoiYWRtaW4ifQ.mSYKR4AFVB-p41FneO_f_J53w9DQHf2kEovi-9dtQuKWzZvgTnLlAIP6zlqXofx5X2kOzXEAXqy8ZG2sdxNdOA",
"expires_in":720}}
*/
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
//获取后端返回来的data
//设置token
setToken(data.access_token) //设置token
commit('SET_TOKEN', data.access_token) //提交token
setExpiresIn(data.expires_in) //设置到期时间
commit('SET_EXPIRES_IN', data.expires_in) //提交到期时间
resolve() //顺序执行,不用返回东西
// resolve代表这个Promise实例成功之后的回调函数
/**
* https://blog.csdn.net/m0_45084130/article/details/120288713
* resolve详解
* 普通函数
// 创建一个普通的函数
function ordinaryFunction() {
return '你是一个好人';
}
// 调用
var acceptValue = ordinaryFunction();
console.log(acceptValue); // 你是一个好人
Promise的resolve
// 创建一个promise出来
function promiseFunction() {
return new Promise(resolve => {
resolve('你是一个好人');
})
}
// 调用
promiseFunction().then(res => {
acceptValue = res;
console.log(acceptValue); // 你是一个好人
})
*/
}).catch(error => {
reject(error) //捕捉异常,返回错误信息
})
})
}
3. 看看后端代码
验证码校验
- ValidateCodeFilter 检测到有验证码
- checkCapcha方法校验验证码,出错返回验证码错误,不出错就放行
ok,验证码这关咱算是过去了,看看登录吧。
1.TokenController类接收请求
@PostMapping("login")
public R<?> login(@RequestBody LoginBody form) {
// R是封装的统一响应体
// LoginBody 就是对应的表单实体,是用户登录对象 ,form就是传来的参数
// 用户登录
LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
// 获取登录token
return R.ok(tokenService.createToken(userInfo));
}
显然,sysLoginService.login()方法为登录方法
return R.ok(tokenService.createToken(userInfo)); 是返回的结果
整个登录过程可以分为这两大部分
2. sysLoginService.login()部分
1. 看看sysLoginService.login()
日志方面暂且不看
SysLogininfor类
这是个系统访问记录实体
里面包括 实体ID、用户账号、登录状态、IP地址、描述信息、访问时间
/**
* 登录
*/
public LoginUser login(String username, String password) {
// 用户名或密码为空 错误
//有一个为空就报错
if (StringUtils.isAnyBlank(username, password)) {
//记录日志信息
recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new ServiceException("用户/密码必须填写");
}
// 密码如果不在指定范围内 错误
// 密码长度不对
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new ServiceException("用户密码不在指定范围");
}
// 用户名不在指定范围内 错误
// 用户名长度不对
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围");
}
// 查询用户信息
//远程方法调用
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
/**
* userResult = {R@12724}
* code = 200
* msg = null
* data = {LoginUser@12725}
* token = null
* userid = null
* username = null
* loginTime = null
* expireTime = null
* ipaddr = null
* permissions = {HashSet@12726} size = 1
* roles = {HashSet@12727} size = 1
* sysUser = {SysUser@12728} "com.ruoyi.system.api.domain.SysUser@73cefbeb[\r\n userId=1\r\n deptId=103\r\n userName=admin\r\n nickName=若依\r\n email=ry@163.com\r\n phonenumber=15888888888\r\n sex=1\r\n avatar=\r\n password=$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2\r\n status=0\r\n delFlag=0\r\n loginIp=127.0.0.1\r\n loginDate=Wed Jan 12 19:59:38 CST 2022\r\n createBy=admin\r\n createTime=Thu Jan 13 03:59:38 CST 2022\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=管理员\r\n dept=com.ruoyi.system.api.domain.SysDept@6ae2bb09[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]\r\n]"
* userId = {Long@12743} 1
* deptId = {Long@12744} 103
* userName = "admin"
* nickName = "若依"
* email = "ry@163.com"
* phonenumber = "15888888888"
* sex = "1"
* avatar = ""
* password = "$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
* status = "0"
* delFlag = "0"
* loginIp = "127.0.0.1"
* loginDate = {Date@12755} "Wed Jan 12 19:59:38 CST 2022"
* dept = {SysDept@12756} "com.ruoyi.system.api.domain.SysDept@6ae2bb09[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]"
* roles = {ArrayList@12757} size = 1
* roleIds = null
* postIds = null
* roleId = null
* searchValue = null
* createBy = "admin"
* createTime = {Date@12759} "Thu Jan 13 03:59:38 CST 2022"
* updateBy = null
* updateTime = null
* remark = "管理员"
* params = {LinkedHashMap@12761} size = 0
*/
// 500==userResult.getCode()
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
//服务调用失败
}
//如果没找到用户
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
}
//如果找到用户,获取用户信息
LoginUser userInfo = userResult.getData();
//获取用户对象
SysUser user = userResult.getData().getSysUser();
//用户删除标志 UserStatus.DELETED 2 user.getDelFlag() 0
// OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
// OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
// 如果密码不相等,则密码错误
// password是用户登录时的明文 admin123 user.getPassword()是系统根据用户名admin在数据库查到的密文
if (!SecurityUtils.matchesPassword(password, user.getPassword())) {
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
throw new ServiceException("用户不存在/密码错误");
}
//记录用户登录成功的信息
recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
//返回用户信息
return userInfo;
}
这里返回的userInfo代表登录成功了。
2. 看看remoteUserService.getUserInfo()方法
调用远程接口获取用户信息
/**
* 通过用户名查询用户信息
* 调用的是system模块下的SysUserController方法
* @param username 用户名
* @param source 请求来源
* @return 结果
*/
@GetMapping("/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
看看被调用的方法
/**
* 获取当前用户信息
*/
// 内部调用
@InnerAuth
@GetMapping("/info/{username}")
public R<LoginUser> info(@PathVariable("username") String username) {
//根据用户名查找用户信息
SysUser sysUser = userService.selectUserByUserName(username);
//如果没有找到
if (StringUtils.isNull(sysUser)) {
return R.fail("用户名或密码错误");
}
// 角色集合
//根据用户id获取用户的角色
Set<String> roles = permissionService.getRolePermission(sysUser.getUserId());
// 权限集合
//根据用户id获取用户的权限
Set<String> permissions = permissionService.getMenuPermission(sysUser.getUserId());
//构建登录用户的视图
LoginUser sysUserVo = new LoginUser();
sysUserVo.setSysUser(sysUser);
sysUserVo.setRoles(roles);
sysUserVo.setPermissions(permissions);
return R.ok(sysUserVo);
}
显然,里面有三次数据查询操作
2.1 selectUserByUserName()
/**
* 通过用户名查询用户
*
* @param userName 用户名
* @return 用户对象信息
*/
@Override
public SysUser selectUserByUserName(String userName)
{
return userMapper.selectUserByUserName(userName);
}
<resultMap type="SysUser" id="SysUserResult">
<id property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
<result property="phonenumber" column="phonenumber" />
<result property="sex" column="sex" />
<result property="avatar" column="avatar" />
<result property="password" column="password" />
<result property="status" column="status" />
<result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<association property="dept" column="dept_id" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
</resultMap>
<sql id="selectUserVo">
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
left join sys_role r on r.role_id = ur.role_id
</sql>
<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
<!--selectUserByUserName 要和方法名一致
parameterType="String" 是获取的参数类型
resultMap="SysUserResult" 是返回值类型
解释:
include是引用SQL代码。
refid 是引用的sql的id名称,一定要唯一。
作用:
有共同的SQL片段,为了不重复写。
方便后期维护。
把字段都写出来,不用*代替,也是为了提高效率。
参考 https://blog.csdn.net/weixin_44839439/article/details/116452552
-->
<include refid="selectUserVo"/>
where u.user_name = #{userName}
</select>
2.2 getRolePermission()
/**
* 获取角色数据权限
*
* @param userId 用户Id
* @return 角色权限信息
*/
@Override
public Set<String> getRolePermission(Long userId) {
Set<String> roles = new HashSet<String>();
// 管理员拥有所有权限
if (SysUser.isAdmin(userId)) {
/**
* public static boolean isAdmin(Long userId) {
* // 如何用户编号为 1L,则他是管理员
* return userId != null && 1L == userId;
* }
*/
roles.add("admin");
} else {
//如果不是1号用户 , 查找这个人的用户权限,并添加到权限列表中
roles.addAll(roleService.selectRolePermissionByUserId(userId));
}
return roles;
}
/**
* 根据用户ID查询权限
*
* @param userId 用户ID
* @return 权限列表
*/
@Override
public Set<String> selectRolePermissionByUserId(Long userId) {
//根据用户ID查询权限
List<SysRole> perms = roleMapper.selectRolePermissionByUserId(userId);
// set 权限去重
Set<String> permsSet = new HashSet<>();
for (SysRole perm : perms) {
if (StringUtils.isNotNull(perm)) {
// 添加权限
permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
}
}
return permsSet;
}
<select id="selectRolePermissionByUserId" parameterType="Long" resultMap="SysRoleResult">
<include refid="selectRoleVo"/>
WHERE r.del_flag = '0' and ur.user_id = #{userId}
</select>
最终返回的就是 这个用户 对应的 所有角色 的 所有权限 去重后 的集合。
2.3 getMenuPermission()
获取菜单数据权限
/**
* 获取菜单数据权限
*
* @param userId 用户Id
* @return 菜单权限信息
*/
@Override
public Set<String> getMenuPermission(Long userId) {
Set<String> perms = new HashSet<String>();
// 管理员拥有所有权限
if (SysUser.isAdmin(userId)) {
//如果是管理员。获取所有的权限
perms.add("*:*:*");
} else {
//如果不是管理员,根据用户id获取菜单数据权限
perms.addAll(menuService.selectMenuPermsByUserId(userId));
}
return perms;
}
/**
* 根据用户ID查询权限
*
* @param userId 用户ID
* @return 权限列表
*/
@Override
public Set<String> selectMenuPermsByUserId(Long userId) {
//根据用户ID查询权限
List<String> perms = menuMapper.selectMenuPermsByUserId(userId);
Set<String> permsSet = new HashSet<>();
for (String perm : perms) {
if (StringUtils.isNotEmpty(perm)) {
// 返回权限去重后的集合
permsSet.addAll(Arrays.asList(perm.trim().split(",")));
}
}
return permsSet;
}
<select id="selectMenuPermsByUserId" parameterType="Long" resultType="String">
select distinct m.perms
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
left join sys_role r on r.role_id = ur.role_id
where m.status = '0' and r.status = '0' and ur.user_id = #{userId}
</select>
3.login中用到的数据
username = "admin"
value = {char[5]@15286} [a, d, m, i, n]
hash = 0
password = "admin123"
value = {char[8]@15260} [a, d, m, i, n, 1, 2, 3]
hash = 0
userResult = {R@11485}
code = 200
msg = null
data = {LoginUser@11486}
token = null
userid = null
username = null
loginTime = null
expireTime = null
ipaddr = null
permissions = {HashSet@15287} size = 1
0 = "*:*:*"
value = {char[5]@15292} [*, :, *, :, *]
hash = 40557962
roles = {HashSet@15288} size = 1
0 = "admin"
value = {char[5]@15295} [a, d, m, i, n]
hash = 92668751
sysUser = {SysUser@11487} "com.ruoyi.system.api.domain.SysUser@44c192df[\r\n userId=1\r\n deptId=103\r\n userName=admin\r\n nickName=若依\r\n email=ry@163.com\r\n phonenumber=15888888888\r\n sex=1\r\n avatar=\r\n password=$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2\r\n status=0\r\n delFlag=0\r\n loginIp=127.0.0.1\r\n loginDate=Wed Jan 12 19:59:38 CST 2022\r\n createBy=admin\r\n createTime=Thu Jan 13 03:59:38 CST 2022\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=管理员\r\n dept=com.ruoyi.system.api.domain.SysDept@42e8fe7e[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]\r\n]"
userId = {Long@15118} 1
deptId = {Long@15119} 103
userName = "admin"
value = {char[5]@15298} [a, d, m, i, n]
hash = 92668751
nickName = "若依"
value = {char[2]@15299} [若, 依]
hash = 1059160
email = "ry@163.com"
value = {char[10]@15300} [r, y, @, 1, 6, 3, ., c, o, m]
hash = 1416900936
phonenumber = "15888888888"
value = {char[11]@15301} [1, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8]
hash = 810583700
sex = "1"
value = {char[1]@15302} [1]
hash = 49
avatar = ""
value = {char[0]@15303} []
hash = 0
password = "$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
value = {char[60]@15304} [$, 2, a, $, 1, 0, $, 7, J, B, 7, 2, 0, y, u, b, V, S, Z, v, U, I, 0, r, E, q, K, /, ., V, q, G, O, Z, T, H, ., u, l, u, 3, 3, d, H, O, i, B, E, 8, B, y, O, h, J, I, r, d, A, u, 2]
hash = -507109186
status = "0"
value = {char[1]@15305} [0]
hash = 48
delFlag = "0"
value = {char[1]@15306} [0]
hash = 48
loginIp = "127.0.0.1"
value = {char[9]@15307} [1, 2, 7, ., 0, ., 0, ., 1]
hash = 1505998205
loginDate = {Date@15130} "Wed Jan 12 19:59:38 CST 2022"
fastTime = 1641988778000
cdate = {Gregorian$Date@15308} "2022-01-12T19:59:38.000+0800"
dept = {SysDept@15131} "com.ruoyi.system.api.domain.SysDept@42e8fe7e[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]"
deptId = {Long@15119} 103
parentId = {Long@15312} 101
ancestors = null
deptName = "研发部门"
orderNum = "1"
leader = "若依"
phone = null
email = null
status = "0"
delFlag = null
parentName = null
children = {ArrayList@15317} size = 0
searchValue = null
createBy = null
createTime = null
updateBy = null
updateTime = null
remark = null
params = {LinkedHashMap@15318} size = 0
roles = {ArrayList@15132} size = 1
0 = {SysRole@15270} "com.ruoyi.system.api.domain.SysRole@bb9af99[\r\n roleId=1\r\n roleName=超级管理员\r\n roleKey=admin\r\n roleSort=1\r\n dataScope=1\r\n menuCheckStrictly=false\r\n deptCheckStrictly=false\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=<null>\r\n]"
roleKey = "admin"
roleId = {Long@15118} 1
roleName = "超级管理员"
roleSort = "1"
dataScope = "1"
menuCheckStrictly = false
deptCheckStrictly = false
status = "0"
delFlag = null
flag = false
menuIds = null
deptIds = null
searchValue = null
createBy = null
createTime = null
updateBy = null
updateTime = null
remark = null
params = {LinkedHashMap@15277} size = 0
roleIds = null
postIds = null
roleId = null
searchValue = null
createBy = "admin"
createTime = {Date@15134} "Thu Jan 13 03:59:38 CST 2022"
updateBy = null
updateTime = null
remark = "管理员"
params = {LinkedHashMap@15136} size = 0
userInfo = {LoginUser@11486}
token = null
userid = null
username = null
loginTime = null
expireTime = null
ipaddr = null
permissions = {HashSet@15287} size = 1
0 = "*:*:*"
value = {char[5]@15292} [*, :, *, :, *]
hash = 40557962
roles = {HashSet@15288} size = 1
0 = "admin"
value = {char[5]@15295} [a, d, m, i, n]
hash = 92668751
sysUser = {SysUser@11487} "com.ruoyi.system.api.domain.SysUser@44c192df[\r\n userId=1\r\n deptId=103\r\n userName=admin\r\n nickName=若依\r\n email=ry@163.com\r\n phonenumber=15888888888\r\n sex=1\r\n avatar=\r\n password=$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2\r\n status=0\r\n delFlag=0\r\n loginIp=127.0.0.1\r\n loginDate=Wed Jan 12 19:59:38 CST 2022\r\n createBy=admin\r\n createTime=Thu Jan 13 03:59:38 CST 2022\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=管理员\r\n dept=com.ruoyi.system.api.domain.SysDept@42e8fe7e[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]\r\n]"
userId = {Long@15118} 1
deptId = {Long@15119} 103
userName = "admin"
nickName = "若依"
email = "ry@163.com"
phonenumber = "15888888888"
sex = "1"
avatar = ""
password = "$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
status = "0"
delFlag = "0"
loginIp = "127.0.0.1"
loginDate = {Date@15130} "Wed Jan 12 19:59:38 CST 2022"
dept = {SysDept@15131} "com.ruoyi.system.api.domain.SysDept@42e8fe7e[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]"
roles = {ArrayList@15132} size = 1
0 = {SysRole@15270} "com.ruoyi.system.api.domain.SysRole@bb9af99[\r\n roleId=1\r\n roleName=超级管理员\r\n roleKey=admin\r\n roleSort=1\r\n dataScope=1\r\n menuCheckStrictly=false\r\n deptCheckStrictly=false\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=<null>\r\n]"
roleKey = "admin"
roleId = {Long@15118} 1
roleName = "超级管理员"
roleSort = "1"
dataScope = "1"
menuCheckStrictly = false
deptCheckStrictly = false
status = "0"
delFlag = null
flag = false
menuIds = null
deptIds = null
searchValue = null
createBy = null
createTime = null
updateBy = null
updateTime = null
remark = null
params = {LinkedHashMap@15277} size = 0
roleIds = null
postIds = null
roleId = null
searchValue = null
createBy = "admin"
createTime = {Date@15134} "Thu Jan 13 03:59:38 CST 2022"
updateBy = null
updateTime = null
remark = "管理员"
params = {LinkedHashMap@15136} size = 0
user = {SysUser@11487} "com.ruoyi.system.api.domain.SysUser@44c192df[\r\n userId=1\r\n deptId=103\r\n userName=admin\r\n nickName=若依\r\n email=ry@163.com\r\n phonenumber=15888888888\r\n sex=1\r\n avatar=\r\n password=$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2\r\n status=0\r\n delFlag=0\r\n loginIp=127.0.0.1\r\n loginDate=Wed Jan 12 19:59:38 CST 2022\r\n createBy=admin\r\n createTime=Thu Jan 13 03:59:38 CST 2022\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=管理员\r\n dept=com.ruoyi.system.api.domain.SysDept@42e8fe7e[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]\r\n]"
userId = {Long@15118} 1
deptId = {Long@15119} 103
userName = "admin"
nickName = "若依"
email = "ry@163.com"
phonenumber = "15888888888"
sex = "1"
avatar = ""
password = "$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
status = "0"
delFlag = "0"
loginIp = "127.0.0.1"
loginDate = {Date@15130} "Wed Jan 12 19:59:38 CST 2022"
dept = {SysDept@15131} "com.ruoyi.system.api.domain.SysDept@42e8fe7e[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]"
roles = {ArrayList@15132} size = 1
0 = {SysRole@15270} "com.ruoyi.system.api.domain.SysRole@bb9af99[\r\n roleId=1\r\n roleName=超级管理员\r\n roleKey=admin\r\n roleSort=1\r\n dataScope=1\r\n menuCheckStrictly=false\r\n deptCheckStrictly=false\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=<null>\r\n]"
roleKey = "admin"
roleId = {Long@15118} 1
roleName = "超级管理员"
value = {char[5]@15279} [超, 级, 管, 理, 员]
hash = 95774577
roleSort = "1"
dataScope = "1"
menuCheckStrictly = false
deptCheckStrictly = false
status = "0"
delFlag = null
flag = false
menuIds = null
deptIds = null
searchValue = null
createBy = null
createTime = null
updateBy = null
updateTime = null
remark = null
params = {LinkedHashMap@15277} size = 0
roleIds = null
postIds = null
roleId = null
searchValue = null
createBy = "admin"
createTime = {Date@15134} "Thu Jan 13 03:59:38 CST 2022"
updateBy = null
updateTime = null
remark = "管理员"
params = {LinkedHashMap@15136} size = 0
3. tokenService.createToken(userInfo)部分
根据userInfo生成登录token
1. createToken()
/**
* 创建令牌
*/
public Map<String, Object> createToken(LoginUser loginUser) {
/**
* LoginUser属性
* * 用户唯一标识 token
* * 用户名id userId
* * 用户名
* * 登录时间
* * 过期时间
* * 登录IP地址 ipaddr
* * 权限列表
* * 角色列表
* * 用户信息
*/
//生成全局唯一的UUID,UUID即为token
String token = IdUtils.fastUUID();
//获取用户id
Long userId = loginUser.getSysUser().getUserId();
//获取用户名
String userName = loginUser.getSysUser().getUserName();
// 添加token,userId,userName,IP地址
loginUser.setToken(token);
loginUser.setUserid(userId);
loginUser.setUsername(userName);
loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
//刷新token
refreshToken(loginUser);
// Jwt存储信息
/**
* Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).
* 该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
* JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,
* 以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,
* 该token也可直接被用于认证,也可被加密。
*/
Map<String, Object> claimsMap = new HashMap<String, Object>();
// user_key:token
claimsMap.put(SecurityConstants.USER_KEY, token);
// user_id :userId
claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
// username:userName
claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
// 接口返回信息
Map<String, Object> rspMap = new HashMap<String, Object>();
// 根据uuid,userId,userName创建access_token
rspMap.put("access_token", JwtUtils.createToken(claimsMap));
// 过期时间 720 min
rspMap.put("expires_in", expireTime);
return rspMap;
}
2. refreshToken()
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser) {
// 谁知当前时间为登录时间
loginUser.setLoginTime(System.currentTimeMillis());
//设置过期时间为当前时间+规定的过期时间
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
// userKey=login_tokens:uuid
/**
*
* userKey login_tokens:ffbf3246-f782-46e8-838b-a1a6eca254f9
*
* loginUser:
* token = "ffbf3246-f782-46e8-838b-a1a6eca254f9"
* userid = {Long@15180} 1
* username = "admin"
* loginTime = {Long@15182} 1642404937201
* expireTime = {Long@15183} 1642448137201
* ipaddr = "127.0.0.1"
* permissions = {HashSet@15185} size = 1
* roles = {HashSet@15186} size = 1
* sysUser = {SysUser@11475} "com.ruoyi.system.api.domain.SysUser@23bf00d8[\r\n userId=1\r\n deptId=103\r\n userName=admin\r\n nickName=若依\r\n email=ry@163.com\r\n phonenumber=15888888888\r\n sex=1\r\n avatar=\r\n password=$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2\r\n status=0\r\n delFlag=0\r\n loginIp=127.0.0.1\r\n loginDate=Wed Jan 12 19:59:38 CST 2022\r\n createBy=admin\r\n createTime=Thu Jan 13 03:59:38 CST 2022\r\n updateBy=<null>\r\n updateTime=<null>\r\n remark=管理员\r\n dept=com.ruoyi.system.api.domain.SysDept@2a0ca240[\r\n deptId=103\r\n parentId=101\r\n ancestors=<null>\r\n deptName=研发部门\r\n orderNum=1\r\n leader=若依\r\n phone=<null>\r\n email=<null>\r\n status=0\r\n delFlag=<null>\r\n createBy=<null>\r\n createTime=<null>\r\n updateBy=<null>\r\n updateTime=<null>\r\n]\r\n]"
*
* expireTime 720 过期时间
*
* TimeUnit.MINUTES 过期时间单位
*/
redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
3. createToken()
/**
* 从数据声明生成令牌
* @param claims 数据声明
* @return 令牌
*/
public static String createToken(Map<String, Object> claims) {
//secret 令牌秘钥 abcdefghijklmnopqrstuvwxyz
// 参考 https://blog.csdn.net/a1522365779/article/details/78296946
// 根据信息生成 token
String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
返回生成的 access_token
最后的最后,打包给前端的数据就只剩
// 接口返回信息
Map<String, Object> rspMap = new HashMap<String, Object>();
// 根据uuid,userId,userName创建access_token
rspMap.put("access_token", JwtUtils.createToken(claimsMap));
// 过期时间 720 min
rspMap.put("expires_in", expireTime);
4. 再来看看前端怎么处理
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
//获取后端返回来的data
//设置token
setToken(data.access_token) //设置token
commit('SET_TOKEN', data.access_token) //提交token
setExpiresIn(data.expires_in) //设置到期时间
commit('SET_EXPIRES_IN', data.expires_in) //提交到期时间
resolve() //顺序执行,不用返回东西
// resolve代表这个Promise实例成功之后的回调函数
/**
* https://blog.csdn.net/m0_45084130/article/details/120288713
* resolve详解
* 普通函数
// 创建一个普通的函数
function ordinaryFunction() {
return '你是一个好人';
}
// 调用
var acceptValue = ordinaryFunction();
console.log(acceptValue); // 你是一个好人
Promise的resolve
// 创建一个promise出来
function promiseFunction() {
return new Promise(resolve => {
resolve('你是一个好人');
})
}
// 调用
promiseFunction().then(res => {
acceptValue = res;
console.log(acceptValue); // 你是一个好人
})
*/
}).catch(error => {
reject(error) //捕捉异常,返回错误信息
})
此时前端在拥有了token和过期时间,剩下再根据token,获取相关数据,登录成功。
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;
//加载中改为false,暴露登录按钮
if (this.captchaOnOff) {
this.getCode();
//重新获取验证码
}
});
这里获取完token后,会继续执行跳转到首页 即 /
在router里的index.js中可以看到,这会进入 index.vue页面
{
path: '',
component: Layout,
redirect: 'index',
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
component: Layout
/* Layout */
import Layout from '@/layout'
这就算进入首页了,再根据token获取数据,算是登录成功了。