refactor: ♻️ 用户信息不再本地持久化,改为内存态:刷新页面后需要重新请求用户信息

This commit is contained in:
Ray.Hao
2025-08-15 09:35:58 +08:00
parent d9b2f55a5e
commit a5da8d5788
8 changed files with 31 additions and 65 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "vue3-element-admin", "name": "vue3-element-admin",
"description": "Vue3 + Vite + TypeScript + Element-Plus 的后台管理模板vue-element-admin 的 Vue3 版本", "description": "Vue3 + Vite + TypeScript + Element-Plus 的后台管理模板vue-element-admin 的 Vue3 版本",
"version": "3.2.0", "version": "3.2.2",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -1,7 +1,7 @@
import { ref, onMounted, onUnmounted, watch, getCurrentInstance } from "vue"; import { ref, onMounted, onUnmounted, watch, getCurrentInstance } from "vue";
import { useStomp } from "./useStomp"; import { useStomp } from "./useStomp";
import { registerWebSocketInstance } from "@/plugins/websocket"; import { registerWebSocketInstance } from "@/plugins/websocket";
import { Auth } from "@/utils/auth"; import { AuthStorage } from "@/utils/auth";
// 全局单例实例 // 全局单例实例
let globalInstance: ReturnType<typeof createOnlineCountHook> | null = null; let globalInstance: ReturnType<typeof createOnlineCountHook> | null = null;

View File

