umi + Nest.js 实现高效权限管理系统

960 阅读3分钟

umimax + Nest.js 实现权限管理系统

前端

在 umi 文档中,我们可以看到有一个称为权限配置的功能,它提供了一套自己的权限管理方案。

权限定义

我们约定将 src/access.ts 作为权限定义文件,在其中可以定义和判断用户是否具有某个权限。

image.png

路由权限

通过在路由配置中添加 access 字段,可以判断路由是否有权限访问。 image.png

功能权限

使用 useAccess 来获取权限,可以获得所有定义的权限。

image.png

获取用户权限信息时机

我们约定在 src/app.tsx 中导出一个 getInitialState 方法,用于初始化信息。例如:

export async function getInitialState(): Promise<{
  user_info?: API.UserInfo;
  roles?: any[];
}> {
  let user_info;
  let roleList;
  const fetchUserInfo = async () => {
    try {
      const token = localStorage.getItem('token');
      const user_info = JSON.parse(localStorage.getItem('user_info') || '');
      if (!token || !user_info) {
        history.push('/login');
      }
      return { token, user_info };
    } catch (error) {
      history.push('/login');
    }
    return { token: null, user_info: null };
  };
  if (history.location.pathname !== '/login') {
    const res = await fetchUserInfo();
    const { roles, ...reset } = res.user_info;
    user_info = reset;
    roleList = roles;
  }
  return {
    user_info,
    roles: roleList,
  };
}

上述代码中,我们在初始化时判断是否为登录页,从而获取存储的信息(权限)。但是,如果登录信息发生改变,则需要重新获取最新的登录信息。因此,在登录时,我们需要重新初始化:

import { useModel } from '@umijs/max';
​
const { refresh } = useModel('@@initialState');
const handleLogin = async (values: any) => {
  const res = await login(values);
  if (res.data) {
    const { token, user_info } = res.data;
    localStorage.setItem('token', token);
    localStorage.setItem('user_info', JSON.stringify(user_info));
    navigate('/');
    refresh();
  }
};

登录成功后,更新本地缓存并重新初始化,即可获取用户最新的权限。

后端

用户 => 角色 => 权限

用户、角色、权限三个表相互关联。

数据库

user.entity.ts

import { Role } from 'src/role/entities/role.entity';
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
​
@Entity({
  name: 'user',
})
export class User {
  @PrimaryGeneratedColumn()
  id: number;
​
  @Column({
    comment: '姓名',
    length: 50,
  })
  name: string;
​
  @Column({
    comment: '密码',
    length: 50,
  })
  password: string;
​
  @Column({
    comment: '头像',
    length: 1000,
    default: 'https://p26-passport.byteacctimg.com/img/user-avatar/312989b46037c16843b1eb44aea82fa2~180x180.awebp?',
  })
  avator: string;
​
  @Column({
    comment: '手机号',
    length: 50,
    default:""
  })
  mobile: string;
​
  @ManyToMany(() => Role)
  @JoinTable({
    name: 'user_role_relation',
  })
  roles: Role[]; // 角色
​
  @Column({
    comment: '是否可用',
    default: true,
  })
  state: boolean;
}

role.entity.ts

import {
    Column,
    CreateDateColumn,
    Entity,
    JoinTable,
    ManyToMany,
    PrimaryGeneratedColumn,
    UpdateDateColumn,
  } from 'typeorm';
  import { Permission } from '../../permission/entities/permission.entity';
  @Entity()
  export class Role {
    @PrimaryGeneratedColumn()
    id: string;
  
    @Column({
      length: 20,
    })
    name: string;
  
    @CreateDateColumn()
    createTime: Date;
  
    @UpdateDateColumn()
    updateTime: Date;
  
    @ManyToMany(() => Permission)
    @JoinTable({
      name: 'role_permission_relation',
    })
    permissions: Permission[];
  }
  

permission.entity.ts

import {
    Column,
    CreateDateColumn,
    Entity,
    PrimaryGeneratedColumn,
    UpdateDateColumn,
  } from 'typeorm';
  
  @Entity()
  export class Permission {
    @PrimaryGeneratedColumn()
    id: string;
  
    @Column({
      length: 50,
    })
    name: string;
  
    @Column({
      length: 100,
      nullable: true,
    })
    desc: string;
​
    @CreateDateColumn()
    createTime: Date;
  
    @UpdateDateColumn()
    updateTime: Date;
  }
  

定义好三个表后,基本完成。

在查询用户信息时,我们也需要查询用户的角色和权限:

 this.userRepository.findOne({
      where: { id, state: true },
      select: ['name', 'avator', 'id', 'mobile'],
      relations: ['roles', 'roles.permissions'],
    });

更新用户角色:

async updateUserRoles(createRoleDto) {
    const id = createRoleDto.userId;
​
    const user = await this.findOne(id);
    if (!user) {
      throw new UnauthorizedException('用户名不存在');
    }
    console.log(user);
​
    // 查询传入数组roles的全部role实体
    const roles = await this.roleRepository.findBy({
      id: In(createRoleDto.roleIds),
    });
    user.roles = roles
    if (roles.length <= 0) {
      throw new Error('roles not found');
    }
​
    return this.userRepository.save(user);
  }

权限和角色的查询和修改与用户表类似。通过这样的设计,权限控制就可以实现了。

总结

以上便是我开发的后台管理系统(umimax + Nest + MySQL)的权限设计。通过这个项目,我学习了后端、数据库知识,以及 Docker 和自动化部署。如果你对这些感兴趣,欢迎一起学习,项目地址:umimax+nest+mysql