海川的日志

企业级管理后台

作者:海川,发表于:2026年1月28日 08:00:00

基于Next.js & Nest.js,完整的企业级管理后台设计方案、架构规划、功能清单、数据模型设计和实现指南。涵盖权限管理、审计日志、数据字典、文件管理等核心功能

前言

企业级管理后台是 B 端应用的核心。但从零开始构建时,很多开发者不清楚应该包含哪些通用功能如何组织代码结构怎样设计权限系统

本文从功能设计出发,讲解企业级管理后台的通用组件架构模式,最后给出分层实现方案。无论你是初创公司还是大型企业,都能从中获得参考。


一、企业级管理后台的核心特征

为什么需要企业级管理后台?

个人应用          企业应用
└─ 单用户        └─ 多用户
└─ 简单功能      └─ 复杂权限
└─ 无审计        └─ 需要审计
└─ 静态界面      └─ 动态配置
└─ 单一部署      └─ 多套部署

企业级管理后台的五大需求

需求为什么解决方案
权限控制不同角色看不同功能RBAC/ABAC权限模型
审计追踪记录所有用户操作,防止纠纷操作日志、变更日志
数据管理支持海量数据和复杂查询分页、搜索、导出
系统配置灵活支持业务变化数据字典、系统参数
扩展性支持业务快速迭代模块化、插件化架构

二、架构设计

整体系统架构

架构图

分层设计(后端)

Request

Controller (路由、入参验证)

Service (业务逻辑)

Repository (数据访问)

Database

Response

各层职责

  • Controller - 接收请求、参数验证、返回响应
  • Service - 业务逻辑、权限校验、事务管理
  • Repository - 数据库操作、查询优化
  • Database - 数据存储

三、通用功能模块清单

1. 用户和认证系统

核心功能

  • 用户注册/登录/登出
  • 密码修改/重置
  • 多设备登录
  • 登录日志
  • 会话管理

数据模型

-- 用户表
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  username VARCHAR(50) UNIQUE NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  real_name VARCHAR(100),
  avatar_url VARCHAR(255),
  status ENUM('active', 'inactive', 'locked'), -- 状态
  last_login_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- 登录日志
CREATE TABLE login_logs (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  login_time TIMESTAMP DEFAULT NOW(),
  login_ip VARCHAR(50),
  login_device VARCHAR(100),
  login_status ENUM('success', 'failed'),
  remark VARCHAR(255)
);

2. 角色和权限管理(RBAC)

核心概念

  • User - 用户
  • Role - 角色(如管理员、经理、员工)
  • Permission - 权限(如查看、编辑、删除)
  • Menu - 菜单(动态菜单)

权限模型

User ← has many → Role ← has many → Permission

                  Menu

数据模型

-- 角色表
CREATE TABLE roles (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) UNIQUE NOT NULL, -- 如 'admin', 'manager'
  description VARCHAR(255),
  status ENUM('active', 'inactive'),
  created_at TIMESTAMP DEFAULT NOW()
);

-- 用户角色关联
CREATE TABLE user_roles (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(user_id, role_id)
);

-- 权限表
CREATE TABLE permissions (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) UNIQUE NOT NULL, -- 如 'user:create', 'user:delete'
  description VARCHAR(255),
  resource VARCHAR(50), -- 资源,如 'user', 'post'
  action VARCHAR(50), -- 操作,如 'create', 'read', 'update', 'delete'
  created_at TIMESTAMP DEFAULT NOW()
);

-- 角色权限关联
CREATE TABLE role_permissions (
  id SERIAL PRIMARY KEY,
  role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
  permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE,
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(role_id, permission_id)
);

-- 菜单表(前端路由和菜单)
CREATE TABLE menus (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL, -- 显示名称,如 '用户管理'
  route VARCHAR(255), -- 路由路径,如 '/users'
  icon VARCHAR(100), -- 图标
  parent_id INTEGER REFERENCES menus(id), -- 父菜单 ID
  order_num INTEGER DEFAULT 0, -- 排序
  visible BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW()
);

-- 菜单权限关联(某个菜单需要什么权限)
CREATE TABLE menu_permissions (
  id SERIAL PRIMARY KEY,
  menu_id INTEGER REFERENCES menus(id) ON DELETE CASCADE,
  permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE,
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(menu_id, permission_id)
);

实现方案

// Nest.js 权限装饰器
import { SetMetadata } from "@nestjs/common";

export const Permissions = (...perms: string[]) =>
  SetMetadata("permissions", perms);

// 使用
@Controller("users")
export class UsersController {
  @Post()
  @Permissions("user:create")
  createUser(dto: CreateUserDto) {
    // ...
  }

  @Delete(":id")
  @Permissions("user:delete")
  deleteUser(@Param("id") id: number) {
    // ...
  }
}

3. 部门管理

核心功能

  • 部门树形结构
  • 部门编辑
  • 部门成员管理

数据模型

