企业级管理后台
作者:海川,发表于: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
八、功能实现清单
快速参考表
| 功能模块 | 核心功能 | 优先级 | 预计时间 |
|---|---|---|---|
| 用户认证 | 登录、登出、密码修改 | P0 | 2 天 |
| 用户管理 | 增删改查、批量操作 | P0 | 3 天 |
| 角色权限 | RBAC、权限检查 | P0 | 4 天 |
| 菜单管理 | 动态菜单、权限控制 | P1 | 2 天 |
| 部门管理 | 部门树、成员管理 | P1 | 2 天 |
| 操作日志 | 记录、查询、导出 | P1 | 3 天 |
| 数据字典 | 字典管理、缓存 | P2 | 2 天 |
| 系统配置 | 参数管理、热更新 | P2 | 1 天 |
| 文件管理 | 上传、下载、删除 | P2 | 3 天 |
| 仪表板 | 数据统计、图表展示 | P3 | 2 天 |
九、开发最佳实践
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
十一、总结和建议
关键要点
- 权限系统是基础 - RBAC 模型简单高效,优于 ABAC
- 审计日志很重要 - 使用拦截器自动记录,避免遗漏
- 分层架构易维护 - Controller → Service → Repository 清晰职责
- 缓存提升性能 - 字典、配置等频繁读取的数据应缓存
- 文档化很关键 - API 文档、数据字典、权限列表要详细
常见陷阱
❌ 权限检查不完整 - 只在前端检查,后端也要检查
❌ 日志记录不及时 - 应该用拦截器自动记录,不要手动
❌ 密码存储不安全 - 必须用 bcrypt 等加密算法
❌ 没有事务管理 - 多步操作要用事务保证一致性
❌ 缺少数据验证 - 前后端都要验证输入数据
进阶优化
📈 大数据量优化:
- 使用分页,避免一次加载所有数据
- 使用索引提升查询速度
- 考虑分库分表
🔒 安全增强:
- 实现二次认证(2FA)
- 使用 OAuth2.0 社交登录
- 实现操作码验证(高风险操作)
⚡ 性能增强:
- 使用 CDN 加速静态资源
- 实现消息队列处理异步任务
- 使用搜索引擎(Elasticsearch)优化查询
📊 数据分析:
- 接入数据埋点系统
- 使用 BI 工具做数据分析
- 建立报表系统
附录:项目清单
部署清单
- 服务器选型和购买
- 域名注册和备案
- SSL 证书配置
- 数据库备份策略
- 日志收集和分析
- 监控告警系统
- 性能优化和测试
- 灾难恢复计划
文档清单
- API 文档(Swagger/OpenAPI)
- 数据库设计文档
- 部署运维文档
- 用户手册
- 权限配置指南
测试清单
- 单元测试
- 集成测试
- 性能测试
- 安全测试
- 用户验收测试
这份文档提供了从零开始搭建企业级管理后台的完整指南。根据项目需求,可以逐步实现各个功能模块,最终形成一个高效、安全、易维护的系统。