likes
comments
collection
share

Vben Admin 源码学习:状态管理-登录用户信息

作者站长头像
站长
· 阅读数 9

0x00 前言

本文将对 Vue-Vben-Admin 的状态管理之登录用户信息源码实现进行分析解读,耐心读完,相信您一定会有所收获!

0x01 user.ts 登录用户信息

文件 src\store\modules\user.ts 声明导出一个store实例 useUserStore 、一个方法 useUserStoreWithOut()用于没有使用 setup 组件时使用。

export const useUserStore = defineStore({
  id: 'app-user', 
  state: {},
  getters: {}
  actions:{}   
});

export function useUserStoreWithOut() {
  return useUserStore(store);
}

State

状态对象定义了登录用户的用户信息、token、角色列表、登录是否超期失效、用户信息最后更新时间。

state: (): UserState => ({
  // 用户信息
  userInfo: null,
  // token
  token: undefined,
  // 角色列表
  roleList: [],
  // 登录是否超期失效
  sessionTimeout: false,
  // 用户信息最后更新时间
  lastUpdateTime: 0,
}), 

接口 UserInfo 定义了 用户id、用户名称、真实名称、头像、描述、用户角色等。 RoleEnum定义了前端角色类型值。

export interface UserInfo {
  // 用户id
  userId: string | number;
  // 用户名称
  username: string;
  // 真实名称
  realName: string;
  // 头像
  avatar?: string;
  // 描述
  desc?: string;
  // 用户指定不同的后台首页
  homePath?: string;
  // 用户角色信息
  roles: RoleInfo[];
}

export interface RoleInfo {
  roleName: string;
  value: string;
} 

export enum RoleEnum {
  // 超级管理员
  SUPER = 'super', 
  // tester
  TEST = 'test',
}

以上接口/类型定义需根据实际业务/服务调整。

src\utils\auth 权限缓存方法

接下会使用权限缓存方法 getAuthCachesetAuthCache,用于获取和设置权限缓存。默认存放于localStorage

// src\utils\auth\index.ts
const { permissionCacheType } = projectSetting;
const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; 

export function getAuthCache<T>(key: BasicKeys) {
  const fn = isLocal ? Persistent.getLocal : Persistent.getSession;
  return fn(key) as T;
}

export function setAuthCache(key: BasicKeys, value) {
  const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
  return fn(key, value, true);
}

缓存方式支持 localStoragesessionStorage,在 src/settings/projectSetting.ts进行设置, 改动后需要清空浏览器缓存后生效。

// src/settings/projectSetting.ts 
const setting: ProjectConfig = {  
  // 权限缓存存放位置。默认存放于 localStorage 
  permissionCacheType: CacheTypeEnum.LOCAL,
}
// src\enums\cacheEnum.ts
export enum CacheTypeEnum {
  SESSION,
  LOCAL,
}

Getter

从状态中获取用户信息、token、角色列表、登录是否超期失效、最后更新时间,若用户信息、token、角色列表为空,则从缓存中获取值。

getters: {
  getUserInfo(): UserInfo {
    return this.userInfo || getAuthCache<UserInfo>(USER_INFO_KEY) || {};
  },
  getToken(): string {
    return this.token || getAuthCache<string>(TOKEN_KEY);
  },
  getRoleList(): RoleEnum[] {
    return this.roleList.length > 0 ? this.roleList : getAuthCache<RoleEnum[]>(ROLES_KEY);
  },
  getSessionTimeout(): boolean {
    return !!this.sessionTimeout;
  },
  getLastUpdateTime(): number {
    return this.lastUpdateTime;
  },
},

Actions

更新用户信息、token、角色列表等,同时使用 setAuthCache 进行缓存。

setToken(info: string | undefined) {
  this.token = info ? info : ''; 
  setAuthCache(TOKEN_KEY, info);
}, 
setRoleList(roleList: RoleEnum[]) {
  this.roleList = roleList;
  setAuthCache(ROLES_KEY, roleList);
},
setUserInfo(info: UserInfo | null) {
  this.userInfo = info;
  this.lastUpdateTime = new Date().getTime();
  setAuthCache(USER_INFO_KEY, info);
},

setSessionTimeout 方法设置用户登录状态。

setSessionTimeout(flag: boolean) {
  this.sessionTimeout = flag;
},