CREATE TABLE departments (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  parent_id INTEGER REFERENCES departments(id), -- 上级部门
  leader_id INTEGER REFERENCES users(id), -- 部门负责人
  code VARCHAR(50) UNIQUE, -- 部门编码
  sort_order INTEGER DEFAULT 0,
  status ENUM('active', 'inactive') DEFAULT 'active',
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- 用户所属部门(多对多关系)
CREATE TABLE user_departments (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  department_id INTEGER REFERENCES departments(id) ON DELETE CASCADE,
  is_main BOOLEAN DEFAULT false, -- 是否主部门
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(user_id, department_id)
);

4. 操作日志和审计追踪

核心功能

  • 记录所有增删改操作
  • 记录操作人、操作时间、操作内容
  • 支持变更对比

数据模型

-- 操作日志
CREATE TABLE operation_logs (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  module_name VARCHAR(100), -- 模块名称,如 'user_management'
  operation VARCHAR(50), -- 操作类型:CREATE, UPDATE, DELETE
  resource_type VARCHAR(100), -- 资源类型,如 'User'
  resource_id INTEGER, -- 资源 ID
  description VARCHAR(255),
  operation_time TIMESTAMP DEFAULT NOW(),
  operation_ip VARCHAR(50),
  user_agent TEXT,
  created_at TIMESTAMP DEFAULT NOW()
);

-- 变更日志(记录字段级别的变化)
CREATE TABLE change_logs (
  id SERIAL PRIMARY KEY,
  operation_log_id INTEGER REFERENCES operation_logs(id) ON DELETE CASCADE,
  field_name VARCHAR(100), -- 字段名
  old_value TEXT, -- 旧值
  new_value TEXT, -- 新值
  change_type VARCHAR(50), -- ADDED, MODIFIED, DELETED
  created_at TIMESTAMP DEFAULT NOW()
);

实现方案

// 创建装饰器来自动记录操作日志
@Controller("users")
export class UsersController {
  @Post()
  @LogOperation("user_management", "CREATE", "User")
  createUser(dto: CreateUserDto) {
    // 操作会自动被记录
  }

  @Patch(":id")
  @LogOperation("user_management", "UPDATE", "User")
  updateUser(@Param("id") id: number, dto: UpdateUserDto) {
    // ...
  }
}

5. 数据字典管理

核心功能

  • 管理系统中的枚举值
  • 支持多语言
  • 缓存优化

使用场景

性别:男、女、其他
状态:启用、禁用、审核中
优先级:高、中、低

数据模型

-- 字典类型
CREATE TABLE dict_types (
  id SERIAL PRIMARY KEY,
  type_code VARCHAR(50) UNIQUE NOT NULL, -- 类型编码,如 'gender'
  type_name VARCHAR(100) NOT NULL, -- 类型名称
  description VARCHAR(255),
  status ENUM('active', 'inactive') DEFAULT 'active',
  sort_order INTEGER DEFAULT 0,
  created_at TIMESTAMP DEFAULT NOW()
);

-- 字典数据
CREATE TABLE dict_data (
  id SERIAL PRIMARY KEY,
  dict_type_id INTEGER REFERENCES dict_types(id) ON DELETE CASCADE,
  label VARCHAR(100) NOT NULL, -- 显示值,如 '男'
  value VARCHAR(100) NOT NULL, -- 实际值,如 'M'
  sort_order INTEGER DEFAULT 0,
  is_default BOOLEAN DEFAULT false,
  status ENUM('active', 'inactive') DEFAULT 'active',
  remark VARCHAR(255),
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(dict_type_id, value)
);

前端使用

// 定义字典类型
export enum DictType {
  Gender = 'gender',
  Status = 'status',
  Priority = 'priority',
}

// 在表单中使用
<Form.Item label="性别">
  <Select options={dicts[DictType.Gender]} />
</Form.Item>

6. 系统参数管理

核心功能

  • 管理系统级别的配置
  • 支持多种数据类型
  • 热更新

数据模型

CREATE TABLE system_configs (
  id SERIAL PRIMARY KEY,
  config_key VARCHAR(100) UNIQUE NOT NULL, -- 参数键,如 'file_upload_max_size'
  config_value TEXT, -- 参数值
  config_type VARCHAR(50), -- 参数类型:string, number, boolean, json
  description VARCHAR(255),
  remark VARCHAR(255),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

使用示例

// 获取配置
const maxFileSize = await configService.get("file_upload_max_size"); // 10485760

// 更新配置
await configService.set("file_upload_max_size", "20971520");

7. 文件管理

核心功能

  • 文件上传/下载
  • 文件列表管理
  • 文件搜索和过滤
  • 垃圾回收

数据模型

CREATE TABLE files (
  id SERIAL PRIMARY KEY,
  original_name VARCHAR(255) NOT NULL, -- 原始文件名
  storage_name VARCHAR(255) NOT NULL UNIQUE, -- 存储文件名
  file_path VARCHAR(500), -- 文件路径
  file_size BIGINT, -- 文件大小(字节)
  file_type VARCHAR(100), -- MIME 类型
  upload_by INTEGER REFERENCES users(id),
  upload_time TIMESTAMP DEFAULT NOW(),
  related_module VARCHAR(100), -- 关联模块,如 'user_avatar'
  related_id INTEGER, -- 关联对象 ID
  status ENUM('active', 'deleted') DEFAULT 'active',
  deleted_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

-- 文件变更日志(谁上传的、何时删除的)
CREATE TABLE file_operation_logs (
  id SERIAL PRIMARY KEY,
  file_id INTEGER REFERENCES files(id),
  operation VARCHAR(50), -- UPLOAD, DELETE, DOWNLOAD
  operator_id INTEGER REFERENCES users(id),
  operation_time TIMESTAMP DEFAULT NOW()
);

8. 消息和通知系统

核心功能

  • 系统消息
  • 用户通知
  • 消息标记已读

数据模型

-- 消息模板
CREATE TABLE message_templates (
  id SERIAL PRIMARY KEY,
  template_name VARCHAR(100) NOT NULL,
  template_code VARCHAR(100) UNIQUE NOT NULL,
  template_content TEXT, -- 模板内容,支持变量替换
  template_type VARCHAR(50), -- email, sms, in_app
  created_at TIMESTAMP DEFAULT NOW()
);

-- 消息记录
CREATE TABLE messages (
  id SERIAL PRIMARY KEY,
  recipient_id INTEGER REFERENCES users(id),
  sender_id INTEGER REFERENCES users(id),
  title VARCHAR(255),
  content TEXT,
  message_type VARCHAR(50), -- info, warning, error, success
  is_read BOOLEAN DEFAULT false,
  read_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

9. 数据导入导出

核心功能

  • 支持 CSV、Excel 格式
  • 批量导入
  • 导出历史记录

实现方案

// 导出接口
@Controller('export')
export class ExportController {
  @Post('users/excel')
  async exportUsersToExcel(
    @Query() filter: FilterUserDto,
    @Res() res: Response,
  ) {
    const users = await this.userService.findAll(filter);
    const buffer = await this.excelService.generateExcel(users);
    res.attachment('users.xlsx');
    res.send(buffer);
  }
}

// 导入接口
@Post('users/import')
@UseInterceptors(FileInterceptor('file'))
async importUsers(@UploadedFile() file: Express.Multer.File) {
  const data = await this.excelService.parseExcel(file);
  return await this.userService.batchCreate(data);
}

10. 系统监控和统计

核心功能

  • 用户统计
  • 操作趋势
  • 系统负载

数据模型

-- 日常统计数据
CREATE TABLE daily_statistics (
  id SERIAL PRIMARY KEY,
  stat_date DATE NOT NULL,
  new_user_count INTEGER DEFAULT 0,
  active_user_count INTEGER DEFAULT 0,
  total_operations INTEGER DEFAULT 0,
  peak_concurrency INTEGER DEFAULT 0, -- 峰值并发数
  avg_response_time FLOAT, -- 平均响应时间(ms)
  error_rate FLOAT, -- 错误率(%)
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(stat_date)
);

四、前端项目结构设计

目录结构

nextjs-admin/
├── public/                 # 静态资源
├── src/
│   ├── app/               # Next.js 13+ App Router
│   │   ├── layout.tsx     # 全局布局
│   │   ├── page.tsx       # 首页
│   │   ├── dashboard/     # 仪表板
│   │   ├── users/         # 用户管理
│   │   │   ├── page.tsx
│   │   │   ├── [id]/
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── permissions/   # 权限管理
│   │   └── settings/      # 系统设置
│   │
│   ├── components/
│   │   ├── common/        # 通用组件
│   │   │   ├── Header.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   ├── Breadcrumb.tsx
│   │   │   └── Footer.tsx
│   │   │
│   │   ├── layout/        # 布局组件
│   │   │   ├── AdminLayout.tsx
│   │   │   ├── AuthLayout.tsx
│   │   │   └── BlankLayout.tsx
│   │   │
│   │   ├── ui/            # UI 组件(从 shadcn/ui 引入)
│   │   │   ├── Button.tsx
│   │   │   ├── Table.tsx
│   │   │   ├── Dialog.tsx
│   │   │   └── Form.tsx
│   │   │
│   │   └── feature/       # 业务组件
│   │       ├── UserList.tsx
│   │       ├── UserForm.tsx
│   │       ├── PermissionTree.tsx
│   │       └── DashboardChart.tsx
│   │
│   ├── lib/
│   │   ├── api.ts         # API 请求封装
│   │   ├── auth.ts        # 认证逻辑
│   │   ├── permissions.ts # 权限检查
│   │   ├── utils.ts       # 通用工具函数
│   │   └── constants.ts   # 常量定义
│   │
│   ├── hooks/             # 自定义 Hooks
│   │   ├── useAuth.ts
│   │   ├── usePermission.ts
│   │   ├── usePagination.ts
│   │   └── useDict.ts
│   │
│   ├── store/             # 状态管理(Zustand 或 Redux)
│   │   ├── authStore.ts
│   │   ├── permissionStore.ts
│   │   └── uiStore.ts
│   │
│   ├── styles/
│   │   ├── globals.css    # 全局样式
│   │   └── theme.css      # 主题定义
│   │
│   ├── types/             # TypeScript 类型定义
│   │   ├── user.ts
│   │   ├── permission.ts
│   │   └── common.ts
│   │
│   └── middleware/        # 中间件
│       └── auth.ts        # 认证中间件

├── .env.local            # 环境变量
├── tailwind.config.js    # Tailwind 配置
├── next.config.js        # Next.js 配置
├── tsconfig.json
├── package.json
└── README.md

关键文件实现

1. API 封装 (lib/api.ts)

import axios, { AxiosInstance, AxiosError } from "axios";

class ApiClient {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: process.env.NEXT_PUBLIC_API_URL,
      timeout: 10000,
    });

    // 请求拦截器:添加 token
    this.client.interceptors.request.use((config) => {
      const token = localStorage.getItem("token");
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });

    // 响应拦截器:处理错误
    this.client.interceptors.response.use(
      (response) => response.data,
      (error: AxiosError) => {
        if (error.response?.status === 401) {
          // 重定向到登录
          window.location.href = "/login";
        }
        throw error;
      },
    );
  }

  get<T>(url: string, config = {}) {
    return this.client.get<any, T>(url, config);
  }

  post<T>(url: string, data?: any, config = {}) {
    return this.client.post<any, T>(url, data, config);
  }

  put<T>(url: string, data?: any, config = {}) {
    return this.client.put<any, T>(url, data, config);
  }

  delete<T>(url: string, config = {}) {
    return this.client.delete<any, T>(url, config);
  }
}

export const api = new ApiClient();

2. 权限检查 Hook (hooks/usePermission.ts)

import { useAuthStore } from "@/store/authStore";

export const usePermission = () => {
  const { permissions } = useAuthStore();

  const hasPermission = (permission: string): boolean => {
    return permissions.includes(permission);
  };

  const can = (action: string, resource: string): boolean => {
    return hasPermission(`${resource}:${action}`);
  };

  const cannot = (action: string, resource: string): boolean => {
    return !can(action, resource);
  };

  return {
    hasPermission,
    can,
    cannot,
  };
};

3. 权限保护组件 (components/ui/CanAccess.tsx)

import { ReactNode } from "react";
import { usePermission } from "@/hooks/usePermission";

interface CanAccessProps {
  permission: string;
  children: ReactNode;
  fallback?: ReactNode;
}

export const CanAccess = ({
  permission,
  children,
  fallback = null,
}: CanAccessProps) => {
  const { hasPermission } = usePermission();

  return hasPermission(permission) ? children : fallback;
};

4. 用户列表页面 (app/users/page.tsx)

'use client';

import { useState, useEffect } from 'react';
import { api } from '@/lib/api';
import { usePermission } from '@/hooks/usePermission';
import { Table, Button, Modal, Form, Input } from 'antd';
import { CanAccess } from '@/components/ui/CanAccess';

export default function UsersPage() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
  const { can } = usePermission();

  useEffect(() => {
    fetchUsers();
  }, [pagination]);

  const fetchUsers = async () => {
    setLoading(true);
    try {
      const data = await api.get('/api/users', {
        params: {
          page: pagination.current,
          pageSize: pagination.pageSize,
        },
      });
      setUsers(data.items);
      setPagination((prev) => ({
        ...prev,
        total: data.total,
      }));
    } finally {
      setLoading(false);
    }
  };

  const columns = [
    { title: 'ID', dataIndex: 'id' },
    { title: '用户名', dataIndex: 'username' },
    { title: '邮箱', dataIndex: 'email' },
    {
      title: '操作',
      render: (_, record) => (
        <div>
          <CanAccess permission="user:read">
            <Button type="link">查看</Button>
          </CanAccess>
          <CanAccess permission="user:update">
            <Button type="link">编辑</Button>
          </CanAccess>
          <CanAccess permission="user:delete">
            <Button type="link" danger>
              删除
            </Button>
          </CanAccess>
        </div>
      ),
    },
  ];

  return (
    <div className="p-6">
      <div className="mb-4 flex justify-between">
        <h1 className="text-2xl font-bold">用户管理</h1>
        <CanAccess permission="user:create">
          <Button type="primary">新增用户</Button>
        </CanAccess>
      </div>

      <Table
        columns={columns}
        dataSource={users}
        loading={loading}
        pagination={pagination}
        onChange={(page) => setPagination(page)}
      />
    </div>
  );
}

