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地址就可以看到文档
相关推荐
@PHARAOH9 分钟前
WHAT - cursor cli 开发范式
前端·ai·ai编程
子兮曰1 小时前
深入 HTML-in-Canvas:当 Canvas 学会了渲染 DOM,前端图形生态要变天了
前端·javascript·canvas
uzong1 小时前
我研读了 500 个 Spring Boot 生产级代码库,90% 都犯了这 7 个致命错误
后端
ws_qy1 小时前
从大模型原理到前端 AI Coding 工程化实践
前端·ai编程
倾颜2 小时前
React 19 源码主线拆解 04:Fiber 到底是什么,React 为什么需要 Fiber?
前端·react.js·源码阅读
AI攻城狮2 小时前
国产大模型能力大比拼,社区有话说
前端
xiaobaoyu2 小时前
ssm知识点梳理
后端
IT_陈寒2 小时前
Vite的public文件夹放静态资源?这坑我替你踩了
前端·人工智能·后端
浮游本尊2 小时前
合同同步逻辑
后端
涵涵(互关)2 小时前
GoView各项目文件中的相关语法2
前端·javascript·vue.js