main.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { NestFactory } from '@nestjs/core';
  2. import { Logger, ValidationPipe } from '@nestjs/common';
  3. import { ConfigService } from '@nestjs/config';
  4. import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
  5. import { AppModule } from './app.module';
  6. async function bootstrap() {
  7. const app = await NestFactory.create(AppModule, {
  8. bufferLogs: true,
  9. });
  10. const logger = new Logger('Bootstrap');
  11. app.useLogger(logger);
  12. const configService = app.get(ConfigService);
  13. const host =
  14. configService.get<string>('STATS_HOST') ??
  15. configService.get<string>('HOST') ??
  16. '0.0.0.0';
  17. const port =
  18. configService.get<number>('STATS_PORT') ??
  19. Number(process.env.STATS_PORT ?? 3302);
  20. const corsOrigin = configService.get<string>('STATS_CORS_ORIGIN') ?? '*';
  21. const corsOriginOption =
  22. corsOrigin === '*'
  23. ? true
  24. : corsOrigin
  25. .split(',')
  26. .map((o) => o.trim())
  27. .filter(Boolean);
  28. app.enableCors({
  29. origin: corsOriginOption,
  30. methods: 'GET,PUT,PATCH,POST,DELETE',
  31. credentials: true,
  32. });
  33. // 👇 Important: this makes /health become /api/v1/health
  34. app.setGlobalPrefix('api/v1', {
  35. exclude: ['/'],
  36. });
  37. app.useGlobalPipes(
  38. new ValidationPipe({
  39. whitelist: true,
  40. transform: true,
  41. transformOptions: {
  42. enableImplicitConversion: true,
  43. },
  44. forbidNonWhitelisted: false,
  45. }),
  46. );
  47. // Setup Swagger (OpenAPI)
  48. const swaggerConfig = new DocumentBuilder()
  49. .setTitle('盒子统计接口文档')
  50. .setDescription(
  51. 'box-stats-api 的公开接口文档,面向前端应用和统计服务。包含用户登录历史等模块。',
  52. )
  53. .setVersion('1.0.0')
  54. .build();
  55. const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
  56. SwaggerModule.setup('api-docs', app, swaggerDocument, {
  57. jsonDocumentUrl: '/api-docs-json',
  58. swaggerOptions: {
  59. persistAuthorization: true,
  60. docExpansion: 'none',
  61. },
  62. });
  63. // Prevent Swagger UI/JSON from being cached (browser + proxies)
  64. app.use(['/api-docs', '/api-docs-json'], (req, res, next) => {
  65. res.setHeader(
  66. 'Cache-Control',
  67. 'no-store, no-cache, must-revalidate, proxy-revalidate',
  68. );
  69. res.setHeader('Pragma', 'no-cache');
  70. res.setHeader('Expires', '0');
  71. next();
  72. });
  73. await app.listen(port, host);
  74. const url = `http://${host}:${port}`;
  75. logger.log(`🚀 box-stats-api listening on ${url} (global prefix: /api/v1)`);
  76. logger.log(`📖 Swagger 文档: ${url}/api-docs`);
  77. logger.log(`📄 Swagger JSON: ${url}/api-docs-json`);
  78. }
  79. bootstrap().catch((error) => {
  80. // eslint-disable-next-line no-console
  81. console.error('❌ Failed to bootstrap box-stats-api', error);
  82. process.exit(1);
  83. });