五、后端项目结构设计

目录结构

nestjs-admin/
├── src/
│   ├── main.ts            # 应用入口
│   │
│   ├── config/            # 配置
│   │   ├── database.config.ts
│   │   ├── auth.config.ts
│   │   └── app.config.ts
│   │
│   ├── modules/
│   │   ├── auth/          # 认证模块
│   │   │   ├── auth.controller.ts
│   │   │   ├── auth.service.ts
│   │   │   ├── auth.module.ts
│   │   │   ├── jwt.strategy.ts
│   │   │   └── decorators/
│   │   │       ├── auth.decorator.ts
│   │   │       └── permissions.decorator.ts
│   │   │
│   │   ├── users/         # 用户管理模块
│   │   │   ├── users.controller.ts
│   │   │   ├── users.service.ts
│   │   │   ├── users.module.ts
│   │   │   ├── entities/
│   │   │   │   └── user.entity.ts
│   │   │   └── dtos/
│   │   │       ├── create-user.dto.ts
│   │   │       └── update-user.dto.ts
│   │   │
│   │   ├── permissions/   # 权限管理模块
│   │   │   ├── permissions.controller.ts
│   │   │   ├── permissions.service.ts
│   │   │   ├── permissions.module.ts
│   │   │   └── entities/
│   │   │       ├── role.entity.ts
│   │   │       ├── permission.entity.ts
│   │   │       └── menu.entity.ts
│   │   │
│   │   ├── audit/         # 审计日志模块
│   │   │   ├── audit.service.ts
│   │   │   ├── audit.module.ts
│   │   │   ├── interceptors/
│   │   │   │   └── audit-log.interceptor.ts
│   │   │   └── entities/
│   │   │       ├── operation-log.entity.ts
│   │   │       └── change-log.entity.ts
│   │   │
│   │   ├── dict/          # 数据字典模块
│   │   │   ├── dict.controller.ts
│   │   │   ├── dict.service.ts
│   │   │   ├── dict.module.ts
│   │   │   └── entities/
│   │   │       ├── dict-type.entity.ts
│   │   │       └── dict-data.entity.ts
│   │   │
│   │   ├── config/        # 系统配置模块
│   │   │   ├── config.controller.ts
│   │   │   ├── config.service.ts
│   │   │   └── config.module.ts
│   │   │
│   │   ├── files/         # 文件管理模块
│   │   │   ├── files.controller.ts
│   │   │   ├── files.service.ts
│   │   │   ├── files.module.ts
│   │   │   └── entities/
│   │   │       └── file.entity.ts
│   │   │
│   │   └── dashboard/     # 仪表板模块
│   │       ├── dashboard.controller.ts
│   │       ├── dashboard.service.ts
│   │       └── dashboard.module.ts
│   │
│   ├── common/            # 通用代码
│   │   ├── decorators/    # 装饰器
│   │   │   ├── api-response.decorator.ts
│   │   │   └── validate.decorator.ts
│   │   │
│   │   ├── filters/       # 异常过滤器
│   │   │   ├── http-exception.filter.ts
│   │   │   └── all-exceptions.filter.ts
│   │   │
│   │   ├── guards/        # 守卫(权限检查)
│   │   │   ├── jwt.guard.ts
│   │   │   ├── permission.guard.ts
│   │   │   └── roles.guard.ts
│   │   │
│   │   ├── interceptors/  # 拦截器
│   │   │   ├── response.interceptor.ts
│   │   │   └── logging.interceptor.ts
│   │   │
│   │   ├── pipes/         # 管道(数据变换和验证)
│   │   │   └── validation.pipe.ts
│   │   │
│   │   ├── strategies/    # 认证策略
│   │   │   └── jwt.strategy.ts
│   │   │
│   │   └── utils/         # 工具函数
│   │       ├── password.util.ts
│   │       └── pagination.util.ts
│   │
│   ├── database/          # 数据库相关
│   │   ├── prisma/
│   │   │   ├── schema.prisma
│   │   │   └── migrations/
│   │   └── seeds/         # 数据库种子
│   │       └── seed.ts
│   │
│   └── app.module.ts      # 应用根模块

