feat(service): 添加自定义请求模块并优化认证逻辑
- 新增 request 目录,实现自定义请求类和拦截器 - 添加全局状态管理,用于存储认证信息 - 优化 token 刷新逻辑,提高请求安全性 - 移除不必要的 console.log 语句- 更新项目依赖,添加 axios库
This commit is contained in:
parent
5315b0fc0c
commit
7dfda1a3d3
@ -5,6 +5,8 @@ import itemDetail from '@/components/itemDetail/index.vue';
|
||||
import {homeStore} from "@/stores/home/index.js";
|
||||
import Column from './components/Column/index.vue'
|
||||
const {fullLive} = homeStore();
|
||||
import { useFetch } from '#app'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
i18n: 'menu.home',
|
||||
|
@ -25,7 +25,6 @@ const onConfirm = ({ selectedValues, selectedOptions }) => {
|
||||
showPicker.value = false
|
||||
}
|
||||
const onBirthdayConfirm = (value) => {
|
||||
console.log(value)
|
||||
birthdayDate.value = value.selectedValues
|
||||
birthday.value = value.selectedValues.join('-')
|
||||
showBirthdayPicker.value = false
|
||||
|
107
app/service/index.js
Normal file
107
app/service/index.js
Normal file
@ -0,0 +1,107 @@
|
||||
// 导入必要的模块和函数
|
||||
import { useRouter } from "vue-router"; // 用于路由导航
|
||||
import Request from "@/service/request/index.js"; // 自定义请求类
|
||||
import { message } from '@/components/x-message/useMessage.js';
|
||||
import {authStore} from "~/stores/auth/index.js"; // 消息提示组件
|
||||
|
||||
// 初始化路由
|
||||
const router = useRouter();
|
||||
let isRefreshing = false; // 标记是否正在刷新token
|
||||
let refreshSubscribers = []; // 存储刷新token的回调函数
|
||||
const config = useRuntimeConfig(); // 获取运行时配置
|
||||
|
||||
// 创建请求实例
|
||||
const request = new Request({
|
||||
baseURL: config.public.NUXT_PUBLIC_API_BASE, // 基础URL
|
||||
timeout: 1000 * 60 * 5, // 超时时间设置为5分钟
|
||||
showMsg: true, // 是否显示消息提示
|
||||
interceptors: {
|
||||
// 请求拦截器
|
||||
requestInterceptors: (config) => {
|
||||
// 根据是否是表单数据设置Content-Type
|
||||
const contentType = config.isFormData ? 'multipart/form-data' : config.contentType || 'application/json';
|
||||
config.headers = {
|
||||
"Content-Type": contentType,
|
||||
"Authorization": localStorage.getItem('token') || '' // 从本地存储获取token
|
||||
};
|
||||
return config; // 返回修改后的配置
|
||||
},
|
||||
// 响应拦截器
|
||||
responseInterceptors: async (response) => {
|
||||
// 如果需要显示消息且状态为1,显示警告消息
|
||||
if (response.config.showMsg && response.data.status === 1) {
|
||||
message.warning(response.data.msg);
|
||||
}
|
||||
// 如果响应码为401,尝试刷新token
|
||||
if (response.data.code === 401) {
|
||||
return getRefreshToken(response);
|
||||
}
|
||||
// 如果响应状态为200, 201, 204,直接返回响应
|
||||
if ([200, 201, 204].includes(response.status)) {
|
||||
return response.config.responseType === "blob" ? response : response;
|
||||
} else {
|
||||
// 处理其他错误情况
|
||||
const errorMsg = response.data.msg || 'An error occurred.';
|
||||
message.error(errorMsg);
|
||||
return Promise.reject(new Error(errorMsg));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 刷新token的函数
|
||||
async function getRefreshToken(response) {
|
||||
const {token,RefreshToken,userInfo}= authStore()
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true; // 标记正在刷新
|
||||
const refreshToken = RefreshToken.value; // 获取刷新token
|
||||
if (refreshToken) {
|
||||
try {
|
||||
// 发送请求刷新token
|
||||
const res = await request.post("/user/refresh/token", { refreshToken });
|
||||
if (res.code === 200) {
|
||||
// 更新本地存储的token和用户信息
|
||||
token.value = res.data.Token;
|
||||
userInfo.value=res.data.AccountInfo
|
||||
RefreshToken.value = res.data.RefreshToken;
|
||||
response.config.headers["Authorization"] = res.data.Token; // 更新请求头的token
|
||||
return request.request(response.config); // 重新发送原始请求
|
||||
} else {
|
||||
handleAuthError(res); // 处理认证错误
|
||||
}
|
||||
} catch (error) {
|
||||
throw error; // 抛出错误
|
||||
} finally {
|
||||
isRefreshing = false; // 重置刷新标记
|
||||
refreshSubscribers.forEach(callback => callback()); // 执行所有等待的回调
|
||||
refreshSubscribers = []; // 清空回调列表
|
||||
}
|
||||
} else {
|
||||
handleAuthError(); // 没有刷新token时处理错误
|
||||
}
|
||||
} else {
|
||||
// 如果正在刷新,返回一个Promise,等待刷新完成后重新发送请求
|
||||
return new Promise(resolve => {
|
||||
refreshSubscribers.push(() => resolve(request.request(response.config)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理认证错误的函数
|
||||
function handleAuthError(res = {}) {
|
||||
router.push("/login"); // 跳转到登录页面
|
||||
const errorMsg = res.message || res.msg || "No refresh token available."; // 错误消息
|
||||
message.error(errorMsg); // 显示错误消息
|
||||
throw new Error(errorMsg); // 抛出错误
|
||||
}
|
||||
|
||||
// 发送请求的函数
|
||||
const fontRequest = (config) => {
|
||||
// 如果是GET请求,将data作为params
|
||||
if (["get", "GET"].includes(config.method)) {
|
||||
config.params = config.data;
|
||||
}
|
||||
return request.request(config); // 发送请求
|
||||
};
|
||||
|
||||
export default fontRequest; // 导出请求函数
|
84
app/service/request/index.js
Normal file
84
app/service/request/index.js
Normal file
@ -0,0 +1,84 @@
|
||||
import axios from 'axios'; // 导入axios库
|
||||
|
||||
// 自定义请求类
|
||||
class Request {
|
||||
constructor(config) {
|
||||
this.instance = axios.create(config); // 创建axios实例
|
||||
this.abortControllerMap = new Map(); // 存储请求的AbortController
|
||||
this.interceptorsObj = config.interceptors; // 存储拦截器对象
|
||||
|
||||
// 默认请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(res) => {
|
||||
const controller = new AbortController(); // 创建新的AbortController
|
||||
const url = res.url || ''; // 获取请求的URL
|
||||
res.signal = controller.signal; // 将信号绑定到请求
|
||||
this.abortControllerMap.set(url, controller); // 存储控制器
|
||||
return res; // 返回请求配置
|
||||
},
|
||||
(err) => Promise.reject(err) // 处理请求错误
|
||||
);
|
||||
|
||||
// 自定义请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
this.interceptorsObj?.requestInterceptors,
|
||||
this.interceptorsObj?.requestInterceptorsCatch
|
||||
);
|
||||
|
||||
// 自定义响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
this.interceptorsObj?.responseInterceptors,
|
||||
this.interceptorsObj?.responseInterceptorsCatch
|
||||
);
|
||||
|
||||
// 默认响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(res) => {
|
||||
const url = res.config.url || ''; // 获取响应的URL
|
||||
this.abortControllerMap.delete(url); // 删除已完成请求的控制器
|
||||
return res.data; // 返回响应数据
|
||||
},
|
||||
(err) => Promise.reject(err) // 处理响应错误
|
||||
);
|
||||
}
|
||||
|
||||
// 发送请求的方法
|
||||
request(config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 如果有自定义请求拦截器,先执行
|
||||
if (config.interceptors?.requestInterceptors) {
|
||||
config = config.interceptors.requestInterceptors(config);
|
||||
}
|
||||
// 发送请求
|
||||
this.instance
|
||||
.request(config)
|
||||
.then((res) => {
|
||||
// 如果有自定义响应拦截器,先执行
|
||||
if (config.interceptors?.responseInterceptors) {
|
||||
res = config.interceptors.responseInterceptors(res);
|
||||
}
|
||||
resolve(res); // 解析响应
|
||||
})
|
||||
.catch(reject); // 捕获错误
|
||||
});
|
||||
}
|
||||
|
||||
// 取消所有请求的方法
|
||||
cancelAllRequest() {
|
||||
for (const [, controller] of this.abortControllerMap) {
|
||||
controller.abort(); // 取消请求
|
||||
}
|
||||
this.abortControllerMap.clear(); // 清空控制器映射
|
||||
}
|
||||
|
||||
// 取消特定请求的方法
|
||||
cancelRequest(url) {
|
||||
const urlList = Array.isArray(url) ? url : [url]; // 确保url是数组
|
||||
for (const _url of urlList) {
|
||||
this.abortControllerMap.get(_url)?.abort(); // 取消请求
|
||||
this.abortControllerMap.delete(_url); // 删除控制器
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Request; // 导出自定义请求类
|
11
app/stores/auth/index.js
Normal file
11
app/stores/auth/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { createGlobalState,useLocalStorage } from '@vueuse/core'
|
||||
export const authStore = createGlobalState(() => {
|
||||
const token=useLocalStorage('token','')
|
||||
const RefreshToken=useLocalStorage('RefreshToken','')
|
||||
const userInfo=useLocalStorage('userInfo','')
|
||||
return{
|
||||
userInfo,
|
||||
RefreshToken,
|
||||
token
|
||||
}
|
||||
})
|
@ -4,7 +4,6 @@ import preload from './app/utils/preload'
|
||||
import { currentLocales } from './i18n/i18n'
|
||||
const envFile = process.env.ENV_FILE || '.env.test'
|
||||
dotenv.config({ path: `./env/${envFile}` })
|
||||
console.log('process.env',process.env)
|
||||
const publicConfig = Object.entries(process.env)
|
||||
.filter(([key]) => key.startsWith('NUXT_PUBLIC_'))
|
||||
.reduce((config, [key, value]) => {
|
||||
|
@ -21,6 +21,7 @@
|
||||
"@nuxtjs/i18n": "^9.1.1",
|
||||
"@vueuse/core": "^12.4.0",
|
||||
"aliyun-aliplayer": "^2.28.5",
|
||||
"axios": "^1.7.9",
|
||||
"dotenv": "^16.4.7",
|
||||
"nuxt": "^3.15.0",
|
||||
"pinyin": "4.0.0-alpha.2",
|
||||
|
@ -23,6 +23,9 @@ importers:
|
||||
aliyun-aliplayer:
|
||||
specifier: ^2.28.5
|
||||
version: 2.28.5
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.4.7
|
||||
@ -1663,6 +1666,9 @@ packages:
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
autoprefixer@10.4.20:
|
||||
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@ -1670,6 +1676,9 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
axios@1.7.9:
|
||||
resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
|
||||
|
||||
b4a@1.6.7:
|
||||
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
|
||||
|
||||
@ -1822,6 +1831,10 @@ packages:
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@1.1.1:
|
||||
resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==}
|
||||
engines: {node: '>= 0.6.x'}
|
||||
@ -2028,6 +2041,10 @@ packages:
|
||||
defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
denque@2.1.0:
|
||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||
engines: {node: '>=0.10'}
|
||||
@ -2304,10 +2321,23 @@ packages:
|
||||
flatted@3.3.2:
|
||||
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
foreground-child@3.3.0:
|
||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.1:
|
||||
resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@ -3337,6 +3367,9 @@ packages:
|
||||
protocols@2.0.1:
|
||||
resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@ -6005,6 +6038,8 @@ snapshots:
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
autoprefixer@10.4.20(postcss@8.4.49):
|
||||
dependencies:
|
||||
browserslist: 4.24.3
|
||||
@ -6015,6 +6050,14 @@ snapshots:
|
||||
postcss: 8.4.49
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
axios@1.7.9:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.1
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
b4a@1.6.7: {}
|
||||
|
||||
babel-plugin-macros@2.8.0:
|
||||
@ -6187,6 +6230,10 @@ snapshots:
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@1.1.1:
|
||||
dependencies:
|
||||
keypress: 0.1.0
|
||||
@ -6372,6 +6419,8 @@ snapshots:
|
||||
|
||||
defu@6.1.4: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
denque@2.1.0: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
@ -6719,11 +6768,19 @@ snapshots:
|
||||
|
||||
flatted@3.3.2: {}
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
foreground-child@3.3.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.1:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
fresh@0.5.2: {}
|
||||
@ -7870,6 +7927,8 @@ snapshots:
|
||||
|
||||
protocols@2.0.1: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
Loading…
Reference in New Issue
Block a user