feat(auth): 实现用户认证状态持久化及权限控制优化

- 新增 pinia-plugin-persistedstate 实现用户状态本地存储
- 重构 API 响应类型为统一模块管理
- 优化路由守卫逻辑,支持 token 自动登录
- 修复权限检查逻辑错误,调整 AI 聊天页访问权限
- 新增用户信息获取接口及类型定义
This commit is contained in:
2026-01-07 20:14:40 +08:00
parent 8e5558d9a2
commit fad13e00e5
13 changed files with 2810 additions and 21 deletions

View File

@@ -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;

View File

@@ -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;
}
}
// 检查是否需要权限访问

View File

@@ -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 {

View File

@@ -9,5 +9,4 @@ export {
type LoginRequest,
type LoginResponse,
type RefreshTokenResponse,
type ApiResponse,
} from './auth'

70
src/api/auth/user.ts Normal file
View 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
View File

@@ -0,0 +1,3 @@
export {
type ApiResponse,
} from './response'

6
src/api/response.ts Normal file
View File

@@ -0,0 +1,6 @@
// 统一响应格式
export interface ApiResponse<T = any> {
success: boolean;
message: string;
data: T;
}

View File

@@ -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");

View File

@@ -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",

View File

@@ -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);
// 网络错误情况也视为未登录