addresses: - 补 PATCH /:id、DELETE /:id(契约 §4 全路由齐备) - 默认地址唯一性事务:create/update/setDefault 先清旧默认再写入 - 首个地址自动设为默认;删除默认地址事务内补偿最新一条 - 归属校验统一:不存在 404,非本人 403 design-list: - 补 PATCH /:id、DELETE /:id、POST /batch-delete(宽松语义返回 deleted 数) - items 强制恰好 1 个元素(一条设计=一条清单,阶段0 决策#1) - designData 白名单 7 键放行、单条 ≤1MB 校验(400) - 单向状态机 DRAFT→SUBMITTED→PROCESSING→DONE,回退/跳级 400 - PATCH 部分更新语义,不传 items 不清 designData(R4 wordcloud 保护) infra: - main.ts: JSON body 上限 100KB→2MB,使契约 1MB 设计数据可达(>2MB 返回 413) - exceptions filter: body-parser entity.too.large 映射 413 - api-contract-v1.md §5 补实现说明(非契约变更) 验证:nest build 通过;本地 3091 实例 + mock 登录实测 35 项全过 (CRUD/默认地址补偿/越权 403/404/状态机/1MB 边界/413/未登录 401) Co-Authored-By: Claude <noreply@anthropic.com>
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
import { ValidationPipe } from '@nestjs/common';
|
||
import { ConfigService } from '@nestjs/config';
|
||
import { NestFactory } from '@nestjs/core';
|
||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||
import * as express from 'express';
|
||
import { AppModule } from './app.module';
|
||
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
|
||
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
|
||
|
||
async function bootstrap(): Promise<void> {
|
||
// 设计清单 designData 契约上限 1MB(api-contract-v1 §5),
|
||
// body 限制放宽到 2MB,超 1MB 的业务校验在 design-list service 返回 400
|
||
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||
app.use(express.json({ limit: '2mb' }));
|
||
app.use(express.urlencoded({ extended: true, limit: '2mb' }));
|
||
const config = app.get(ConfigService);
|
||
|
||
app.setGlobalPrefix('api', { exclude: ['health'] });
|
||
app.enableCors();
|
||
|
||
// 全局管道:剥离未声明字段 + 自动类型转换 + 拒绝多余字段
|
||
app.useGlobalPipes(
|
||
new ValidationPipe({
|
||
whitelist: true,
|
||
transform: true,
|
||
forbidNonWhitelisted: true,
|
||
}),
|
||
);
|
||
|
||
// 统一响应包裹 + 统一异常输出
|
||
app.useGlobalInterceptors(new TransformInterceptor());
|
||
app.useGlobalFilters(new AllExceptionsFilter());
|
||
|
||
// Swagger 文档,生产环境可置空 SWAGGER_PATH 关闭
|
||
const swaggerPath = config.get<string>('app.swaggerPath');
|
||
if (swaggerPath) {
|
||
const docConfig = new DocumentBuilder()
|
||
.setTitle('wxmp-backend API')
|
||
.setDescription('微信小程序后端接口文档')
|
||
.setVersion('0.1.0')
|
||
.addBearerAuth()
|
||
.build();
|
||
const document = SwaggerModule.createDocument(app, docConfig);
|
||
SwaggerModule.setup(swaggerPath, app, document);
|
||
}
|
||
|
||
const port = config.get<number>('app.port') ?? 3000;
|
||
await app.listen(port);
|
||
// eslint-disable-next-line no-console
|
||
console.log(`应用已启动: http://localhost:${port} | Swagger: /${swaggerPath}`);
|
||
}
|
||
|
||
void bootstrap();
|