更新权限+token

This commit is contained in:
yuhawu 2025-07-19 18:20:02 +08:00
parent 74a9910782
commit a821bef69f
5 changed files with 90 additions and 124 deletions

View File

@ -7,7 +7,8 @@ export function setupRequestInterceptors(instance) {
// 判断 token 是否存在 // 判断 token 是否存在
if (token) { if (token) {
// 如果存在,则将 token 添加到请求头中的 Authorization 字段 // 如果存在,则将 token 添加到请求头中的 Authorization 字段
config.headers.Authorization = token // config.headers.Authorization = token
config.headers.Authorization = `Bearer ${token}`
} }
// 返回修改后的请求配置,继续发送请求 // 返回修改后的请求配置,继续发送请求
return config return config

View File

@ -1,35 +1,21 @@
//响应拦截器
import axios from 'axios'; // 引入axios用于创建新请求 import axios from 'axios'; // 引入axios用于创建新请求
import { ElMessage, ElMessageBox } from 'element-plus'; // 引入Element Plus的消息组件 import { ElMessage, ElMessageBox } from 'element-plus'; // 引入Element Plus的消息组件
import store from '../../store'; // 引入store用于统一处理登出 import store from '../../store/Index'; // 引入store用于统一处理登出
import axiosInstance from '../../utils/axios'; // 引入已配置的axios实例 import axiosInstance from '../../utils/axios'; // 引入已配置的axios实例
export function setupResponseInterceptors(instance) { export function setupResponseInterceptors(instance) {
// 用于存储正在刷新token的Promise
let isRefreshing = false; let isRefreshing = false;
// 存储等待token刷新的请求队列
let requests = []; let requests = [];
// 统一处理登录过期的函数
const handleTokenExpired = (message = '登录已过期,请重新登录') => { const handleTokenExpired = (message = '登录已过期,请重新登录') => {
// 显示确认对话框 ElMessageBox.confirm(message, '提示', {
ElMessageBox.confirm( confirmButtonText: '重新登录',
message, type: 'warning',
'提示', showCancelButton: false,
{ center: true
confirmButtonText: '重新登录', }).then(() => {
type: 'warning',
showCancelButton: false,
center: true
}
).then(() => {
// 用户点击确认按钮后执行
// 使用store的logout action清除认证状态
store.dispatch('logout'); store.dispatch('logout');
// 跳转到登录页
window.location.href = '/login'; window.location.href = '/login';
}).catch(() => {
// 用户关闭对话框,不做任何操作
}); });
}; };
@ -46,104 +32,75 @@ export function setupResponseInterceptors(instance) {
return data; return data;
}, },
async error => { async error => {
// 获取原始请求配置
const originalRequest = error.config; const originalRequest = error.config;
// 检查是否有响应
if (!error.response) { if (!error.response) {
// 网络错误或请求被中断
return Promise.reject(error); return Promise.reject(error);
} }
// 检查错误响应中的各种可能的错误信息位置 const statusCode = error.response.status;
const errorResponse = error.response; console.log("statusCode", statusCode)
console.log('错误响应:', errorResponse); // 401表示accessToken过期尝试用refreshToken刷新
const errorData = errorResponse.data || {}; if ((statusCode === 401 || statusCode === 500) && !originalRequest._retry) {
const errorMessage = errorData.status || errorData.message || errorData.error || errorResponse.statusText || ''; originalRequest._retry = true;
const errorStack = errorData.stack || ''; console.log("isRefreshing", isRefreshing)
const statusCode = errorResponse.status; if (!isRefreshing) {
console.log('错误响应:', statusCode); isRefreshing = true;
// 处理403错误权限不足
if (errorResponse.status === "403") {
// 显示权限不足提示,但不重定向
ElMessage.error(errorMessage || '您没有权限执行此操作');
return Promise.reject("您没有权限执行此操作");
}
// 检查是否为令牌错误(检查多种可能的错误信息格式) try {
const isTokenError = statusCode === 500 const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
// 如果是令牌错误,尝试刷新令牌或直接跳转登录页
if (isTokenError) {
// 如果是401错误且没有在刷新中尝试刷新令牌
if (statusCode === 401 || statusCode === 500 || statusCode === 403 && !originalRequest._retry ) {
// 标记该请求已尝试过重试
originalRequest._retry = true;
// 如果当前没有在刷新token
if (!isRefreshing) {
isRefreshing = true;
try {
// 从localStorage获取refreshToken
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
// 无refreshToken跳转到登录页
handleTokenExpired();
return Promise.reject(error);
}
// 创建表单数据
const formData = new FormData();
formData.append('refreshToken', refreshToken);
// 调用刷新token接口使用配置好的axios实例
const response = await axiosInstance.post('/admin/getAccessToken', formData);
// 根据实际返回的数据结构获取token
const responseData = response.data;
const accessToken = responseData.accessToken;
const newRefreshToken = responseData.refreshToken;
// 更新本地存储
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', newRefreshToken);
// 更新当前请求的Authorization头
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
// 更新全局axios和axiosInstance的默认Authorization头确保后续所有请求都带上新token
axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
console.log('新token设置成功:', accessToken);
// 执行队列中的所有请求
requests.forEach(cb => cb(accessToken));
requests = [];
// 重新发送之前失败的请求
return instance(originalRequest);
} catch (refreshError) {
// 刷新token失败清除token并跳转到登录页
handleTokenExpired(); handleTokenExpired();
return Promise.reject(refreshError); return Promise.reject(error);
} finally {
isRefreshing = false;
} }
} else {
// 如果已经在刷新中,将请求加入队列 // 调用刷新接口
return new Promise(resolve => { const formData = new FormData();
requests.push(token => { formData.append('refreshToken', refreshToken);
originalRequest.headers.Authorization = token;
resolve(instance(originalRequest)); const response = await axiosInstance.post('/admin/refreshToken', formData);
}); console.log('刷新token响应:', response);
});
// 后端返回格式: { code: 200, data: { accessToken: "xxx", refreshToken: "xxx" } }
// 由于axios拦截器已经处理了response.data所以这里直接使用response.data
const responseData = response.data || response;
console.log('响应数据:', responseData);
const accessToken = responseData.accessToken;
const newRefreshToken = responseData.refreshToken;
if (!accessToken) {
throw new Error('刷新token失败未获取到新的accessToken');
}
// 更新本地存储
localStorage.setItem('accessToken', accessToken);
if (newRefreshToken) {
localStorage.setItem('refreshToken', newRefreshToken);
}
// 更新请求头
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
// 处理队列中的请求
requests.forEach(cb => cb(accessToken));
requests = [];
return instance(originalRequest);
} catch (refreshError) {
handleTokenExpired();
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
} }
} else { } else {
// 其他令牌错误,直接处理登录过期 // 正在刷新中,将请求加入队列
handleTokenExpired(); return new Promise(resolve => {
return Promise.reject(error); requests.push(token => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(instance(originalRequest));
});
});
} }
} }

