main.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { NestFactory } from '@nestjs/core';
  2. import { Logger, ValidationPipe } from '@nestjs/common';
  3. import { ConfigService } from '@nestjs/config';
  4. import helmet from 'helmet';
  5. import compression from 'compression';
  6. import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
  7. // import * as path from 'path';
  8. // import * as express from 'express';
  9. import { AppModule } from './app.module';
  10. async function bootstrap() {
  11. const app = await NestFactory.create(AppModule, {
  12. bufferLogs: true,
  13. });
  14. const logger = new Logger('Bootstrap');
  15. app.useLogger(logger);
  16. const configService = app.get(ConfigService);
  17. const host =
  18. configService.get<string>('APP_HOST') ??
  19. configService.get<string>('HOST') ??
  20. '0.0.0.0';
  21. const port =
  22. configService.get<number>('APP_PORT') ??
  23. Number(process.env.APP_PORT ?? 3301);
  24. const corsOrigin = configService.get<string>('APP_CORS_ORIGIN') ?? '*';
  25. const corsOriginOption =
  26. corsOrigin === '*'
  27. ? true
  28. : corsOrigin
  29. .split(',')
  30. .map((o) => o.trim())
  31. .filter(Boolean);
  32. app.enableCors({
  33. origin: corsOriginOption,
  34. methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'],
  35. credentials: true,
  36. });
  37. // const imageRoot = path.resolve(
  38. // configService.get<string>('IMAGE_ROOT_PATH') || '/data/box-images',
  39. // );
  40. // app.use(
  41. // '/images',
  42. // express.static(imageRoot, {
  43. // setHeaders: (res: any) => {
  44. // res.setHeader('Access-Control-Allow-Origin', corsOrigin);
  45. // res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
  46. // res.setHeader(
  47. // 'Access-Control-Allow-Headers',
  48. // 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
  49. // );
  50. // },
  51. // }),
  52. // );
  53. // 👇 Important: this makes /health become /api/v1/health
  54. app.setGlobalPrefix('api/v1', {
  55. exclude: ['/'],
  56. });
  57. app.use(helmet());
  58. app.use(compression());
  59. app.useGlobalPipes(
  60. new ValidationPipe({
  61. whitelist: true,
  62. transform: true,
  63. transformOptions: {
  64. enableImplicitConversion: true,
  65. },
  66. forbidNonWhitelisted: false,
  67. }),
  68. );
  69. // Setup Swagger (OpenAPI)
  70. const swaggerConfig = new DocumentBuilder()
  71. .setTitle('盒子应用接口文档')
  72. .setDescription(
  73. 'box-app-api 的公开接口文档,面向前端应用。包含广告、视频、首页等模块。',
  74. )
  75. .setVersion('1.0.0')
  76. .build();
  77. const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
  78. SwaggerModule.setup('api-docs', app, swaggerDocument, {
  79. jsonDocumentUrl: '/api-docs-json',
  80. swaggerOptions: {
  81. persistAuthorization: true,
  82. docExpansion: 'none',
  83. },
  84. });
  85. // Prevent Swagger UI/JSON from being cached (browser + proxies)
  86. app.use(['/api-docs', '/api-docs-json'], (req, res, next) => {
  87. res.setHeader(
  88. 'Cache-Control',
  89. 'no-store, no-cache, must-revalidate, proxy-revalidate',
  90. );
  91. res.setHeader('Pragma', 'no-cache');
  92. res.setHeader('Expires', '0');
  93. next();
  94. });
  95. await app.listen(port, host);
  96. const url = `http://${host}:${port}`;
  97. logger.log(`🚀 box-app-api listening on ${url} (global prefix: /api/v1)`);
  98. logger.log(`📖 Swagger 文档: ${url}/api-docs`);
  99. logger.log(`📄 Swagger JSON: ${url}/api-docs-json`);
  100. }
  101. bootstrap().catch((error) => {
  102. // eslint-disable-next-line no-console
  103. console.error('❌ Failed to bootstrap box-app-api', error);
  104. process.exit(1);
  105. });