feat(api): 开发auth、login等接口,规范格式化

This commit is contained in:
R524809
2026-09-03 13:11:06 +08:00
parent 195b6d25e9
commit 037928c778
82 changed files with 9199 additions and 5236 deletions
+144 -5
View File
@@ -1,12 +1,151 @@
import 'reflect-metadata';
import { execFile } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import type { NextFunction, Request, Response } from 'express';
import helmet from 'helmet';
import { AppModule } from './app.module.js';
import { ApiErrorFilter } from './common/api-error.filter.js';
import { APP_CONFIG, type AppConfig } from './config/app-config.module.js';
import { AppLogger } from './logging/app-logger.js';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({ origin: process.env.WEB_ORIGIN ?? 'http://localhost:8100' });
await app.listen(Number(process.env.API_PORT ?? 8200));
type RequestWithId = Request & {
requestId?: string;
};
function openSwaggerInDevelopment(config: AppConfig, logger: AppLogger): void {
if (
config.NODE_ENV !== 'development' ||
!config.swaggerEnabled ||
process.platform !== 'darwin'
) {
return;
}
const swaggerUrl = `http://localhost:${config.API_PORT}/api-docs`;
// 只在本机开发时打开浏览器,生产环境和非 macOS 主机不执行外部进程。
execFile('open', [swaggerUrl], (error) => {
if (error) {
logger.warn('swagger_auto_open_failed', {
error: error.message,
url: swaggerUrl,
});
}
});
}
void bootstrap();
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bodyParser: false,
// Keep Nest's bootstrap errors visible until AppLogger is installed below.
logger: ['error', 'warn', 'log'],
});
const config = app.get<AppConfig>(APP_CONFIG);
const logger = new AppLogger(config.LOG_LEVEL);
app.useLogger(logger);
if (config.trustedProxyCidrs.length > 0) {
// Never trust forwarded headers unless the deployment names its proxy networks explicitly.
app.getHttpAdapter()
.getInstance()
.set('trust proxy', config.trustedProxyCidrs);
}
app.enableCors({
origin: config.WEB_ORIGIN,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: [
'Authorization',
'Content-Type',
'Idempotency-Key',
'If-Match',
'X-Atlas-Platform-Id',
'X-Request-Id',
],
exposedHeaders: ['X-Request-Id'],
});
app.use(helmet({ contentSecurityPolicy: false }));
app.useBodyParser('json', { limit: config.API_BODY_LIMIT_BYTES });
app.useBodyParser('urlencoded', {
limit: config.API_BODY_LIMIT_BYTES,
extended: true,
});
app.use(
(request: RequestWithId, response: Response, next: NextFunction) => {
const typedRequest = request;
const typedResponse = response;
const suppliedId = typedRequest.headers['x-request-id'];
const requestId =
typeof suppliedId === 'string' &&
/^[A-Za-z0-9_-]{8,128}$/.test(suppliedId)
? suppliedId
: `req_${randomUUID()}`;
const startedAt = performance.now();
typedRequest.requestId = requestId;
typedResponse.setHeader('X-Request-Id', requestId);
typedResponse.on('finish', () => {
logger.log('http_request_completed', {
request_id: requestId,
method: typedRequest.method,
path: typedRequest.originalUrl,
status_code: typedResponse.statusCode,
duration_ms: Math.round(performance.now() - startedAt),
});
});
next();
},
);
app.useGlobalFilters(new ApiErrorFilter(logger));
app.setGlobalPrefix('api', { exclude: ['health', 'ready'] });
app.enableShutdownHooks();
if (config.swaggerEnabled) {
const swaggerConfig = new DocumentBuilder()
.setTitle('Atlas Seller Kit API')
.setDescription('Atlas 卖家工作台 HTTP API 文档')
.setVersion('0.1.0')
.addBearerAuth(
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
'access-token',
)
.build();
const swaggerDocument = SwaggerModule.createDocument(
app,
swaggerConfig,
);
SwaggerModule.setup('api-docs', app, swaggerDocument, {
swaggerOptions: { persistAuthorization: true },
});
}
await app.listen(config.API_PORT);
logger.log('api_started', {
port: config.API_PORT,
swagger_enabled: config.swaggerEnabled,
});
openSwaggerInDevelopment(config, logger);
}
void bootstrap().catch((error: unknown) => {
const details =
error instanceof Error
? {
name: error.name,
message: error.message,
stack: error.stack,
}
: error;
console.error(
JSON.stringify({
timestamp: new Date().toISOString(),
level: 'error',
message: 'api_startup_failed',
error: details,
}),
);
process.exitCode = 1;
});