fix: bbs与ai存储refreshToken
This commit is contained in:
@@ -12,8 +12,8 @@ VITE_WEB_BASE_API = '/dev-api'
|
|||||||
# VITE_WEB_BASE_API='/prod-api'
|
# VITE_WEB_BASE_API='/prod-api'
|
||||||
|
|
||||||
# 本地接口
|
# 本地接口
|
||||||
VITE_API_URL = http://localhost:19001/api/app
|
# VITE_API_URL = http://localhost:19001/api/app
|
||||||
# VITE_API_URL=http://ccnetcore.com:19001/api/app
|
VITE_API_URL=http://ccnetcore.com:19001/api/app
|
||||||
|
|
||||||
# SSO单点登录url
|
# SSO单点登录url
|
||||||
VITE_SSO_SEVER_URL='http://localhost:18001'
|
VITE_SSO_SEVER_URL='http://localhost:18001'
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { post } from '@/utils/request';
|
|||||||
|
|
||||||
export const login = (data: LoginDTO) => post<LoginVO>('/auth/login', data).json();
|
export const login = (data: LoginDTO) => post<LoginVO>('/auth/login', data).json();
|
||||||
|
|
||||||
|
// 刷新token
|
||||||
|
export const updateToken = (data: any) => post<any>('/account/refresh', data).json();
|
||||||
|
|
||||||
// 邮箱验证码
|
// 邮箱验证码
|
||||||
export const emailCode = (data: EmailCodeDTO) => post('/resource/email/code', data).json();
|
export const emailCode = (data: EmailCodeDTO) => post('/resource/email/code', data).json();
|
||||||
|
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ function handleThirdPartyLogin() {
|
|||||||
try {
|
try {
|
||||||
// 清理监听
|
// 清理监听
|
||||||
window.removeEventListener('message', messageHandler);
|
window.removeEventListener('message', messageHandler);
|
||||||
const { token } = event.data;
|
const { token, refreshToken } = event.data;
|
||||||
userStore.setToken(token);
|
userStore.setToken(token, refreshToken);
|
||||||
const resUserInfo = await getUserInfo();
|
const resUserInfo = await getUserInfo();
|
||||||
userStore.setUserInfo(resUserInfo.data);
|
userStore.setUserInfo(resUserInfo.data);
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
|
|||||||
@@ -6,12 +6,20 @@ export const useUserStore = defineStore(
|
|||||||
'user',
|
'user',
|
||||||
() => {
|
() => {
|
||||||
const token = ref<string>();
|
const token = ref<string>();
|
||||||
|
const refreshToken = ref<string | undefined>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const setToken = (value: string) => {
|
const setToken = (value: string, refreshValue?: string) => {
|
||||||
|
// 让接口报401
|
||||||
|
// token.value = `${value}cdsfds`;
|
||||||
|
|
||||||
token.value = value;
|
token.value = value;
|
||||||
|
if (refreshValue) {
|
||||||
|
refreshToken.value = refreshValue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const clearToken = () => {
|
const clearToken = () => {
|
||||||
token.value = void 0;
|
token.value = void 0;
|
||||||
|
refreshToken.value = void 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const userInfo = ref<LoginUser>();
|
const userInfo = ref<LoginUser>();
|
||||||
@@ -44,6 +52,7 @@ export const useUserStore = defineStore(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
|
refreshToken,
|
||||||
setToken,
|
setToken,
|
||||||
clearToken,
|
clearToken,
|
||||||
userInfo,
|
userInfo,
|
||||||
|
|||||||
@@ -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 { ElMessage } from 'element-plus';
|
||||||
import hookFetch from 'hook-fetch';
|
import hookFetch from 'hook-fetch';
|
||||||
import { sseTextDecoderPlugin } from 'hook-fetch/plugins';
|
import { sseTextDecoderPlugin } from 'hook-fetch/plugins';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
// 初始化指纹(单例模式)
|
// 初始化指纹
|
||||||
const fpPromise = FingerprintJS.load();
|
const fpPromise = FingerprintJS.load();
|
||||||
|
|
||||||
// 获取浏览器指纹(缓存结果)
|
|
||||||
async function getFingerprint(): Promise<string> {
|
async function getFingerprint(): Promise<string> {
|
||||||
const fp = await fpPromise;
|
const fp = await fpPromise;
|
||||||
const { visitorId } = await fp.get();
|
return (await fp.get()).visitorId;
|
||||||
return visitorId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标准响应格式
|
|
||||||
interface BaseResponse<T = any> {
|
interface BaseResponse<T = any> {
|
||||||
code: number;
|
code?: number; // 变为可选字段
|
||||||
data: T;
|
data: T;
|
||||||
msg: string;
|
msg?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'hook-fetch' {
|
declare module 'hook-fetch' {
|
||||||
@@ -29,6 +24,10 @@ declare module 'hook-fetch' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新控制变量
|
||||||
|
let isRefreshing = false;
|
||||||
|
let pendingRequests: (() => void)[] = [];
|
||||||
|
|
||||||
export const request = hookFetch.create<BaseResponse>({
|
export const request = hookFetch.create<BaseResponse>({
|
||||||
baseURL: import.meta.env.VITE_WEB_BASE_API,
|
baseURL: import.meta.env.VITE_WEB_BASE_API,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -37,7 +36,7 @@ export const request = hookFetch.create<BaseResponse>({
|
|||||||
plugins: [
|
plugins: [
|
||||||
sseTextDecoderPlugin({ json: true, prefix: 'data:' }),
|
sseTextDecoderPlugin({ json: true, prefix: 'data:' }),
|
||||||
{
|
{
|
||||||
name: 'fingerprint-plugin', // 新增指纹插件
|
name: 'fingerprint-plugin',
|
||||||
beforeRequest: async (config) => {
|
beforeRequest: async (config) => {
|
||||||
try {
|
try {
|
||||||
const fingerprint = await getFingerprint();
|
const fingerprint = await getFingerprint();
|
||||||
@@ -45,66 +44,110 @@ export const request = hookFetch.create<BaseResponse>({
|
|||||||
config.headers.set('X-Fingerprint', fingerprint);
|
config.headers.set('X-Fingerprint', fingerprint);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Failed to generate fingerprint:', error);
|
console.error('指纹生成失败:', error);
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'adapt-array-response',
|
name: 'jwt-auth',
|
||||||
afterResponse: async (response) => {
|
beforeRequest: (config) => {
|
||||||
if (typeof response.result?.code === 'number') {
|
const userStore = useUserStore();
|
||||||
return response;
|
if (userStore.token) {
|
||||||
|
config.headers = new Headers(config.headers);
|
||||||
|
config.headers.set('Authorization', `Bearer ${userStore.token}`);
|
||||||
}
|
}
|
||||||
return {
|
return config;
|
||||||
...response,
|
},
|
||||||
result: {
|
afterResponse: async (response: any, config: any) => {
|
||||||
code: 200,
|
const userStore = useUserStore();
|
||||||
data: response.result,
|
console.log('网络请求响应----', response);
|
||||||
msg: 'success',
|
// 成功请求(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<BaseResponse> {
|
|
||||||
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 post = request.post;
|
||||||
export const get = request.get;
|
export const get = request.get;
|
||||||
export const put = request.put;
|
export const put = request.put;
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import useUserStore from "@/stores/user";
|
import useUserStore from "@/stores/user";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { Session, Local } from "@/utils/storage";
|
import {Local, Session} from "@/utils/storage";
|
||||||
import{computed} from 'vue'
|
import {computed} from 'vue'
|
||||||
import {
|
import {getUserDetailInfo, userLogin, userLogout, userRegister, userRetrievePassword,} from "@/apis/auth";
|
||||||
userLogin,
|
|
||||||
getUserDetailInfo,
|
|
||||||
userLogout,
|
|
||||||
userRegister, userRetrievePassword,
|
|
||||||
} from "@/apis/auth";
|
|
||||||
const TokenKey = "AccessToken";
|
const TokenKey = "AccessToken";
|
||||||
|
const RefreshTokenKey = "RefreshToken";
|
||||||
export const AUTH_MENUS = "AUTH_MENUS";
|
export const AUTH_MENUS = "AUTH_MENUS";
|
||||||
export const AUTH_USER = "AUTH_USER";
|
export const AUTH_USER = "AUTH_USER";
|
||||||
|
|
||||||
@@ -35,6 +32,10 @@ export default function useAuths(opt) {
|
|||||||
var token= Local.get(TokenKey);
|
var token= Local.get(TokenKey);
|
||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
// 获取token
|
||||||
|
const getRefreshToken = () => {
|
||||||
|
return Local.get(RefreshTokenKey);
|
||||||
|
};
|
||||||
|
|
||||||
const isLogin=computed(()=>{
|
const isLogin=computed(()=>{
|
||||||
return getToken()? true : false
|
return getToken()? true : false
|
||||||
@@ -53,6 +54,15 @@ const currentUserInfo=computed(()=>{
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
// 存储RefreshToken到cookies
|
||||||
|
const setRefreshToken = (token) => {
|
||||||
|
if (token == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Local.set(RefreshTokenKey, token);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
const logoutFun = async () => {
|
const logoutFun = async () => {
|
||||||
@@ -141,9 +151,10 @@ const currentUserInfo=computed(()=>{
|
|||||||
|
|
||||||
// 登录成功之后的操作
|
// 登录成功之后的操作
|
||||||
const loginSuccess = async (res) => {
|
const loginSuccess = async (res) => {
|
||||||
const { token } = res.data;
|
const { token,refreshToken } = res.data;
|
||||||
|
|
||||||
setToken(token);
|
setToken(token);
|
||||||
|
setRefreshToken(refreshToken);
|
||||||
useUserStore().updateToken(token);
|
useUserStore().updateToken(token);
|
||||||
try {
|
try {
|
||||||
// 存储用户信息
|
// 存储用户信息
|
||||||
@@ -198,7 +209,9 @@ const currentUserInfo=computed(()=>{
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
getToken,
|
getToken,
|
||||||
|
getRefreshToken,
|
||||||
setToken,
|
setToken,
|
||||||
|
setRefreshToken,
|
||||||
removeToken,
|
removeToken,
|
||||||
loginFun,
|
loginFun,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import "nprogress/nprogress.css";
|
|||||||
import useUserStore from "@/stores/user";
|
import useUserStore from "@/stores/user";
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false });
|
NProgress.configure({ showSpinner: false });
|
||||||
const { getToken, logoutFun } = useAuths();
|
const { getToken, logoutFun ,getRefreshToken} = useAuths();
|
||||||
const whiteList = ["/login", "/auth-redirect", "/bind", "/register"];
|
const whiteList = ["/login", "/auth-redirect", "/bind", "/register"];
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
const hasToken = getToken();
|
const hasToken = getToken();
|
||||||
|
const refreshToken = getRefreshToken();
|
||||||
if (to.path === "/login" || to.path === "/index") {
|
if (to.path === "/login" || to.path === "/index") {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const isPopup = window.opener && window.opener !== window;
|
const isPopup = window.opener && window.opener !== window;
|
||||||
@@ -24,7 +25,8 @@ router.beforeEach((to, from, next) => {
|
|||||||
const targetOrigin = new URL(decodeURIComponent(redirectUri)).origin;
|
const targetOrigin = new URL(decodeURIComponent(redirectUri)).origin;
|
||||||
window.opener.postMessage({
|
window.opener.postMessage({
|
||||||
type: 'SSO_LOGIN_SUCCESS',
|
type: 'SSO_LOGIN_SUCCESS',
|
||||||
token: hasToken
|
token: hasToken,
|
||||||
|
refreshToken: refreshToken,
|
||||||
}, targetOrigin);
|
}, targetOrigin);
|
||||||
// 立即关闭窗口
|
// 立即关闭窗口
|
||||||
setTimeout(() => window.close(), 100);
|
setTimeout(() => window.close(), 100);
|
||||||
|
|||||||
Reference in New Issue
Block a user