main.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import 'reflect-metadata';
  2. import { ValidationPipe } from '@nestjs/common';
  3. import { ConfigService } from '@nestjs/config';
  4. import { NestFactory } from '@nestjs/core';
  5. import {
  6. FastifyAdapter,
  7. NestFastifyApplication,
  8. } from '@nestjs/platform-fastify';
  9. import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
  10. import { Logger } from 'nestjs-pino';
  11. import multipart from '@fastify/multipart';
  12. import { AppModule } from './app.module';
  13. import fastifyStatic from '@fastify/static';
  14. import * as path from 'path';
  15. // @ts-expect-error: allow JSON.stringify(BigInt)
  16. BigInt.prototype.toJSON = function () {
  17. const value = this as bigint;
  18. if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
  19. return this.toString();
  20. }
  21. return Number(this);
  22. };
  23. async function bootstrap() {
  24. const fastifyAdapter = new FastifyAdapter() as FastifyAdapter;
  25. // multipart for file upload (e.g. OSS, S3)
  26. await fastifyAdapter.register(multipart as any, {
  27. limits: {
  28. fileSize: 30 * 1024 * 1024, // 30 MB
  29. },
  30. });
  31. // after creating fastifyAdapter but before NestFactory.create:
  32. // Read allowed origin from env (fallback to '*')
  33. const corsOrg = process.env.APP_CORS_ORIGIN || '*';
  34. // await fastifyAdapter.register(fastifyStatic as any, {
  35. // root: path.resolve(process.env.IMAGE_ROOT_PATH || '/data/box-images'),
  36. // prefix: '/images/',
  37. // setHeaders: (res: any, pathName: any, stat: any) => {
  38. // // CORS
  39. // res.setHeader('Access-Control-Allow-Origin', corsOrg);
  40. // res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
  41. // res.setHeader(
  42. // 'Access-Control-Allow-Headers',
  43. // 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
  44. // );
  45. // },
  46. // });
  47. const app = await NestFactory.create<NestFastifyApplication>(
  48. AppModule,
  49. fastifyAdapter,
  50. {
  51. bufferLogs: true,
  52. snapshot: true,
  53. },
  54. );
  55. // nestjs-pino logger
  56. app.useLogger(app.get(Logger));
  57. // Global API prefix; mgnt routes stay under /api/v1/mgnt/*
  58. app.setGlobalPrefix('api/v1');
  59. app.useGlobalPipes(
  60. new ValidationPipe({
  61. transform: true,
  62. whitelist: true,
  63. forbidNonWhitelisted: false,
  64. }),
  65. );
  66. const configService = app.get(ConfigService);
  67. const host =
  68. configService.get<string>('MGNT_HOST') ??
  69. configService.get<string>('MGNT_HOST') ??
  70. '0.0.0.0';
  71. const port =
  72. configService.get<number>('MGNT_PORT') ?? Number(process.env.PORT ?? 3300);
  73. const corsOrigin = configService.get<string>('MGNT_CORS_ORIGIN') ?? '*';
  74. const corsOriginOption =
  75. corsOrigin === '*'
  76. ? true
  77. : corsOrigin
  78. .split(',')
  79. .map((o) => o.trim())
  80. .filter(Boolean);
  81. app.enableCors({
  82. origin: corsOriginOption,
  83. methods: 'GET,PUT,PATCH,POST,DELETE',
  84. credentials: true,
  85. });
  86. if (process.env.NODE_ENV !== 'production') {
  87. const swaggerConfig = new DocumentBuilder()
  88. .setTitle('盒子管理系统管理后台 API 文档')
  89. .setDescription('盒子管理系统管理后台接口 (api/v1/mgnt/*)')
  90. .setVersion('1.0')
  91. .addBearerAuth()
  92. .addServer(`http://${host}:${port}`, '本地开发环境')
  93. .addTag('系统 - 授权', '管理后台授权相关接口 (mgnt/auth)')
  94. .addTag('系统 - 用户', '管理后台用户管理接口 (mgnt/users)')
  95. .addTag('系统 - 角色', '管理后台角色管理接口 (mgnt/roles)')
  96. .addTag('系统 - 菜单', '管理后台菜单管理接口 (mgnt/menus)')
  97. .build();
  98. const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
  99. SwaggerModule.setup('api-docs', app, swaggerDocument, {
  100. jsonDocumentUrl: '/api-docs-json',
  101. swaggerOptions: {
  102. docExpansion: 'none',
  103. tagsSorter: 'alpha',
  104. operationsSorter: 'alpha',
  105. },
  106. });
  107. // Prevent Swagger UI/JSON from being cached (browser + proxies)
  108. const fastify = app.getHttpAdapter().getInstance();
  109. fastify.addHook('onSend', (request, reply, payload, done) => {
  110. if (
  111. request.url?.startsWith('/api-docs') ||
  112. request.url === '/api-docs-json'
  113. ) {
  114. reply.header(
  115. 'Cache-Control',
  116. 'no-store, no-cache, must-revalidate, proxy-revalidate',
  117. );
  118. reply.header('Pragma', 'no-cache');
  119. reply.header('Expires', '0');
  120. }
  121. done(null, payload);
  122. });
  123. }
  124. await app.listen(port, host);
  125. const url = `http://${host}:${port}`;
  126. console.log(`🚀 box-mgnt-api is running at: ${url}`);
  127. console.log(`📚 API Documentation: ${url}/api-docs`);
  128. console.log(`📄 Swagger JSON: ${url}/api-docs-json`);
  129. console.log(`🔧 Management APIs: ${url}/api/v1/mgnt/*`);
  130. }
  131. bootstrap();