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地址就可以看到文档
相关推荐
苏三说技术34 分钟前
Spring AI Alibaba已停更了,Java还有希望吗?
后端
飘尘41 分钟前
SVG和Canvas,前端里的两支“画笔”,用的时候怎么选择?
前端·javascript·面试
松就是我902981 小时前
如何搭建一个 Agent 系统:方法论、理想形态与一次真实落地
后端
Layer1 小时前
即时通讯系统的关键设计
后端·架构
计算机魔术师2 小时前
DeepSeek 据报道接近完成至少 800 亿元融资,腾讯与宁德时代参与
前端
golang学习记2 小时前
rust开发,选VS Code还是RustRover?
开发语言·后端·rust
可乐ea2 小时前
多模型编排的三层:框架、模型路由与提供商路由
前端·网络·人工智能·ai智能体·多智能体协作·多模型编排·大模型路由
打工仔折腾 AI2 小时前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
青柠之夏cc4 小时前
前端拖拽功能原生实现,不引入拖拽库完成业务
开发语言·前端·python
geovindu4 小时前
rust: Facade Pattern
开发语言·后端·设计模式·rust·外观模式