我用 NestJS + Vue3 + Prisma + PostgreSQL 打造了一个企业级 sass 多租户平台
🎯 我用 NestJS + Vue3 + Prisma + PostgreSQL 打造了一个企业级 sass 多租户平台
一个生产级的全栈管理系统开源项目,从零到一的实战分享
前言
这是一个基于 NestJS + Vue3 + Prisma + PostgreSQL ** 构建的全栈管理系统,不是简单的 CRUD,而是一个生产级别**的解决方案。它包含了多租户架构、RBAC权限管理、请求加密、完善的日志监控等企业级特性。
🔗 项目链接
🌟 GitHub 开源地址:github.com/linlingqin7…
🎮 在线体验地址:www.linlingqin.top/
📖 项目文档地址:项目文档
体验账号:demo / demo123 (租户 000000)
完整权限账号:admin / admin123 (租户 000000)
📱 先看效果
登录页面
![]()
支持账号密码登录、验证码验证、记住密码
首页仪表板
![]()
系统概览、快捷入口、数据统计、图表展示
用户管理
![]()
用户列表、角色分配、部门选择、状态管理
角色管理
![]()
角色权限配置、菜单权限树、数据权限范围
- ✨ 现代化的界面设计,支持深色模式
- 📱 响应式布局,完美适配各种屏幕
- 🎨 丰富的组件和交互效果
� 完整功能截图
菜单管理
![]()
菜单树形结构、路由配置、图标选择、权限标识
部门管理
![]()
组织架构树、部门人员、数据权限
岗位管理
![]()
岗位配置、岗位排序、状态管理
字典管理
![]()
数据字典维护、字典项配置
参数配置
![]()
系统参数、动态配置、参数分类
通知公告
![]()
公告发布、通知管理、类型分类
租户管理
![]()
多租户列表、套餐配置、租户状态
定时任务
![]()
Cron 任务配置、执行日志、任务管理
系统监控
![]()
服务器状态、CPU/内存使用率、磁盘信息
缓存监控
![]()
Redis 缓存信息、命令统计、键值管理
在线用户
![]()
实时在线用户、会话管理、强制下线
操作日志
![]()
操作记录、请求参数、响应结果、异常捕获
登录日志
![]()
登录历史、IP 地址、浏览器信息、登录状态
缓存列表
![]()
缓存键管理、过期时间、缓存清理
主题配置
![]()
多主题切换、深色模式、主题色配置、布局模式
✨ 核心特性
🏢 多租户 SaaS 架构
这是本项目的一大亮点。实现了完整的租户数据隔离:
// 所有数据库查询自动按租户过滤
const users = await prisma.sysUser.findMany({
where: { name: 'John' }
// tenantId 会自动注入,无需手动添加
});
// 跳过租户过滤(特殊场景)
@IgnoreTenant()
async getAllTenants() {
return await this.prisma.tenant.findMany();
}
技术实现:
- 基于 Prisma Extension 实现透明的租户过滤
- 通过 TenantGuard 从请求头自动识别租户
- 超级管理员(租户 000000)可跨租户管理
适用场景:
- SaaS 平台
- 企业内部多部门系统
- 白标产品
🔐 RBAC 权限管理
不只是简单的角色权限,而是多层级、细粒度的权限控制:
@Controller('users')
export class UserController {
// 需要特定权限才能访问
@RequirePermission('system:user:add')
@Post()
create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
// 需要特定角色
@RequireRole('admin')
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(id);
}
}
权限层级:
- 菜单级别:控制菜单显示/隐藏
- 按钮级别:控制页面内按钮权限
- 数据级别:全部/本部门/仅本人等数据范围
前端权限控制:
<template>
<!-- 按钮级权限 -->
<n-button v-if="hasPermission('system:user:add')">
添加用户
</n-button>
</template>
🔒 请求加密机制
敏感数据传输采用 AES + RSA 混合加密:
加密流程:
- 前端生成随机 AES 密钥
- 用 AES-CBC 加密请求数据
- 用服务端 RSA 公钥加密 AES 密钥
- 发送
{encryptedKey, encryptedData}+ headerx-encrypted: true
// 后端自动解密
@Post('login')
async login(@Body() dto: LoginDto) {
// dto 已自动解密,直接使用
return this.authService.login(dto);
}
// 跳过解密(非敏感接口)
@SkipDecrypt()
@Get('captcha')
async getCaptcha() {
return this.captchaService.generate();
}
优势:
- 保护密码、Token 等敏感信息
- 防止中间人攻击
- 对业务代码透明,由拦截器统一处理
📊 完善的日志监控
基于 Pino 实现的高性能结构化日志:
// 自动记录操作日志
@Operlog({
businessType: BusinessTypeEnum.UPDATE,
title: '修改用户'
})
@Put(':id')
async update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
return this.userService.update(id, dto);
}
监控能力:
- 结构化日志:自动记录 requestId、tenantId、username
- 敏感字段脱敏:password、token 等自动隐藏
- 操作审计:记录谁在什么时间做了什么
- 登录日志:登录历史、IP、浏览器等信息
- 健康检查:K8s liveness/readiness 探针
-
Prometheus 指标:暴露
/api/metrics端点
日志输出示例:
{
"level": "info",
"time": "2025-12-22T10:30:00.000Z",
"requestId": "a1b2c3d4",
"tenantId": "000001",
"username": "admin",
"method": "POST",
"url": "/api/system/user",
"statusCode": 200,
"duration": 45
}
🎭 演示账户系统
专为产品演示设计的只读账户机制:
// Demo 账户拦截器
@UseInterceptors(DemoInterceptor)
@Controller('users')
export class UserController {
@Post() // Demo 账户会被自动拦截
create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
@Get() // 查询操作不受影响
findAll() {
return this.userService.findAll();
}
}
特性:
- 52 个只读权限,可查看所有模块
- 自动拦截所有写操作(POST/PUT/DELETE/PATCH)
- 返回友好的提示信息
- 适合演示站点、产品 Demo
🌐 国际化支持
前后端完整的 i18n 方案:
// 后端
throw new ApiException(ErrorEnum.USER_NOT_FOUND);
// 自动返回对应语言的错误信息
// 前端
const { t } = useI18n();
console.log(t('system.user.name')); // 根据语言返回对应文本
支持中文、英文,可轻松扩展其他语言。
🛠️ 技术栈详解
后端技术栈
| 技术 | 版本 | 核心应用场景 | 技术亮点 |
|---|---|---|---|
| NestJS | 10.x | 企业级框架,构建可扩展的服务端应用 | • 依赖注入 • 模块化设计 • 装饰器语法 • 内置 TypeScript |
| Prisma | 5.x | 类型安全的数据库 ORM | • 自动生成类型 • 数据库迁移 • 强大的查询构建器 • 多数据库支持 |
| PostgreSQL | 14+ | 主数据库,存储核心业务数据 | • ACID 事务 • JSON 支持 • 丰富的数据类型 • 强大的查询优化 |
| Redis | 7+ | 缓存、Session、分布式锁 | • 高性能缓存 • 数据过期策略 • 发布订阅 • 分布式锁 |
| JWT | - | 无状态身份认证 | • Token 认证 • Refresh Token • 跨域认证 • 移动端友好 |
| Passport | - | 认证中间件 | • 策略模式 • 多种认证方式 • 易于扩展 |
| Pino | - | 高性能结构化日志 | • JSON 日志 • 低开销 • 日志轮转 • 多传输通道 |
| Swagger | - | API 文档自动生成 | • 交互式文档 • 自动类型推导 • 在线测试 |
| Terminus | - | 健康检查与监控 | • K8s 探针 • 数据库检查 • 内存监控 • 自定义检查 |
| class-validator | - | 数据验证 | • 装饰器验证 • 自定义规则 • 嵌套验证 |
| class-transformer | - | 数据转换 | • DTO 转换 • 类型映射 • 数据脱敏 |
| @nestjs/schedule | - | 定时任务调度 | • Cron 表达式 • 间隔任务 • 超时控制 |
| nestjs-cls | - | 请求上下文管理 | • 请求追踪 • 用户上下文 • 租户上下文 |
前端技术栈
| 技术 | 版本 | 核心应用场景 | 技术亮点 |
|---|---|---|---|
| Vue 3 | 3.5+ | 渐进式前端框架 | • Composition API • 响应式系统 • 虚拟 DOM • 组件化开发 |
| Vite | 7+ | 新一代构建工具 | • 极速冷启动 • HMR 热更新 • 按需编译 • Rollup 打包 |
| Naive UI | 最新 | 企业级组件库 | • Vue 3 组合式 API • TypeScript 支持 • 主题定制 • 200+ 组件 |
| UnoCSS | 最新 | 即时原子化 CSS 引擎 | • 零运行时 • 高性能 • 预设系统 • 按需生成 |
| Pinia | 最新 | 下一代状态管理 | • 轻量级 • TypeScript 支持 • 模块化 • DevTools 支持 |
| Vue Router | 4+ | 官方路由管理 | • 动态路由 • 路由守卫 • 懒加载 • 嵌套路由 |
| Axios | 最新 | HTTP 请求库 | • Promise API • 拦截器 • 请求取消 • 自动转换 |
| TypeScript | 5.x | 类型安全的 JavaScript | • 静态类型检查 • IDE 智能提示 • 重构支持 • 接口定义 |
| VueUse | 最新 | Vue 组合式函数集合 | • 常用 Hooks • 响应式工具 • 浏览器 API 封装 |
| Elegant Router | 最新 | 基于文件的路由系统 | • 自动生成路由 • 约定式路由 • 类型安全 |
| ECharts | 5+ | 数据可视化图表 | • 丰富的图表类型 • 响应式设计 • 主题定制 |
| CryptoJS | - | 加密算法库 | • AES 加密 • RSA 加密 • MD5/SHA 哈希 |
🏗️ 系统架构详解
📐 整体架构图
┌─────────────────────────────────┐
│ 用户/客户端层 │
│ ┌──────────┐ ┌─────────────┐ │
│ │ 浏览器 │ │ 移动端App │ │
│ └──────────┘ └─────────────┘ │
└──────────────┬──────────────────┘
│ HTTPS
┌──────────────▼──────────────────┐
│ CDN / Nginx 网关 │
│ • 静态资源缓存 │
│ • 反向代理 │
│ • 负载均衡 │
│ • SSL 证书 │
└──────────────┬──────────────────┘
│
┌──────────────────────┴──────────────────────┐
│ │
┌───────────────▼────────────────┐ ┌───────────────────▼──────────┐
│ 前端应用 (Vue3) │ │ 后端应用 (NestJS) │
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ UI 层 (Naive UI) │ │ │ │ 控制器层 (Controllers)│ │
│ │ • 组件库 │ │ │ │ • 路由定义 │ │
│ │ • 主题定制 │ │ │ │ • 请求验证 │ │
│ │ • 响应式布局 │ │ │ │ • 参数转换 │ │
│ └──────────────────────────┘ │ │ └────────────────────────┘ │
│ │ │ │ │
│ ┌──────────────────────────┐ │ │ ┌───────────▼─────────────┐ │
│ │ 状态层 (Pinia Store) │ │ │ │ 守卫层 (Guards) │ │
│ │ • 全局状态 │ │ │ │ 1. TenantGuard │ │
│ │ • 用户信息 │ │ │ │ 2. AuthGuard │ │
│ │ • 权限数据 │ │ │ │ 3. RolesGuard │ │
│ └──────────────────────────┘ │ │ │ 4. PermissionGuard │ │
│ │ │ └───────────┬─────────────┘ │
│ ┌──────────────────────────┐ │ │ │ │
│ │ 路由层 (Vue Router) │ │ │ ┌───────────▼─────────────┐ │
│ │ • 动态路由 │ │ │ │ 拦截器层 (Interceptors)│ │
│ │ • 路由守卫 │ │ │ │ 1. DecryptInterceptor │ │
│ │ • 懒加载 │ │ │ │ 2. DemoInterceptor │ │
│ └──────────────────────────┘ │ │ │ 3. TransformInter... │ │
│ │ │ │ 4. LoggingInterceptor │ │
│ ┌──────────────────────────┐ │ │ └───────────┬─────────────┘ │
│ │ 请求层 (Axios) │ │ │ │ │
│ │ • 请求拦截 │◄─┼───────┼──────────────┤ │
│ │ • 响应拦截 │ │ │ ┌───────────▼─────────────┐ │
│ │ • 错误处理 │ │ │ │ 业务层 (Services) │ │
│ │ • 请求加密 │ │ │ │ • 系统管理 Service │ │
│ └──────────────────────────┘ │ │ │ • 权限管理 Service │ │
│ │ │ │ • 监控管理 Service │ │
└─────────────────────────────────┘ │ │ • 租户管理 Service │ │
│ └───────────┬─────────────┘ │
│ │ │
│ ┌───────────▼─────────────┐ │
│ │ 数据访问层 (Prisma) │ │
│ │ • Schema 定义 │ │
│ │ • 类型生成 │ │
│ │ • 查询构建器 │ │
│ │ • 租户扩展 │ │
│ └───────────┬─────────────┘ │
└──────────────┼────────────────┘
│
┌────────────────────────────────┼────────────────┐
│ │ │
┌───────────▼──────────┐ ┌──────────▼──────────┐ │
│ PostgreSQL 数据库 │ │ Redis 缓存 │ │
│ • 主数据存储 │ │ • 会话存储 │ │
│ • 事务支持 │ │ • 数据缓存 │ │
│ • 索引优化 │ │ • 分布式锁 │ │
└───────────────────────┘ └─────────────────────┘ │
│
┌──────────────────────────────────────────────────────────┐ │
│ 外部服务集成 │ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │ │
│ │ OSS 对象存储│ │ 邮件服务 │ │ 短信服务 │ │ │
│ │ • 阿里云 │ │ • SMTP │ │ • 阿里云 │ │ │
│ │ • 七牛云 │ │ • SendGrid │ │ • 腾讯云 │ │ │
│ │ • MinIO │ └──────────────┘ └────────────────┘ │ │
│ └─────────────┘ │ │
│ ┌──────────────────────────────────────────────────────┐│ │
│ │ 监控与日志 ││ │
│ │ • Prometheus (指标采集) ││ │
│ │ • Grafana (可视化) ││ │
│ │ • Pino Logger (日志) ││ │
│ │ • Terminus (健康检查) ││ │
│ └──────────────────────────────────────────────────────┘│ │
└──────────────────────────────────────────────────────────┘ │
│
┌──────────────────────────────────────────────────┘
│ 部署环境 (可选)
│ ┌──────────────────────────────┐
│ │ Docker 容器化 │
│ │ • 应用容器 │
│ │ • 数据库容器 │
│ │ • Redis 容器 │
│ └──────────────────────────────┘
│ ┌──────────────────────────────┐
│ │ Kubernetes 编排 │
│ │ • Pod 管理 │
│ │ • Service 暴露 │
│ │ • Ingress 路由 │
│ └──────────────────────────────┘
└───────────────────────────────────
🔄 完整请求处理流程
┌─────────────┐
│ 1. 客户端 │ 发起 HTTP 请求
└──────┬──────┘
│
▼
┌─────────────────────────────────────────┐
│ 2. 前端请求拦截器 │
│ • 添加 Authorization Token │
│ • 添加租户 ID (x-tenant-id) │
│ • 敏感数据 AES+RSA 加密 │
│ • 添加请求 ID (x-request-id) │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 3. Nginx 网关 │
│ • SSL 终止 │
│ • 静态资源服务 │
│ • 反向代理到后端 │
│ • 请求日志记录 │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 4. NestJS 中间件层 │
│ • CORS 处理 │
│ • Body 解析 │
│ • Helmet 安全头 │
│ • Request ID 生成 │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 5. 守卫层 (Guards) - 按顺序执行 │
│ ┌───────────────────────────────────┐ │
│ │ 5.1 TenantGuard │ │
│ │ • 提取请求头中的租户 ID │ │
│ │ • 验证租户是否存在且有效 │ │
│ │ • 设置租户上下文 (CLS) │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 5.2 JwtAuthGuard │ │
│ │ • 验证 JWT Token 有效性 │ │
│ │ • 解析用户信息 │ │
│ │ • 设置用户上下文 │ │
│ │ • 检查 Token 是否过期 │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 5.3 RolesGuard (可选) │ │
│ │ • 检查用户角色 │ │
│ │ • 验证是否满足角色要求 │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 5.4 PermissionGuard │ │
│ │ • 检查用户权限 │ │
│ │ • 验证是否有接口访问权限 │ │
│ │ • 支持权限组合 (AND/OR) │ │
│ └───────────────────────────────────┘ │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 6. 拦截器层 (Interceptors) - 前置 │
│ ┌───────────────────────────────────┐ │
│ │ 6.1 DecryptInterceptor │ │
│ │ • 检测加密请求头 │ │
│ │ • RSA 解密 AES 密钥 │ │
│ │ • AES 解密请求体 │ │
│ │ • 替换原始请求数据 │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 6.2 DemoInterceptor │ │
│ │ • 检测是否为演示账户 │ │
│ │ • 拦截写操作 (POST/PUT/DELETE) │ │
│ │ • 返回友好提示信息 │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 6.3 LoggingInterceptor (开始) │ │
│ │ • 记录请求开始时间 │ │
│ │ • 记录请求基本信息 │ │
│ └───────────────────────────────────┘ │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 7. Pipe 管道验证 │
│ • ValidationPipe (DTO 验证) │
│ • ParseIntPipe (参数转换) │
│ • 自定义验证管道 │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 8. Controller 控制器 │
│ • 接收请求参数 │
│ • 调用 Service 方法 │
│ • 应用装饰器 (@Operlog 等) │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 9. Service 业务层 │
│ • 业务逻辑处理 │
│ • 数据验证 │
│ • 调用 Prisma 查询 │
│ • 缓存操作 (Redis) │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 10. Prisma ORM 层 │
│ ┌───────────────────────────────────┐ │
│ │ 10.1 Tenant Extension │ │
│ │ • 自动注入租户过滤条件 │ │
│ │ • 所有查询自动添加 tenantId │ │
│ │ • 支持 @IgnoreTenant 跳过 │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 10.2 查询执行 │ │
│ │ • 参数化查询 (防 SQL 注入) │ │
│ │ • 事务支持 │ │
│ │ • 查询优化 │ │
│ └───────────────────────────────────┘ │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 11. 数据库层 │
│ • PostgreSQL 查询执行 │
│ • 索引查找 │
│ • 事务提交/回滚 │
└──────┬──────────────────────────────────┘
│
▼ (返回数据)
┌─────────────────────────────────────────┐
│ 12. 拦截器层 (Interceptors) - 后置 │
│ ┌───────────────────────────────────┐ │
│ │ 12.1 TransformInterceptor │ │
│ │ • 统一响应格式 │ │
│ │ • {code, msg, data, timestamp} │ │
│ │ • 脱敏处理 │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ 12.2 LoggingInterceptor (结束) │ │
│ │ • 计算请求耗时 │ │
│ │ • 记录响应状态码 │ │
│ │ • 记录到操作日志表 │ │
│ │ • 输出结构化日志 │ │
│ └───────────────────────────────────┘ │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 13. 异常过滤器 (Exception Filters) │
│ • 捕获异常 │
│ • 格式化错误响应 │
│ • 记录错误日志 │
│ • 返回友好错误信息 │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 14. 响应返回客户端 │
│ • HTTP Response │
│ • 状态码 + 响应体 │
└─────────────────────────────────────────┘
🏢 多租户架构图
┌──────────────────────────────────────────────────────────┐
│ 租户 A 用户 │
│ (tenantId: 000001) │
└────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────────────────────────────────────┐
│ 租户 B 用户 │
│ (tenantId: 000002) │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ NestJS 应用 - TenantGuard │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 1. 提取请求头中的租户 ID (x-tenant-id) │ │
│ │ 2. 验证租户是否存在且状态为启用 │ │
│ │ 3. 将租户 ID 存入请求上下文 (CLS) │ │
│ │ 4. 后续所有操作自动使用该租户上下文 │ │
│ └────────────────────────────────────────────────────┘ │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Prisma Client - Tenant Extension │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 所有数据库查询自动注入租户过滤: │ │
│ │ │ │
│ │ 原始查询: │ │
│ │ prisma.sysUser.findMany({ where: { ... } }) │ │
│ │ │ │
│ │ 自动转换为: │ │
│ │ prisma.sysUser.findMany({ │ │
│ │ where: { │ │
│ │ tenantId: '000001', // 自动注入 │ │
│ │ ... │ │
│ │ } │ │
│ │ }) │ │
│ │ │ │
│ │ 支持的操作: │ │
│ │ • findMany / findUnique / findFirst │ │
│ │ • create / createMany │ │
│ │ • update / updateMany │ │
│ │ • delete / deleteMany │ │
│ │ • count / aggregate │ │
│ └────────────────────────────────────────────────────┘ │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ PostgreSQL 数据库 │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 租户 A 数据 │ │ 租户 B 数据 │ │
│ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │
│ │ │ tenantId: 0001 │ │ │ │ tenantId: 0002 │ │ │
│ │ │ user_id: 1 │ │ │ │ user_id: 100 │ │ │
│ │ │ name: 张三 │ │ │ │ name: 李四 │ │ │
│ │ └────────────────┘ │ │ └────────────────┘ │ │
│ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │
│ │ │ tenantId: 0001 │ │ │ │ tenantId: 0002 │ │ │
│ │ │ user_id: 2 │ │ │ │ user_id: 101 │ │ │
│ │ │ name: 王五 │ │ │ │ name: 赵六 │ │ │
│ │ └────────────────┘ │ │ └────────────────┘ │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ✓ 数据完全隔离,互不干扰 │
│ ✓ 共享数据库,降低成本 │
│ ✓ tenantId 字段建立索引,查询高效 │
└───────────────────────────────────────────────────────────┘
特殊场景: 超级管理员查询
┌──────────────────────────────────────────────────────────┐
│ @IgnoreTenant() // 跳过租户过滤 │
│ async getAllTenants() { │
│ return await this.prisma.tenant.findMany(); │
│ } │
│ // 可以查询所有租户的数据 │
└──────────────────────────────────────────────────────────┘
🔐 权限控制架构图
┌────────────────────────────────────────────────────────────┐
│ 用户登录成功 │
└────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ JWT Token 生成 (包含用户基本信息) │
│ { │
│ userId: 1, │
│ username: 'admin', │
│ tenantId: '000000', │
│ roles: ['admin'], │
│ exp: 1640000000 // 过期时间 │
│ } │
└────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 前端存储 Token │
│ • localStorage.setItem('token', token) │
│ • 每次请求自动携带: Authorization: Bearer ${token} │
└────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 后端接收请求 - 权限检查流程 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Step 1: JwtAuthGuard │ │
│ │ ├─ 验证 Token 签名 │ │
│ │ ├─ 检查 Token 是否过期 │ │
│ │ ├─ 解析用户信息 │ │
│ │ └─ 从 Redis 加载完整用户权限 │ │
│ │ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ Redis 缓存的用户权限数据 │ │ │
│ │ │ user:permissions:1 │ │ │
│ │ │ { │ │ │
│ │ │ roles: ['admin', 'user'], │ │ │
│ │ │ permissions: [ │ │ │
│ │ │ 'system:user:add', │ │ │
│ │ │ 'system:user:edit', │ │ │
│ │ │ 'system:user:delete', │ │ │
│ │ │ 'system:user:query', │ │ │
│ │ │ 'system:role:*', // 通配符 │ │ │
│ │ │ ... │ │ │
│ │ │ ], │ │ │
│ │ │ menuIds: [1,2,3,4,5,...], │ │ │
│ │ │ dataScope: 'DEPT_AND_CHILD' │ │ │
│ │ │ } │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Step 2: RolesGuard (可选) │ │
│ │ @RequireRole('admin') │ │
│ │ ├─ 检查用户是否拥有指定角色 │ │
│ │ ├─ 支持多角色: @RequireRole('admin', 'manager') │ │
│ │ └─ 支持角色组合: AND / OR │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Step 3: PermissionGuard │ │
│ │ @RequirePermission('system:user:edit') │ │
│ │ ├─ 提取接口所需权限 │ │
│ │ ├─ 检查用户权限列表 │ │
│ │ ├─ 支持通配符匹配 (system:user:*) │ │
│ │ └─ 支持权限组合: AND / OR │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────┬───────────────────────────────────────┘
│
┌───────────┴───────────┐
│ 权限验证通过 │ 权限验证失败
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ 执行业务逻辑 │ │ 返回 403 Forbidden │
│ • Controller │ │ { code: 403, │
│ • Service │ │ msg: '无权限访问' }│
│ • Prisma │ └──────────────────────┘
└──────────────────┘
数据权限控制 (Data Scope):
┌────────────────────────────────────────────────────────────┐
│ 在 Service 层根据用户的 dataScope 过滤数据: │
│ │
│ async findUsers(query, user) { │
│ const where = { ...query }; │
│ │
│ if (user.dataScope === 'ALL') { │
│ // 查询所有数据 (超级管理员) │
│ } else if (user.dataScope === 'DEPT_AND_CHILD') { │
│ // 查询本部门及子部门数据 │
│ where.deptId = { in: user.deptIds }; │
│ } else if (user.dataScope === 'DEPT') { │
│ // 只查询本部门数据 │
│ where.deptId = user.deptId; │
│ } else if (user.dataScope === 'SELF') { │
│ // 只查询自己的数据 │
│ where.userId = user.userId; │
│ } │
│ │
│ return await this.prisma.user.findMany({ where }); │
│ } │
└────────────────────────────────────────────────────────────┘
🔒 请求加密架构图
┌────────────────────────────────────────────────────────────┐
│ 前端 - 加密流程 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. 准备敏感数据 │ │
│ │ const data = { │ │
│ │ username: 'admin', │ │
│ │ password: 'admin123' // 敏感信息 │ │
│ │ }; │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 2. 生成随机 AES 密钥 (每次请求都不同) │ │
│ │ const aesKey = CryptoJS.lib.WordArray.random(16); │ │
│ │ // 例: "a1b2c3d4e5f6g7h8" │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 3. 用 AES 密钥加密数据 │ │
│ │ const encryptedData = CryptoJS.AES.encrypt( │ │
│ │ JSON.stringify(data), │ │
│ │ aesKey, │ │
│ │ { mode: CryptoJS.mode.CBC, iv: randomIV } │ │
│ │ ); │ │
│ │ // 结果: "U2FsdGVkX1+..." (Base64) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 4. 用服务端 RSA 公钥加密 AES 密钥 │ │
│ │ const encryptedKey = RSA.encrypt( │ │
│ │ aesKey.toString(), │ │
│ │ serverPublicKey // 服务端提供的公钥 │ │
│ │ ); │ │
│ │ // RSA 2048 加密 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 5. 发送加密请求 │ │
│ │ POST /api/login │ │
│ │ Headers: │ │
│ │ x-encrypted: true // 标识加密请求 │ │
│ │ Body: │ │
│ │ { │ │
│ │ encryptedKey: "MIIBIj...", // RSA 加密的密钥 │ │
│ │ encryptedData: "U2FsdG..." // AES 加密的数据 │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────┬───────────────────────────────────────┘
│
│ HTTPS 加密传输
│
▼
┌────────────────────────────────────────────────────────────┐
│ 后端 - 解密流程 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. DecryptInterceptor 拦截请求 │ │
│ │ if (request.headers['x-encrypted'] === 'true') { │ │
│ │ // 执行解密流程 │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 2. 用服务端 RSA 私钥解密 AES 密钥 │ │
│ │ const aesKey = RSA.decrypt( │ │
│ │ encryptedKey, │ │
│ │ serverPrivateKey // 服务端的私钥 │ │
│ │ ); │ │
│ │ // 得到: "a1b2c3d4e5f6g7h8" │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 3. 用 AES 密钥解密数据 │ │
│ │ const decryptedData = AES.decrypt( │ │
│ │ encryptedData, │ │
│ │ aesKey │ │
│ │ ); │ │
│ │ // 得到原始数据 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 4. 解析 JSON 并替换 request.body │ │
│ │ request.body = JSON.parse(decryptedData); │ │
│ │ // { │ │
│ │ // username: 'admin', │ │
│ │ // password: 'admin123' │ │
│ │ // } │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 5. 后续流程使用解密后的数据 │ │
│ │ Controller 和 Service 直接使用 request.body │ │
│ │ 完全透明,无需关心加解密逻辑 │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
安全优势:
┌────────────────────────────────────────────────────────────┐
│ ✓ 双重加密: AES (对称) + RSA (非对称) │
│ ✓ 每次请求的 AES 密钥都不同,无法重放攻击 │
│ ✓ RSA 私钥只存在服务端,前端无法解密 │
│ ✓ 即使 HTTPS 被破解,数据仍然加密 │
│ ✓ 密码等敏感信息永不明文传输 │
└────────────────────────────────────────────────────────────┘
🎯 核心功能模块详解
1️⃣ 系统管理模块
👤 用户管理
完整的用户生命周期管理,支持企业级用户体系:
- 用户 CRUD:新增、编辑、删除、批量操作
- 角色分配:支持一个用户多个角色
- 部门归属:关联组织架构,实现数据权限隔离
- 密码管理:密码重置、密码强度验证、定期修改提醒
- 状态管理:启用/禁用、锁定/解锁
- 导入导出:批量导入用户、导出 Excel
- 用户画像:登录统计、操作记录、权限视图
🎭 角色管理
基于 RBAC 的灵活权限控制:
- 权限分配:菜单权限树选择,支持半选状态
- 数据权限:全部数据/本部门/本部门及子部门/仅本人
- 角色继承:支持角色间的权限继承关系
- 动态权限:权限变更实时生效,无需重新登录
- 权限预览:可视化展示角色拥有的所有权限
📋 菜单管理
动态菜单配置,支持无限层级:
- 树形结构:可视化的菜单树编辑
- 菜单类型:目录、菜单、按钮三种类型
- 图标选择:内置图标库,支持自定义
- 路由配置:前端路由路径、组件路径
- 权限标识:按钮级权限控制标识
- 显示控制:菜单显示/隐藏、缓存控制
- 外链菜单:支持外部链接菜单
🏢 部门管理
企业组织架构管理:
- 树形结构:无限层级的部门树
- 部门负责人:设置部门负责人
- 排序控制:自定义部门显示顺序
- 数据权限:基于部门的数据隔离
- 人员统计:部门人员数量统计
💼 岗位管理
岗位体系管理:
- 岗位定义:岗位名称、编码、排序
- 状态管理:启用/停用岗位
- 人员关联:查看岗位下的所有人员
📖 字典管理
系统数据字典统一管理:
- 字典类型:定义字典分类(如:用户状态、性别等)
- 字典数据:维护具体的字典项
- 缓存支持:字典数据自动缓存,提升性能
- 前端使用:前端统一调用字典接口
⚙️ 参数配置
系统参数动态配置:
- 配置项管理:新增、编辑、删除配置项
- 配置分类:系统配置、业务配置等
- 缓存刷新:配置变更自动刷新缓存
- 配置校验:支持配置值格式验证
📢 通知公告
系统公告发布与管理:
- 公告发布:富文本编辑器,支持图文混排
- 公告类型:通知、公告、系统消息
- 发布控制:立即发布、定时发布
- 阅读状态:已读/未读状态跟踪
2️⃣ 系统监控模块
👥 在线用户
实时监控在线用户状态:
- 在线列表:显示当前所有在线用户
- 用户信息:用户名、IP 地址、登录时间、浏览器
- 强制下线:管理员可强制用户下线
- 会话管理:查看用户会话信息
- 实时统计:在线用户数量统计
📝 操作日志
完整的操作审计系统:
- 自动记录:通过装饰器自动记录操作
- 详细信息:操作人、操作时间、操作类型、请求参数、响应结果
- 异常捕获:自动记录异常操作和错误堆栈
- 条件查询:按用户、时间、模块、操作类型查询
- 数据导出:导出日志用于审计
🔐 登录日志
登录安全审计:
- 登录记录:成功/失败的登录记录
- 安全信息:IP 地址、地理位置、浏览器、操作系统
- 异常检测:异常登录行为提醒
- 统计分析:登录时段分析、地域分析
⏰ 定时任务
灵活的任务调度系统:
- Cron 表达式:支持标准 Cron 表达式
- 任务管理:启动、停止、立即执行
- 执行日志:任务执行历史、成功/失败记录
- 并发控制:任务并发执行控制
- 超时设置:任务执行超时时间设置
- 错误重试:失败自动重试机制
🖥️ 系统监控
服务器运行状态监控:
- CPU 监控:CPU 使用率、核心数
- 内存监控:内存使用情况、JVM 信息
- 磁盘监控:磁盘使用率、读写速度
- 网络监控:网络流量统计
- 进程信息:进程 PID、运行时长
💚 健康检查
K8s 友好的健康检查:
- Liveness 探针:应用存活检查
- Readiness 探针:应用就绪检查
- 数据库检查:PostgreSQL 连接状态
- Redis 检查:Redis 连接状态
- 磁盘检查:磁盘空间检查
- 内存检查:内存使用检查
-
Prometheus 指标:暴露
/api/metrics端点
📁 文件上传
多存储支持的文件管理:
- 本地存储:存储到服务器本地
- 阿里云 OSS:存储到阿里云对象存储
- 七牛云:存储到七牛云
- MinIO:私有化对象存储
- 文件预览:图片、PDF 等在线预览
- 缩略图:自动生成图片缩略图
3️⃣ 多租户管理模块
🏘️ 租户管理
完整的 SaaS 租户管理:
- 租户 CRUD:新增、编辑、删除租户
- 租户信息:租户名称、联系人、到期时间
- 套餐绑定:为租户分配功能套餐
- 状态管理:启用、停用、过期控制
- 数据隔离:自动的租户数据隔离
- 容量限制:用户数、存储空间限制
📦 租户套餐
灵活的套餐体系:
- 套餐定义:基础版、标准版、企业版
- 功能权限:菜单权限按套餐分配
- 资源限制:用户数、存储空间限制
- 套餐升级:支持套餐在线升级
🔒 数据隔离
安全的多租户数据隔离:
- 自动过滤:数据库查询自动按租户过滤
- 跨租户查询:超级管理员可查询所有租户
- 租户切换:支持切换查看不同租户数据
- 数据迁移:租户数据导入导出
4️⃣ 演示账户系统
专为产品演示设计的安全机制:
- 只读权限:52 个查询权限,可查看所有模块
- 写操作拦截:自动拦截所有 POST/PUT/DELETE/PATCH 请求
- 友好提示:操作被拦截时给出友好提示
- 灵活配置:基于 RBAC 可随时调整权限范围
- 演示重置:定时重置演示数据(可选)
🚀 快速开始
环境要求
- Node.js >= 20.19.0
- PostgreSQL >= 14
- Redis >= 7
- pnpm >= 8.0
安装步骤
1. 克隆项目
git clone https://github.com/your-repo/nest-admin-soybean.git
cd nest-admin-soybean
2. 后端配置
cd server
pnpm install
# 生成 RSA 密钥对(用于加密)
pnpm generate:keys
# 配置数据库连接
# 编辑 src/config/index.ts 中的数据库配置
# 初始化数据库
pnpm prisma:seed
3. 前端配置
cd admin-naive-ui
pnpm install
# 配置后端接口地址
# 编辑 .env.development 文件
4. 启动项目
# 启动后端 (8080端口)
cd server
pnpm start:dev
# 启动前端 (9527端口)
cd admin-naive-ui
pnpm dev
5. 访问系统
- 前端地址:http://localhost:9527
- 后端接口:http://localhost:8080/api
- API 文档:http://localhost:8080/api-docs
默认账号:
- 超级管理员:admin / admin123 (租户 000000)
- 演示账户:demo / demo123 (租户 000000)
💪 技术亮点详解
1. 多租户实现原理
核心思路:通过 Prisma Extension 在 ORM 层面实现透明的租户过滤。
// tenant.extension.ts
export function tenantExtension(tenantId: string) {
return Prisma.defineExtension({
query: {
// 对所有模型的查询自动添加租户过滤
$allModels: {
async findMany({ args, query }) {
args.where = { ...args.where, tenantId };
return query(args);
},
// ... findUnique, create, update, delete 同理
}
}
});
}
// prisma.service.ts
get client() {
const tenantId = TenantContext.getTenantId();
if (!tenantId) return this._client;
return this._client.$extends(tenantExtension(tenantId));
}
优势:
- 业务代码无需关心租户逻辑
- 避免忘记添加租户条件导致的数据泄露
- 统一管理,易于维护
2. 请求加密的实现
前端加密:
// encryption.ts
export function encryptRequest(data: any) {
// 1. 生成随机 AES 密钥
const aesKey = CryptoJS.lib.WordArray.random(16);
// 2. AES 加密数据
const encryptedData = CryptoJS.AES.encrypt(
JSON.stringify(data),
aesKey,
{ mode: CryptoJS.mode.CBC }
).toString();
// 3. RSA 加密 AES 密钥
const encryptedKey = rsaEncrypt(aesKey.toString(), serverPublicKey);
return { encryptedKey, encryptedData };
}
后端解密:
// decrypt.interceptor.ts
@Injectable()
export class DecryptInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
if (request.headers['x-encrypted'] === 'true') {
const { encryptedKey, encryptedData } = request.body;
// 1. RSA 解密 AES 密钥
const aesKey = rsaDecrypt(encryptedKey, privateKey);
// 2. AES 解密数据
const decryptedData = aesDecrypt(encryptedData, aesKey);
// 3. 替换 body
request.body = JSON.parse(decryptedData);
}
return next.handle();
}
}
3. 权限系统的设计
采用装饰器 + 守卫的组合模式:
// permission.guard.ts
@Injectable()
export class PermissionGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const requiredPermission = this.reflector.get(
'permission',
context.getHandler()
);
if (!requiredPermission) return true;
const user = context.switchToHttp().getRequest().user;
return user.permissions.includes(requiredPermission);
}
}
// 使用
@RequirePermission('system:user:edit')
@Put(':id')
updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto) {
return this.userService.update(id, dto);
}
权限数据结构:
// 权限标识:模块:功能:操作
'system:user:add' // 添加用户
'system:user:edit' // 编辑用户
'system:user:delete' // 删除用户
'system:user:query' // 查询用户
4. 日志系统的优化
自动日志收集:
// logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(private logger: Logger) {}
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const startTime = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - startTime;
this.logger.info({
requestId: request.id,
tenantId: request.tenantId,
username: request.user?.username,
method: request.method,
url: request.url,
statusCode: context.switchToHttp().getResponse().statusCode,
duration,
});
})
);
}
}
敏感字段脱敏:
// 自动隐藏敏感字段
const sensitiveFields = ['password', 'token', 'secret'];
this.logger.info(redactSensitive(data, sensitiveFields));
🎨 前端特色
1. 文件路由系统
使用 @elegant-router/vue 实现基于文件的路由:
src/views/
├── system/
│ ├── user/
│ │ └── index.vue → /system/user
│ ├── role/
│ │ └── index.vue → /system/role
│ └── menu/
│ └── index.vue → /system/menu
自动生成路由:
pnpm gen-route
2. 原子化 CSS
使用 UnoCSS,支持 Tailwind 风格:
<template>
<div class="flex items-center justify-between p-4 bg-white dark:bg-dark">
<span class="text-lg font-bold">用户管理</span>
<n-button type="primary">添加用户</n-button>
</div>
</template>
3. 状态管理
Pinia setup 语法:
// useAuthStore.ts
export const useAuthStore = defineStore('auth', () => {
const token = ref(getToken());
const userInfo = ref<UserInfo | null>(null);
async function login(credentials: LoginDto) {
const { token: newToken, user } = await api.login(credentials);
token.value = newToken;
userInfo.value = user;
setToken(newToken);
}
return { token, userInfo, login };
});
4. 请求封装
统一的 Axios 封装,支持自动加密:
// api.ts
export function fetchUserList(params: UserQueryDto) {
return request<PageResult<User>>({
url: '/system/user',
method: 'GET',
params
});
}
export function createUser(data: CreateUserDto) {
return request({
url: '/system/user',
method: 'POST',
data,
encrypt: true // 自动加密
});
}
🔮 未来规划
- 微服务架构:拆分为独立的微服务
- 消息队列:集成 RabbitMQ/Kafka
- 分布式追踪:接入 OpenTelemetry
- GraphQL API:提供 GraphQL 接口
- 移动端:开发配套的移动应用
- 低代码平台:可视化表单设计器
- 更多数据库:支持 MySQL、MongoDB
- 云原生:K8s Helm Chart
如果觉得不错,欢迎 Star ⭐️:github.com/linlingqin7…
#NestJS #Vue3 #后台管理系统 #全栈开发 #开源项目