nestjs drizzle-orm 构建rbac权限系统

最近一直在用nestjs,nextjs相关,为了学习跟练习 nestjs 就顺便使用了最新的drizzle-orm构建了最小rbac系统便于学习工作

准备工作:

  • gitee仓库 clone代码到本地https://gitee.com/kang841331654/nestjs-drizzle.git
  • 准备你的env文件
  • 启动项目成功

系统功能

  1. auth jwt 登录验证
  2. menu菜单管理
  3. dept部门管理
  4. role角色管理
  5. log日志管理
  6. use角色管理
  7. redis
  8. 请求限制频率
  9. 邮件
  10. Knife4j swagger日志增强
  11. nestjs 事件发布模块

具体技术栈

  1. nest.js
  2. mysql
  3. redis
  4. zod
  5. typescript
  6. drizzle-orm

后续计划

  1. 单点的登录,管理员踢人下线
  2. 验证码登录
  3. 操作日志

1,初始化drizzle-orm 数据库表此操作直接拉取代码就可以,如果自己尝试新建drizzle-orm 并且初始化出数据库自己实现可以直接复制

js 复制代码
import {
  mysqlTable,
  int,
  varchar,
  char,
  timestamp,
  datetime,
  primaryKey,
  mysqlEnum,
  text,
  uniqueIndex,
  index
} from 'drizzle-orm/mysql-core';
import { sql } from 'drizzle-orm';
import { relations } from 'drizzle-orm';

