feat(auth): 实现用户认证状态持久化及权限控制优化
- 新增 pinia-plugin-persistedstate 实现用户状态本地存储 - 重构 API 响应类型为统一模块管理 - 优化路由守卫逻辑,支持 token 自动登录 - 修复权限检查逻辑错误,调整 AI 聊天页访问权限 - 新增用户信息获取接口及类型定义
This commit is contained in:
5
bun.lock
5
bun.lock
@@ -8,6 +8,7 @@
|
||||
"@arco-design/web-vue": "^2.57.0",
|
||||
"axios": "^1.13.2",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "4",
|
||||
},
|
||||
@@ -205,6 +206,8 @@
|
||||
|
||||
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
@@ -333,6 +336,8 @@
|
||||
|
||||
"pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
|
||||
|
||||
"pinia-plugin-persistedstate": ["pinia-plugin-persistedstate@4.7.1", "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz", { "dependencies": { "defu": "^6.1.4" }, "peerDependencies": { "@nuxt/kit": ">=3.0.0", "@pinia/nuxt": ">=0.10.0", "pinia": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "@pinia/nuxt", "pinia"] }, "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
2691
package-lock.json
generated
Normal file
2691
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@
|
||||
"@arco-design/web-vue": "^2.57.0",
|
||||
"axios": "^1.13.2",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "4"
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ const checkAccess = (
|
||||
}
|
||||
// 如果用户登录才能访问
|
||||
if (needAccess === ACCESS_ENUM.USER) {
|
||||
if (loginUserAccess !== ACCESS_ENUM.NOT_LOGIN) {
|
||||
if (loginUserAccess === ACCESS_ENUM.NOT_LOGIN) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ const checkAccess = (
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 如果说啥都不符合,那还说啥了,直接拒了
|
||||
return false;
|
||||
// 权限通过
|
||||
return true;
|
||||
};
|
||||
export default checkAccess;
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { LoginUesr } from "../store/types";
|
||||
import { useUserStore } from "../store/user";
|
||||
import ACCESS_ENUM from "./accessEnum";
|
||||
import checkAccess from "./checkAccess";
|
||||
import { getAccessToken } from "@/utils/token";
|
||||
|
||||
const userStore = useUserStore();
|
||||
/**
|
||||
* 检查是否需要权限访问
|
||||
* @param to 要访问的路由
|
||||
@@ -39,12 +39,17 @@ const redirectWithAccess = (
|
||||
};
|
||||
// 这里接收异步函数,是因为下面要调用 userStore.getLoginUser()
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const userStore = useUserStore(); // 必须在守卫内部获取 store,确保 pinia 已安装
|
||||
console.log("登陆用户信息", userStore.loginUser);
|
||||
let loginUser = userStore.loginUser;
|
||||
// 如果之前没登陆过,自动登录
|
||||
if (!loginUser || !loginUser.userRole) {
|
||||
await userStore.getLoginUser();
|
||||
loginUser = userStore.loginUser;
|
||||
// 如果之前没登陆过,且有 token,尝试自动登录
|
||||
// 没有 token 时不调用 API,避免未登录状态重复弹出错误提示
|
||||
if (!loginUser || !loginUser.userRole || loginUser.userRole === ACCESS_ENUM.NOT_LOGIN) {
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
await userStore.getLoginUser();
|
||||
loginUser = userStore.loginUser;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要权限访问
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import request from "@/plugins/axios";
|
||||
import type { ApiResponse } from "../response";
|
||||
|
||||
/**
|
||||
* 认证服务 API 类型定义
|
||||
*/
|
||||
|
||||
// 统一响应格式
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
|
||||
// 登录请求参数
|
||||
export interface LoginRequest {
|
||||
|
||||
@@ -9,5 +9,4 @@ export {
|
||||
type LoginRequest,
|
||||
type LoginResponse,
|
||||
type RefreshTokenResponse,
|
||||
type ApiResponse,
|
||||
} from './auth'
|
||||
70
src/api/auth/user.ts
Normal file
70
src/api/auth/user.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import request from "@/plugins/axios";
|
||||
import type { ApiResponse } from "../response";
|
||||
/**
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userAccount: string;
|
||||
|
||||
|
||||
/**
|
||||
* 开放平台id
|
||||
*/
|
||||
unionId?: string;
|
||||
|
||||
/**
|
||||
* 公众号openId
|
||||
*/
|
||||
mpOpenId?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
userName: string;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
userAvatar?: string;
|
||||
|
||||
/**
|
||||
* 用户简介
|
||||
*/
|
||||
userProfile?: string;
|
||||
|
||||
/**
|
||||
* 用户角色:user/admin/ban
|
||||
*/
|
||||
userRole: 'user' | 'admin' | 'ban';
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
* @returns 当前登录用户信息
|
||||
*/
|
||||
export const getUserInfoByToken = () => {
|
||||
return request<ApiResponse<UserInfo>>({
|
||||
url: "/v1/auth/getUserInfo",
|
||||
method: "GET",
|
||||
})
|
||||
}
|
||||
3
src/api/index.ts
Normal file
3
src/api/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export {
|
||||
type ApiResponse,
|
||||
} from './response'
|
||||
6
src/api/response.ts
Normal file
6
src/api/response.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// 统一响应格式
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
10
src/main.ts
10
src/main.ts
@@ -4,11 +4,15 @@ import "@arco-design/web-vue/dist/arco.css";
|
||||
import "./style.css";
|
||||
import App from "./App.vue";
|
||||
import router from "./router/router";
|
||||
import {createPinia} from "pinia";
|
||||
import { createPinia } from "pinia";
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
import "./access";
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(ArcoVue);
|
||||
app.use(pinia)
|
||||
app.mount("#app");
|
||||
app.mount("#app");
|
||||
|
||||
@@ -25,7 +25,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
path: "/ai/chat",
|
||||
name: "AiChatView",
|
||||
component: AiChatView,
|
||||
meta: { hideInMenu: false, access: ACCESS_ENUM.NOT_LOGIN },
|
||||
meta: { hideInMenu: false, access: ACCESS_ENUM.USER },
|
||||
},
|
||||
{
|
||||
path: "/user/login",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from "pinia";
|
||||
import ACCESS_ENUM from "../access/accessEnum";
|
||||
import type { LoginUesr } from "../store/types";
|
||||
import { getUserInfoByToken } from "@/api/auth/user";
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -11,12 +12,20 @@ export const useUserStore = defineStore("user", {
|
||||
userRole: ACCESS_ENUM.NOT_LOGIN,
|
||||
} as LoginUesr,
|
||||
}),
|
||||
persist: {
|
||||
key: "user-store",
|
||||
storage: localStorage,
|
||||
pick: ["loginUser"], // 只持久化 loginUser
|
||||
},
|
||||
actions: {
|
||||
// 获取登录用户
|
||||
async getLoginUser() {
|
||||
try {
|
||||
// 从后端获取当前登录用户信息
|
||||
|
||||
const res = await getUserInfoByToken();
|
||||
console.log("获取登录用户成功", res);
|
||||
if(res.data.success === true){
|
||||
this.updateUserLoginStatus(res.data.data);
|
||||
}
|
||||
}catch(e) {
|
||||
console.error("获取登录用户失败", e);
|
||||
// 网络错误情况也视为未登录
|
||||
|
||||
Reference in New Issue
Block a user