resetState 方法清空重置用户登录状态。

resetState() {
  this.userInfo = null;
  this.token = '';
  this.roleList = [];
  this.sessionTimeout = false;
},

用户登录

login() 方法用于用户登录后获取用户信息,返回一个 Promise 对象。

  1. 调用了 loginApi,此服务接口为数据mock&联调,根据项目自行替换真实服务。
  2. 获取 token 后调用 setToken 更新状态。
  3. 调用 afterLoginAction 方法进行登录后预处理操作。
async login(
  params: LoginParams & {
    goHome?: boolean;
    mode?: ErrorMessageMode;
  },
): Promise<GetUserInfoModel | null> {
  try {
    const { goHome = true, mode, ...loginParams } = params;
    const data = await loginApi(loginParams, mode);
    const { token } = data;

    // save token
    this.setToken(token);
    return this.afterLoginAction(goHome);
  } catch (error) {
    return Promise.reject(error);
  }
},

afterLoginAction 方法用于用户登录后,进行角色、权限、菜单、路由等配置操作。

  1. 调用 getUserInfoAction 获取登录用户信息,
    • 调用 mock 服务 getUserInfo()
    • 根据返回用户登录信息,设置角色列表,
    • 更新状态对象值。
  2. 调用权限存储构建路由列表(详细逻辑稍后文章会详细讲解)。
    • 若是路由还没态添加过,调用 buildRoutesAction()方法, 根据不同的权限处理方式构建路由列表
    • 根据用户指定不同的后台首页进行跳转。
async afterLoginAction(goHome?: boolean): Promise<GetUserInfoModel | null> {
  if (!this.getToken) return null;
  // get user info
  const userInfo = await this.getUserInfoAction();

  ...
  
    const permissionStore = usePermissionStore();
    if (!permissionStore.isDynamicAddedRoute) {
      const routes = await permissionStore.buildRoutesAction();
      routes.forEach((route) => {
        router.addRoute(route as unknown as RouteRecordRaw);
      });
      router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
      permissionStore.setDynamicAddedRoute(true);
    }
    goHome && (await router.replace(userInfo?.homePath || PageEnum.BASE_HOME));
    
  ...
  
  return userInfo;
},
async getUserInfoAction(): Promise<UserInfo | null> {
  if (!this.getToken) return null;
  const userInfo = await getUserInfo();
  const { roles = [] } = userInfo;
  if (isArray(roles)) {
    const roleList = roles.map((item) => item.value) as RoleEnum[];
    this.setRoleList(roleList);
  } else {
    userInfo.roles = [];
    this.setRoleList([]);
  }
  this.setUserInfo(userInfo);
  return userInfo;
}, 

登录页面中调用 userStore.login() 方法进行系统登录操作。

// src\views\sys\login\LoginForm.vue

const userInfo = await userStore.login({
  password: data.password,
  username: data.account,
  mode: 'none', //不要默认的错误提示
});
if (userInfo) {
  notification.success({
    message: t('sys.login.loginSuccessTitle'),
    description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
    duration: 3,
  });
}

用户注销

logout 注销方法调用 mock 服务 doLogout() ,同时清空 token和用户信息,设置用户登录状态失效,然后跳转系统登录界面。

// 用户注销
async logout(goLogin = false) {
 if (this.getToken) {
   try {
     await doLogout();
   } catch {
     console.log('注销Token失败');
   }
 }
 this.setToken(undefined);
 this.setSessionTimeout(false);
 this.setUserInfo(null);
 goLogin && router.push(PageEnum.BASE_LOGIN);
}, 

confirmLoginOut() 会弹出系统确认框,确认后调用 logout 方法。

// 退出系统确认框
confirmLoginOut() {
  const { createConfirm } = useMessage();
  const { t } = useI18n();
  createConfirm({
    iconType: 'warning',
    title: () => h('span', t('sys.app.logoutTip')),
    content: () => h('span', t('sys.app.logoutMessage')),
    onOk: async () => {
      await this.logout(true);
    },
  });
},

Vben Admin 源码学习:状态管理-登录用户信息

0x02 📚参考

"Promise",MDN "权限",vben admin

0x03 关注专栏

此文章已收录到专栏中 👇,可以直接关注。