diff --git a/Yi.Ai.Vue3/.env.development b/Yi.Ai.Vue3/.env.development index b38eb0cf..b32755dd 100644 --- a/Yi.Ai.Vue3/.env.development +++ b/Yi.Ai.Vue3/.env.development @@ -12,8 +12,8 @@ VITE_WEB_BASE_API = '/dev-api' # VITE_WEB_BASE_API='/prod-api' # 本地接口 -VITE_API_URL = http://localhost:19001/api/app -# VITE_API_URL=http://ccnetcore.com:19001/api/app +# VITE_API_URL = http://localhost:19001/api/app +VITE_API_URL=http://ccnetcore.com:19001/api/app # SSO单点登录url VITE_SSO_SEVER_URL='http://localhost:18001' diff --git a/Yi.Ai.Vue3/src/api/auth/index.ts b/Yi.Ai.Vue3/src/api/auth/index.ts index c1b21b92..ce10f956 100644 --- a/Yi.Ai.Vue3/src/api/auth/index.ts +++ b/Yi.Ai.Vue3/src/api/auth/index.ts @@ -3,6 +3,9 @@ import { post } from '@/utils/request'; export const login = (data: LoginDTO) => post('/auth/login', data).json(); +// 刷新token +export const updateToken = (data: any) => post('/account/refresh', data).json(); + // 邮箱验证码 export const emailCode = (data: EmailCodeDTO) => post('/resource/email/code', data).json(); diff --git a/Yi.Ai.Vue3/src/components/LoginDialog/index.vue b/Yi.Ai.Vue3/src/components/LoginDialog/index.vue index 521db32a..b1af9101 100644 --- a/Yi.Ai.Vue3/src/components/LoginDialog/index.vue +++ b/Yi.Ai.Vue3/src/components/LoginDialog/index.vue @@ -76,8 +76,8 @@ function handleThirdPartyLogin() { try { // 清理监听 window.removeEventListener('message', messageHandler); - const { token } = event.data; - userStore.setToken(token); + const { token, refreshToken } = event.data; + userStore.setToken(token, refreshToken); const resUserInfo = await getUserInfo(); userStore.setUserInfo(resUserInfo.data); // 关闭弹窗 diff --git a/Yi.Ai.Vue3/src/stores/modules/user.ts b/Yi.Ai.Vue3/src/stores/modules/user.ts index bd34be9b..7948f135 100644 --- a/Yi.Ai.Vue3/src/stores/modules/user.ts +++ b/Yi.Ai.Vue3/src/stores/modules/user.ts @@ -6,12 +6,20 @@ export const useUserStore = defineStore( 'user', () => { const token = ref(); + const refreshToken = ref(); const router = useRouter(); - const setToken = (value: string) => { + const setToken = (value: string, refreshValue?: string) => { + // 让接口报401 + // token.value = `${value}cdsfds`; + token.value = value; + if (refreshValue) { + refreshToken.value = refreshValue; + } }; const clearToken = () => { token.value = void 0; + refreshToken.value = void 0; }; const userInfo = ref(); @@ -44,6 +52,7 @@ export const useUserStore = defineStore( return { token, + refreshToken, setToken, clearToken, userInfo, diff --git a/Yi.Ai.Vue3/src/utils/request.ts b/Yi.Ai.Vue3/src/utils/request.ts index 92ef1822..e65af17c 100644 --- a/Yi.Ai.Vue3/src/utils/request.ts +++ b/Yi.Ai.Vue3/src/utils/request.ts @@ -1,26 +1,21 @@ -import type { HookFetchPlugin } from 'hook-fetch'; -import FingerprintJS from '@fingerprintjs/fingerprintjs'; // 新增指纹库 +import FingerprintJS from '@fingerprintjs/fingerprintjs'; import { ElMessage } from 'element-plus'; import hookFetch from 'hook-fetch'; import { sseTextDecoderPlugin } from 'hook-fetch/plugins'; import router from '@/routers'; import { useUserStore } from '@/stores'; -// 初始化指纹(单例模式) +// 初始化指纹 const fpPromise = FingerprintJS.load(); - -// 获取浏览器指纹(缓存结果) async function getFingerprint(): Promise { const fp = await fpPromise; - const { visitorId } = await fp.get(); - return visitorId; + return (await fp.get()).visitorId; } -// 标准响应格式 interface BaseResponse { - code: number; + code?: number; // 变为可选字段 data: T; - msg: string; + msg?: string; } declare module 'hook-fetch' { @@ -29,6 +24,10 @@ declare module 'hook-fetch' { } } +// 刷新控制变量 +let isRefreshing = false; +let pendingRequests: (() => void)[] = []; + export const request = hookFetch.create({ baseURL: import.meta.env.VITE_WEB_BASE_API, headers: { @@ -37,7 +36,7 @@ export const request = hookFetch.create({ plugins: [ sseTextDecoderPlugin({ json: true, prefix: 'data:' }), { - name: 'fingerprint-plugin', // 新增指纹插件 + name: 'fingerprint-plugin', beforeRequest: async (config) => { try { const fingerprint = await getFingerprint(); @@ -45,66 +44,110 @@ export const request = hookFetch.create({ config.headers.set('X-Fingerprint', fingerprint); } catch (error) { - console.error('Failed to generate fingerprint:', error); + console.error('指纹生成失败:', error); } return config; }, }, { - name: 'adapt-array-response', - afterResponse: async (response) => { - if (typeof response.result?.code === 'number') { - return response; + name: 'jwt-auth', + beforeRequest: (config) => { + const userStore = useUserStore(); + if (userStore.token) { + config.headers = new Headers(config.headers); + config.headers.set('Authorization', `Bearer ${userStore.token}`); } - return { - ...response, - result: { - code: 200, - data: response.result, - msg: 'success', - }, - }; + return config; + }, + afterResponse: async (response: any, config: any) => { + const userStore = useUserStore(); + console.log('网络请求响应----', response); + // 成功请求(HTTP状态码200-299) + if (response.ok) { + // 兼容响应体可能没有code字段的情况 + const data = await response.json(); + return { + ...response, + result: data.code ? data : { code: 200, data, msg: 'success' }, + }; + } + + // 处理HTTP状态码401 + if (response.status === 401) { + // 刷新Token接口自身失败 + if (config.url?.includes('/account/refresh')) { + userStore.logout(); + userStore.openLoginDialog(); + throw new Error('刷新Token失败'); + } + + // 将请求加入队列 + if (isRefreshing) { + return new Promise((resolve) => { + pendingRequests.push(() => resolve(request(config))); + }); + } + + // 开始刷新流程 + isRefreshing = true; + try { + const refreshRes = await request.post('/account/refresh', { + refresh_token: userStore.refreshToken, + }).json(); + + // 更新Token + userStore.setToken(refreshRes.data.token, refreshRes.data.refreshToken); + + // 重试原始请求 + config.headers.set('Authorization', `Bearer ${refreshRes.data.token}`); + const retryResponse = await request(config); + + // 执行等待中的请求 + pendingRequests.forEach(cb => cb()); + pendingRequests = []; + return retryResponse; + } + catch (error) { + // 刷新失败清除状态 + userStore.logout(); + userStore.openLoginDialog(); + throw error; + } + finally { + isRefreshing = false; + } + } + + // 处理HTTP状态码403 + if (response.status === 403) { + router.replace({ name: '403' }); + const errorMsg = (await response.json().catch(() => ({})))?.msg || '无权限访问'; + ElMessage.error(errorMsg); + throw new Error('Forbidden'); + } + + // 其他错误状态码 + const errorData = await response.json().catch(() => ({})); + ElMessage.error(errorData.msg || `请求失败: ${response.status}`); + throw errorData; + }, + // 错误处理 + async onError(error: any, config: any) { + console.log('网络请求错误----', error); + console.log('网络请求错误--config--', config); + + // 可以处理或转换错误 + if (error.status === 401) { + // 处理未授权错误 + return new Error('Please login first'); + } + return error; }, }, ], }); -// JWT插件(保持原有逻辑) -function jwtPlugin(): HookFetchPlugin { - const userStore = useUserStore(); - return { - name: 'jwt', - beforeRequest: async (config) => { - if (userStore.token) { - config.headers = new Headers(config.headers); - config.headers.set('authorization', `Bearer ${userStore.token}`); - } - return config; - }, - afterResponse: async (response) => { - if (response.result?.code === 200) - return response; - - if (response.result?.code === 403) { - router.replace({ name: '403' }); - ElMessage.error(response.result?.msg); - return Promise.reject(response); - } - - if (response.result?.code === 401) { - userStore.logout(); - userStore.openLoginDialog(); - } - - ElMessage.error(response.result?.msg); - return Promise.reject(response); - }, - }; -} - -request.use(jwtPlugin()); - -// 导出方法(保持原有) +// 保持原有导出 export const post = request.post; export const get = request.get; export const put = request.put; diff --git a/Yi.Bbs.Vue3/src/hooks/useAuths.js b/Yi.Bbs.Vue3/src/hooks/useAuths.js index fa32eadc..6b40986e 100644 --- a/Yi.Bbs.Vue3/src/hooks/useAuths.js +++ b/Yi.Bbs.Vue3/src/hooks/useAuths.js @@ -1,21 +1,18 @@ -import { ElMessage, ElMessageBox } from "element-plus"; +import {ElMessage, ElMessageBox} from "element-plus"; import useUserStore from "@/stores/user"; import router from "@/router"; -import { Session, Local } from "@/utils/storage"; -import{computed} from 'vue' -import { - userLogin, - getUserDetailInfo, - userLogout, - userRegister, userRetrievePassword, -} from "@/apis/auth"; +import {Local, Session} from "@/utils/storage"; +import {computed} from 'vue' +import {getUserDetailInfo, userLogin, userLogout, userRegister, userRetrievePassword,} from "@/apis/auth"; + const TokenKey = "AccessToken"; +const RefreshTokenKey = "RefreshToken"; export const AUTH_MENUS = "AUTH_MENUS"; export const AUTH_USER = "AUTH_USER"; export default function useAuths(opt) { - + const defaultOpt = { @@ -35,6 +32,10 @@ export default function useAuths(opt) { var token= Local.get(TokenKey); return token; }; + // 获取token + const getRefreshToken = () => { + return Local.get(RefreshTokenKey); + }; const isLogin=computed(()=>{ return getToken()? true : false @@ -42,7 +43,7 @@ const isLogin=computed(()=>{ const currentUserInfo=computed(()=>{ return useUserStore(); -}); +}); // 存储token到cookies const setToken = (token) => { @@ -50,7 +51,16 @@ const currentUserInfo=computed(()=>{ return false; } Local.set(TokenKey, token); - + + return true; + }; + // 存储RefreshToken到cookies + const setRefreshToken = (token) => { + if (token == null) { + return false; + } + Local.set(RefreshTokenKey, token); + return true; }; @@ -141,9 +151,10 @@ const currentUserInfo=computed(()=>{ // 登录成功之后的操作 const loginSuccess = async (res) => { - const { token } = res.data; + const { token,refreshToken } = res.data; setToken(token); + setRefreshToken(refreshToken); useUserStore().updateToken(token); try { // 存储用户信息 @@ -195,10 +206,12 @@ const currentUserInfo=computed(()=>{ // console.log(error); // } }; - + return { getToken, + getRefreshToken, setToken, + setRefreshToken, removeToken, loginFun, getUserInfo, diff --git a/Yi.Bbs.Vue3/src/permission.js b/Yi.Bbs.Vue3/src/permission.js index 6ba38d23..e9670f15 100644 --- a/Yi.Bbs.Vue3/src/permission.js +++ b/Yi.Bbs.Vue3/src/permission.js @@ -6,12 +6,13 @@ import "nprogress/nprogress.css"; import useUserStore from "@/stores/user"; NProgress.configure({ showSpinner: false }); -const { getToken, logoutFun } = useAuths(); +const { getToken, logoutFun ,getRefreshToken} = useAuths(); const whiteList = ["/login", "/auth-redirect", "/bind", "/register"]; router.beforeEach((to, from, next) => { NProgress.start(); const hasToken = getToken(); + const refreshToken = getRefreshToken(); if (to.path === "/login" || to.path === "/index") { const urlParams = new URLSearchParams(window.location.search); const isPopup = window.opener && window.opener !== window; @@ -24,7 +25,8 @@ router.beforeEach((to, from, next) => { const targetOrigin = new URL(decodeURIComponent(redirectUri)).origin; window.opener.postMessage({ type: 'SSO_LOGIN_SUCCESS', - token: hasToken + token: hasToken, + refreshToken: refreshToken, }, targetOrigin); // 立即关闭窗口 setTimeout(() => window.close(), 100);