乐闻世界logo
搜索文章和话题

NestJS

NestJS 是一种基于 TypeScript 的后端框架,它结合了 Express 和 Angular 的优点,提供了一种现代化、模块化和可扩展的开发方式。NestJS 的主要目标是提供一个高效、可维护和可测试的服务端应用程序框架,同时提供了许多有用的功能和工具,如依赖注入、模块化体系结构、中间件、管道、拦截器、异常过滤器等。 NestJS 的主要特点包括: 基于 TypeScript:NestJS 是一种基于 TypeScript 的框架,支持静态类型检查和强类型编程,提高了代码的可维护性和可读性。 模块化体系结构:NestJS 使用模块化体系结构,将应用程序拆分为多个模块,每个模块可以独立开发、测试和部署,提高了代码的可扩展性和可维护性。 依赖注入:NestJS 支持依赖注入,通过注入依赖项来实现松耦合的架构设计,提高了代码的可测试性和可维护性。 中间件和管道:NestJS 提供了中间件和管道机制,可以在请求和响应之间添加额外的逻辑,如身份验证、日志记录、异常处理等,提高了应用程序的可靠性和安全性。 异常过滤器:NestJS 提供了异常过滤器,可以捕获应用程序中的异常并进行处理,提高了应用程序的稳定性和可靠性。 NestJS 可以用于构建各种类型的后端服务,如 RESTful API、WebSocket 服务、微服务等。NestJS 社区提供了许多有用的扩展和插件,如 Swagger UI、TypeORM、GraphQL 等,可以帮助开发人员更加高效地构建和管理后端服务。 如果您想成为一名后端开发人员,NestJS 是一个非常有用的框架,需要掌握 TypeScript 的基础知识和 NestJS 的开发方式,了解常用的模块和工具,如路由、控制器、服务、中间件、管道、拦截器等。掌握 NestJS 可以帮助您更加高效和灵活地构建和管理后端服务,为自己的职业发展和个人成长打下坚实的基础。
NestJS
查看更多相关内容
NestJS 部署和 DevOps 如何实现?# NestJS 部署和 DevOps 详解 ## 部署概述 部署是将应用程序从开发环境转移到生产环境的过程。NestJS 应用程序可以通过多种方式部署,包括传统服务器、容器化部署、云服务等。 ## 1. Docker 容器化 ### 创建 Dockerfile ```dockerfile # 构建阶段 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 生产阶段 FROM node:18-alpine AS runner WORKDIR /app ENV NODE_ENV production COPY package*.json ./ RUN npm ci --only=production COPY --from=builder /app/dist ./dist EXPOSE 3000 CMD ["node", "dist/main.js"] ``` ### 创建 .dockerignore ``` node_modules dist .git .env *.log ``` ### 构建 Docker 镜像 ```bash docker build -t nestjs-app . ``` ### 运行 Docker 容器 ```bash docker run -p 3000:3000 nestjs-app ``` ## 2. Docker Compose ### docker-compose.yml ```yaml version: '3.8' services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_HOST=db - DATABASE_PORT=3306 - DATABASE_USER=root - DATABASE_PASSWORD=password - DATABASE_NAME=nestjs depends_on: - db restart: unless-stopped db: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_DATABASE=nestjs ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql restart: unless-stopped volumes: mysql_data: ``` ### 启动服务 ```bash docker-compose up -d ``` ## 3. Kubernetes 部署 ### Deployment 配置 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nestjs-app spec: replicas: 3 selector: matchLabels: app: nestjs-app template: metadata: labels: app: nestjs-app spec: containers: - name: nestjs-app image: nestjs-app:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: "production" - name: DATABASE_HOST valueFrom: secretKeyRef: name: db-secret key: host resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 5 ``` ### Service 配置 ```yaml apiVersion: v1 kind: Service metadata: name: nestjs-app-service spec: selector: app: nestjs-app ports: - protocol: TCP port: 80 targetPort: 3000 type: LoadBalancer ``` ### Ingress 配置 ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nestjs-app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: nestjs-app-service port: number: 80 ``` ## 4. CI/CD 管道 ### GitHub Actions 配置 ```yaml name: CI/CD Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm run test - name: Run lint run: npm run lint - name: Build run: npm run build build-and-push: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: username/nestjs-app:latest,username/nestjs-app:${{ github.sha }} cache-from: type=registry,ref=username/nestjs-app:latest cache-to: type=inline deploy: needs: build-and-push runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Deploy to Kubernetes uses: azure/k8s-deploy@v4 with: manifests: | k8s/deployment.yaml k8s/service.yaml images: | username/nestjs-app:${{ github.sha }} kubeconfig: ${{ secrets.KUBE_CONFIG }} ``` ### GitLab CI 配置 ```yaml stages: - test - build - deploy variables: NODE_ENV: test test: stage: test image: node:18 script: - npm ci - npm run test - npm run lint cache: paths: - node_modules/ build: stage: build image: docker:latest services: - docker:dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main deploy: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/nestjs-app nestjs-app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main ``` ## 5. 环境变量管理 ### 使用 .env 文件 ```bash # .env.production NODE_ENV=production PORT=3000 DATABASE_HOST=localhost DATABASE_PORT=3306 DATABASE_USER=root DATABASE_PASSWORD=password DATABASE_NAME=nestjs JWT_SECRET=your-secret-key ``` ### 使用 ConfigModule ```typescript import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV}`, }), ], }) export class AppModule {} ``` ### Kubernetes Secrets ```yaml apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: host: bG9jYWxob3N0 port: MzMwNg== user: cm9vdA== password: cGFzc3dvcmQ= ``` ## 6. 健康检查 ### 健康检查端点 ```typescript import { Controller, Get } from '@nestjs/common'; import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus'; @Controller('health') export class HealthController { constructor( private health: HealthCheckService, private db: TypeOrmHealthIndicator, ) {} @Get() @HealthCheck() check() { return this.health.check([ () => this.db.pingCheck('database'), ]); } } ``` ### Kubernetes 健康检查 ```yaml livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 ``` ## 7. 日志管理 ### 结构化日志 ```typescript import { Logger } from '@nestjs/common'; export class AppService { private readonly logger = new Logger(AppService.name); async getData() { this.logger.log('Fetching data', { userId: 123, action: 'fetch' }); // 业务逻辑 } } ``` ### 使用 Winston ```typescript import { WinstonModule } from 'nest-winston'; import * as winston from 'winston'; @Module({ imports: [ WinstonModule.forRoot({ transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), winston.format.json(), ), }), new winston.transports.File({ filename: 'logs/error.log', level: 'error', }), new winston.transports.File({ filename: 'logs/combined.log', }), ], }), ], }) export class AppModule {} ``` ## 8. 监控和告警 ### 使用 Prometheus ```typescript import { Controller, Get } from '@nestjs/common'; import { MetricsService } from './metrics.service'; @Controller('metrics') export class MetricsController { constructor(private metricsService: MetricsService) {} @Get() getMetrics() { return this.metricsService.getMetrics(); } } ``` ### Grafana 仪表板 ```yaml apiVersion: v1 kind: ConfigMap metadata: name: grafana-dashboard data: dashboard.json: | { "dashboard": { "title": "NestJS Application", "panels": [ { "title": "Request Rate", "targets": [ { "expr": "rate(http_requests_total[5m])" } ] } ] } } ``` ## 9. 负载均衡 ### Nginx 配置 ```nginx upstream nestjs_backend { server nestjs-app-1:3000; server nestjs-app-2:3000; server nestjs-app-3:3000; } server { listen 80; server_name api.example.com; location / { proxy_pass http://nestjs_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` ### AWS ALB 配置 ```yaml Resources: LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: nestjs-alb Subnets: - !Ref SubnetA - !Ref SubnetB SecurityGroups: - !Ref SecurityGroup Type: application TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: nestjs-tg Port: 3000 Protocol: HTTP VpcId: !Ref VPC Targets: - Id: !Ref EC2Instance1 - Id: !Ref EC2Instance2 ``` ## 10. 灾难恢复 ### 数据库备份 ```bash #!/bin/bash # backup.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/backups" DATABASE="nestjs" mysqldump -u root -p$MYSQL_ROOT_PASSWORD $DATABASE | gzip > $BACKUP_DIR/db_backup_$DATE.sql.gz # 保留最近7天的备份 find $BACKUP_DIR -name "db_backup_*.sql.gz" -mtime +7 -delete ``` ### 自动扩展 ```yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nestjs-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nestjs-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 ``` ## 部署最佳实践 1. **环境隔离**:开发、测试、生产环境分离 2. **自动化部署**:使用 CI/CD 自动化部署流程 3. **版本控制**:所有配置文件纳入版本控制 4. **监控告警**:实施全面的监控和告警机制 5. **备份策略**:定期备份关键数据 6. **安全加固**:实施安全最佳实践 7. **文档化**:维护详细的部署文档 8. **回滚计划**:准备快速回滚方案 9. **性能测试**:部署前进行性能测试 10. **渐进式发布**:使用蓝绿部署或金丝雀发布 ## 总结 NestJS 部署和 DevOps 提供了: - 灵活的容器化方案 - 强大的编排支持 - 自动化的 CI/CD 流程 - 完善的监控体系 - 可靠的灾难恢复 掌握部署和 DevOps 是将 NestJS 应用程序成功交付到生产环境的关键。通过合理使用容器化、编排、CI/CD 和监控工具,可以构建出高可用、可扩展的生产环境,确保应用程序的稳定运行和快速迭代。
服务端 · 2月21日 17:11
NestJS 性能优化有哪些方法?# NestJS 性能优化详解 ## 性能优化概述 性能优化是构建高性能 NestJS 应用程序的关键。通过合理的架构设计、代码优化和资源管理,可以显著提升应用程序的响应速度和吞吐量。 ## 1. 数据库优化 ### 查询优化 #### 使用索引 ```typescript @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Index() @Column() email: string; @Index() @Column() username: string; @Column() name: string; } ``` #### 避免 N+1 查询 ```typescript // 不好的方式 - N+1 查询 async getUsersWithOrders() { const users = await this.userRepository.find(); for (const user of users) { user.orders = await this.orderRepository.find({ where: { userId: user.id } }); } return users; } // 好的方式 - 使用 JOIN async getUsersWithOrders() { return this.userRepository.find({ relations: ['orders'], }); } ``` #### 使用分页 ```typescript async findAll(page: number = 1, limit: number = 10) { const [data, total] = await this.userRepository.findAndCount({ skip: (page - 1) * limit, take: limit, }); return { data, total, page, totalPages: Math.ceil(total / limit), }; } ``` ### 连接池配置 ```typescript TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [User], extra: { connectionLimit: 20, // 根据服务器配置调整 }, }) ``` ## 2. 缓存策略 ### 使用 Redis 缓存 ```typescript import { Injectable, Inject } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; @Injectable() export class UserService { constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} async findOne(id: number) { const cacheKey = `user_${id}`; const cachedUser = await this.cacheManager.get(cacheKey); if (cachedUser) { return cachedUser; } const user = await this.userRepository.findOne({ where: { id } }); await this.cacheManager.set(cacheKey, user, 3600); // 缓存 1 小时 return user; } } ``` ### HTTP 缓存 ```typescript import { Controller, Get, Header } from '@nestjs/common'; @Controller('users') export class UsersController { @Get() @Header('Cache-Control', 'public, max-age=300') // 缓存 5 分钟 findAll() { return this.usersService.findAll(); } } ``` ### 拦截器缓存 ```typescript import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class CacheInterceptor implements NestInterceptor { private cache = new Map(); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const cacheKey = request.url; if (this.cache.has(cacheKey)) { return of(this.cache.get(cacheKey)); } return next.handle().pipe( tap(data => { this.cache.set(cacheKey, data); }), ); } } ``` ## 3. 异步处理 ### 使用异步/等待 ```typescript // 好的方式 async findAll() { return this.userRepository.find(); } // 不好的方式 - 同步阻塞 findAll() { return this.userRepository.findSync(); } ``` ### 并行处理 ```typescript // 不好的方式 - 串行执行 async getUserData(userId: number) { const user = await this.userRepository.findOne({ where: { id: userId } }); const orders = await this.orderRepository.find({ where: { userId } }); const notifications = await this.notificationRepository.find({ where: { userId } }); return { user, orders, notifications }; } // 好的方式 - 并行执行 async getUserData(userId: number) { const [user, orders, notifications] = await Promise.all([ this.userRepository.findOne({ where: { id: userId } }), this.orderRepository.find({ where: { userId } }), this.notificationRepository.find({ where: { userId } }), ]); return { user, orders, notifications }; } ``` ### 使用队列处理耗时任务 ```typescript import { Injectable } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bull'; import { Queue } from 'bull'; @Injectable() export class EmailService { constructor(@InjectQueue('email') private emailQueue: Queue) {} async sendEmail(to: string, subject: string, body: string) { await this.emailQueue.add('send-email', { to, subject, body }); } } ``` ## 4. 压缩 ### 启用 Gzip 压缩 ```typescript import compression from 'compression'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(compression()); await app.listen(3000); } ``` ### 配置压缩级别 ```typescript app.use(compression({ level: 6, // 压缩级别 1-9 threshold: 1024, // 只压缩大于 1KB 的响应 })); ``` ## 5. 静态资源优化 ### 使用 CDN ```typescript import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 配置静态资源使用 CDN app.useStaticAssets('public', { prefix: '/static/', cacheControl: true, maxAge: 31536000, // 1 年 }); await app.listen(3000); } ``` ### 图片优化 ```typescript import { Controller, Get, Param, Res } from '@nestjs/common'; import { Response } from 'express'; import sharp from 'sharp'; @Controller('images') export class ImageController { @Get(':filename') async getImage(@Param('filename') filename: string, @Res() res: Response) { const image = sharp(`./public/images/${filename}`); // 根据请求参数优化图片 const width = parseInt(req.query.width) || 800; const height = parseInt(req.query.height) || 600; image .resize(width, height) .jpeg({ quality: 80 }) .pipe(res); } } ``` ## 6. 代码优化 ### 使用懒加载模块 ```typescript @Module({ imports: [ // 懒加载模块 UsersModule, OrdersModule, ], }) export class AppModule {} ``` ### 避免不必要的计算 ```typescript // 不好的方式 async processUsers(users: User[]) { return users.map(user => { const expensiveResult = this.expensiveCalculation(user); return { ...user, result: expensiveResult }; }); } // 好的方式 - 使用缓存 async processUsers(users: User[]) { const cache = new Map(); return users.map(user => { const cacheKey = user.id; if (!cache.has(cacheKey)) { cache.set(cacheKey, this.expensiveCalculation(user)); } return { ...user, result: cache.get(cacheKey) }; }); } ``` ### 使用流处理大数据 ```typescript import { Controller, Get, StreamableFile } from '@nestjs/common'; import { createReadStream } from 'fs'; @Controller('files') export class FileController { @Get('download/:filename') downloadFile(@Param('filename') filename: string): StreamableFile { const file = createReadStream(`./files/${filename}`); return new StreamableFile(file); } } ``` ## 7. 监控和分析 ### 性能监控 ```typescript import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(LoggingInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const now = Date.now(); const request = context.switchToHttp().getRequest(); return next.handle().pipe( tap(() => { const duration = Date.now() - now; this.logger.log( `${request.method} ${request.url} - ${duration}ms`, ); }), ); } } ``` ### 使用 APM 工具 ```typescript import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { Agent } from '@elastic/apm-node'; const apm = new Agent({ serviceName: 'nestjs-app', serverUrl: 'http://localhost:8200', }); async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ``` ## 8. 负载均衡 ### 使用 PM2 集群模式 ```bash npm install pm2 -g pm2 start dist/main.js -i max --name nestjs-app ``` ### 配置 PM2 ```javascript // ecosystem.config.js module.exports = { apps: [{ name: 'nestjs-app', script: './dist/main.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, }, }], }; ``` ## 9. 内存优化 ### 避免内存泄漏 ```typescript // 不好的方式 - 可能导致内存泄漏 @Injectable() export class CacheService { private cache = new Map(); set(key: string, value: any) { this.cache.set(key, value); } } // 好的方式 - 使用 TTL @Injectable() export class CacheService { private cache = new Map(); private ttl = 3600000; // 1 小时 set(key: string, value: any) { this.cache.set(key, { value, expires: Date.now() + this.ttl, }); // 定期清理过期缓存 this.cleanup(); } private cleanup() { const now = Date.now(); for (const [key, data] of this.cache.entries()) { if (data.expires < now) { this.cache.delete(key); } } } } ``` ### 使用对象池 ```typescript @Injectable() export class ObjectPool<T> { private pool: T[] = []; private factory: () => T; constructor(factory: () => T, size: number = 10) { this.factory = factory; for (let i = 0; i < size; i++) { this.pool.push(factory()); } } acquire(): T { return this.pool.pop() || this.factory(); } release(obj: T): void { this.pool.push(obj); } } ``` ## 10. 网络优化 ### 使用 HTTP/2 ```typescript import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { NestExpressApplication } from '@nestjs/platform-express'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); await app.listen(3000); } bootstrap(); ``` ### 配置 Keep-Alive ```typescript async function bootstrap() { const app = await NestFactory.create(AppModule); const server = app.getHttpServer(); server.keepAliveTimeout = 65000; server.headersTimeout = 66000; await app.listen(3000); } ``` ## 性能优化最佳实践 1. **监控性能**:持续监控应用程序性能指标 2. **基准测试**:建立性能基准并定期测试 3. **渐进优化**:逐步优化,每次只优化一个方面 4. **代码审查**:定期进行代码审查以发现性能问题 5. **使用缓存**:合理使用缓存减少数据库查询 6. **异步处理**:使用异步和并行处理提高效率 7. **资源压缩**:启用压缩减少传输数据量 8. **负载均衡**:使用负载均衡分散请求压力 9. **定期清理**:定期清理缓存和临时数据 10. **文档记录**:记录优化过程和结果 ## 总结 NestJS 性能优化提供了: - 数据库查询优化 - 多种缓存策略 - 异步处理能力 - 资源压缩技术 - 监控和分析工具 掌握性能优化是构建高性能 NestJS 应用程序的关键。通过合理应用各种优化技术和最佳实践,可以显著提升应用程序的性能、响应速度和用户体验。性能优化是一个持续的过程,需要根据实际应用场景不断调整和改进。
服务端 · 2月21日 17:11
NestJS GraphQL 如何集成?# NestJS GraphQL 集成详解 ## GraphQL 概述 GraphQL 是一种用于 API 的查询语言和运行时环境。NestJS 通过 `@nestjs/graphql` 包提供了完整的 GraphQL 支持,使开发者能够构建灵活、高效的 GraphQL API。 ## 安装依赖 ```bash npm install @nestjs/graphql graphql apollo-server-express npm install -D @types/graphql ``` ## 基本配置 ### 配置 GraphQL 模块 ```typescript import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, autoSchemaFile: join(process.cwd(), 'src/schema.gql'), sortSchema: true, playground: true, introspection: true, context: ({ req }) => ({ req }), }), ], }) export class AppModule {} ``` ## 定义 Schema ### 使用装饰器定义 Schema ```typescript import { Field, Int, ObjectType, Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql'; import { User } from './entities/user.entity'; @ObjectType() export class User { @Field(() => ID) id: number; @Field() name: string; @Field() email: string; @Field(() => Int) age: number; @Field() isActive: boolean; @Field() createdAt: Date; @Field() updatedAt: Date; } ``` ### 定义输入类型 ```typescript import { InputType, Field } from '@nestjs/graphql'; @InputType() export class CreateUserInput { @Field() name: string; @Field() email: string; @Field() password: string; @Field(() => Int, { nullable: true }) age?: number; } @InputType() export class UpdateUserInput { @Field(() => ID) id: number; @Field({ nullable: true }) name?: string; @Field({ nullable: true }) email?: string; @Field(() => Int, { nullable: true }) age?: number; } ``` ## 创建 Resolver ### 基本 Resolver ```typescript import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql'; import { UsersService } from './users.service'; import { User } from './entities/user.entity'; import { CreateUserInput, UpdateUserInput } from './dto/user.input'; @Resolver(() => User) export class UsersResolver { constructor(private usersService: UsersService) {} @Query(() => [User]) async users(): Promise<User[]> { return this.usersService.findAll(); } @Query(() => User, { nullable: true }) async user(@Args('id', { type: () => ID }) id: number): Promise<User> { return this.usersService.findOne(id); } @Mutation(() => User) async createUser(@Args('input') input: CreateUserInput): Promise<User> { return this.usersService.create(input); } @Mutation(() => User, { nullable: true }) async updateUser(@Args('input') input: UpdateUserInput): Promise<User> { return this.usersService.update(input.id, input); } @Mutation(() => Boolean) async deleteUser(@Args('id', { type: () => ID }) id: number): Promise<boolean> { return this.usersService.remove(id); } } ``` ### 使用自定义装饰器 ```typescript import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const CurrentUser = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.user; }, ); // 在 Resolver 中使用 @Mutation(() => User) async createUser( @Args('input') input: CreateUserInput, @CurrentUser() user: any, ): Promise<User> { return this.usersService.create(input, user.id); } ``` ## 关系和加载 ### 一对一关系 ```typescript @ObjectType() export class Profile { @Field(() => ID) id: number; @Field() bio: string; @Field(() => User) user: User; } @ObjectType() export class User { @Field(() => ID) id: number; @Field() name: string; @Field(() => Profile, { nullable: true }) profile?: Profile; } ``` ### 一对多关系 ```typescript @ObjectType() export class Post { @Field(() => ID) id: number; @Field() title: string; @Field() content: string; @Field(() => User) author: User; } @ObjectType() export class User { @Field(() => ID) id: number; @Field() name: string; @Field(() => [Post]) posts: Post[]; } ``` ### 多对多关系 ```typescript @ObjectType() export class Tag { @Field(() => ID) id: number; @Field() name: string; @Field(() => [Post]) posts: Post[]; } @ObjectType() export class Post { @Field(() => ID) id: number; @Field() title: string; @Field(() => [Tag]) tags: Tag[]; } ``` ## 分页 ### 基于游标的分页 ```typescript import { ArgsType, Field, Int } from '@nestjs/graphql'; @ArgsType() export class PaginationArgs { @Field(() => Int, { nullable: true }) first?: number; @Field(() => String, { nullable: true }) after?: string; } @ObjectType() export class PaginatedUsers { @Field(() => [User]) data: User[]; @Field(() => Boolean) hasNextPage: boolean; @Field(() => String, { nullable: true }) endCursor?: string; } @Resolver(() => User) export class UsersResolver { @Query(() => PaginatedUsers) async users(@Args() pagination: PaginationArgs): Promise<PaginatedUsers> { const { data, hasNextPage, endCursor } = await this.usersService.paginate(pagination); return { data, hasNextPage, endCursor }; } } ``` ### 基于偏移量的分页 ```typescript @ArgsType() export class OffsetPaginationArgs { @Field(() => Int, { nullable: true, defaultValue: 0 }) skip?: number; @Field(() => Int, { nullable: true, defaultValue: 10 }) take?: number; } @ObjectType() export class OffsetPaginatedUsers { @Field(() => [User]) data: User[]; @Field(() => Int) total: number; @Field(() => Int) page: number; @Field(() => Int) totalPages: number; } @Resolver(() => User) export class UsersResolver { @Query(() => OffsetPaginatedUsers) async users(@Args() pagination: OffsetPaginationArgs): Promise<OffsetPaginatedUsers> { const { data, total, page, totalPages } = await this.usersService.paginate(pagination); return { data, total, page, totalPages }; } } ``` ## 订阅(Subscriptions) ### 配置订阅 ```typescript import { PubSub } from 'graphql-subscriptions'; @Module({ imports: [ GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, installSubscriptionHandlers: true, context: ({ req, connection }) => { if (connection) { return { req: connection.context }; } return { req }; }, }), ], }) export class AppModule {} ``` ### 创建订阅 Resolver ```typescript import { Resolver, Subscription, Root } from '@nestjs/graphql'; import { PubSub } from 'graphql-subscriptions'; @ObjectType() export class Message { @Field(() => ID) id: number; @Field() content: string; @Field() createdAt: Date; } @Resolver(() => Message) export class MessagesResolver { private pubSub: PubSub; constructor() { this.pubSub = new PubSub(); } @Subscription(() => Message, { filter: (payload, variables) => { return payload.chatId === variables.chatId; }, }) messageAdded(@Root() message: Message, @Args('chatId') chatId: number): Message { return message; } async publishMessage(message: Message) { await this.pubSub.publish('messageAdded', { messageAdded: message }); } } ``` ## 数据加载器(DataLoader) ### 创建 DataLoader ```typescript import { DataLoader } from 'dataloader'; import { UsersService } from './users.service'; export class UserDataLoader { private loader: DataLoader<number, User>; constructor(private usersService: UsersService) { this.loader = new DataLoader(async (ids) => { const users = await this.usersService.findByIds(ids); return ids.map(id => users.find(user => user.id === id)); }); } async load(id: number): Promise<User> { return this.loader.load(id); } async loadMany(ids: number[]): Promise<User[]> { return this.loader.loadMany(ids); } } ``` ### 在 Resolver 中使用 DataLoader ```typescript import { Resolver, Query, Args, ID, Parent } from '@nestjs/graphql'; import { UserDataLoader } from './user-dataloader'; @Resolver(() => Post) export class PostsResolver { constructor(private userDataLoader: UserDataLoader) {} @Query(() => [Post]) async posts(): Promise<Post[]> { return this.postsService.findAll(); } @FieldResolver(() => User) async author(@Parent() post: Post): Promise<User> { return this.userDataLoader.load(post.authorId); } } ``` ## 验证和转换 ### 使用 class-validator ```typescript import { IsEmail, IsString, MinLength } from 'class-validator'; @InputType() export class CreateUserInput { @Field() @IsEmail() email: string; @Field() @IsString() @MinLength(6) password: string; @Field() @IsString() name: string; } ``` ### 自定义验证器 ```typescript import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'; export function IsStrongPassword(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ name: 'isStrongPassword', target: object.constructor, propertyName: propertyName, options: validationOptions, validator: { validate(value: any) { const hasUpperCase = /[A-Z]/.test(value); const hasLowerCase = /[a-z]/.test(value); const hasNumber = /[0-9]/.test(value); return hasUpperCase && hasLowerCase && hasNumber; }, defaultMessage(args: ValidationArguments) { return 'Password must contain uppercase, lowercase, and numbers'; }, }, }); }; } @InputType() export class CreateUserInput { @Field() @IsStrongPassword() password: string; } ``` ## 错误处理 ### 自定义错误类 ```typescript import { ApolloError } from 'apollo-server-express'; export class UserNotFoundError extends ApolloError { constructor(id: number) { super(`User with ID ${id} not found`, 'USER_NOT_FOUND', { id, }); } } export class ValidationError extends ApolloError { constructor(message: string, fields: string[]) { super(message, 'VALIDATION_ERROR', { fields }); } } ``` ### 在 Resolver 中使用错误 ```typescript @Resolver(() => User) export class UsersResolver { constructor(private usersService: UsersService) {} @Query(() => User, { nullable: true }) async user(@Args('id', { type: () => ID }) id: number): Promise<User> { const user = await this.usersService.findOne(id); if (!user) { throw new UserNotFoundError(id); } return user; } @Mutation(() => User) async createUser(@Args('input') input: CreateUserInput): Promise<User> { try { return await this.usersService.create(input); } catch (error) { throw new ValidationError('Invalid input', error.fields); } } } ``` ## 权限控制 ### 创建权限守卫 ```typescript import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; @Injectable() export class GqlAuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const ctx = GqlExecutionContext.create(context); const { req } = ctx.getContext(); if (!req.user) { throw new UnauthorizedException(); } return true; } } ``` ### 使用权限守卫 ```typescript import { Resolver, Mutation, UseGuards } from '@nestjs/graphql'; import { GqlAuthGuard } from './guards/gql-auth.guard'; @Resolver(() => User) export class UsersResolver { @Mutation(() => User) @UseGuards(GqlAuthGuard) async createUser(@Args('input') input: CreateUserInput): Promise<User> { return this.usersService.create(input); } } ``` ## 性能优化 ### 查询复杂度分析 ```typescript import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, validationRules: [ queryComplexity({ maximumComplexity: 1000, variables: {}, onComplete: (complexity) => { console.log(`Query complexity: ${complexity}`); }, }), ], }) ``` ### 查询深度限制 ```typescript import { depthLimit } from 'graphql-depth-limit'; GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, validationRules: [ depthLimit(5), ], }) ``` ## 最佳实践 1. **Schema 优先**:优先使用 Schema 优先的方法 2. **类型安全**:充分利用 TypeScript 的类型系统 3. **分页**:始终实现分页以避免大量数据传输 4. **数据加载器**:使用 DataLoader 解决 N+1 查询问题 5. **错误处理**:提供清晰的错误信息 6. **权限控制**:实现适当的权限控制机制 7. **性能监控**:监控查询性能和复杂度 8. **文档化**:使用 GraphQL Playground 和文档工具 ## 总结 NestJS GraphQL 集成提供了: - 完整的 GraphQL 支持 - 灵活的 Schema 定义 - 强大的类型安全 - 丰富的查询功能 - 易于集成的生态系统 掌握 NestJS GraphQL 集成是构建现代、灵活 API 的关键。通过合理使用 GraphQL 的特性,可以构建出高效、类型安全、易于维护的 API,满足前端对数据的精确需求。GraphQL 的查询语言特性使客户端能够精确获取所需数据,减少网络传输和提升性能。
服务端 · 2月21日 17:11
NestJS 安全和认证如何实现?安全性是任何应用程序的关键组成部分。NestJS 提供了多种安全机制,包括身份验证、授权、数据加密、防护措施等,帮助开发者构建安全可靠的应用程序。 ## 身份验证(Authentication) ### JWT 认证 #### 安装依赖 ```bash npm install @nestjs/jwt @nestjs/passport passport passport-jwt npm install -D @types/passport-jwt ``` #### 创建 JWT 策略 ```typescript import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { ConfigService } from '@nestjs/config'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private configService: ConfigService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: configService.get('JWT_SECRET'), }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; } } ``` #### 创建认证服务 ```typescript import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { UsersService } from '../users/users.service'; import * as bcrypt from 'bcrypt'; @Injectable() export class AuthService { constructor( private usersService: UsersService, private jwtService: JwtService, ) {} async validateUser(username: string, password: string): Promise<any> { const user = await this.usersService.findByUsername(username); if (user && await bcrypt.compare(password, user.password)) { const { password, ...result } = user; return result; } return null; } async login(user: any) { const payload = { username: user.username, sub: user.userId }; return { access_token: this.jwtService.sign(payload), }; } async register(username: string, password: string) { const hashedPassword = await bcrypt.hash(password, 10); return this.usersService.create({ username, password: hashedPassword }); } } ``` #### 创建认证控制器 ```typescript import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LocalAuthGuard } from './local-auth.guard'; import { JwtAuthGuard } from './jwt-auth.guard'; @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @UseGuards(LocalAuthGuard) @Post('login') async login(@Request() req) { return this.authService.login(req.user); } @Post('register') async register(@Body() registerDto: { username: string; password: string }) { return this.authService.register(registerDto.username, registerDto.password); } @UseGuards(JwtAuthGuard) @Post('profile') getProfile(@Request() req) { return req.user; } } ``` ### 本地认证策略 ```typescript import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { AuthService } from './auth.service'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super({ usernameField: 'username', passwordField: 'password', }); } async validate(username: string, password: string): Promise<any> { const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); } return user; } } ``` ### OAuth2 认证 #### 安装依赖 ```bash npm install @nestjs/passport passport passport-google-oauth20 npm install -D @types/passport-google-oauth20 ``` #### 创建 Google OAuth 策略 ```typescript import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy, VerifyCallback } from 'passport-google-oauth20'; import { ConfigService } from '@nestjs/config'; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { constructor(private configService: ConfigService) { super({ clientID: configService.get('GOOGLE_CLIENT_ID'), clientSecret: configService.get('GOOGLE_CLIENT_SECRET'), callbackURL: configService.get('GOOGLE_CALLBACK_URL'), scope: ['email', 'profile'], }); } async validate(accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise<any> { const { name, emails, photos } = profile; const user = { email: emails[0].value, firstName: name.givenName, lastName: name.familyName, picture: photos[0].value, accessToken, }; done(null, user); } } ``` ## 授权(Authorization) ### 基于角色的访问控制(RBAC) ```typescript import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user?.roles?.includes(role)); } } ``` ### 角色装饰器 ```typescript import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); ``` ### 使用角色守卫 ```typescript @Controller('users') export class UsersController { @Get('admin') @UseGuards(JwtAuthGuard, RolesGuard) @Roles('admin') getAdminData() { return 'Admin data'; } } ``` ## 密码加密 ### 使用 bcrypt ```typescript import * as bcrypt from 'bcrypt'; async hashPassword(password: string): Promise<string> { const salt = await bcrypt.genSalt(10); return bcrypt.hash(password, salt); } async comparePassword(password: string, hash: string): Promise<boolean> { return bcrypt.compare(password, hash); } ``` ## 安全中间件 ### Helmet ```typescript import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(helmet()); await app.listen(3000); } ``` ### CORS 配置 ```typescript async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors({ origin: 'https://example.com', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', credentials: true, }); await app.listen(3000); } ``` ### 速率限制 ```typescript import rateLimit from 'express-rate-limit'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use( rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs }), ); await app.listen(3000); } ``` ## 数据验证 ### 使用 class-validator ```typescript import { IsEmail, IsString, MinLength } from 'class-validator'; export class CreateUserDto { @IsEmail() email: string; @IsString() @MinLength(6) password: string; } ``` ### 全局验证管道 ```typescript import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.listen(3000); } ``` ## SQL 注入防护 ### 使用参数化查询 ```typescript // 不安全 const query = `SELECT * FROM users WHERE name = '${name}'`; // 安全 const query = `SELECT * FROM users WHERE name = ?`; const result = await this.userRepository.query(query, [name]); ``` ### 使用 ORM ```typescript // TypeORM 自动防止 SQL 注入 const user = await this.userRepository.findOne({ where: { name: username }, }); ``` ## XSS 防护 ### 输入清理 ```typescript import * as xss from 'xss'; function sanitizeInput(input: string): string { return xss(input); } ``` ### 内容安全策略(CSP) ```typescript import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use( helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], }, }), ); await app.listen(3000); } ``` ## CSRF 防护 ### 使用 csurf ```typescript import * as csurf from 'csurf'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(csurf({ cookie: true })); await app.listen(3000); } ``` ## 安全最佳实践 1. **使用 HTTPS**:始终在生产环境中使用 HTTPS 2. **环境变量**:敏感信息存储在环境变量中 3. **密码加密**:使用 bcrypt 等安全算法加密密码 4. **输入验证**:验证所有用户输入 5. **最小权限原则**:用户只拥有必要的权限 6. **定期更新依赖**:保持依赖项更新以修复安全漏洞 7. **日志记录**:记录安全相关事件 8. **错误处理**:不向客户端暴露敏感错误信息 9. **会话管理**:使用安全的会话管理 10. **安全头**:设置适当的安全头 ## 总结 NestJS 安全和认证提供了: * 多种身份验证策略 * 灵活的授权机制 * 强大的数据保护 * 全面的安全防护 * 易于集成的安全工具 掌握安全性和认证是构建安全、可靠的 NestJS 应用程序的关键。通过合理使用认证策略、授权机制和安全最佳实践,可以保护应用程序免受各种安全威胁,确保用户数据和系统的安全。
服务端 · 2月17日 22:41
NestJS 如何集成数据库?## 数据库集成概述 NestJS 提供了对多种数据库的完整支持,包括关系型数据库(如 MySQL、PostgreSQL)和非关系型数据库(如 MongoDB)。通过使用 TypeORM、Prisma、Mongoose 等 ORM/ODM 库,开发者可以轻松实现数据库操作。 ## TypeORM 集成 ### 安装依赖 ```bash npm install @nestjs/typeorm typeorm npm install mysql2 # 或 pg、sqlite3、better-sqlite3 等 ``` ### 配置 TypeORM ```typescript import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [User], synchronize: true, // 生产环境应设为 false logging: true, }), TypeOrmModule.forFeature([User]), ], providers: [UserService], controllers: [UserController], }) export class UserModule {} ``` ### 创建实体 ```typescript import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column({ unique: true }) email: string; @Column() password: string; @Column() name: string; @Column({ default: true }) isActive: boolean; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; } ``` ### 创建 Repository ```typescript import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './entities/user.entity'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} async create(createUserDto: CreateUserDto): Promise<User> { const user = this.userRepository.create(createUserDto); return this.userRepository.save(user); } async findAll(): Promise<User[]> { return this.userRepository.find(); } async findOne(id: number): Promise<User> { return this.userRepository.findOne({ where: { id } }); } async update(id: number, updateUserDto: any): Promise<User> { await this.userRepository.update(id, updateUserDto); return this.findOne(id); } async remove(id: number): Promise<void> { await this.userRepository.delete(id); } async findByEmail(email: string): Promise<User> { return this.userRepository.findOne({ where: { email } }); } } ``` ## Prisma 集成 ### 安装依赖 ```bash npm install @prisma/client npm install prisma --save-dev npx prisma init ``` ### 配置 Prisma ```prisma // prisma/schema.prisma datasource db { provider = "mysql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) email String @unique password String name String isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` ### 创建 Prisma 服务 ```typescript import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { async onModuleInit() { await this.$connect(); } async onModuleDestroy() { await this.$disconnect(); } } ``` ### 使用 Prisma ```typescript import { Injectable } from '@nestjs/common'; import { PrismaService } from './prisma.service'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UserService { constructor(private prisma: PrismaService) {} async create(createUserDto: CreateUserDto) { return this.prisma.user.create({ data: createUserDto, }); } async findAll() { return this.prisma.user.findMany(); } async findOne(id: number) { return this.prisma.user.findUnique({ where: { id }, }); } async update(id: number, updateUserDto: any) { return this.prisma.user.update({ where: { id }, data: updateUserDto, }); } async remove(id: number) { return this.prisma.user.delete({ where: { id }, }); } } ``` ## Mongoose 集成 ### 安装依赖 ```bash npm install @nestjs/mongoose mongoose ``` ### 配置 Mongoose ```typescript import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { User, UserSchema } from './schemas/user.schema'; import { UserService } from './user.service'; import { UserController } from './user.controller'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost:27017/nest'), MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), ], providers: [UserService], controllers: [UserController], }) export class UserModule {} ``` ### 创建 Schema ```typescript import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document } from 'mongoose'; @Schema() export class User extends Document { @Prop({ required: true, unique: true }) email: string; @Prop({ required: true }) password: string; @Prop({ required: true }) name: string; @Prop({ default: true }) isActive: boolean; @Prop({ default: Date.now }) createdAt: Date; @Prop({ default: Date.now }) updatedAt: Date; } export const UserSchema = SchemaFactory.createForClass(User); ``` ### 创建 Mongoose 服务 ```typescript import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { User } from './schemas/user.schema'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UserService { constructor( @InjectModel(User.name) private userModel: Model<User>, ) {} async create(createUserDto: CreateUserDto): Promise<User> { const createdUser = new this.userModel(createUserDto); return createdUser.save(); } async findAll(): Promise<User[]> { return this.userModel.find().exec(); } async findOne(id: string): Promise<User> { return this.userModel.findOne({ _id: id }).exec(); } async update(id: string, updateUserDto: any): Promise<User> { return this.userModel.findByIdAndUpdate(id, updateUserDto, { new: true }).exec(); } async remove(id: string): Promise<User> { return this.userModel.findByIdAndDelete(id).exec(); } } ``` ## 数据库迁移 ### TypeORM 迁移 ```bash # 生成迁移 npm run typeorm migration:generate -- -n CreateUserMigration # 运行迁移 npm run typeorm migration:run # 回滚迁移 npm run typeorm migration:revert ``` ### Prisma 迁移 ```bash # 创建迁移 npx prisma migrate dev --name init # 应用迁移 npx prisma migrate deploy # 重置数据库 npx prisma migrate reset ``` ## 事务处理 ### TypeORM 事务 ```typescript import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, DataSource } from 'typeorm'; import { User } from './entities/user.entity'; import { Order } from './entities/order.entity'; @Injectable() export class OrderService { constructor( private dataSource: DataSource, @InjectRepository(User) private userRepository: Repository<User>, @InjectRepository(Order) private orderRepository: Repository<Order>, ) {} async createOrderWithUser(userId: number, orderData: any) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { const user = await queryRunner.manager.findOne(User, { where: { id: userId } }); const order = queryRunner.manager.create(Order, { ...orderData, user, }); await queryRunner.manager.save(order); await queryRunner.commitTransaction(); return order; } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } } } ``` ### Prisma 事务 ```typescript async createOrderWithUser(userId: number, orderData: any) { return this.prisma.$transaction(async (prisma) => { const user = await prisma.user.findUnique({ where: { id: userId }, }); const order = await prisma.order.create({ data: { ...orderData, user: { connect: { id: userId }, }, }, }); return order; }); } ``` ## 关系映射 ### TypeORM 关系 ```typescript @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => Order, order => order.user) orders: Order[]; } @Entity('orders') export class Order { @PrimaryGeneratedColumn() id: number; @Column() userId: number; @ManyToOne(() => User, user => user.orders) user: User; @Column() product: string; } ``` ### Prisma 关系 ```prisma model User { id Int @id @default(autoincrement()) name String orders Order[] } model Order { id Int @id @default(autoincrement()) userId Int user User @relation(fields: [userId], references: [id]) product String } ``` ## 数据库连接池配置 ### TypeORM 连接池 ```typescript TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, extra: { connectionLimit: 10, }, }) ``` ### Prisma 连接池 ```prisma datasource db { provider = "mysql" url = env("DATABASE_URL") // 连接池配置 connection_limit = 10 } ``` ## 最佳实践 1. **使用 DTO**:使用数据传输对象来验证和转换数据 2. **环境变量**:使用环境变量管理数据库配置 3. **迁移管理**:使用迁移来管理数据库结构变更 4. **事务处理**:在需要原子性操作时使用事务 5. **索引优化**:为常用查询字段添加索引 6. **连接池**:合理配置连接池大小 7. **软删除**:实现软删除而不是物理删除 8. **查询优化**:避免 N+1 查询问题 ## 总结 NestJS 数据库集成提供了: * 对多种数据库的支持 * 灵活的 ORM/ODM 选择 * 完整的类型安全 * 强大的关系映射 * 便捷的事务处理 掌握数据库集成是构建数据驱动的 NestJS 应用程序的基础。通过合理选择和使用 ORM/ODM,遵循最佳实践,可以构建出高性能、可维护的数据访问层。
服务端 · 2月17日 22:40
什么是 NestJS 框架,它的核心特点和应用场景是什么?NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,使用 TypeScript 构建,并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)的元素。 ## 核心特点 1. **基于 TypeScript**:NestJS 完全使用 TypeScript 编写,提供强类型支持 2. **模块化架构**:应用程序被组织成模块,每个模块封装一组相关功能 3. **依赖注入**:内置强大的依赖注入系统,便于测试和维护 4. **Express/Fastify 兼容**:底层可以使用 Express 或 Fastify 作为 HTTP 服务器 5. **装饰器驱动**:大量使用装饰器来简化代码编写 6. **可测试性**:提供完整的测试工具和最佳实践 ## 与其他框架的对比 * **与 Express 对比**:NestJS 提供了更结构化的架构和内置的依赖注入,而 Express 更轻量级但需要手动组织代码 * **与 Koa 对比**:NestJS 提供了更完整的开箱即用功能,而 Koa 更注重中间件的灵活性 * **与 Meteor 对比**:NestJS 是后端框架,而 Meteor 是全栈框架 ## 适用场景 * 企业级后端 API 开发 * 微服务架构 * 实时应用程序(使用 WebSocket) * GraphQL API 开发 * RESTful API 开发 ## 为什么选择 NestJS 1. **学习曲线平缓**:对于有 Angular 经验的开发者来说,NestJS 的概念非常熟悉 2. **可维护性高**:模块化和依赖注入使代码更易于维护 3. **生态系统丰富**:支持多种数据库、ORM、验证库等 4. **社区活跃**:拥有活跃的社区和丰富的文档 5. **TypeScript 支持**:提供完整的类型安全和智能提示 ## 基本架构 NestJS 应用程序由以下核心概念组成: * **Modules(模块)**:组织应用程序的基本单元 * **Controllers(控制器)**:处理传入请求并返回响应 * **Providers(提供者)**:处理业务逻辑 * **Services(服务)**:Provider 的一种,用于封装可重用的业务逻辑 * **Pipes(管道)**:数据转换和验证 * **Guards(守卫)**:权限控制和路由保护 * **Interceptors(拦截器)**:在函数执行前后添加额外逻辑 * **Exception Filters(异常过滤器)**:处理异常和错误响应 ## 总结 NestJS 是一个现代化的 Node.js 后端框架,它通过提供结构化的架构、强大的依赖注入系统和丰富的功能集,使开发者能够构建可维护、可测试和可扩展的服务器端应用程序。它特别适合需要长期维护的大型项目和企业级应用。
服务端 · 2月17日 22:40
NestJS 测试和最佳实践有哪些?## NestJS 测试概述 NestJS 提供了完整的测试支持,包括单元测试、集成测试和端到端测试。测试框架基于 Jest,提供了丰富的测试工具和实用程序。 ## 测试类型 ### 1. 单元测试(Unit Testing) 单元测试用于测试单个函数、类或模块的行为,通常不涉及外部依赖。 #### 服务单元测试示例 ```typescript import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; import { Repository } from 'typeorm'; describe('UsersService', () => { let service: UsersService; let repository: Repository<User>; const mockUserRepository = { create: jest.fn(), save: jest.fn(), find: jest.fn(), findOne: jest.fn(), update: jest.fn(), delete: jest.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getRepositoryToken(User), useValue: mockUserRepository, }, ], }).compile(); service = module.get<UsersService>(UsersService); repository = module.get<Repository<User>>(getRepositoryToken(User)); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('create', () => { it('should create a new user', async () => { const createUserDto = { name: 'John', email: 'john@example.com' }; const user = { id: 1, ...createUserDto }; mockUserRepository.create.mockReturnValue(user); mockUserRepository.save.mockResolvedValue(user); const result = await service.create(createUserDto); expect(repository.create).toHaveBeenCalledWith(createUserDto); expect(repository.save).toHaveBeenCalledWith(user); expect(result).toEqual(user); }); }); describe('findAll', () => { it('should return an array of users', async () => { const users = [ { id: 1, name: 'John', email: 'john@example.com' }, { id: 2, name: 'Jane', email: 'jane@example.com' }, ]; mockUserRepository.find.mockResolvedValue(users); const result = await service.findAll(); expect(repository.find).toHaveBeenCalled(); expect(result).toEqual(users); }); }); }); ``` ### 2. 集成测试(Integration Testing) 集成测试用于测试多个组件之间的交互,通常涉及数据库、外部服务等。 #### 控制器集成测试示例 ```typescript import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/users (GET)', () => { return request(app.getHttpServer()) .get('/users') .expect(200) .expect([]); }); it('/users (POST)', () => { return request(app.getHttpServer()) .post('/users') .send({ name: 'John', email: 'john@example.com' }) .expect(201) .expect(res => { expect(res.body).toHaveProperty('id'); expect(res.body.name).toBe('John'); }); }); afterEach(async () => { await app.close(); }); }); ``` ### 3. 端到端测试(E2E Testing) 端到端测试模拟真实用户场景,测试整个应用程序的流程。 ```typescript import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('UsersController (e2e)', () => { let app: INestApplication; let userId: number; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); }); it('should create a user', async () => { const response = await request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com' }) .expect(201); userId = response.body.id; expect(response.body).toHaveProperty('id'); expect(response.body.name).toBe('John Doe'); }); it('should get all users', async () => { const response = await request(app.getHttpServer()) .get('/users') .expect(200); expect(Array.isArray(response.body)).toBe(true); }); it('should get a user by id', async () => { const response = await request(app.getHttpServer()) .get(`/users/${userId}`) .expect(200); expect(response.body.id).toBe(userId); }); it('should update a user', async () => { const response = await request(app.getHttpServer()) .patch(`/users/${userId}`) .send({ name: 'Jane Doe' }) .expect(200); expect(response.body.name).toBe('Jane Doe'); }); it('should delete a user', async () => { await request(app.getHttpServer()) .delete(`/users/${userId}`) .expect(200); }); afterAll(async () => { await app.close(); }); }); ``` ## 测试工具和实用程序 ### 1. Test.createTestingModule() 创建测试模块,用于配置测试环境。 ```typescript const module: TestingModule = await Test.createTestingModule({ imports: [AppModule], providers: [UsersService], }).compile(); ``` ### 2. Mock 提供者 使用 mock 对象替换真实的提供者。 ```typescript { provide: UsersService, useValue: { findAll: jest.fn().mockResolvedValue([]), findOne: jest.fn().mockResolvedValue({ id: 1 }), }, } ``` ### 3. Jest 函数 使用 Jest 的 mock 功能。 ```typescript jest.fn() // 创建 mock 函数 jest.mock() // 模块 mock jest.spyOn() // 监听对象方法 mockResolvedValue() // 设置返回值 mockRejectedValue() // 设置拒绝值 mockReturnValue() // 设置同步返回值 mockReturnValueOnce() // 设置一次性返回值 ``` ## 最佳实践 ### 1. 测试组织 ``` src/ ├── users/ │ ├── users.controller.spec.ts │ ├── users.service.spec.ts │ └── users.module.spec.ts test/ ├── app.e2e-spec.ts └── users.e2e-spec.ts ``` ### 2. 测试命名约定 * 使用 `describe` 分组相关测试 * 使用 `it` 或 `test` 描述单个测试用例 * 测试名称应该清晰描述测试内容 ```typescript describe('UsersService', () => { describe('create', () => { it('should create a new user', async () => { // 测试逻辑 }); it('should throw an error if email already exists', async () => { // 测试逻辑 }); }); }); ``` ### 3. 测试隔离 每个测试应该独立运行,不依赖其他测试的状态。 ```typescript beforeEach(() => { // 在每个测试前重置状态 }); afterEach(() => { // 在每个测试后清理 }); ``` ### 4. 测试覆盖率 使用 Jest 的覆盖率功能确保代码质量。 ```bash npm run test:cov ``` 在 `package.json` 中配置: ```json { "jest": { "collectCoverageFrom": [ "src/**/*.(t|j)s", "!src/main.ts", "!src/**/*.module.ts", "!src/**/*.dto.ts" ], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } } } ``` ### 5. Mock 外部依赖 对于数据库、API 等外部依赖,使用 mock 避免实际调用。 ```typescript const mockRepository = { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), delete: jest.fn(), }; ``` ### 6. 测试异步代码 正确处理异步代码的测试。 ```typescript // 使用 async/await it('should return user', async () => { const result = await service.findOne(1); expect(result).toBeDefined(); }); // 使用 Promise it('should return user', () => { return service.findOne(1).then(result => { expect(result).toBeDefined(); }); }); // 使用 done 回调 it('should return user', (done) => { service.findOne(1).then(result => { expect(result).toBeDefined(); done(); }); }); ``` ### 7. 测试边界情况 确保测试覆盖各种边界情况和错误场景。 ```typescript describe('findOne', () => { it('should return user when found', async () => { // 正常情况 }); it('should throw NotFoundException when user not found', async () => { // 用户不存在的情况 }); it('should throw BadRequestException when id is invalid', async () => { // 无效 ID 的情况 }); }); ``` ### 8. 使用测试数据库 在集成测试中使用测试数据库,避免影响生产数据。 ```typescript beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'sqlite', database: ':memory:', entities: [User], synchronize: true, }), ], }).compile(); }); ``` ## 性能测试 ### 1. 负载测试 使用工具如 Artillery 或 k6 进行负载测试。 ```javascript // artillery.config.js module.exports = { config: { target: 'http://localhost:3000', phases: [ { duration: 60, arrivalRate: 10 }, ], }, scenarios: [ { flow: [ { get: { url: '/users' } }, ], }, ], }; ``` ### 2. 响应时间测试 确保 API 响应时间在可接受范围内。 ```typescript it('should respond within 200ms', async () => { const start = Date.now(); await request(app.getHttpServer()).get('/users'); const duration = Date.now() - start; expect(duration).toBeLessThan(200); }); ``` ## 代码质量最佳实践 ### 1. 代码风格 使用 ESLint 和 Prettier 保持代码风格一致。 ```bash npm run lint npm run format ``` ### 2. 类型安全 充分利用 TypeScript 的类型系统。 ```typescript interface CreateUserDto { name: string; email: string; } ``` ### 3. 错误处理 统一错误处理机制。 ```typescript @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { // 统一错误处理逻辑 } } ``` ### 4. 日志记录 使用 NestJS Logger 进行结构化日志记录。 ```typescript import { Logger } from '@nestjs/common'; export class UsersService { private readonly logger = new Logger(UsersService.name); async findAll() { this.logger.log('Finding all users'); // 业务逻辑 } } ``` ### 5. 环境配置 使用 @nestjs/config 管理环境变量。 ```typescript import { ConfigService } from '@nestjs/config'; export class UsersService { constructor(private configService: ConfigService) { const apiKey = this.configService.get('API_KEY'); } } ``` ### 6. 文档化 使用 Swagger 生成 API 文档。 ```typescript import { ApiTags, ApiOperation } from '@nestjs/swagger'; @ApiTags('users') @Controller('users') export class UsersController { @Get() @ApiOperation({ summary: 'Get all users' }) findAll() { return this.usersService.findAll(); } } ``` ## 总结 NestJS 测试和最佳实践提供了: * 完整的测试支持框架 * 灵活的测试工具和实用程序 * 清晰的测试组织结构 * 高质量的代码标准 * 良好的开发体验 掌握测试和最佳实践是构建高质量 NestJS 应用程序的关键。通过全面的测试覆盖和遵循最佳实践,可以确保应用程序的可靠性、可维护性和可扩展性。测试不仅仅是质量保证,更是开发过程中的重要组成部分。
服务端 · 2月17日 22:36
NestJS WebSocket 和实时功能如何实现?## WebSocket 概述 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。NestJS 通过 `@nestjs/websockets` 和 `@nestjs/platform-socket.io` 包提供了对 WebSocket 的完整支持,使开发者能够轻松构建实时应用程序。 ## NestJS WebSocket 基础 ### 安装依赖 ```bash npm install @nestjs/websockets @nestjs/platform-socket.io ``` ### 创建 WebSocket 网关 网关(Gateway)是 NestJS 中处理 WebSocket 连接的类,类似于控制器处理 HTTP 请求。 ```typescript import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: '*', }, }) export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; afterInit(server: Server) { console.log('WebSocket server initialized'); } handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); } handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); } @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { this.server.emit('message', payload); } } ``` ### 注册网关 ```typescript import { Module } from '@nestjs/common'; import { ChatGateway } from './chat.gateway'; @Module({ providers: [ChatGateway], }) export class ChatModule {} ``` ## WebSocket 网关装饰器 ### @WebSocketGateway() 配置 WebSocket 网关。 ```typescript @WebSocketGateway({ namespace: '/chat', // 命名空间 cors: { // CORS 配置 origin: '*', }, path: '/ws', // 路径 transports: ['websocket'], // 传输方式 }) export class ChatGateway {} ``` ### @WebSocketServer() 注入 WebSocket 服务器实例。 ```typescript @WebSocketServer() server: Server; ``` ### @SubscribeMessage() 订阅 WebSocket 消息。 ```typescript @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { // 处理消息 } ``` ### @MessageBody() 提取消息体。 ```typescript @SubscribeMessage('message') handleMessage(@MessageBody() data: any): void { console.log(data); } ``` ### @ConnectedSocket() 获取连接的 Socket 实例。 ```typescript @SubscribeMessage('message') handleMessage(@ConnectedSocket() client: Socket): void { console.log(client.id); } ``` ## 网关生命周期钩子 ### OnGatewayInit ```typescript afterInit(server: Server) { console.log('Gateway initialized'); } ``` ### OnGatewayConnection ```typescript handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); } ``` ### OnGatewayDisconnect ```typescript handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); } ``` ## 实时聊天应用示例 ### 聊天网关 ```typescript import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; interface Message { user: string; text: string; timestamp: Date; } @WebSocketGateway({ cors: { origin: '*', }, }) export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private connectedClients: Map<string, Socket> = new Map(); handleConnection(client: Socket) { this.connectedClients.set(client.id, client); this.server.emit('user-joined', { userId: client.id, userCount: this.connectedClients.size, }); } handleDisconnect(client: Socket) { this.connectedClients.delete(client.id); this.server.emit('user-left', { userId: client.id, userCount: this.connectedClients.size, }); } @SubscribeMessage('join-room') handleJoinRoom(client: Socket, room: string): void { client.join(room); client.emit('joined-room', room); } @SubscribeMessage('leave-room') handleLeaveRoom(client: Socket, room: string): void { client.leave(room); client.emit('left-room', room); } @SubscribeMessage('send-message') handleMessage(client: Socket, payload: Message): void { const message: Message = { ...payload, timestamp: new Date(), }; this.server.emit('message', message); } @SubscribeMessage('private-message') handlePrivateMessage(client: Socket, payload: { to: string; message: Message }): void { const recipient = this.connectedClients.get(payload.to); if (recipient) { recipient.emit('private-message', payload.message); } } } ``` ### 前端客户端 ```typescript import { io, Socket } from 'socket.io-client'; class ChatClient { private socket: Socket; constructor(url: string) { this.socket = io(url); } connect() { this.socket.on('connect', () => { console.log('Connected to server'); }); this.socket.on('message', (message: Message) => { console.log('Received message:', message); }); this.socket.on('user-joined', (data) => { console.log('User joined:', data); }); this.socket.on('user-left', (data) => { console.log('User left:', data); }); } sendMessage(message: Message) { this.socket.emit('send-message', message); } sendPrivateMessage(to: string, message: Message) { this.socket.emit('private-message', { to, message }); } joinRoom(room: string) { this.socket.emit('join-room', room); } leaveRoom(room: string) { this.socket.emit('leave-room', room); } disconnect() { this.socket.disconnect(); } } ``` ## 使用 WebSocket 守卫 ```typescript import { WebSocketGateway, SubscribeMessage, OnGatewayConnection } from '@nestjs/websockets'; import { UseGuards } from '@nestjs/common'; import { WsGuard } from './ws.guard'; @WebSocketGateway() @UseGuards(WsGuard) export class ChatGateway implements OnGatewayConnection { @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { // 只有通过验证的客户端才能处理消息 } } ``` ### WebSocket 守卫示例 ```typescript import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { Socket } from 'socket.io'; @Injectable() export class WsGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const client: Socket = context.switchToWs().getClient(); const token = client.handshake.auth.token; if (!token) { throw new UnauthorizedException(); } // 验证 token return this.validateToken(token); } private validateToken(token: string): boolean { // 实现 token 验证逻辑 return true; } } ``` ## 使用 WebSocket 拦截器 ```typescript import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets'; import { UseInterceptors } from '@nestjs/common'; import { LoggingInterceptor } from './logging.interceptor'; @WebSocketGateway() @UseInterceptors(LoggingInterceptor) export class ChatGateway { @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { // 消息会被拦截器处理 } } ``` ## 使用 WebSocket 管道 ```typescript import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets'; import { UsePipes } from '@nestjs/common'; import { ValidationPipe } from './validation.pipe'; @WebSocketGateway() @UsePipes(new ValidationPipe()) export class ChatGateway { @SubscribeMessage('message') handleMessage(@MessageBody() message: Message): void { // 消息会被管道验证 } } ``` ## 实时通知系统 ### 通知网关 ```typescript import { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; interface Notification { type: 'info' | 'warning' | 'error' | 'success'; title: string; message: string; timestamp: Date; } @WebSocketGateway({ namespace: '/notifications', cors: { origin: '*' }, }) export class NotificationGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private userSockets: Map<string, Set<string>> = new Map(); handleConnection(client: Socket) { const userId = client.handshake.query.userId as string; if (!this.userSockets.has(userId)) { this.userSockets.set(userId, new Set()); } this.userSockets.get(userId).add(client.id); } handleDisconnect(client: Socket) { const userId = client.handshake.query.userId as string; const sockets = this.userSockets.get(userId); if (sockets) { sockets.delete(client.id); if (sockets.size === 0) { this.userSockets.delete(userId); } } } sendNotificationToUser(userId: string, notification: Notification) { const sockets = this.userSockets.get(userId); if (sockets) { sockets.forEach(socketId => { this.server.to(socketId).emit('notification', notification); }); } } sendBroadcastNotification(notification: Notification) { this.server.emit('notification', notification); } } ``` ### 在服务中使用通知网关 ```typescript import { Injectable } from '@nestjs/common'; import { NotificationGateway } from './notification.gateway'; @Injectable() export class NotificationService { constructor(private notificationGateway: NotificationGateway) {} sendOrderNotification(userId: string, orderId: string) { this.notificationGateway.sendNotificationToUser(userId, { type: 'info', title: '订单更新', message: `您的订单 ${orderId} 已更新`, timestamp: new Date(), }); } sendSystemAlert(message: string) { this.notificationGateway.sendBroadcastNotification({ type: 'warning', title: '系统通知', message, timestamp: new Date(), }); } } ``` ## 最佳实践 1. **命名空间**:使用命名空间组织不同类型的 WebSocket 连接 2. **房间**:使用房间功能管理用户组 3. **认证**:在连接时进行身份验证 4. **错误处理**:妥善处理连接错误和消息错误 5. **资源清理**:在断开连接时清理资源 6. **消息验证**:使用管道验证传入的消息 7. **日志记录**:记录连接和断开事件 8. **性能监控**:监控连接数和消息吞吐量 ## 总结 NestJS WebSocket 和实时功能提供了: * 完整的 WebSocket 支持 * 灵活的网关系统 * 丰富的装饰器和钩子 * 与 NestJS 生态系统的无缝集成 * 易于构建实时应用程序 掌握 NestJS WebSocket 功能是构建实时应用程序的关键。通过合理使用网关、命名空间、房间和认证机制,可以构建出高性能、可扩展的实时应用,如聊天应用、实时通知系统、协作工具等。
服务端 · 2月17日 22:35
NestJS 中间件和守卫的区别是什么?## 中间件(Middleware)的概念 中间件是在路由处理程序之前调用的函数,可以访问请求和响应对象,以及应用程序的请求-响应周期中的 `next()` 中间件函数。中间件主要用于: * 执行任何代码 * 更改请求和响应对象 * 结束请求-响应周期 * 调用堆栈中的下一个中间件函数 ### 中间件的基本结构 ```typescript import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.url}`); next(); } } ``` ### 应用中间件 #### 在模块中应用中间件 ```typescript import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { LoggerMiddleware } from './logger.middleware'; @Module({ imports: [], controllers: [], providers: [], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); } } ``` #### 限制中间件应用到特定路由 ```typescript configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude('cats', { path: 'cats', method: RequestMethod.GET }) .forRoutes(CatsController); } ``` #### 应用多个中间件 ```typescript configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware, AuthMiddleware) .forRoutes(CatsController); } ``` ### 函数式中间件 ```typescript export function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); } // 应用函数式中间件 configure(consumer: MiddlewareConsumer) { consumer .apply(logger) .forRoutes(CatsController); } ``` ### 全局中间件 ```typescript import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { logger } from './common/middleware/logger.middleware'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(3000); } bootstrap(); ``` ## 守卫(Guards)的概念 守卫是一个使用 `@Injectable()` 装饰器的类,实现了 `CanActivate` 接口。守卫负责确定请求是否应该由路由处理程序处理。主要用于: * 身份验证 * 授权 * 权限检查 ### 守卫的基本结构 ```typescript import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return this.validateRequest(request); } private validateRequest(request: any): boolean { // 验证逻辑 return true; } } ``` ### 使用守卫 #### 在控制器上使用守卫 ```typescript @Controller('cats') @UseGuards(AuthGuard) export class CatsController { @Get() findAll() { return 'This action returns all cats'; } } ``` #### 在特定路由上使用守卫 ```typescript @Controller('cats') export class CatsController { @Get() @UseGuards(AuthGuard) findAll() { return 'This action returns all cats'; } } ``` #### 全局守卫 ```typescript import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth.guard'; @Module({ providers: [ { provide: APP_GUARD, useClass: AuthGuard, }, ], }) export class AppModule {} ``` ### 角色守卫示例 ```typescript import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); } } // 创建角色装饰器 import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); // 使用角色守卫 @Controller('cats') @UseGuards(RolesGuard) export class CatsController { @Get() @Roles('admin') findAll() { return 'This action returns all cats'; } } ``` ## 中间件 vs 守卫 ### 中间件的特点 * 在 Express 中间件链中执行 * 可以访问请求和响应对象 * 在路由处理程序之前执行 * 适用于全局逻辑(如日志记录、CORS) * 不了解路由处理程序 ### 守卫的特点 * 在 NestJS 依赖注入系统中执行 * 可以访问 ExecutionContext * 在中间件之后、拦截器之前执行 * 适用于权限和授权逻辑 * 了解路由处理程序和类 ### 选择指南 * 使用中间件:日志记录、请求解析、CORS、压缩等 * 使用守卫:身份验证、授权、权限检查等 ## 自定义装饰器 ### 从请求中提取用户 ```typescript import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( (data: string | undefined, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; return data ? user?.[data] : user; }, ); // 使用自定义装饰器 @Get() findOne(@User('id') userId: string) { return this.catsService.findOne(userId); } ``` ## JWT 认证守卫示例 ```typescript import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class JwtAuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { throw new UnauthorizedException(); } try { const payload = await this.jwtService.verifyAsync(token); request['user'] = payload; } catch { throw new UnauthorizedException(); } return true; } private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } } ``` ## 最佳实践 1. **职责分离**:中间件用于全局逻辑,守卫用于权限控制 2. **使用装饰器**:创建自定义装饰器简化守卫使用 3. **错误处理**:在守卫中抛出适当的异常 4. **性能考虑**:避免在守卫中执行耗时操作 5. **测试覆盖**:为中间件和守卫编写测试 6. **文档化**:为中间件和守卫添加清晰的文档 7. **避免过度使用**:只在需要时使用中间件和守卫 ## 总结 NestJS 中间件和守卫系统提供了: * 灵活的请求处理机制 * 强大的权限控制能力 * 清晰的关注点分离 * 易于测试和维护的代码结构 掌握中间件和守卫是构建安全、可维护的 NestJS 应用程序的关键。中间件处理全局逻辑,守卫处理权限控制,它们共同构成了应用程序的安全和功能基础。
服务端 · 2月17日 22:34
NestJS 微服务和架构如何设计?## 微服务架构概述 微服务架构是一种将应用程序构建为一组小型、独立服务的方法,每个服务运行在自己的进程中,通过轻量级机制(通常是 HTTP API 或消息队列)进行通信。NestJS 提供了完整的微服务支持,使开发者能够轻松构建可扩展的分布式系统。 ## NestJS 微服务基础 ### 安装依赖 ```bash npm install @nestjs/microservices ``` ### 创建微服务 ```typescript import { NestFactory } from '@nestjs/core'; import { Transport, MicroserviceOptions } from '@nestjs/microservices'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>( AppModule, { transport: Transport.TCP, options: { host: '127.0.0.1', port: 8877, }, }, ); await app.listen(); } bootstrap(); ``` ### 混合应用(HTTP + 微服务) ```typescript import { NestFactory } from '@nestjs/core'; import { Transport, MicroserviceOptions } from '@nestjs/microservices'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); const microservice = app.connectMicroservice<MicroserviceOptions>({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 8877, }, }); await app.startAllMicroservices(); await app.listen(3000); } bootstrap(); ``` ## 消息模式 ### 1. 消息模式(Message Pattern) ```typescript import { Controller } from '@nestjs/common'; import { EventPattern, Payload, Ctx, RmqContext } from '@nestjs/microservices'; @Controller() export class MathController { @MessagePattern({ cmd: 'sum' }) accumulate(@Payload() data: number[]): number { return (data || []).reduce((a, b) => a + b, 0); } } ``` ### 2. 事件模式(Event Pattern) ```typescript import { Controller } from '@nestjs/common'; import { EventPattern, Payload } from '@nestjs/microservices'; @Controller() export class NotificationController { @EventPattern('user_created') async handleUserCreated(@Payload() data: Record<string, unknown>) { // 处理用户创建事件 } } ``` ## 传输层 ### 1. TCP 传输 ```typescript { transport: Transport.TCP, options: { host: '127.0.0.1', port: 8877, }, } ``` ### 2. Redis 传输 ```typescript { transport: Transport.REDIS, options: { host: 'localhost', port: 6379, }, } ``` ### 3. NATS 传输 ```typescript { transport: Transport.NATS, options: { url: 'nats://localhost:4222', }, } ``` ### 4. MQTT 传输 ```typescript { transport: Transport.MQTT, options: { url: 'mqtt://localhost:1883', }, } ``` ### 5. RabbitMQ 传输 ```typescript { transport: Transport.RMQ, options: { urls: ['amqp://localhost:5672'], queue: 'cats_queue', queueOptions: { durable: false, }, }, } ``` ### 6. Kafka 传输 ```typescript { transport: Transport.KAFKA, options: { client: { brokers: ['localhost:9092'], }, consumer: { groupId: 'my-consumer', }, }, } ``` ## 客户端(Client) ### 创建客户端代理 ```typescript import { Controller, Get } from '@nestjs/common'; import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices'; @Controller() export class AppController { private client: ClientProxy; constructor() { this.client = ClientProxyFactory.create({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 8877, }, }); } @Get() async getSum() { return this.client.send({ cmd: 'sum' }, [1, 2, 3, 4, 5]); } } ``` ### 使用模块配置客户端 ```typescript import { Module } from '@nestjs/common'; import { ClientsModule, Transport } from '@nestjs/microservices'; @Module({ imports: [ ClientsModule.register([ { name: 'MATH_SERVICE', transport: Transport.TCP, options: { host: '127.0.0.1', port: 8877, }, }, ]), ], controllers: [AppController], }) export class AppModule {} ``` ### 注入客户端 ```typescript import { Controller, Get, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; @Controller() export class AppController { constructor(@Inject('MATH_SERVICE') private client: ClientProxy) {} @Get() async getSum() { return this.client.send({ cmd: 'sum' }, [1, 2, 3, 4, 5]); } } ``` ## 消息确认 ### 手动确认 ```typescript import { Controller } from '@nestjs/common'; import { EventPattern, Payload, Ctx, RmqContext } from '@nestjs/microservices'; @Controller() export class NotificationController { @EventPattern('notification_created') async handleNotificationCreated( @Payload() data: any, @Ctx() context: RmqContext, ) { const channel = context.getChannelRef(); const originalMsg = context.getMessage(); // 处理消息 await this.processNotification(data); // 手动确认消息 channel.ack(originalMsg); } } ``` ### 配置预取计数 ```typescript { transport: Transport.RMQ, options: { urls: ['amqp://localhost:5672'], queue: 'notifications_queue', prefetchCount: 10, queueOptions: { durable: false, }, }, } ``` ## 微服务架构模式 ### 1. API 网关模式 ```typescript import { Controller, Get, Param, Post, Body } from '@nestjs/common'; import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices'; @Controller('api') export class ApiController { private userService: ClientProxy; private orderService: ClientProxy; constructor() { this.userService = ClientProxyFactory.create({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 8877 }, }); this.orderService = ClientProxyFactory.create({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 8878 }, }); } @Get('users/:id') getUser(@Param('id') id: string) { return this.userService.send({ cmd: 'get_user' }, { id }); } @Post('orders') createOrder(@Body() orderData: any) { return this.orderService.send({ cmd: 'create_order' }, orderData); } } ``` ### 2. 事件驱动架构 ```typescript // 订单服务 @Controller() export class OrderController { @EventPattern('order_created') async handleOrderCreated(@Payload() order: any) { // 处理订单创建事件 await this.sendNotification(order); await this.updateInventory(order); } } // 通知服务 @Controller() export class NotificationController { @EventPattern('order_created') async handleOrderCreated(@Payload() order: any) { // 发送通知 await this.sendEmail(order.userEmail, 'Order created'); } } // 库存服务 @Controller() export class InventoryController { @EventPattern('order_created') async handleOrderCreated(@Payload() order: any) { // 更新库存 await this.reduceStock(order.items); } } ``` ### 3. CQRS 模式 ```typescript // Command Handler @Controller() export class OrderCommandController { @MessagePattern({ cmd: 'create_order' }) async createOrder(@Payload() command: CreateOrderCommand) { return this.commandBus.execute(command); } } // Query Handler @Controller() export class OrderQueryController { @MessagePattern({ cmd: 'get_order' }) async getOrder(@Payload() query: GetOrderQuery) { return this.queryBus.execute(query); } } ``` ## 服务发现 ### 使用 Consul ```typescript import { Module } from '@nestjs/common'; import { ClientsModule, Transport } from '@nestjs/microservices'; @Module({ imports: [ ClientsModule.register([ { name: 'USER_SERVICE', transport: Transport.TCP, options: { host: 'user-service', port: 3000, }, }, ]), ], }) export class AppModule {} ``` ### 使用 Kubernetes Service Discovery ```typescript { transport: Transport.TCP, options: { host: process.env.USER_SERVICE_HOST || 'user-service', port: parseInt(process.env.USER_SERVICE_PORT) || 3000, }, } ``` ## 分布式追踪 ### 集成 OpenTelemetry ```typescript import { Controller } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; import { trace } from '@opentelemetry/api'; @Controller() export class OrderController { @MessagePattern({ cmd: 'create_order' }) async createOrder(@Payload() data: any) { const tracer = trace.getTracer('order-service'); const span = tracer.startSpan('create_order'); try { const result = await this.orderService.create(data); span.end(); return result; } catch (error) { span.recordException(error); span.end(); throw error; } } } ``` ## 最佳实践 1. **服务边界**:明确定义服务边界和职责 2. **异步通信**:使用异步消息进行服务间通信 3. **幂等性**:确保操作是幂等的,可以安全重试 4. **错误处理**:实现适当的错误处理和重试机制 5. **监控**:实施全面的监控和日志记录 6. **配置管理**:使用配置中心管理服务配置 7. **版本控制**:实现 API 版本控制策略 8. **测试**:编写集成测试和契约测试 ## 总结 NestJS 微服务和架构提供了: * 完整的微服务支持 * 多种传输层选择 * 灵活的消息模式 * 强大的客户端代理 * 易于构建分布式系统 掌握 NestJS 微服务架构是构建可扩展、可维护的企业级应用的关键。通过合理使用微服务模式、事件驱动架构和最佳实践,可以构建出高性能、可靠的分布式系统。微服务架构使团队能够独立开发、部署和扩展各个服务,提高开发效率和系统弹性。
服务端 · 2月17日 22:33