目录
封装 axios 模块 💨
我们希望封装出来的 axios
模块,至少需要具备一种能力,那就是:根据当前模式的不同,设定不同的 BaseUrl
,因为通常情况下企业级项目在 开发状态 和 生产状态 下它的 baseUrl
是不同的。
我们可以在项目中创建两个文件:
.env.development
.env.production
它们分别对应 开发状态 和 生产状态。
我们可以在上面两个文件中分别写入以下代码:
.env.development
:
# 标志
ENV = 'development'
# base api
VUE_APP_BASE_API = '/api'
.env.production
:
# 标志
ENV = 'production'
# base api
VUE_APP_BASE_API = '/prod-api'
有了这两个文件之后,我们就可以创建对应的 axios 模块
创建 utils / request.js ,我们在这里配置发送 axios 请求的基本信息,写入如下代码:
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
export default service
这里 process.env.VUE_APP_BASE_API 可以根据当前模式的不同来设定我们前文设置好的 baseURL
有了 axios 模块之后,接下来我们就可以
- 封装接口请求模块
- 封装登录请求动
封装接口请求模块 💨
创建 api 文件夹,我们把所有的接口请求模块都放到这个文件夹里,例如登陆注册是一个模块,请求首页文章信息是一个模块:
创建 sys.js ,我们在这里编写登录的接口请求函数:
import request from '@/utils/request'
/**
* 登录
*/
export const login = data => {
return request({
url: '/sys/login',
method: 'POST',
data
})
}
现在调用这个方法返回的就是一个promise对象
封装登录请求动作 💨
modules/user.js:
import { login } from '@/api/sys'
import md5 from 'md5'
export default {
namespaced: true,
state: () => ({}),
mutations: {},
actions: {
login(context, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({
username,
password: md5(password)
})
.then(data => {
resolve()
})
.catch(err => {
reject(err)
})
})
}
}
}
注意:这里的md5是一种加密方法,用于加密密码,同学们可以 npm install 来安装。在user.js的actions中,我们把登录请求的动作封装好了,我们在返回的promise对象中调用接口请求模块 login,然后把参数传递进去,返回的promise实例我们通过 then 和 catch 进行接收。
然后我们还需要在 store 的 index.js 中完成注册:
import { createStore } from 'vuex'
import user from './modules/user.js'
export default createStore({
modules: {
user
}
})
触发登录动作 💨
我们先看一下登录界面主要逻辑的代码:
<template>
<el-form
class="login-form"
ref="loginFromRef"
:model="loginForm"
:rules="loginRules"
>
...
<el-button
type="primary"
style="width: 100%; margin-bottom: 30px"
:loading="loading"
@click="handleLogin"
>登录</el-button
>
</el-form>
</template>
<script setup>
import { ref } from 'vue'
import { validatePassword } from './rules'
import { useStore } from 'vuex'
...
// 登录动作处理
const loading = ref(false)
const loginFromRef = ref(null)
const store = useStore()
const handleLogin = () => {
loginFromRef.value.validate(valid => {
if (!valid) return
loading.value = true
store
.dispatch('user/login', loginForm.value)
.then(() => {
loading.value = false
// TODO: 登录后操作
})
.catch(err => {
console.log(err)
loading.value = false
})
})
}
</script>
然后通过 store.dispatch 来调用 user 模块下的 login 登陆方法,然后把表单的内容作为参数传递进去。通过 then 指定成功后的回调,catch 指定失败后的回调。
但是当我们这样做完以后是有问题的,控制台会报错 404 ,该错误表示,我们当前请求的接口不存在。出现这个问题的原因,是因为我们在前面配置环境变量时指定了 开发环境下,请求的 BaseUrl 为 /api ,所以我们真实发出的请求为:/api/sys/login 。
这样的一个请求会被自动键入到当前前端所在的服务中,所以我们最终就得到了 http://192.168.18.42:8081/api/sys/login
这样的一个请求路径。
而想要处理这个问题,那么可以通过指定的 WebPack DevServer 的形式,代理当前的 url
请求。而指定这个代理非常简单,是一种近乎固定的配置方案。在 vue.config.js 中,加入以下代码:
module.exports = {
devServer: {
// 配置反向代理
proxy: {
// 当地址中有/api的时候会触发代理机制
'/api': {
// 要代理的服务器地址 这里不用写 api
target: 'https://api.imooc-admin.lgdsunday.club/',
changeOrigin: true // 是否跨域
}
}
},
...
}
重新启动服务,再次进行请求,即可得到返回数据:
本地缓存处理方案 💨
在登陆成功后,后台返回给我们的信息一般都有 token ,通常情况下,在获取到 token 之后,我们会把 token 进行缓存,而缓存的方式将会分为两种:
- 本地缓存:
LocalStorage
- 全局状态管理:
Vuex
保存在 LocalStorage
是为了方便实现 自动登录功能,保存在 vuex
中是为了后面在其他位置进行使用,那么下面我们就分别来实现对应的缓存方案。
创建 utils/storage.js
文件,封装四个对应方法:
/**
* 存储数据
*/
export const setItem = (key, value) => {
// 将数组、对象类型的数据转化为 JSON 字符串进行存储
if (typeof value === 'object') {
value = JSON.stringify(value)
}
window.localStorage.setItem(key, value)
}
/**
* 获取数据
*/
export const getItem = key => {
const data = window.localStorage.getItem(key)
try {
return JSON.parse(data)
} catch (err) {
return data
}
}
/**
* 删除数据
*/
export const removeItem = key => {
window.localStorage.removeItem(key)
}
/**
* 删除所有数据
*/
export const removeAllItem = key => {
window.localStorage.clear()
}
因为获取的 token 值是一个常量,所以我们单独建一个文件来存储常量,创建 constant
常量目录 constant/index.js:
export const TOKEN = 'token'
在 vuex 的 user 模块下处理 token 的存储:
import { login } from '@/api/sys'
import md5 from 'md5'
import { setItem, getItem } from '@/utils/storage'
import { TOKEN } from '@/constant'
export default {
namespaced: true,
state: () => ({
token: getItem(TOKEN) || ''
}),
mutations: {
setToken(state, token) {
state.token = token
setItem(TOKEN, token)
}
},
actions: {
login(context, userInfo) {
...
.then(data => {
this.commit('user/setToken', data.data.data.token)
resolve()
})
...
})
}
}
}
此时,当点击登陆时,即可把 token
保存至 vuex
与 localStorage
中
响应数据的统一处理 💨
在上一小节中,我们保存了服务端返回的 token 。但是有一个地方比较难受,那就是在 vuex 的 user 模块中,我们获取数据端的 token 数据,通过 data.data.data.token 的形式进行获取。
在 utils/request.js
中实现以下代码:
import axios from 'axios'
import { ElMessage } from 'element-plus'
...
// 响应拦截器
service.interceptors.response.use(
response => {
const { success, message, data } = response.data
// 要根据success的成功与否决定下面的操作
if (success) {
return data
} else {
// 业务错误
ElMessage.error(message) // 提示错误消息
return Promise.reject(new Error(message))
}
},
error => {
// TODO: 将来处理 token 超时问题
ElMessage.error(error.message) // 提示错误信息
return Promise.reject(error)
}
)
export default service
此时,对于 vuex 中的 user 模块就可以进行以下修改了:
this.commit('user/setToken', data.token)
登录鉴权解决方案 💨
首先我们先去对 登录鉴权 进行一个定义,什么是 登录鉴权 呢?
- 当用户未登陆时,不允许进入除
login
之外的其他页面。 - 用户登录后,
token
未过期之前,不允许进入login
页面。
而想要实现这个功能,那么最好的方式就是通过 路由守卫 来进行实现。
那么明确好了 登录鉴权 的概念之后,接下来就可以去实现一下,在路由 index.js 页面进行配置:
// 白名单
const whiteList = ['/login']
/**
* 路由前置守卫
*/
router.beforeEach(async (to, from, next) => {
// 存在 token ,进入主页
if (store.state.user.token) {
if (to.path === '/login') {
next('/')
} else {
next()
}
} else {
// 没有token的情况下,可以进入白名单
if (whiteList.indexOf(to.path) > -1) {
next()
} else {
next('/login')
}
}
})
配置白名单是因为如果他没有 token ,可能是没有登录,也可能页面是 404 或者 401 状态,我们不应该设定只要没 token 都跳转到登录页面。