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地址就可以看到文档
相关推荐
渣渣盟16 小时前
探索Word2Vec:从文本向量化到中文语料处理
前端·javascript·python·文本向量化
Pu_Nine_916 小时前
Vue 3 + TypeScript 项目性能优化全链路实战:从 2.1MB 到 130KB 的蜕变
前端·vue.js·性能优化·typescript·1024程序员节
云枫晖17 小时前
Webpack系列-Loader
前端·webpack
aggression17 小时前
代码敲击乐:让你了解前端的动静结合和移动端的适配性
前端
yinuo17 小时前
深入理解与实战 Git Submodule
前端
骑自行车的码农17 小时前
React 事件收集函数
前端·react.js
一个处女座的程序猿O(∩_∩)O17 小时前
Vue CLI 插件开发完全指南:从原理到实战
前端·javascript·vue.js
代码扳手17 小时前
Golang 实战:用 Watermill 构建订单事件流系统,一文掌握概念与应用
后端·go
麻木森林17 小时前
利用Apipost 的AI能力轻松破解接口测试的效率与质量困局
后端·api
小蜜蜂dry17 小时前
JavaScript 原型
前端·javascript