当时初学 Web 开发的时候,除了写一个网页博客外,第二个选择无非就是一个后台管理系统,可以应用于多种需要数据管理类项目中。
基于NestJs + TypeScript + TypeORM + Redis + MySql + Vben Admin编写的一款前后端分离的权限管理系统
演示地址:KzAdmin 管理员账号:admin 密码:123456
前端
基于Vben Admin开发,使用 Vue3、Vite、TypeScript 等最新技术栈,内置常用功能组件、权限验证、动态路由。
仓库地址:https://github.com/kuizuo/kz-vue-admin
项目结构
├── build # 打包脚本相关
│ ├── config # 配置文件
│ ├── generate # 生成器
│ ├── script # 脚本
│ └── vite # vite配置
├── mock # mock文件夹
├── public # 公共静态资源目录
├── src # 主目录
│ ├── api # 接口文件
│ ├── assets # 资源文件
│ │ ├── icons # icon sprite 图标文件夹
│ │ ├── images # 项目存放图片的文件夹
│ │ └── svg # 项目存放svg图片的文件夹
│ ├── components # 公共组件
│ ├── design # 样式文件
│ ├── directives # 指令
│ ├── enums # 枚举/常量
│ ├── hooks # hook
│ │ ├── component # 组件相关hook
│ │ ├── core # 基础hook
│ │ ├── event # 事件相关hook
│ │ ├── setting # 配置相关hook
│ │ └── web # web相关hook
│ ├── layouts # 布局文件
│ │ ├── default # 默认布局
│ │ ├── iframe # iframe布局
│ │ └── page # 页面布局
│ ├── locales # 多语言
│ ├── logics # 逻辑
│ ├── main.ts # 主入口
│ ├── router # 路由配置
│ ├── settings # 项目配置
│ │ ├── componentSetting.ts # 组件配置
│ │ ├── designSetting.ts # 样式配置
│ │ ├── encryptionSetting.ts # 加密配置
│ │ ├── localeSetting.ts # 多语言配置
│ │ ├── projectSetting.ts # 项目配置
│ │ └── siteSetting.ts # 站点配置
│ ├── store # 数据仓库
│ ├── utils # 工具类
│ └── views # 页面
├── test # 测试
│ └── server # 测试用到的服务
│ ├── api # 测试服务器
│ ├── upload # 测试上传服务器
│ └── websocket # 测试ws服务器
├── types # 类型文件
├── vite.config.ts # vite配置文件
└── windi.config.ts # windcss配置文件
启动项目
建议使用 pnpm 包管理器来管理 node 项目,使用npm install -g pnpm
即可安装。
pnpm install
pnpm run dev
运行结果
vite v2.9.5 dev server running at:
> Network: https://192.168.184.1:3100/
> Local: https://localhost:3100/
ready in 5057ms.
注: 开发环境下首次载入项目会稍慢(Vite 在动态解析依赖)
更多关于前端项目规范可直接参考 Vben Admin 文档 ,非常详细了。
后端
基于 NestJs + TypeScript + TypeORM + Redis + MySql 编写的前后端分离权限管理系统
仓库地址:https://github.com/kuizuo/kz-nest-admin
项目结构
|─setup-swagger.ts # Swaager文档配置
|─main.ts # 主入口
|─config # 配置文件
|─shared
| |─redis # redisModule
| | |─redis.module.ts
| | |─redis.interface.ts
| | |─redis.constants.ts
| |─shared.module.ts
| |─services # 全局通用Provider
|─app.module.ts
|─mission
| |─mission.module.ts
| |─mission.decorator.ts # 任务装饰器,所有任务都需要定义该装饰器,否则无法运行
| |─jobs # 后台定时任务定义
|─common # 系统通用定义
| |─dto # 通用DTO定义
| |─contants
| | |─error-code.contants.ts # 系统错误码定义
| | |─decorator.contants.ts # 装饰器常量
| |─filters # 通用过滤器定义
| |─interceptors # 通用拦截器定义
| |─decorators # 通用装饰器定义
| |─exceptions # 系统内置通用异常定义
| |─class # Class Model 不使用Interface定义,使用Interface无法让Swagger识别
|─modules
| |─admin
| | |─core # 核心功能
| | | |─interceptors # 后台管理拦截器定义
| | | |─decorators # 后台管理注解定义
| | | |─provider # 后台管理提供者定义
| | | |─guards # 后台管理守卫定义
| | |─system # 系统模块定义
| | |─account # 用户账户模块定义
| | |─login # 登录模块定义
| | |─admin.module.ts # 后台管理模块
| | |─admin.constants.ts # 后台管理模块通用常量
| | |─admin.interface.ts # Admin通用interface定义
| |─ws # Socket模块
|─entities # TypeORM 实体文件定义
启动项目
依赖安装与执行 打包命令前端与后端一致,但需要提前修改.env.development 中数据库相关配置,并执行 sql/init.sql 来初始化数据。
实现
项目中大部分的目录结构设计参照与sf-nest-admin,但主要为了贴合自我的代码风格修改部分数据字段名,接口方法,接口响应格式等等。
同时对于大部分这类后台管理的 demo,通常都会定义用户,角色,菜单,部门。而我将部门相关代码删除,因为对于我后续项目大概率用不上这些部分,然后删了一些不相关的模块,主要写的这套模板还是用作自己后续的管理类项目。
用户-角色-权限
这套系统中最为重要的一部分便是权限管理,不过在这套后台管理系统中这里的权限与菜单共用,前端路由渲染菜单,后端鉴权。后文的菜单表也就作为权限表而言。
在这三张表中关系如下(这里使用外键与数据库模型为例,实际项目并未用到外键,也不推荐使用)
用户-角色 与 角色-权限都采用的多对多的关系,即新创建一个表用于映射两表关系。这些都属于 mysql 基础,不做过多赘述。
在权限管理中,最为重要的便是权限表了,由于这套后台管理系统中还涉及到前端的左侧菜单,所以将这里的 permission 表替换为 menu 表,字 段 permission 表示权限值。数据库中的 menu 表如下
对于主要字段介绍:
-
parent:对于有父子关系的表,会创建一个 parent_id(这里为 parent)字段用于表示父节点,无则为顶级节点。
-
permission:权限标识,根据后端接口而定,比如新增用户的 url 为
sys/user/add
,那么权限标识通常将/替换成:,也就是sys:user:add
(主要防止和接口的 url 混淆)。 -
type:0 目录 1 菜单(前端组件) 2 权限,由于是菜单与权限混用,所以用 type 来区分。
-
icon:左侧菜单图标
-
order_no:左侧菜单排序
-
component:组件,目录为 LAYOUT,菜单则为对应组件,权限则无
有了这些数据,要做的是将他们拼接为前端菜单管理,根据角色获取所有菜单,根据用户的所有权限的树结构数据。
前端菜单管理
获取所有的菜单列表数据,通过递归生成对应的菜单树结构,具体递归代码在src/modules/core/permission/index.ts
中的generatorMenu
方法中。
具体拼接数据过多,可自行打开控制台(F12)->网络 到菜单管理页中获取数据可得,这里便不做展示(后文拼接数据同理)。
根据角色获取所有菜单
首先根据用户 id 找到该用户的所有角色 id,然后通过联表找到角色 id 所对应的菜单数据。
/**
* 根据角色获取所有菜单
*/
async getMenus(uid: number): Promise<string[]> {
const roleIds = await this.roleService.getRoleIdByUser(uid);
let menus: SysMenu[] = [];
if (includes(roleIds, this.rootRoleId)) {
menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } });
} else {
menus = await this.menuRepository
.createQueryBuilder('menu')
.innerJoinAndSelect('sys_role_menu', 'role_menu', 'menu.id = role_menu.menu_id')
.andWhere('role_menu.role_id IN (:...roldIds)', { roldIds: roleIds })
.orderBy('menu.order_no', 'ASC')
.getMany();
}
const menuList = generatorRouters(menus);
return menuList;
}
同样generatorRouters
函数也在src/modules/core/permission/index.ts
中。