feat(service): 添加自定义请求模块并优化认证逻辑

- 新增 request 目录,实现自定义请求类和拦截器
- 添加全局状态管理,用于存储认证信息
- 优化 token 刷新逻辑,提高请求安全性
- 移除不必要的 console.log 语句- 更新项目依赖,添加 axios库
This commit is contained in:
xingyy 2025-01-16 11:48:12 +08:00
parent 5315b0fc0c
commit 7dfda1a3d3
8 changed files with 264 additions and 2 deletions

View File

@ -5,6 +5,8 @@ import itemDetail from '@/components/itemDetail/index.vue';
import {homeStore} from "@/stores/home/index.js"; import {homeStore} from "@/stores/home/index.js";
import Column from './components/Column/index.vue' import Column from './components/Column/index.vue'
const {fullLive} = homeStore(); const {fullLive} = homeStore();
import { useFetch } from '#app'
definePageMeta({ definePageMeta({
layout: 'default', layout: 'default',
i18n: 'menu.home', i18n: 'menu.home',

View File

@ -25,7 +25,6 @@ const onConfirm = ({ selectedValues, selectedOptions }) => {
showPicker.value = false showPicker.value = false
} }
const onBirthdayConfirm = (value) => { const onBirthdayConfirm = (value) => {
console.log(value)
birthdayDate.value = value.selectedValues birthdayDate.value = value.selectedValues
birthday.value = value.selectedValues.join('-') birthday.value = value.selectedValues.join('-')
showBirthdayPicker.value = false showBirthdayPicker.value = false

107
app/service/index.js Normal file
View 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; // 导出请求函数

View 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
View 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
}
})

View File

@ -4,7 +4,6 @@ import preload from './app/utils/preload'
import { currentLocales } from './i18n/i18n' import { currentLocales } from './i18n/i18n'
const envFile = process.env.ENV_FILE || '.env.test' const envFile = process.env.ENV_FILE || '.env.test'
dotenv.config({ path: `./env/${envFile}` }) dotenv.config({ path: `./env/${envFile}` })
console.log('process.env',process.env)
const publicConfig = Object.entries(process.env) const publicConfig = Object.entries(process.env)
.filter(([key]) => key.startsWith('NUXT_PUBLIC_')) .filter(([key]) => key.startsWith('NUXT_PUBLIC_'))
.reduce((config, [key, value]) => { .reduce((config, [key, value]) => {

View File

@ -21,6 +21,7 @@
"@nuxtjs/i18n": "^9.1.1", "@nuxtjs/i18n": "^9.1.1",
"@vueuse/core": "^12.4.0", "@vueuse/core": "^12.4.0",
"aliyun-aliplayer": "^2.28.5", "aliyun-aliplayer": "^2.28.5",
"axios": "^1.7.9",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"nuxt": "^3.15.0", "nuxt": "^3.15.0",
"pinyin": "4.0.0-alpha.2", "pinyin": "4.0.0-alpha.2",

View File

@ -23,6 +23,9 @@ importers:
aliyun-aliplayer: aliyun-aliplayer:
specifier: ^2.28.5 specifier: ^2.28.5
version: 2.28.5 version: 2.28.5
axios:
specifier: ^1.7.9
version: 1.7.9
dotenv: dotenv:
specifier: ^16.4.7 specifier: ^16.4.7
version: 16.4.7 version: 16.4.7
@ -1663,6 +1666,9 @@ packages:
async@3.2.6: async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
autoprefixer@10.4.20: autoprefixer@10.4.20:
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@ -1670,6 +1676,9 @@ packages:
peerDependencies: peerDependencies:
postcss: ^8.1.0 postcss: ^8.1.0
axios@1.7.9:
resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
b4a@1.6.7: b4a@1.6.7:
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
@ -1822,6 +1831,10 @@ packages:
colorette@2.0.20: colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} 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: commander@1.1.1:
resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==} resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==}
engines: {node: '>= 0.6.x'} engines: {node: '>= 0.6.x'}
@ -2028,6 +2041,10 @@ packages:
defu@6.1.4: defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
denque@2.1.0: denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@ -2304,10 +2321,23 @@ packages:
flatted@3.3.2: flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} 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: foreground-child@3.3.0:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'} engines: {node: '>=14'}
form-data@4.0.1:
resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
engines: {node: '>= 6'}
fraction.js@4.3.7: fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@ -3337,6 +3367,9 @@ packages:
protocols@2.0.1: protocols@2.0.1:
resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
punycode@2.3.1: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -6005,6 +6038,8 @@ snapshots:
async@3.2.6: {} async@3.2.6: {}
asynckit@0.4.0: {}
autoprefixer@10.4.20(postcss@8.4.49): autoprefixer@10.4.20(postcss@8.4.49):
dependencies: dependencies:
browserslist: 4.24.3 browserslist: 4.24.3
@ -6015,6 +6050,14 @@ snapshots:
postcss: 8.4.49 postcss: 8.4.49
postcss-value-parser: 4.2.0 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: {} b4a@1.6.7: {}
babel-plugin-macros@2.8.0: babel-plugin-macros@2.8.0:
@ -6187,6 +6230,10 @@ snapshots:
colorette@2.0.20: {} colorette@2.0.20: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
commander@1.1.1: commander@1.1.1:
dependencies: dependencies:
keypress: 0.1.0 keypress: 0.1.0
@ -6372,6 +6419,8 @@ snapshots:
defu@6.1.4: {} defu@6.1.4: {}
delayed-stream@1.0.0: {}
denque@2.1.0: {} denque@2.1.0: {}
depd@2.0.0: {} depd@2.0.0: {}
@ -6719,11 +6768,19 @@ snapshots:
flatted@3.3.2: {} flatted@3.3.2: {}
follow-redirects@1.15.9: {}
foreground-child@3.3.0: foreground-child@3.3.0:
dependencies: dependencies:
cross-spawn: 7.0.6 cross-spawn: 7.0.6
signal-exit: 4.1.0 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: {} fraction.js@4.3.7: {}
fresh@0.5.2: {} fresh@0.5.2: {}
@ -7870,6 +7927,8 @@ snapshots:
protocols@2.0.1: {} protocols@2.0.1: {}
proxy-from-env@1.1.0: {}
punycode@2.3.1: {} punycode@2.3.1: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}