View File

@ -47,7 +47,7 @@ export function deleteRole(id) {
*/ */
export function getRolePermissions(roleId) { export function getRolePermissions(roleId) {
return axios({ return axios({
url: `/admin/role/permissions/${roleId}`, url: `/admin/role/permissions/selectRoleAndPermissions/${roleId}`,
method: 'get' method: 'get'
}) })
} }

View File

@ -95,8 +95,8 @@
<el-form-item label="邮箱" prop="email"> <el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" /> <el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item> </el-form-item>
<el-form-item label="角色" prop="roleId"> <el-form-item label="角色" prop="roleIds">
<el-select v-model="form.roleId" placeholder="请选择角色"> <el-select v-model="form.roleIds" placeholder="请选择角色" multiple collapse-tags>
<el-option <el-option
v-for="role in roleList" v-for="role in roleList"
:key="role.id" :key="role.id"
@ -148,7 +148,7 @@ const form = reactive({
nickname: '', nickname: '',
phone: '', phone: '',
email: '', email: '',
roleId: null // ID roleIds: [] // ID
}) })
// //
@ -190,8 +190,8 @@ const rules = reactive({
email: [ email: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' } { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
], ],
roleId: [ roleIds: [
{ required: true, message: '请选择角色', trigger: 'change' } { required: true, message: '请选择角色', trigger: 'change', type: 'array' }
] ]
}) })
@ -277,6 +277,14 @@ const getUserDetail = async (id) => {
form[key] = res.data[key] form[key] = res.data[key]
} }
}) })
//
if (res.data.roleIds) {
form.roleIds = res.data.roleIds
} else if (res.data.roleId) {
//
form.roleIds = res.data.roleId ? [res.data.roleId] : []
}
} else { } else {
ElMessage.error(res.message || '获取用户信息失败') ElMessage.error(res.message || '获取用户信息失败')
dialogVisible.value = false dialogVisible.value = false

View File

@ -114,7 +114,7 @@ export default {
id: '', id: '',
parentId: null, parentId: null,
permissionName: '', permissionName: '',
permissionCode: '', permissionCode: null,
description: '', description: '',
type: 1, type: 1,
path: '', path: '',
@ -171,7 +171,7 @@ export default {
permissionForm.id = '' permissionForm.id = ''
permissionForm.parentId = parentNode ? parentNode.id : null permissionForm.parentId = parentNode ? parentNode.id : null
permissionForm.permissionName = '' permissionForm.permissionName = ''
permissionForm.permissionCode = '' permissionForm.permissionCode = null
permissionForm.description = '' permissionForm.description = ''
permissionForm.type = 1 permissionForm.type = 1
permissionForm.path = '' permissionForm.path = ''
@ -179,7 +179,7 @@ export default {
permissionForm.icon = '' permissionForm.icon = ''
permissionForm.sort = 0 permissionForm.sort = 0
permissionForm.status = 1 permissionForm.status = 1
permissionForm.isDel = 0 // permissionForm.isDel = 0
parentPermissionName.value = parentNode ? parentNode.name : '无(根权限)' parentPermissionName.value = parentNode ? parentNode.name : '无(根权限)'
@ -233,7 +233,7 @@ export default {
try { try {
// //
if (permissionForm.type === 1) { if (permissionForm.type === 1) {
permissionForm.permissionCode = ''; permissionForm.permissionCode = null;
} }
const data = { const data = {