「手把手教学」Monorepo项目搭建与管理——实战案例

Monorepo项目搭建与管理


文章目录

    • Monorepo项目搭建与管理
    • @[TOC]
      • 实战案例第一部分:Vue 组件库搭建
        • 1.1 创建 Vue 组件库子包
        • 1.2 安装 Vue 相关依赖
        • 1.3 创建组件示例
        • 1.4 配置 Vite 构建
      • 实战案例第二部分:创建 NestJS 后端服务
        • 2.1 创建 NestJS 子包
        • 2.2 安装 NestJS 核心依赖
        • 2.3 生成 NestJS 项目骨架
        • 2.4 基础服务代码示例
        • 2.5 配置跨域支持(为前端联调准备)
      • 实战案例第三部分:Prisma + MySQL 集成
        • 3.1 为 NestJS 服务安装 Prisma
        • 3.2 初始化 Prisma
        • 3.3 配置 MySQL 连接
        • 3.4 创建 Prisma 服务模块
        • 当前已实现功能
      • 实战案例第四部分:数据库迁移与 CRUD 开发
        • 4.1 执行数据库迁移
        • 4.2 创建用户模块
        • 4.3 实现 CRUD 接口
        • 4.4 测试 API 接口
        • 关键验证点
      • 实战案例第五部分:前端联调实现
        • 5.1 创建 Vue 前端应用
        • 5.2 配置前端应用依赖
        • 5.3 核心代码实现
        • 5.4 配置全局样式
        • 5.5 修改 Vite 配置
        • 联调验证步骤
        • 常见问题解决方案
      • 实战案例第六部分:完善前端功能
        • 6.1 集成 Pinia 状态管理
        • 6.2 实现用户列表展示
        • 6.3 优化用户创建流程
        • 当前功能验证
      • 实战案例第七部分:JWT 权限认证系统实现
        • 7.1 后端认证模块搭建
          • 7.1.1 安装核心依赖
          • 7.1.2 扩展用户模型
          • 7.1.3 创建认证模块
        • 7.2 核心认证逻辑实现
          • 7.2.1 JWT 配置
          • 7.2.2 密码加密服务
          • 7.2.3 JWT 策略配置
        • 7.3 认证接口开发
          • 7.3.1 登录接口
        • 7.4 前端认证集成
          • 7.4.1 登录组件实现
          • 7.4.2 请求拦截器配置
        • 7.5 安全增强配置
          • 7.5.1 受保护路由示例
          • 7.5.2 自动刷新令牌方案
        • 关键安全实践
        • 验证步骤
      • 实战案例第八部分:RBAC 权限控制系统实现
        • 8.1 数据库模型扩展
          • 8.1.1 修改 Prisma Schema
        • 8.2 核心权限模块搭建
          • 8.2.1 创建权限守卫
          • 8.2.2 创建权限装饰器
        • 8.3 权限管理接口开发
          • 8.3.1 创建角色管理模块
          • 8.3.2 实现角色管理服务
          • 8.3.3 权限管理接口示例
        • 8.4 前端权限管理界面(示例)
          • 8.4.1 权限管理组件
          • 8.4.2 动态路由配置
        • RBAC 系统验证流程
        • 性能优化建议
      • 实战案例第九部分:日志系统与错误监控
        • 9.1 后端日志系统集成
          • 9.1.1 安装日志依赖
          • 9.1.2 配置 Winston 日志
          • 9.1.3 集成到 NestJS
        • 9.2 前端错误监控
          • 9.2.1 集成 Sentry(示例)
        • 9.3 性能监控
          • 9.3.1 安装 Prometheus
          • 9.3.2 配置指标收集
      • 实战案例第十部分:容器化部署
        • 10.1 Docker 化后端服务
          • 10.1.1 创建 Dockerfile
          • 10.1.2 编写 docker-compose.yml

实战案例第一部分:Vue 组件库搭建

1.1 创建 Vue 组件库子包
mkdir -p packages/ui
cd packages/ui
pnpm init

关键配置 (packages/ui/package.json):

{
  "name": "@demo/ui",
  "version": "1.0.0",
  "main": "dist/index.umd.cjs",
  "module": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "dev": "vite build --watch",
    "build": "vite build"
  }
}
1.2 安装 Vue 相关依赖
# 在根目录执行
pnpm add vue @vitejs/plugin-vue -D --filter @demo/ui
pnpm add sass -D -w  # 公共开发依赖
1.3 创建组件示例
// packages/ui/src/Button/Button.vue
<script setup lang="ts">
defineProps<{ text: string }>()
</script>