export const users = mysqlTable('sys_user', {
  userId: int('user_id').primaryKey().autoincrement().notNull(),
  deptId: int('dept_id'),
  userName: varchar('user_name', { length: 30 }).notNull(),
  nickName: varchar('nick_name', { length: 30 }).notNull(),
  userType: varchar('user_type', { length: 2 }).notNull().default('00'),
  email: varchar('email', { length: 50 }).notNull().default(''),
  phonenumber: varchar('phonenumber', { length: 11 }).notNull().default(''),
  sex: char('sex', { length: 1 }).notNull().default('0'),
  password: varchar('password', { length: 200 }).notNull().default(''),
  status: char('status', { length: 1 }).notNull().default('0'),
  delFlag: char('del_flag', { length: 1 }).notNull().default('0'),
  loginIp: varchar('login_ip', { length: 128 }).notNull().default(''),
  createBy: varchar('create_by', { length: 64 }).notNull().default(''),
  createTime: datetime('create_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  updateBy: varchar('update_by', { length: 64 }).notNull().default(''),
  updateTime: datetime('update_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  remark: varchar('remark', { length: 500 }),
  avatar: varchar('avatar', { length: 255 }).notNull().default(''),
  loginDate: timestamp('login_date'),
});

// 部门表
export const depts = mysqlTable('sys_dept', {
  deptId: int('dept_id').primaryKey().autoincrement().notNull(),
  parentId: int('parent_id').notNull().default(0),
  ancestors: varchar('ancestors', { length: 50 }).notNull().default('0'),
  deptName: varchar('dept_name', { length: 30 }).notNull(),
  orderNum: int('order_num').notNull().default(0),
  leader: varchar('leader', { length: 20 }).notNull(),
  phone: varchar('phone', { length: 11 }).notNull().default(''),
  email: varchar('email', { length: 50 }).notNull().default(''),
  status: char('status', { length: 1 }).notNull().default('0'),
  delFlag: char('del_flag', { length: 1 }).notNull().default('0'),
  createBy: varchar('create_by', { length: 64 }).notNull().default(''),
  createTime: datetime('create_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  updateBy: varchar('update_by', { length: 64 }).notNull().default(''),
  updateTime: datetime('update_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  remark: varchar('remark', { length: 500 }),
});

// 菜单权限表
export const menus = mysqlTable('sys_menu', {
  menuId: int('menu_id').primaryKey().autoincrement().notNull(),
  menuName: varchar('menu_name', { length: 50 }).notNull(),
  parentId: int('parent_id').notNull(),
  orderNum: int('order_num').notNull().default(0),
  path: varchar('path', { length: 200 }).notNull().default(''),
  component: varchar('component', { length: 255 }),
  query: varchar('query', { length: 255 }).notNull().default(''),
  isFrame: char('is_frame', { length: 1 }).notNull().default('1'),
  isCache: char('is_cache', { length: 1 }).notNull().default('0'),
  menuType: char('menu_type', { length: 1 }).notNull().default('M'),
  visible: char('visible', { length: 1 }).notNull().default('0'),
  status: char('status', { length: 1 }).notNull().default('0'),
  perms: varchar('perms', { length: 100 }).notNull().default(''),
  icon: varchar('icon', { length: 100 }).notNull().default(''),
  createBy: varchar('create_by', { length: 64 }).notNull().default(''),
  createTime: datetime('create_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  updateBy: varchar('update_by', { length: 64 }).notNull().default(''),
  updateTime: datetime('update_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  remark: varchar('remark', { length: 500 }),
  delFlag: char('del_flag', { length: 1 }).notNull().default('0'),
});

// 角色表
export const roles = mysqlTable('sys_role', {
  roleId: int('role_id').primaryKey().autoincrement().notNull(),
  roleName: varchar('role_name', { length: 30 }).notNull(),
  roleKey: varchar('role_key', { length: 100 }).notNull(), // 角色权限字符串
  roleSort: int('role_sort').notNull().default(0),
  dataScope: char('data_scope', { length: 1 }).notNull().default('1'), // 数据范围(1:全部数据权限 2:自定数据权限)
  menuCheckStrictly: char('menu_check_strictly', { length: 1 }).notNull().default('1'),
  deptCheckStrictly: char('dept_check_strictly', { length: 1 }).notNull().default('1'),
  status: char('status', { length: 1 }).notNull().default('0'),
  delFlag: char('del_flag', { length: 1 }).notNull().default('0'),
  createBy: varchar('create_by', { length: 64 }).notNull().default(''),
  createTime: datetime('create_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  updateBy: varchar('update_by', { length: 64 }).notNull().default(''),
  updateTime: datetime('update_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  remark: varchar('remark', { length: 500 }),
});

// 权限表
export const permissions = mysqlTable('sys_permission', {
  permissionId: int('permission_id').primaryKey().autoincrement().notNull(),
  permissionName: varchar('permission_name', { length: 50 }).notNull(),
  permissionKey: varchar('permission_key', { length: 100 }).notNull().unique(), // 权限唯一标识
  permissionType: char('permission_type', { length: 1 }).notNull().default('B'), // 类型: B-业务, A-API, D-数据
  status: char('status', { length: 1 }).notNull().default('0'),
  createBy: varchar('create_by', { length: 64 }).notNull().default(''),
  createTime: datetime('create_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  updateBy: varchar('update_by', { length: 64 }).notNull().default(''),
  updateTime: datetime('update_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
  remark: varchar('remark', { length: 500 }),
});

// 登录日志表
export const loginLogs = mysqlTable('sys_logininfor', {
  infoId: int('info_id').primaryKey().autoincrement().notNull(),
  userName: varchar('user_name', { length: 50 }).notNull().default(''),
  ipaddr: varchar('ipaddr', { length: 128 }).notNull().default(''),
  loginLocation: varchar('login_location', { length: 255 }).notNull().default(''),
  browser: varchar('browser', { length: 50 }).notNull().default(''),
  os: varchar('os', { length: 50 }).notNull().default(''),
  status: char('status', { length: 1 }).notNull().default('0'), // 0-成功, 1-失败
  msg: varchar('msg', { length: 255 }).notNull().default(''),
  loginTime: datetime('login_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
}, (t) => ({
  userNameIndex: index('idx_user_name').on(t.userName),
  statusIndex: index('idx_status').on(t.status),
  loginTimeIndex: index('idx_login_time').on(t.loginTime),
}));

// 用户和角色关联表
export const userRoles = mysqlTable('sys_user_role', {
  userId: int('user_id').notNull(),
  roleId: int('role_id').notNull(),
}, (t) => ({
  pk: primaryKey({ columns: [t.userId, t.roleId] }),
}));

// 角色和菜单关联表
export const roleMenus = mysqlTable('sys_role_menu', {
  roleId: int('role_id').notNull(),
  menuId: int('menu_id').notNull(),
}, (t) => ({
  pk: primaryKey({ columns: [t.roleId, t.menuId] }),
}));

// 角色与部门关联表
export const roleDepts = mysqlTable('sys_role_dept', {
  roleId: int('role_id').notNull(),
  deptId: int('dept_id').notNull(),
}, (t) => ({
  pk: primaryKey({ columns: [t.roleId, t.deptId] }),
}));

// 角色和权限关联表
export const rolePermissions = mysqlTable('sys_role_permission', {
  roleId: int('role_id').notNull(),
  permissionId: int('permission_id').notNull(),
}, (t) => ({
  pk: primaryKey({ columns: [t.roleId, t.permissionId] }),
}));

// 定义关系
export const usersRelations = relations(users, ({ many }) => ({
  userRoles: many(userRoles),
}));

export const rolesRelations = relations(roles, ({ many }) => ({
  userRoles: many(userRoles),
  roleMenus: many(roleMenus),
  roleDepts: many(roleDepts),
  rolePermissions: many(rolePermissions),
}));

export const menusRelations = relations(menus, ({ many }) => ({
  roleMenus: many(roleMenus),
}));

export const deptsRelations = relations(depts, ({ many }) => ({
  roleDepts: many(roleDepts),
}));

export const permissionsRelations = relations(permissions, ({ many }) => ({
  rolePermissions: many(rolePermissions),
}));

export const userRolesRelations = relations(userRoles, ({ one }) => ({
  user: one(users, {
    fields: [userRoles.userId],
    references: [users.userId],
  }),
  role: one(roles, {
    fields: [userRoles.roleId],
    references: [roles.roleId],
  }),
}));

export const roleMenusRelations = relations(roleMenus, ({ one }) => ({
  role: one(roles, {
    fields: [roleMenus.roleId],
    references: [roles.roleId],
  }),
  menu: one(menus, {
    fields: [roleMenus.menuId],
    references: [menus.menuId],
  }),
}));

export const roleDeptRelations = relations(roleDepts, ({ one }) => ({
  role: one(roles, {
    fields: [roleDepts.roleId],
    references: [roles.roleId],
  }),
  dept: one(depts, {
    fields: [roleDepts.deptId],
    references: [depts.deptId],
  }),
}));

export const rolePermissionRelations = relations(rolePermissions, ({ one }) => ({
  role: one(roles, {
    fields: [rolePermissions.roleId],
    references: [roles.roleId],
  }),
  permission: one(permissions, {
    fields: [rolePermissions.permissionId],
    references: [permissions.permissionId],
  }),
})); 

2 配置你的env环境

js 复制代码
# 数据库连接配置
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=你的数据库密码
DB_NAME=你的数据库名称


# swagger
SWAGGER_ENABLE = true
SWAGGER_PATH = api-docs
SWAGGER_VERSION = 1.0

# 应用配置
APP_NAME="nest.js admin 管理后台"
APP_PORT="3000"
NODE_ENV="development"

# redis
REDIS_PORT = 6379
REDIS_HOST = 127.0.0.1
REDIS_PASSWORD = ''
REDIS_DB = 0

# smtp
SMTP_HOST = smtp.163.com
SMTP_PORT = 465
SMTP_USER = nest_admin@163.com
SMTP_PASS = VIPLLOIPMETTROYU
 

3 clone 系统到你本地然后安装依赖

js 复制代码
pnpm install

or

bun install

or

yarn install

4 成功运行项目

  • 项目做了swagger 跟 knife4j js版的增强接入
  • 访问启动的对应api地址就可以看到文档
相关推荐
ZhuAiQuan17 分钟前
[electron]开发环境驱动识别失败
前端·javascript·electron
nyf_unknown22 分钟前
(vue)将dify和ragflow页面嵌入到vue3项目
前端·javascript·vue.js
胡gh26 分钟前
数组开会:splice说它要动刀,map说它只想看看。
javascript·后端·面试
Pure_Eyes26 分钟前
go 常见面试题
开发语言·后端·golang
胡gh35 分钟前
浏览器:我要用缓存!服务器:你缓存过期了!怎么把数据挽留住,这是个问题。
前端·面试·node.js
你挚爱的强哥1 小时前
SCSS上传图片占位区域样式
前端·css·scss
奶球不是球1 小时前
css新特性
前端·css
Nicholas681 小时前
flutter滚动视图之Viewport、RenderViewport源码解析(六)
前端
无羡仙1 小时前
React 状态更新:如何避免为嵌套数据写一长串 ...?
前端·react.js
Cisyam2 小时前
使用Bright Data API轻松构建LinkedIn职位数据采集系统
后端