├── test/
│   ├── jest.config.js
│   └── ...

├── .env.example
├── .env.local
├── Dockerfile
├── docker-compose.yml
├── prisma/
│   └── schema.prisma
├── package.json
├── tsconfig.json
└── README.md

关键文件实现

1. Prisma Schema (prisma/schema.prisma)

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

// 用户表
model User {
  id                Int       @id @default(autoincrement())
  username          String    @unique
  email             String    @unique
  passwordHash      String
  realName          String?
  avatarUrl         String?
  status            UserStatus @default(ACTIVE)
  lastLoginAt       DateTime?
  createdAt         DateTime  @default(now())
  updatedAt         DateTime  @updatedAt

  userRoles         UserRole[]
  loginLogs         LoginLog[]
  operationLogs     OperationLog[]
  files             File[]

  @@map("users")
}

enum UserStatus {
  ACTIVE
  INACTIVE
  LOCKED
}

// 角色表
model Role {
  id                Int       @id @default(autoincrement())
  name              String    @unique
  description       String?
  status            RoleStatus @default(ACTIVE)
  createdAt         DateTime  @default(now())

  userRoles         UserRole[]
  rolePermissions   RolePermission[]

  @@map("roles")
}

enum RoleStatus {
  ACTIVE
  INACTIVE
}

