普通视图

发现新文章,点击刷新页面。
昨天 — 2025年12月23日首页

我用 NestJS + Vue3 + Prisma + PostgreSQL 打造了一个企业级 sass 多租户平台

2025年12月23日 16:23

🎯 我用 NestJS + Vue3 + Prisma + PostgreSQL 打造了一个企业级 sass 多租户平台

一个生产级的全栈管理系统开源项目,从零到一的实战分享

前言

这是一个基于 NestJS + Vue3 + Prisma + PostgreSQL ** 构建的全栈管理系统,不是简单的 CRUD,而是一个生产级别**的解决方案。它包含了多租户架构、RBAC权限管理、请求加密、完善的日志监控等企业级特性。


🔗 项目链接

🌟 GitHub 开源地址github.com/linlingqin7…
🎮 在线体验地址www.linlingqin.top/
📖 项目文档地址项目文档

体验账号:demo / demo123 (租户 000000)
完整权限账号:admin / admin123 (租户 000000)


📱 先看效果

登录页面

login.png

支持账号密码登录、验证码验证、记住密码

首页仪表板

dashboard.png

系统概览、快捷入口、数据统计、图表展示

用户管理

user.png

用户列表、角色分配、部门选择、状态管理

角色管理

role.png

角色权限配置、菜单权限树、数据权限范围

  • ✨ 现代化的界面设计,支持深色模式
  • 📱 响应式布局,完美适配各种屏幕
  • 🎨 丰富的组件和交互效果

� 完整功能截图

菜单管理

menu.png

菜单树形结构、路由配置、图标选择、权限标识

部门管理

dept.png

组织架构树、部门人员、数据权限

岗位管理

post.png

岗位配置、岗位排序、状态管理

字典管理

dict.png

数据字典维护、字典项配置

参数配置

paramSet.png

系统参数、动态配置、参数分类

通知公告

notice.png

公告发布、通知管理、类型分类

租户管理

tenant.png

多租户列表、套餐配置、租户状态

定时任务

job.png

Cron 任务配置、执行日志、任务管理

系统监控

monitor.png

服务器状态、CPU/内存使用率、磁盘信息

缓存监控

monitorCache.png

Redis 缓存信息、命令统计、键值管理

在线用户

online.png

实时在线用户、会话管理、强制下线

操作日志

operlog.png

操作记录、请求参数、响应结果、异常捕获

登录日志

loginLog.png

登录历史、IP 地址、浏览器信息、登录状态

缓存列表

cacheList.png

缓存键管理、过期时间、缓存清理

主题配置

theme.png

多主题切换、深色模式、主题色配置、布局模式

✨ 核心特性

🏢 多租户 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);
  }
}

权限层级

  1. 菜单级别:控制菜单显示/隐藏
  2. 按钮级别:控制页面内按钮权限
  3. 数据级别:全部/本部门/仅本人等数据范围

前端权限控制

<template>
  <!-- 按钮级权限 -->
  <n-button v-if="hasPermission('system:user:add')">
    添加用户
  </n-button>
</template>

🔒 请求加密机制

敏感数据传输采用 AES + RSA 混合加密

加密流程

  1. 前端生成随机 AES 密钥
  2. 用 AES-CBC 加密请求数据
  3. 用服务端 RSA 公钥加密 AES 密钥
  4. 发送 {encryptedKey, encryptedData} + header x-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. 后续流程使用解密后的数据                          │  │
│  │    ControllerService 直接使用 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. 访问系统

默认账号

  • 超级管理员: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 #后台管理系统 #全栈开发 #开源项目

❌
❌