@@ -1,5 +1,5 @@
import { Client, type IMessage, type StompSubscription } from "@stomp/stompjs"; import { Client, type IMessage, type StompSubscription } from "@stomp/stompjs";
import { Auth } from "@/utils/auth"; import { AuthStorage } from "@/utils/auth";
export interface UseStompOptions { export interface UseStompOptions {
/** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */ /** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */

View File

@@ -1,5 +1,5 @@
import { useDictSync } from "@/composables/useDictSync"; import { useDictSync } from "@/composables/useDictSync";
import { Auth } from "@/utils/auth"; import { AuthStorage } from "@/utils/auth";
// 不直接导入 store 或 userStore // 不直接导入 store 或 userStore
// 全局 WebSocket 实例管理 // 全局 WebSocket 实例管理

View File

@@ -3,16 +3,17 @@ import { store } from "@/store";
import AuthAPI, { type LoginFormData } from "@/api/auth.api"; import AuthAPI, { type LoginFormData } from "@/api/auth.api";
import UserAPI, { type UserInfo } from "@/api/system/user.api"; import UserAPI, { type UserInfo } from "@/api/system/user.api";
import { Auth } from "@/utils/auth"; import { AuthStorage } from "@/utils/auth";
import { usePermissionStoreHook } from "@/store/modules/permission.store"; import { usePermissionStoreHook } from "@/store/modules/permission.store";
import { useDictStoreHook } from "@/store/modules/dict.store"; import { useDictStoreHook } from "@/store/modules/dict.store";
import { useTagsViewStore } from "@/store"; import { useTagsViewStore } from "@/store";
import { cleanupWebSocket } from "@/plugins/websocket"; import { cleanupWebSocket } from "@/plugins/websocket";
export const useUserStore = defineStore("user", () => { export const useUserStore = defineStore("user", () => {
const userInfo = useStorage<UserInfo>("userInfo", {} as UserInfo); // 用户信息
const userInfo = ref<UserInfo>({} as UserInfo);
// 记住我状态 // 记住我状态
const rememberMe = ref(Auth.getRememberMe()); const rememberMe = ref(AuthStorage.getRememberMe());
/** /**
* 登录 * 登录
@@ -27,7 +28,7 @@ export const useUserStore = defineStore("user", () => {
const { accessToken, refreshToken } = data; const { accessToken, refreshToken } = data;
// 保存记住我状态和token // 保存记住我状态和token
rememberMe.value = LoginFormData.rememberMe; rememberMe.value = LoginFormData.rememberMe;
Auth.setTokens(accessToken, refreshToken, rememberMe.value); AuthStorage.setTokens(accessToken, refreshToken, rememberMe.value);
resolve(); resolve();
}) })
.catch((error) => { .catch((error) => {
@@ -104,7 +105,7 @@ export const useUserStore = defineStore("user", () => {
*/ */
function resetUserState() { function resetUserState() {
// 清除用户凭证 // 清除用户凭证
Auth.clearAuth(); AuthStorage.clearAuth();
// 重置用户信息 // 重置用户信息
userInfo.value = {} as UserInfo; userInfo.value = {} as UserInfo;
} }
@@ -113,7 +114,7 @@ export const useUserStore = defineStore("user", () => {
* 刷新 token * 刷新 token
*/ */
function refreshToken() { function refreshToken() {
const refreshToken = Auth.getRefreshToken(); const refreshToken = AuthStorage.getRefreshToken();
if (!refreshToken) { if (!refreshToken) {
return Promise.reject(new Error("没有有效的刷新令牌")); return Promise.reject(new Error("没有有效的刷新令牌"));
@@ -124,7 +125,7 @@ export const useUserStore = defineStore("user", () => {
.then((data) => { .then((data) => {
const { accessToken, refreshToken: newRefreshToken } = data; const { accessToken, refreshToken: newRefreshToken } = data;
// 更新令牌,保持当前记住我状态 // 更新令牌,保持当前记住我状态
Auth.setTokens(accessToken, newRefreshToken, Auth.getRememberMe()); AuthStorage.setTokens(accessToken, newRefreshToken, AuthStorage.getRememberMe());
resolve(); resolve();
}) })
.catch((error) => { .catch((error) => {
@@ -137,7 +138,7 @@ export const useUserStore = defineStore("user", () => {
return { return {
userInfo, userInfo,
rememberMe, rememberMe,
isLoggedIn: () => !!Auth.getAccessToken(), isLoggedIn: () => !!AuthStorage.getAccessToken(),
getUserInfo, getUserInfo,
login, login,
logout, logout,

View File

@@ -1,78 +1,43 @@
import { Storage } from "./storage"; import { Storage } from "./storage";
import { AUTH_KEYS } from "@/constants"; import { AUTH_KEYS } from "@/constants";
/** // 更语义化的命名:仅负责本地凭证与偏好的读写
* 身份验证工具类 export const AuthStorage = {
* 集中管理所有与认证相关的功能,包括: getAccessToken(): string {
* - 登录状态判断
* - Token 的存取
* - 记住我功能的状态管理
*/
export class Auth {
/**
* 获取当前有效的访问令牌
* 会根据"记住我"状态从适当的存储位置获取
* @returns 当前有效的访问令牌
*/
static getAccessToken(): string {
const isRememberMe = Storage.get<boolean>(AUTH_KEYS.REMEMBER_ME, false); const isRememberMe = Storage.get<boolean>(AUTH_KEYS.REMEMBER_ME, false);
// 根据"记住我"状态决定从哪个存储位置获取token
return isRememberMe return isRememberMe
? Storage.get(AUTH_KEYS.ACCESS_TOKEN, "") ? Storage.get(AUTH_KEYS.ACCESS_TOKEN, "")
: Storage.sessionGet(AUTH_KEYS.ACCESS_TOKEN, ""); : Storage.sessionGet(AUTH_KEYS.ACCESS_TOKEN, "");
} },
/** getRefreshToken(): string {
* 获取刷新令牌
* @returns 当前有效的刷新令牌
*/
static getRefreshToken(): string {
const isRememberMe = Storage.get<boolean>(AUTH_KEYS.REMEMBER_ME, false); const isRememberMe = Storage.get<boolean>(AUTH_KEYS.REMEMBER_ME, false);
return isRememberMe return isRememberMe
? Storage.get(AUTH_KEYS.REFRESH_TOKEN, "") ? Storage.get(AUTH_KEYS.REFRESH_TOKEN, "")
: Storage.sessionGet(AUTH_KEYS.REFRESH_TOKEN, ""); : Storage.sessionGet(AUTH_KEYS.REFRESH_TOKEN, "");
} },
/** setTokens(accessToken: string, refreshToken: string, rememberMe: boolean): void {
* 设置访问令牌和刷新令牌
* @param accessToken 访问令牌
* @param refreshToken 刷新令牌
* @param rememberMe 是否记住我
*/
static setTokens(accessToken: string, refreshToken: string, rememberMe: boolean): void {
// 保存"记住我"状态
Storage.set(AUTH_KEYS.REMEMBER_ME, rememberMe); Storage.set(AUTH_KEYS.REMEMBER_ME, rememberMe);
if (rememberMe) { if (rememberMe) {
// 使用localStorage长期保存
Storage.set(AUTH_KEYS.ACCESS_TOKEN, accessToken); Storage.set(AUTH_KEYS.ACCESS_TOKEN, accessToken);
Storage.set(AUTH_KEYS.REFRESH_TOKEN, refreshToken); Storage.set(AUTH_KEYS.REFRESH_TOKEN, refreshToken);
} else { } else {
// 使用sessionStorage临时保存
Storage.sessionSet(AUTH_KEYS.ACCESS_TOKEN, accessToken); Storage.sessionSet(AUTH_KEYS.ACCESS_TOKEN, accessToken);
Storage.sessionSet(AUTH_KEYS.REFRESH_TOKEN, refreshToken); Storage.sessionSet(AUTH_KEYS.REFRESH_TOKEN, refreshToken);
// 清除localStorage中可能存在的token
Storage.remove(AUTH_KEYS.ACCESS_TOKEN); Storage.remove(AUTH_KEYS.ACCESS_TOKEN);
Storage.remove(AUTH_KEYS.REFRESH_TOKEN); Storage.remove(AUTH_KEYS.REFRESH_TOKEN);
} }
} },
/** clearAuth(): void {
* 清除所有身份验证相关的数据
*/
static clearAuth(): void {
Storage.remove(AUTH_KEYS.ACCESS_TOKEN); Storage.remove(AUTH_KEYS.ACCESS_TOKEN);
Storage.remove(AUTH_KEYS.REFRESH_TOKEN); Storage.remove(AUTH_KEYS.REFRESH_TOKEN);
Storage.sessionRemove(AUTH_KEYS.ACCESS_TOKEN); Storage.sessionRemove(AUTH_KEYS.ACCESS_TOKEN);
Storage.sessionRemove(AUTH_KEYS.REFRESH_TOKEN); Storage.sessionRemove(AUTH_KEYS.REFRESH_TOKEN);
// 不清除记住我设置,保留用户偏好 },
}
/** getRememberMe(): boolean {
* 获取"记住我"状态
* @returns 是否记住我
*/
static getRememberMe(): boolean {
return Storage.get<boolean>(AUTH_KEYS.REMEMBER_ME, false); return Storage.get<boolean>(AUTH_KEYS.REMEMBER_ME, false);
} },
} };

View File

@@ -2,7 +2,7 @@ import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from "axio
import qs from "qs"; import qs from "qs";
import { useUserStoreHook } from "@/store/modules/user.store"; import { useUserStoreHook } from "@/store/modules/user.store";
import { ResultEnum } from "@/enums/api/result.enum"; import { ResultEnum } from "@/enums/api/result.enum";
import { Auth } from "@/utils/auth"; import { AuthStorage } from "@/utils/auth";
import router from "@/router"; import router from "@/router";
/** /**
@@ -20,7 +20,7 @@ const httpRequest = axios.create({
*/ */
httpRequest.interceptors.request.use( httpRequest.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
const accessToken = Auth.getAccessToken(); const accessToken = AuthStorage.getAccessToken();
// 如果 Authorization 设置为 no-auth则不携带 Token // 如果 Authorization 设置为 no-auth则不携带 Token
if (config.headers.Authorization !== "no-auth" && accessToken) { if (config.headers.Authorization !== "no-auth" && accessToken) {
@@ -104,7 +104,7 @@ async function refreshTokenAndRetry(config: InternalAxiosRequestConfig): Promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 封装需要重试的请求 // 封装需要重试的请求
const retryRequest = () => { const retryRequest = () => {
const newToken = Auth.getAccessToken(); const newToken = AuthStorage.getAccessToken();
if (newToken && config.headers) { if (newToken && config.headers) {
config.headers.Authorization = `Bearer ${newToken}`; config.headers.Authorization = `Bearer ${newToken}`;
} }

View File

@@ -115,7 +115,7 @@ import AuthAPI, { type LoginFormData } from "@/api/auth.api";
import router from "@/router"; import router from "@/router";
import { useUserStore } from "@/store"; import { useUserStore } from "@/store";
import CommonWrapper from "@/components/CommonWrapper/index.vue"; import CommonWrapper from "@/components/CommonWrapper/index.vue";
import { Auth } from "@/utils/auth"; import { AuthStorage } from "@/utils/auth";
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
@@ -130,7 +130,7 @@ const isCapsLock = ref(false);
// 验证码图片Base64字符串 // 验证码图片Base64字符串
const captchaBase64 = ref(); const captchaBase64 = ref();
// 记住我 // 记住我
const rememberMe = Auth.getRememberMe(); const rememberMe = AuthStorage.getRememberMe();
const loginFormData = ref<LoginFormData>({ const loginFormData = ref<LoginFormData>({
username: "admin", username: "admin",