// 用户-角色关联
model UserRole {
  id                Int       @id @default(autoincrement())
  userId            Int
  roleId            Int
  user              User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  role              Role      @relation(fields: [roleId], references: [id], onDelete: Cascade)
  createdAt         DateTime  @default(now())

  @@unique([userId, roleId])
  @@map("user_roles")
}

// 权限表
model Permission {
  id                Int       @id @default(autoincrement())
  name              String    @unique
  description       String?
  resource          String
  action            String
  createdAt         DateTime  @default(now())

  rolePermissions   RolePermission[]
  menuPermissions   MenuPermission[]

  @@map("permissions")
}

// 角色-权限关联
model RolePermission {
  id                Int       @id @default(autoincrement())
  roleId            Int
  permissionId      Int
  role              Role      @relation(fields: [roleId], references: [id], onDelete: Cascade)
  permission        Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
  createdAt         DateTime  @default(now())

  @@unique([roleId, permissionId])
  @@map("role_permissions")
}

// 菜单表
model Menu {
  id                Int       @id @default(autoincrement())
  name              String
  route             String?
  icon              String?
  parentId          Int?
  parent            Menu?     @relation("ParentMenu", fields: [parentId], references: [id])
  children          Menu[]    @relation("ParentMenu")
  orderNum          Int       @default(0)
  visible           Boolean   @default(true)
  createdAt         DateTime  @default(now())

  menuPermissions   MenuPermission[]

  @@map("menus")
}

// 菜单-权限关联
model MenuPermission {
  id                Int       @id @default(autoincrement())
  menuId            Int
  permissionId      Int
  menu              Menu      @relation(fields: [menuId], references: [id], onDelete: Cascade)
  permission        Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
  createdAt         DateTime  @default(now())

  @@unique([menuId, permissionId])
  @@map("menu_permissions")
}

