最近一直在用nestjs,nextjs相关,为了学习跟练习 nestjs 就顺便使用了最新的drizzle-orm构建了最小rbac系统便于学习工作
准备工作:
- gitee仓库 clone代码到本地
https://gitee.com/kang841331654/nestjs-drizzle.git
- 准备你的env文件
- 启动项目成功
系统功能
- auth jwt 登录验证
- menu菜单管理
- dept部门管理
- role角色管理
- log日志管理
- use角色管理
- redis
- 请求限制频率
- 邮件
- Knife4j swagger日志增强
- nestjs 事件发布模块
具体技术栈
- nest.js
- mysql
- redis
- zod
- typescript
- drizzle-orm
后续计划
- 单点的登录,管理员踢人下线
- 验证码登录
- 操作日志
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地址就可以看到文档