<template>
  <button class="ui-button">{{ text }}</button>
</template>

<style scoped lang="scss">
.ui-button { background: #f0f; }
</style>
// packages/ui/src/index.ts
export { default as UButton } from './Button/Button.vue'
1.4 配置 Vite 构建
// packages/ui/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: 'src/index.ts',
      name: 'DemoUI',
      fileName: (format) => `index.${format}.js`
    },
    rollupOptions: {
      external: ['vue'],
      output: { globals: { vue: 'Vue' } }
    }
  }
})

实战案例第二部分:创建 NestJS 后端服务

2.1 创建 NestJS 子包
mkdir -p apps/server
cd apps/server
pnpm init
2.2 安装 NestJS 核心依赖
# 在根目录执行(避免全局安装 CLI)
pnpm add @nestjs/core @nestjs/common rxjs reflect-metadata --filter @demo/server
pnpm add @nestjs/cli @nestjs/platform-express typescript @types/node -D --filter @demo/server
2.3 生成 NestJS 项目骨架
# 在 apps/server 目录操作
npx @nestjs/cli new . --package-manager=pnpm --skip-install

# 手动修复依赖引用(因 Monorepo 特殊结构)
修改 apps/server/package.json:
"dependencies": {
  "@nestjs/common": "workspace:*",
  "@nestjs/core": "workspace:*"
}
2.4 基础服务代码示例
// apps/server/src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getData() {
    return { message: 'Hello from NestJS!' };
  }
}
2.5 配置跨域支持(为前端联调准备)
// apps/server/src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({ origin: 'http://localhost:5173' });  // Vue 应用默认端口
  await app.listen(3000);
}

实战案例第三部分:Prisma + MySQL 集成

3.1 为 NestJS 服务安装 Prisma
# 在根目录操作
pnpm add prisma @prisma/client --filter @demo/server
pnpm add -D prisma --filter @demo/server
3.2 初始化 Prisma
# 进入 server 目录初始化
cd apps/server
pnpm exec prisma init

# 生成的文件结构:
apps/server/
├── prisma/
│   ├── schema.prisma    # 数据模型定义
│   └── migrations/      # 迁移记录
└── .env                 # 数据库配置
3.3 配置 MySQL 连接
# .env 文件
DATABASE_URL="mysql://root:password@localhost:3306/monorepo_db"
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
  output   = "../node_modules/.prisma/client"  // 修复 Monorepo 路径问题
}

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

model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
}
3.4 创建 Prisma 服务模块
// apps/server/src/prisma/prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect()
  }
}
// apps/server/src/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common'
import { PrismaService } from './prisma.service'

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService]
})
export class PrismaModule {}

当前已实现功能
  1. 数据库连接配置
  2. Prisma 服务模块化
  3. 基础用户模型定义

下一步将演示:

  • 数据库迁移操作
  • CRUD 接口开发
  • 前端联调演示

实战案例第四部分:数据库迁移与 CRUD 开发

4.1 执行数据库迁移
# 在 server 目录下执行
pnpm exec prisma migrate dev --name init