// 登录日志
model LoginLog {
  id                Int       @id @default(autoincrement())
  userId            Int
  user              User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  loginTime         DateTime  @default(now())
  loginIp           String?
  loginDevice       String?
  loginStatus       LoginStatus
  remark            String?
  createdAt         DateTime  @default(now())

  @@map("login_logs")
}

enum LoginStatus {
  SUCCESS
  FAILED
}

// 操作日志
model OperationLog {
  id                Int       @id @default(autoincrement())
  userId            Int
  user              User      @relation(fields: [userId], references: [id], onDelete: SetNull)
  moduleName        String
  operation         Operation
  resourceType      String
  resourceId        Int?
  description       String?
  operationTime     DateTime  @default(now())
  operationIp       String?
  userAgent         String?
  createdAt         DateTime  @default(now())

  changeLogs        ChangeLog[]

  @@map("operation_logs")
}

enum Operation {
  CREATE
  UPDATE
  DELETE
  READ
  EXPORT
}

// 变更日志
model ChangeLog {
  id                Int       @id @default(autoincrement())
  operationLogId    Int
  operationLog      OperationLog @relation(fields: [operationLogId], references: [id], onDelete: Cascade)
  fieldName         String
  oldValue          String?
  newValue          String?
  changeType        ChangeType
  createdAt         DateTime  @default(now())

  @@map("change_logs")
}

enum ChangeType {
  ADDED
  MODIFIED
  DELETED
}

// 文件表
model File {
  id                Int       @id @default(autoincrement())
  originalName      String
  storageName       String    @unique
  filePath          String
  fileSize          BigInt
  fileType          String
  uploadBy          Int
  user              User      @relation(fields: [uploadBy], references: [id], onDelete: SetNull)
  uploadTime        DateTime  @default(now())
  relatedModule     String?
  relatedId         Int?
  status            FileStatus @default(ACTIVE)
  deletedAt         DateTime?
  createdAt         DateTime  @default(now())

  @@map("files")
}

enum FileStatus {
  ACTIVE
  DELETED
}

2. 权限守卫 (common/guards/permission.guard.ts)

import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class PermissionGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private jwtService: JwtService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requiredPermissions = this.reflector.get<string[]>(
      "permissions",
      context.getHandler(),
    );

    if (!requiredPermissions) {
      return true; // 如果没有指定权限要求,允许访问
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user) {
      throw new ForbiddenException("用户未认证");
    }

    const hasPermission = requiredPermissions.some((permission) =>
      user.permissions.includes(permission),
    );

    if (!hasPermission) {
      throw new ForbiddenException("您没有权限访问此资源");
    }

    return true;
  }
}

3. 审计日志拦截器 (modules/audit/interceptors/audit-log.interceptor.ts)

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
import { AuditService } from "../audit.service";
import { Reflector } from "@nestjs/core";

@Injectable()
export class AuditLogInterceptor implements NestInterceptor {
  constructor(
    private auditService: AuditService,
    private reflector: Reflector,
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const { method, path, body, query } = request;

    // 获取装饰器中定义的审计信息
    const auditInfo = this.reflector.get<any>("audit", context.getHandler());

    if (!auditInfo) {
      return next.handle();
    }

    const startTime = Date.now();

    return next.handle().pipe(
      tap((response) => {
        const duration = Date.now() - startTime;

        this.auditService.log({
          userId: user?.id,
          moduleName: auditInfo.module,
          operation: auditInfo.operation,
          resourceType: auditInfo.resource,
          resourceId: body?.id || query?.id,
          description: auditInfo.description,
          operationIp: this.getClientIp(request),
          userAgent: request.headers["user-agent"],
        });
      }),
    );
  }

  private getClientIp(request: any): string {
    return (
      request.headers["x-forwarded-for"]?.split(",")[0] ||
      request.connection.remoteAddress
    );
  }
}

4. 用户服务 (modules/users/users.service.ts)

