下载工作台
PaaS 组件实战

NestJS / Gin 框架集成实战

试读上半部分 · 解锁后可读全文

第 17 章 · NestJS / Gin 框架集成实战

本章与 第 13 章 Spring Boot第 16 章 Django/Flask/FastAPI 同级,覆盖 Node.js(NestJS)Go(Gin) 在 Kubernetes 内对接全部 PaaS 组件的生产级写法。

框架语言典型场景进程
NestJSTypeScript企业 API、微服务、与前端同构 TSNode dist/main.js
GinGo高性能网关、云原生微服务、低资源占用单二进制

前置:第 2~5 章组件已部署;镜像经第 11 章 Registry 推送。Go 语言基础见 go-dev;Gin 工程化开发见 gin-web ch01~ch15(本章在 K8s 内对接 PaaS 组件,与语言课分工见 gin-web ch17)。


17.1 工程结构对照

nestjs-shop/                    gin-shop/
├── src/                        ├── cmd/server/
│   ├── main.ts                 │   └── main.go
│   ├── app.module.ts           ├── internal/
│   ├── orders/                 │   ├── handler/
│   └── health/                 │   ├── service/
├── package.json                │   └── config/
├── Dockerfile                  ├── go.mod
└── nest-cli.json               └── Dockerfile

集群 DNS(与第 16 章相同):

mysql.data.svc.cluster.local:3306
redis.data.svc.cluster.local:6379
rabbitmq.middleware.svc.cluster.local:5672
kafka.middleware.svc.cluster.local:9092
rocketmq-namesrv.middleware.svc.cluster.local:9876
elasticsearch.middleware.svc.cluster.local:9200
minio.data.svc.cluster.local:9000
nacos.middleware.svc.cluster.local:8848
keycloak.security.svc.cluster.local:8080

17.2 依赖对照

能力NestJS 包Gin / Go 模块
ORM / MySQL@nestjs/typeorm + mysql2gorm.io/gorm + gorm.io/driver/mysql
Redis@nestjs/cache-manager + cache-manager-redis-yetgithub.com/redis/go-redis/v9
MongoDBmongoose / mongodbgo.mongodb.org/mongo-driver
消息队列@nestjs/microservices + kafkajsgithub.com/IBM/sarama
RabbitMQ@nestjs/bull + bullgithub.com/rabbitmq/amqp091-go
RocketMQrocketmq-client-nodejsgithub.com/apache/rocketmq-client-go/v2
ES@elastic/elasticsearchgithub.com/elastic/go-elasticsearch/v8
MinIO@aws-sdk/client-s3github.com/minio/minio-go/v7
配置@nestjs/configgithub.com/spf13/viper
Nacosnacos npm SDKgithub.com/nacos-group/nacos-sdk-go
OIDCpassport + openid-clientgithub.com/coreos/go-oidc/v3
限流@nestjs/throttlergithub.com/ulule/limiter/v3
指标@willsoto/nestjs-prometheusgithub.com/prometheus/client_golang

17.3 NestJS 完整集成

17.3.1 package.json 核心依赖

{
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/config": "^3.2.0",
    "@nestjs/typeorm": "^10.0.0",
    "@nestjs/cache-manager": "^2.2.0",
    "@nestjs/bull": "^10.1.0",
    "@nestjs/throttler": "^5.1.0",
    "@willsoto/nestjs-prometheus": "^6.0.0",
    "typeorm": "^0.3.20",
    "mysql2": "^3.9.0",
    "cache-manager-redis-yet": "^5.0.0",
    "bull": "^4.12.0",
    "kafkajs": "^2.2.4",
    "rocketmq-client-nodejs": "^1.0.0",
    "@elastic/elasticsearch": "^8.14.0",
    "@aws-sdk/client-s3": "^3.600.0",
    "nacos": "^2.6.0"
  }
}

17.3.2 app.module.ts(配置 + 中间件全集)

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule } from '@nestjs/cache-manager';
import { BullModule } from '@nestjs/bull';
import { ThrottlerModule } from '@nestjs/throttler';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { redisStore } from 'cache-manager-redis-yet';
import { OrdersModule } from './orders/orders.module';
import { HealthController } from './health/health.controller';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    ThrottlerModule.forRoot([{ ttl: 60000, limit: 100 }]),
    PrometheusModule.register(),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => ({
        type: 'mysql',
        host: cfg.get('MYSQL_HOST', 'mysql.data.svc.cluster.local'),
        port: +cfg.get('MYSQL_PORT', 3306),
        username: cfg.get('MYSQL_USER', 'app'),
        password: cfg.get('MYSQL_PASSWORD'),
        database: cfg.get('MYSQL_DATABASE', 'appdb'),
        autoLoadEntities: true,
        synchronize: false,
      }),
    }),
    CacheModule.registerAsync({
      isGlobal: true,
      inject: [ConfigService],
      useFactory: async (cfg: ConfigService) => ({
        store: await redisStore({ url: cfg.get('REDIS_URL') }),
      }),
    }),
    BullModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => ({
        redis: cfg.get('REDIS_URL'),
        // 或 RabbitMQ:createClient URL amqp://...
      }),
    }),
    OrdersModule,
  ],
  controllers: [HealthController],
})
export class AppModule {}

17.3.3 业务服务示例 orders.service.ts

import { Injectable, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
import { Client, Producer } from 'rocketmq-client-nodejs';
import { Kafka } from 'kafkajs';
import { Client as EsClient } from '@elastic/elasticsearch';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { ConfigService } from '@nestjs/config';
import { Order } from './order.entity';

@Injectable()
export class OrdersService {
  private kafka = new Kafka({
    brokers: [this.cfg.get('KAFKA_BOOTSTRAP', 'kafka.middleware:9092')],
  });
  private es = new EsClient({
    node: this.cfg.get('ES_URL', 'http://elasticsearch.middleware:9200'),
  });

  constructor(
    @InjectRepository(Order) private repo: Repository<Order>,
    @Inject(CACHE_MANAGER) private cache: Cache,
    @InjectQueue('notify') private notifyQueue: Queue,
    private cfg: ConfigService,
  ) {}

  async create(dto: Partial<Order>) {
    const order = await this.repo.save(dto);
    await this.cache.set(`order:${order.id}`, order, 3600);
    await this.notifyQueue.add('email', { orderId: order.id });
    const producer = this.kafka.producer();
    await producer.connect();
    await producer.send({
      topic: 'user-events',
      messages: [{ value: JSON.stringify({ event: 'order_created', id: order.id }) }],
    });
    await producer.disconnect();
    return order;
  }

  async searchProducts(q: string) {
    const res = await this.es.search({
      index: 'products',
      query: { match: { name: q } },
    });
    return res.hits.hits;
  }

  async uploadToMinio(key: string, body: Buffer) {
    const s3 = new S3Client({
      endpoint: this.cfg.get('MINIO_ENDPOINT'),
      region: 'us-east-1',
      credentials: {
        accessKeyId: this.cfg.get('MINIO_ACCESS_KEY')!,
        secretAccessKey: this.cfg.get('MINIO_SECRET_KEY')!,
      },
      forcePathStyle: true,
    });
    await s3.send(new PutObjectCommand({
      Bucket: this.cfg.get('MINIO_BUCKET', 'uploads'),
      Key: key,
      Body: body,
    }));
  }
}

17.3.4 main.ts + 健康检查

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT || 3000);
}
bootstrap();
// health.controller.ts
@Controller('health')
export class HealthController {
  @Get()
  ok() { return { status: 'ok' }; }
}

17.3.5 Dockerfile 与 Deployment

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3000

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。