若依官方的前后端分离版中,前端用的Vue2,这个有人改了Vue3的前端出来。刚好用来学习:
https://gitee.com/weifengze/RuoYi-Vue3
运行前后端项目
首先运行项目
启动前端,npm install
、npm run dev
启动后端,按教程配置数据库、redis环境,启动即可
页面实现文件在:RUOYI-VUE3/src/views/login.vue
验证码
function getCode() {
getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img; //后端传来的验证码图片地址
loginForm.value.uuid = res.uuid;
}
});
}
//页面初始化, 获取验证码
getCode();
getCodeImg()
方法封装在 RUOYI-VUE3/src/api/login.js
// 获取验证码
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}
前端Vue和后端Springboot交互时通常使用axios
(ajax)。
发现getCodeImg()
方法内还有一个 request()
的封装在:RUOYI-VUE3/src/utils/request.js
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
})
这个VITE_APP_BASE_API
常量,根据不同的运行情况,在这些文件里面:
例如现在运行的是dev环境。就在.env.development
中
前端发生的请求url为:http://localhost/dev-api/captchaImage
就是request.js
这里封装了一个baseURL, 然后getCodeImg()
方法里写了/captchaImage
接口地址。
反向代理
后端运行在localhost:8080端口上,前端运行在localhost:80端口上。
浏览器访问的是前端项目,如果要调用后端接口,会跨域。
跨域问题可以由后端解决:写一个允许跨域的配置类
跨域问题也可以由前端解决:反向代理。
反向代理的配置在:RUOYI-VUE3/vite.config.js
中:
// vite 相关配置
server: {
port: 80,
host: true,
open: true,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}
}
},
代码解释:
'/dev-api'
是代理的前缀路径,表示所有以 ‘/dev-api’ 开头的请求都会被代理。
target: 'http://localhost:8080'
指定了代理的目标地址,即真实的后端服务地址。
changeOrigin: true
通常是设置为 true,以便确保请求头中的 Host 字段保持一致,防止一些反向代理的问题。
rewrite: (p) => p.replace(/^\/dev-api/, '')
用于重写请求路径,它将路径中的 ‘/dev-api’ 前缀去掉,以适应后端的实际路径。
这样配置之后,当你在前端代码中发起类似于 ‘/dev-api/captchaImage’ 的请求时,
Vite 将把这个请求代理到 ‘http://localhost:8080/captchaImage’。
登录
和验证码一样的流程,登录功能主要由handleLogin()
方法实现
userStrore
在RUOYI-VUE3/src/store/module/user.js
是Vuex的用法
Vuex:
集中式状态管理: 当应用变得复杂时,组件之间共享状态可能变得困难。Vuex 提供了一个集中式的状态管理机制,使状态的变化可预测且容易调试。
全局访问: 通过 Vuex,可以在任何组件中访问相同的状态。这样,你就可以在组件之间共享数据,而不需要通过繁琐的组件通信来传递数据。
状态持久化: Vuex 允许你将状态持久化到本地存储,以便在页面刷新或重新加载时仍然保留应用的状态。
userStrore
const useUserStore = defineStore(
'user',
{
state: () => ({
token: getToken(),
name: '',
avatar: '',
roles: [],
permissions: []
}),
actions: {
login(userInfo) {
// ...
},
getInfo() {
// ...
},
logOut() {
// ...
}
}
}
)
export default useUserStore
defineStore
是 Vuex 4 中新引入的函数,用于创建 store 模块。
'user'
是模块的名称。
state
中定义了用户模块的初始状态,包括 token、name、avatar、roles 和 permissions 等初始状态信息。
action
中定义登录相关的 actions:
最后导出useUserStore
,以便在应用的其他地方可以引入并使用这个 store 模块。
外层login(userInfo)
方法.
根据传入的用户信息构建并返回一个Promise,它是es6提供的异步处理的对象
里面的login(username, password, code, uuid)
和前面的getCodeImg()
方法一样,在RUOYI-VUE3/src/api/login.js
中,封装的一个request
,最终还是ajax
login(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 => {
setToken(res.token)
this.token = res.token
resolve()
}).catch(error => {
reject(error)
})
})
},
登录成功后,后端返回的结果中有一个Token.
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
setToken(res.token)
就是把这个后端返回的Token放到Cookies中。
Vue Router
//使用 Vue Router 中的 useRouter 函数创建了一个 router 对象。
const router = useRouter()
常见用法就是:
// 字符串
router.push('home')
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
//对象
router.push({ path: redirect.value || "/" });
router的 push方法和replace方法的详细用法:https://blog.csdn.net/sunhuaqiang1/article/details/85220888
点击登录后,后端业务会去验证:验证码、账号、密码。正确就登录成功。后端登录方法中还会调用recordLoginInfo()
方法记录登录信息,写入日志。
获取用户角色和权限
通过浏览器的请求可以发现,登录成功后,还会调用getInfo
和getRouters
它们是在RUOYI-VUE3/src/permission.js
中被调用的
beforeEach
是 Vue Router
提供的全局前置守卫函数,前端每个页面进行跳转前都会执行这个函数。
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && useSettingsStore().setTitle(to.meta.title)
/* 如果即将进入的路由是 /login,则直接重定向到根路径 /,表示已经登录的情况下不允许再访问登录页面。*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
//如果角色信息未获取,则去请求user_info和路由表
if (useUserStore().roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
useUserStore().getInfo().then(() => {
isRelogin.show = false
usePermissionStore().generateRoutes().then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
useUserStore().logOut().then(() => {
ElMessage.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
具体的动态路由和角色权限管理,且听下回分解