import { Injectable } from "@nestjs/common";
import { PrismaService } from "@nestjs/prisma";
import { CreateUserDto } from "./dtos/create-user.dto";
import { UpdateUserDto } from "./dtos/update-user.dto";
import * as bcrypt from "bcrypt";

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  async create(dto: CreateUserDto) {
    const passwordHash = await bcrypt.hash(dto.password, 10);

    return this.prisma.user.create({
      data: {
        username: dto.username,
        email: dto.email,
        passwordHash,
        realName: dto.realName,
      },
      include: {
        userRoles: {
          include: {
            role: {
              include: {
                rolePermissions: {
                  include: {
                    permission: true,
                  },
                },
              },
            },
          },
        },
      },
    });
  }

  async findAll(
    page: number = 1,
    pageSize: number = 10,
    filters?: Record<string, any>,
  ) {
    const skip = (page - 1) * pageSize;

    const where = this.buildWhereClause(filters);

    const [items, total] = await Promise.all([
      this.prisma.user.findMany({
        where,
        skip,
        take: pageSize,
        include: {
          userRoles: {
            include: {
              role: true,
            },
          },
        },
      }),
      this.prisma.user.count({ where }),
    ]);

    return {
      items,
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  async findById(id: number) {
    return this.prisma.user.findUniqueOrThrow({
      where: { id },
      include: {
        userRoles: {
          include: {
            role: {
              include: {
                rolePermissions: {
                  include: {
                    permission: true,
                  },
                },
              },
            },
          },
        },
      },
    });
  }

  async update(id: number, dto: UpdateUserDto) {
    return this.prisma.user.update({
      where: { id },
      data: dto,
      include: {
        userRoles: {
          include: {
            role: true,
          },
        },
      },
    });
  }

  async delete(id: number) {
    return this.prisma.user.delete({
      where: { id },
    });
  }

  private buildWhereClause(filters?: Record<string, any>) {
    if (!filters) return {};

    const where: any = {};

    if (filters.username) {
      where.username = { contains: filters.username };
    }

    if (filters.email) {
      where.email = { contains: filters.email };
    }

    if (filters.status) {
      where.status = filters.status;
    }

    return where;
  }
}

六、认证和授权流程

登录流程

┌─────────────┐
│  用户输入   │
│ 用户名密码  │
└──────┬──────┘
       │ POST /api/auth/login

┌──────────────────────────────┐
│  验证用户名和密码           │
│  检查账户状态               │
└──────┬───────────────────────┘

       ├─ 验证失败 → 返回 401

       │ 验证成功

┌──────────────────────────────┐
│  查询用户角色和权限         │
│  生成 JWT Token             │
└──────┬───────────────────────┘


┌──────────────────────────────┐
│  返回 Token 和用户信息      │
│  前端存储 Token             │
└──────────────────────────────┘

实现代码

// auth.service.ts
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { PrismaService } from "@nestjs/prisma";
import * as bcrypt from "bcrypt";

@Injectable()
export class AuthService {
  constructor(
    private prisma: PrismaService,
    private jwtService: JwtService,
  ) {}

  async login(username: string, password: string) {
    // 查询用户
    const user = await this.prisma.user.findUniqueOrThrow({
      where: { username },
      include: {
        userRoles: {
          include: {
            role: {
              include: {
                rolePermissions: {
                  include: {
                    permission: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    // 验证密码
    const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
    if (!isPasswordValid) {
      throw new UnauthorizedException("用户名或密码错误");
    }

    // 检查账户状态
    if (user.status !== "ACTIVE") {
      throw new UnauthorizedException("账户已被禁用");
    }

    // 提取权限
    const permissions = user.userRoles.flatMap((ur) =>
      ur.role.rolePermissions.map((rp) => rp.permission.name),
    );

    // 生成 Token
    const token = this.jwtService.sign({
      id: user.id,
      username: user.username,
      permissions,
    });

    // 记录登录日志
    await this.prisma.loginLog.create({
      data: {
        userId: user.id,
        loginStatus: "SUCCESS",
        loginIp: "192.168.1.1", // 从请求中获取
      },
    });

    return {
      token,
      user: {
        id: user.id,
        username: user.username,
        email: user.email,
        realName: user.realName,
        avatarUrl: user.avatarUrl,
        permissions,
      },
    };
  }
}

权限检查流程

用户请求 API

JWT 守卫验证 Token
    ↓ Token 有效
提取用户信息和权限

权限守卫检查

├─ 有权限 → 允许执行
└─ 无权限 → 返回 403

七、Docker 部署

Docker Compose 配置

docker-compose.yml

version: "3.8"

services:
  # PostgreSQL 数据库
  postgres:
    image: postgres:15-alpine
    container_name: admin-postgres
    environment:
      POSTGRES_DB: admin_db
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin123
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - admin-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: admin-redis
    ports:
      - "6379:6379"
    networks:
      - admin-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # 后端服务
  backend:
    build:
      context: ./nestjs-admin
      dockerfile: Dockerfile
    container_name: admin-backend
    environment:
      NODE_ENV: development
      DATABASE_URL: postgresql://admin:admin123@postgres:5432/admin_db
      REDIS_URL: redis://redis:6379
      JWT_SECRET: your-secret-key
      JWT_EXPIRATION: 7d
    ports:
      - "3001:3000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - admin-network
    volumes:
      - ./nestjs-admin:/app
      - /app/node_modules
    command: npm run start:dev

  # 前端服务
  frontend:
    build:
      context: ./nextjs-admin
      dockerfile: Dockerfile
    container_name: admin-frontend
    environment:
      NEXT_PUBLIC_API_URL: http://localhost:3001/api
    ports:
      - "3000:3000"
    depends_on:
      - backend
    networks:
      - admin-network
    volumes:
      - ./nextjs-admin:/app
      - /app/node_modules

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: admin-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - frontend
      - backend
    networks:
      - admin-network

volumes:
  postgres_data:

networks:
  admin-network:
    driver: bridge

Dockerfile (Nest.js 后端)

# 构建阶段
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY --from=builder /app/dist ./dist

EXPOSE 3000

CMD ["node", "dist/main.js"]

Dockerfile (Next.js 前端)

# 构建阶段
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine

WORKDIR /app

RUN npm install -g next

COPY package*.json ./
RUN npm ci --only=production

COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public

EXPOSE 3000

CMD ["npm", "start"]

启动命令

docker-compose up -d

八、功能实现清单

快速参考表

功能模块核心功能优先级预计时间
用户认证登录、登出、密码修改P02 天
用户管理增删改查、批量操作P03 天
角色权限RBAC、权限检查P04 天
菜单管理动态菜单、权限控制P12 天
部门管理部门树、成员管理P12 天
操作日志记录、查询、导出P13 天
数据字典字典管理、缓存P22 天
系统配置参数管理、热更新P21 天
文件管理上传、下载、删除P23 天
仪表板数据统计、图表展示P32 天

九、开发最佳实践

1. API 设计规范

RESTful 规范

GET    /api/users              # 列表
POST   /api/users              # 创建
GET    /api/users/:id          # 详情
PATCH  /api/users/:id          # 更新
DELETE /api/users/:id          # 删除

# 批量操作
POST   /api/users/batch-delete # 批量删除

响应格式

// 成功响应
{
  code: 0,
  message: "操作成功",
  data: {
    id: 1,
    username: "admin",
    ...
  }
}

// 错误响应
{
  code: -1,
  message: "用户不存在",
  data: null
}

2. 错误处理

// 统一异常过滤器
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ExecutionContext) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    let message = "Internal Server Error";

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      message = exception.getResponse()["message"];
    } else if (exception instanceof PrismaClientKnownRequestError) {
      // Prisma 错误处理
      status = HttpStatus.BAD_REQUEST;
      message = "数据库操作失败";
    }

    response.status(status).json({
      code: -1,
      message,
      data: null,
    });
  }
}

3. 性能优化

数据库查询优化

// ❌ N+1 查询问题
const users = await User.find();
for (const user of users) {
  const roles = await Role.find({ userId: user.id }); // 查询 N 次
}

// ✅ 使用 include 关联查询
const users = await prisma.user.findMany({
  include: {
    userRoles: {
      include: {
        role: true,
      },
    },
  },
});

缓存策略

@Injectable()
export class CacheService {
  constructor(private redis: RedisService) {}

  async getWithCache(key: string, fetchFn: () => Promise<any>) {
    // 先从缓存取
    const cached = await this.redis.get(key);
    if (cached) {
      return JSON.parse(cached);
    }

    // 缓存中没有,从数据库取
    const data = await fetchFn();

    // 存入缓存,30 分钟过期
    await this.redis.set(key, JSON.stringify(data), 30 * 60);

    return data;
  }
}

4. 安全最佳实践

密码安全

// 使用 bcrypt 加密
const passwordHash = await bcrypt.hash(password, 10);

// 验证
const isValid = await bcrypt.compare(password, passwordHash);

SQL 注入防护

// ❌ 危险
const users = await prisma.$queryRaw`
  SELECT * FROM users WHERE username = ${username}
`;

// ✅ 使用参数化查询
const users = await prisma.user.findMany({
  where: { username },
});

CORS 配置

app.enableCors({
  origin: process.env.CLIENT_URL,
  credentials: true,
  methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
});

十、快速开始指南

项目初始化

1. 克隆或创建项目

# 后端
nest new nestjs-admin
cd nestjs-admin

# 前端
npx create-next-app@latest nextjs-admin --typescript --tailwind
cd nextjs-admin

2. 安装依赖

后端:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install @prisma/client prisma
npm install bcrypt
npm install redis @nestjs/cache-manager cache-manager
npm install class-validator class-transformer

前端:

npm install axios zustand
npm install antd @ant-design/icons
npm install shadcn-ui
npm install recharts nivo
npm install daisyui

3. 初始化数据库

# Prisma 初始化
npx prisma init

# 配置 DATABASE_URL 在 .env

# 生成 Prisma 客户端
npx prisma generate

# 执行迁移
npx prisma migrate dev --name init

4. 启动开发服务

# 后端
npm run start:dev

# 前端
npm run dev

十一、总结和建议

关键要点

  1. 权限系统是基础 - RBAC 模型简单高效,优于 ABAC
  2. 审计日志很重要 - 使用拦截器自动记录,避免遗漏
  3. 分层架构易维护 - Controller → Service → Repository 清晰职责
  4. 缓存提升性能 - 字典、配置等频繁读取的数据应缓存
  5. 文档化很关键 - API 文档、数据字典、权限列表要详细

常见陷阱

权限检查不完整 - 只在前端检查,后端也要检查
日志记录不及时 - 应该用拦截器自动记录,不要手动
密码存储不安全 - 必须用 bcrypt 等加密算法
没有事务管理 - 多步操作要用事务保证一致性
缺少数据验证 - 前后端都要验证输入数据

进阶优化

📈 大数据量优化

  • 使用分页,避免一次加载所有数据
  • 使用索引提升查询速度
  • 考虑分库分表

🔒 安全增强

  • 实现二次认证(2FA)
  • 使用 OAuth2.0 社交登录
  • 实现操作码验证(高风险操作)

性能增强

  • 使用 CDN 加速静态资源
  • 实现消息队列处理异步任务
  • 使用搜索引擎(Elasticsearch)优化查询

📊 数据分析

  • 接入数据埋点系统
  • 使用 BI 工具做数据分析
  • 建立报表系统

附录:项目清单

部署清单

  • 服务器选型和购买
  • 域名注册和备案
  • SSL 证书配置
  • 数据库备份策略
  • 日志收集和分析
  • 监控告警系统
  • 性能优化和测试
  • 灾难恢复计划

文档清单

  • API 文档(Swagger/OpenAPI)
  • 数据库设计文档
  • 部署运维文档
  • 用户手册
  • 权限配置指南

测试清单

  • 单元测试
  • 集成测试
  • 性能测试
  • 安全测试
  • 用户验收测试

这份文档提供了从零开始搭建企业级管理后台的完整指南。根据项目需求,可以逐步实现各个功能模块,最终形成一个高效、安全、易维护的系统。