# 验证生成的 SQL
cat prisma/migrations/*/migration.sql
4.2 创建用户模块
nest generate module users
nest generate controller users
nest generate service users
4.3 实现 CRUD 接口
// apps/server/src/users/users.service.ts
import { Injectable } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'

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

  async create(userData: { name: string; email: string }) {
    return this.prisma.user.create({ data: userData })
  }

  async findAll() {
    return this.prisma.user.findMany()
  }

  async findOne(id: number) {
    return this.prisma.user.findUnique({ where: { id } })
  }
}
// apps/server/src/users/users.controller.ts
import { Controller, Post, Body, Get, Param } from '@nestjs/common'
import { UsersService } from './users.service'

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() userData: { name: string; email: string }) {
    return this.usersService.create(userData)
  }

  @Get()
  findAll() {
    return this.usersService.findAll()
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id)
  }
}

4.4 测试 API 接口
# 启动服务端
pnpm --filter @demo/server start:dev

# 使用 curl 测试
curl -X POST -H "Content-Type: application/json" -d '{"name":"John","email":"[email protected]"}' http://localhost:3000/users
curl http://localhost:3000/users

关键验证点
  1. 检查数据库是否生成 User
  2. 确认 API 返回正确的 JSON 数据
  3. 观察 Prisma 客户端的查询日志

实战案例第五部分:前端联调实现

5.1 创建 Vue 前端应用
# 删除原 React 应用(如存在)
rm -rf apps/web-app

# 创建新 Vue 应用
pnpm create vite@latest web-app -- --template vue-ts
mv web-app apps/
5.2 配置前端应用依赖
# 安装必要依赖
pnpm add axios @demo/ui --filter @demo/web-app
pnpm add -D @types/node --filter @demo/web-app
5.3 核心代码实现
// apps/web-app/src/api/user.ts
import axios from 'axios'

const api = axios.create({
  baseURL: 'http://localhost:3000',
  timeout: 5000
})

export const userAPI = {
  createUser: (data: { name: string; email: string }) => api.post('/users', data),
  getUsers: () => api.get('/users')
}

<script setup lang="ts">
import { ref } from 'vue'
import { UButton } from '@demo/ui'
import { userAPI } from '../api/user'

const formData = ref({ name: '', email: '' })

const handleSubmit = async () => {
  try {
    await userAPI.createUser(formData.value)
    formData.value = { name: '', email: '' }
    alert('用户创建成功!')
  } catch (error) {
    console.error('创建用户失败:', error)
  }
}
script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="formData.name" placeholder="姓名">
    <input v-model="formData.email" placeholder="邮箱">
    <UButton type="submit" text="创建用户" />
  form>
template>
5.4 配置全局样式
// apps/web-app/src/styles/main.scss
@import '@demo/ui/styles'; // 引入组件库样式

body {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}
5.5 修改 Vite 配置
// apps/web-app/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src',
      '@demo/ui': '../../packages/ui/src'
    }
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/main.scss";`
      }
    }
  }
})

联调验证步骤
  1. 同时启动前后端服务
pnpm --filter @demo/server start:dev  # 后端
pnpm --filter @demo/web-app dev      # 前端
  1. 功能验证
  • 访问 http://localhost:5173
  • 提交用户表单后检查:
    • 数据库记录是否生成
    • 网络请求是否成功(200 状态码)
    • 页面交互反馈是否正常

常见问题解决方案

Q1:跨域请求失败

  • 检查 NestJS 的 enableCors 配置
  • 确认前端端口是否在允许列表

Q2:组件库样式未生效

  • 检查 Vite 的 additionalData 配置
  • 确认组件库的样式文件导出路径

Q3:Prisma 客户端未初始化

  • 确认数据库服务是否运行
  • 检查 .env 文件数据库连接字符串

实战案例第六部分:完善前端功能

6.1 集成 Pinia 状态管理
# 安装 Pinia
pnpm add pinia @vueuse/core --filter @demo/web-app
// apps/web-app/src/stores/user.ts
import { defineStore } from 'pinia'
import { userAPI } from '../api/user'
import { ref } from 'vue'

export const useUserStore = defineStore('users', () => {
  const users = ref<any[]>([])
  const loading = ref(false)
  const error = ref<string | null>(null)

  const fetchUsers = async () => {
    try {
      loading.value = true
      const response = await userAPI.getUsers()
      users.value = response.data
    } catch (err) {
      error.value = '获取用户列表失败'
    } finally {
      loading.value = false
    }
  }

  return { users, loading, error, fetchUsers }
})
6.2 实现用户列表展示

<script setup lang="ts">
import { useUserStore } from '../stores/user'
import { onMounted } from 'vue'

const store = useUserStore()

onMounted(() => {
  store.fetchUsers()
})
script>

<template>
  <div v-if="store.loading">加载中...div>
  <div v-else-if="store.error" class="error">{{ store.error }}div>
  <ul v-else>
    <li v-for="user in store.users" :key="user.id">
      {{ user.name }} - {{ user.email }}
    li>
  ul>
template>

<style scoped>
.error { color: red; }
style>
6.3 优化用户创建流程
// 修改 UserForm.vue 中的 handleSubmit 方法
const handleSubmit = async () => {
  try {
    await userAPI.createUser(formData.value)
    store.fetchUsers()  // 创建成功后刷新列表
    formData.value = { name: '', email: '' }
  } catch (error) {
    store.error = '用户创建失败'
  }
}

当前功能验证
  1. 用户创建后列表自动刷新
  2. 统一的加载状态管理
  3. 错误信息的可视化展示

实战案例第七部分:JWT 权限认证系统实现

7.1 后端认证模块搭建
7.1.1 安装核心依赖
# 在根目录执行
pnpm add @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt @types/passport-jwt --filter @demo/server
7.1.2 扩展用户模型
// apps/server/prisma/schema.prisma
model User {
  id        Int     @id @default(autoincrement())
  name      String
  email     String  @unique
  password  String  // 新增密码字段
}

执行迁移:

pnpm exec prisma migrate dev --name add_password_field
7.1.3 创建认证模块
nest generate module auth
nest generate service auth
nest generate controller auth
7.2 核心认证逻辑实现
7.2.1 JWT 配置
// apps/server/src/auth/constants.ts
export const jwtConstants = {
  secret: 'YOUR_SECRET_KEY',  // 生产环境应使用环境变量
  expiresIn: '60s'            // 访问令牌有效期
};
7.2.2 密码加密服务
// apps/server/src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';

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

  async validateUser(email: string, pass: string): Promise<any> {
    const user = await this.prisma.user.findUnique({ where: { email } });
    if (user && await bcrypt.compare(pass, user.password)) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async register(userData: { name: string; email: string; password: string }) {
    const hashedPassword = await bcrypt.hash(userData.password, 10);
    return this.prisma.user.create({
      data: { ...userData, password: hashedPassword }
    });
  }
}
7.2.3 JWT 策略配置
// apps/server/src/auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, email: payload.email };
  }
}
7.3 认证接口开发
7.3.1 登录接口
// apps/server/src/auth/auth.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { JwtService } from '@nestjs/jwt';

@Controller('auth')
export class AuthController {
  constructor(
    private authService: AuthService,
    private jwtService: JwtService
  ) {}

  @Post('register')
  async register(@Body() userData: { name: string; email: string; password: string }) {
    return this.authService.register(userData);
  }

  @UseGuards(AuthGuard('local'))
  @Post('login')
  async login(@Body() user: { email: string; password: string }) {
    const payload = { email: user.email, sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}
7.4 前端认证集成
7.4.1 登录组件实现

<script setup lang="ts">
import { ref } from 'vue';
import axios from 'axios';
import { useRouter } from 'vue-router';

const formData = ref({ email: '', password: '' });
const error = ref('');
const router = useRouter();

const handleLogin = async () => {
  try {
    const response = await axios.post('http://localhost:3000/auth/login', formData.value);
    localStorage.setItem('access_token', response.data.access_token);
    router.push('/dashboard');
  } catch (err) {
    error.value = '登录失败,请检查凭证';
  }
};
script>

<template>
  <div class="login-form">
    <input v-model="formData.email" type="email" placeholder="邮箱">
    <input v-model="formData.password" type="password" placeholder="密码">
    <button @click="handleLogin">登录button>
    <p v-if="error" class="error-message">{{ error }}p>
  div>
template>
7.4.2 请求拦截器配置
// apps/web-app/src/api/user.ts
api.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});
7.5 安全增强配置
7.5.1 受保护路由示例
// apps/server/src/users/users.controller.ts
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('users')
@UseGuards(JwtAuthGuard)
export class UsersController {
  // 原有方法保持不变
}
7.5.2 自动刷新令牌方案
// apps/web-app/src/api/user.ts
api.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      // 实现刷新令牌逻辑
      const newToken = await refreshToken();
      localStorage.setItem('access_token', newToken);
      return api(originalRequest);
    }
    return Promise.reject(error);
  }
);

关键安全实践
  1. 密码存储:使用 bcrypt 哈希算法存储密码
  2. 令牌传输:通过 Authorization Header 传输 JWT
  3. 令牌时效:设置短期有效的访问令牌(建议 15-60 分钟)
  4. HTTPS 强制:生产环境必须启用 HTTPS
  5. 敏感信息保护:JWT Secret 应通过环境变量注入

验证步骤
  1. 注册新用户
curl -X POST -H "Content-Type: application/json" -d '{
  "name": "Admin",
  "email": "[email protected]",
  "password": "P@ssw0rd"
}' http://localhost:3000/auth/register
  1. 登录获取令牌
curl -X POST -H "Content-Type: application/json" -d '{
  "email": "[email protected]",
  "password": "P@ssw0rd"
}' http://localhost:3000/auth/login
  1. 访问受保护接口
curl -H "Authorization: Bearer " http://localhost:3000/users

实战案例第八部分:RBAC 权限控制系统实现

8.1 数据库模型扩展
8.1.1 修改 Prisma Schema
// apps/server/prisma/schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  password  String
  roles     UserRole[]
}

model Role {
  id          Int       @id @default(autoincrement())
  name        String    @unique
  description String?
  users       UserRole[]
  permissions RolePermission[]
}

model Permission {
  id          Int       @id @default(autoincrement())
  action      String    @unique  // 示例:create_user
  description String?
  roles       RolePermission[]
}

// 中间表
model UserRole {
  userId Int  @map("user_id")
  roleId Int  @map("role_id")
  user   User @relation(fields: [userId], references: [id])
  role   Role @relation(fields: [roleId], references: [id])

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

model RolePermission {
  roleId       Int  @map("role_id")
  permissionId Int  @map("permission_id")
  role         Role @relation(fields: [roleId], references: [id])
  permission   Permission @relation(fields: [permissionId], references: [id])

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

执行迁移:

pnpm exec prisma migrate dev --name add_rbac_models
8.2 核心权限模块搭建
8.2.1 创建权限守卫
// apps/server/src/auth/permissions.guard.ts
import {
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';

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

  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 token = request.headers.authorization?.split(' ')[1];
    const payload = this.jwtService.verify(token);
    
    const userWithPermissions = await this.prisma.user.findUnique({
      where: { id: payload.sub },
      include: {
        roles: {
          include: {
            role: {
              include: {
                permissions: {
                  include: { permission: true }
                }
              }
            }
          }
        }
      }
    });

    const userPermissions = userWithPermissions.roles
      .flatMap(r => r.role.permissions)
      .map(p => p.permission.action);

    const hasPermission = requiredPermissions.every(p => 
      userPermissions.includes(p)
    );

    if (!hasPermission) throw new ForbiddenException('权限不足');
    return true;
  }
}
8.2.2 创建权限装饰器
// apps/server/src/auth/permissions.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const RequirePermissions = (...permissions: string[]) =>
  SetMetadata('permissions', permissions);
8.3 权限管理接口开发
8.3.1 创建角色管理模块
nest generate module roles
nest generate controller roles
nest generate service roles
8.3.2 实现角色管理服务
// apps/server/src/roles/roles.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

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

  async createRole(name: string, description?: string) {
    return this.prisma.role.create({
      data: { name, description }
    });
  }

  async assignRoleToUser(userId: number, roleId: number) {
    return this.prisma.userRole.create({
      data: { userId, roleId }
    });
  }

  async addPermissionToRole(roleId: number, permissionId: number) {
    return this.prisma.rolePermission.create({
      data: { roleId, permissionId }
    });
  }
}
8.3.3 权限管理接口示例
// apps/server/src/roles/roles.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { RolesService } from './roles.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RequirePermissions } from '../auth/permissions.decorator';

@Controller('roles')
@UseGuards(JwtAuthGuard)
export class RolesController {
  constructor(private readonly rolesService: RolesService) {}

  @Post()
  @RequirePermissions('manage_roles')
  createRole(@Body() { name, description }: { name: string; description?: string }) {
    return this.rolesService.createRole(name, description);
  }

  @Post('assign')
  @RequirePermissions('manage_roles')
  assignRole(@Body() { userId, roleId }: { userId: number; roleId: number }) {
    return this.rolesService.assignRoleToUser(userId, roleId);
  }

  @Post('permissions')
  @RequirePermissions('manage_permissions')
  addPermission(@Body() { roleId, permissionId }: { roleId: number; permissionId: number }) {
    return this.rolesService.addPermissionToRole(roleId, permissionId);
  }
}
8.4 前端权限管理界面(示例)
8.4.1 权限管理组件

<script setup lang="ts">
import { ref } from 'vue';
import axios from 'axios';

const newRole = ref({ name: '', description: '' });
const roleAssignment = ref({ userId: '', roleId: '' });

const createRole = async () => {
  try {
    await axios.post('/roles', newRole.value);
    alert('角色创建成功');
  } catch (error) {
    console.error('创建角色失败:', error);
  }
};
script>

<template>
  <div class="role-management">
    <h3>创建新角色h3>
    <input v-model="newRole.name" placeholder="角色名称">
    <input v-model="newRole.description" placeholder="描述">
    <button @click="createRole">创建角色button>
  div>
template>
8.4.2 动态路由配置
// apps/web-app/src/router.ts
import { createRouter, createWebHistory } from 'vue-router';
import { useUserStore } from './stores/user';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('./components/UserList.vue') },
    { 
      path: '/admin/roles',
      component: () => import('./components/Admin/RoleManager.vue'),
      meta: { requiresAuth: true, requiredPermissions: ['manage_roles'] }
    }
  ]
});

router.beforeEach((to, from, next) => {
  const store = useUserStore();
  if (to.meta.requiresAuth && !store.isAuthenticated) {
    next('/login');
  } else if (to.meta.requiredPermissions) {
    const hasPermission = to.meta.requiredPermissions.every(p => 
      store.userPermissions.includes(p)
    );
    hasPermission ? next() : next('/forbidden');
  } else {
    next();
  }
});

RBAC 系统验证流程
  1. 创建管理员角色
curl -X POST -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{
  "name": "admin",
  "description": "系统管理员"
}' http://localhost:3000/roles
  1. 分配权限给角色
curl -X POST -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{
  "roleId": 1,
  "permissionId": 1
}' http://localhost:3000/roles/permissions
  1. 验证权限控制
# 使用普通用户令牌尝试访问管理接口
curl -X POST -H "Authorization: Bearer " http://localhost:3000/roles
# 应返回 403 Forbidden

性能优化建议
  1. 权限缓存:将用户权限缓存在Redis中,设置合理过期时间
  2. 批量查询优化:使用 Prisma 的 $queryRaw 进行复杂权限查询
  3. 预加载策略:在用户登录时预加载权限数据
  4. 权限树设计:实现层级权限结构(如 user:createuser:delete

实战案例第九部分:日志系统与错误监控

9.1 后端日志系统集成
9.1.1 安装日志依赖
pnpm add winston winston-daily-rotate-file morgan nest-winston --filter @demo/server
9.1.2 配置 Winston 日志
// apps/server/src/logger/winston.config.ts
import { WinstonModuleOptions } from 'nest-winston';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';

const transport = new DailyRotateFile({
  filename: 'logs/application-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  zippedArchive: true,
  maxSize: '20m',
  maxFiles: '14d',
});

export const winstonConfig: WinstonModuleOptions = {
  levels: {
    critical: 0,
    error: 1,
    warn: 2,
    info: 3,
    debug: 4,
  },
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      ),
    }),
    transport
  ],
};
9.1.3 集成到 NestJS
// apps/server/src/main.ts
import { WinstonModule } from 'nest-winston';
import { winstonConfig } from './logger/winston.config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger(winstonConfig)
  });
  // ...其他配置
}
9.2 前端错误监控
9.2.1 集成 Sentry(示例)
pnpm add @sentry/vue @sentry/tracing --filter @demo/web-app
// apps/web-app/src/main.ts
import * as Sentry from '@sentry/vue';

const app = createApp(App);

Sentry.init({
  app,
  dsn: 'YOUR_DSN_HERE',
  integrations: [
    new Sentry.BrowserTracing({
      routingInstrumentation: Sentry.vueRouterInstrumentation(router),
    }),
  ],
  tracesSampleRate: 1.0,
});
9.3 性能监控
9.3.1 安装 Prometheus
pnpm add @nestjs/metrics prom-client --filter @demo/server
9.3.2 配置指标收集
// apps/server/src/metrics/metrics.module.ts
import { Module } from '@nestjs/common';
import { PrometheusModule } from '@nestjs/metrics';

@Module({
  imports: [
    PrometheusModule.register({
      defaultLabels: ['app'],
      path: '/metrics',
    })
  ],
  exports: [PrometheusModule]
})
export class MetricsModule {}

实战案例第十部分:容器化部署

10.1 Docker 化后端服务
10.1.1 创建 Dockerfile
# apps/server/Dockerfile
FROM node:18-alpine

WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm i -g pnpm && pnpm i --frozen-lockfile

COPY . .
RUN pnpm build

ENV NODE_ENV production
EXPOSE 3000
CMD ["node", "dist/main.js"]
10.1.2 编写 docker-compose.yml
version: '3.8'

services:
  server:
    build: ./apps/server
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=mysql://root:password@db:3306/monorepo_db
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: monorepo_db
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

相关文章:「手把手教学」拆解 Monorepo 搭建与管理全流程

你可能感兴趣的:(Monorepo,实战,前端)