第一部分:认识Nest.js的「骨架」1.1 框架定位想象你要盖房子:

第一部分:认识Nest.js的「骨架」

1.1 框架定位

想象你要盖房子:

  • Express/Fastify = 给你砖头和水泥(基础工具)

  • Nest = 预制钢结构框架+施工图纸(企业级架构方案)

核心价值:用Angular风格的架构解决 Node.js 后端松散问题

1.2 核心三件套
// 典型结构示例
@Module({
  controllers: [CatsController],  // 接待处:处理请求
  providers: [CatsService],       // 工人:实际干活的
  imports: [DatabaseModule]       // 工具箱:其他模块的工具
})
export class CatsModule {}
1.3 依赖注入(DI)

场景:咖啡店点单

  • 传统方式:你自己去后厨找牛奶、咖啡豆(紧密耦合)

  • DI模式:告诉服务员「我要拿铁」,后厨自动配齐(解耦)

代码体现:

// 错误示范:直接实例化(自己冲进后厨)
classCatController{
constructor(){
    this.catService =newCatService();// 耦合性强
}
}
// 正确做法:声明依赖(优雅点单)
@Controller()
classCatController{
constructor(privatereadonly catService: CatService){}// Nest自动注入
}
1.4 第一个Hello World(动手验证)

步骤分解:

  1. 安装脚手架:npm i -g @nestjs/cli

  2. 创建项目:nest new hello-world

  3. 核心文件结构:

src/
├─ main.ts          // 程序入口(点火装置)
├─ app.module.ts    // 主模块(大脑)
└─ app.controller.ts // 路由控制器(接线员)

修改app.controller.ts:

@Get()
getHello(): string {
  return '你好,这里是自定义问候语!'; // 修改官方默认响应
}
1.5 典型目录结构

以下是一份典型的Nest.js项目目录结构及注释说明,适用于中大型项目开发:

├── src/
│   ├── common/              # 通用共享模块
│   │   ├── decorators/      # 自定义装饰器(如@Roles)
│   │   ├── filters/         # 异常过滤器(全局异常处理)
│   │   ├── interceptors/    # 拦截器(响应格式化/日志)
│   │   └── middleware/      # 全局中间件(CORS/日志)
│   │
│   ├── config/              # 配置相关
│   │   ├── app.config.ts    # 应用基础配置
│   │   └── env/            # 环境配置校验
│   │
│   ├── database/            # 数据库模块
│   │   ├── entities/        # TypeORM实体类
│   │   ├── migrations/      # 数据库迁移脚本
│   │   └── database.module.ts
│   │
│   ├── auth/                # 认证授权模块
│   │   ├── strategies/      # Passport策略(JWT/Local)
│   │   ├── guards/          # 守卫(角色验证)
│   │   ├── dto/            # 数据传输对象(登录/注册)
│   │   └── auth.module.ts
│   │
│   ├── user/                # 用户模块
│   │   ├── user.controller.ts
│   │   ├── user.service.ts
│   │   ├── dto/            # DTO(创建/更新用户)
│   │   └── user.module.ts
│   │
│   ├── app.module.ts        # 根模块
│   └── main.ts              # 应用入口文件
│
├── test/                   # 测试目录
│   ├── e2e/               # 端到端测试
│   └── unit/              # 单元测试
│
├── public/                # 静态资源目录
├── uploads/               # 文件上传存储目录
│
├── .env                   # 本地环境变量
├── .env.prod              # 生产环境变量
├── docker-compose.yml     # Docker容器编排
├── Dockerfile             # Docker构建配置
├── nest-cli.json          # Nest CLI配置
├── package.json           # 依赖管理
└── tsconfig.json          # TypeScript配置

第二部分:控制器——你的业务「接线员」

2.1 控制器的本质

想象你有一个快递分拣中心:

  • 包裹(请求)进入传送带

  • 分拣机器人(控制器)根据:

    • 目的地地址(路由路径)

    • 包裹类型(HTTP方法)
      决定送到哪个处理区(处理方法)

2.2 创建业务控制器
# 命令行生成控制器(最佳实践)
nest generate controller user

生成文件结构:

src/
└─ user/
   ├─ user.controller.ts
   ├─ user.service.ts
   └─ user.module.ts
2.3 路由方法实战(Restful风格)
@Controller('users')// 基础路径 /users
exportclassUserController{
// GET /users/profile
@Get('profile')
getProfile(){
    return'用户张三的个人页面';
}
// POST /users/register
@Post('register')
registerUser(@Body() userData:any){
    console.log('收到注册数据:', userData);
    return{ success:true, userId:123};
}
// 动态路由参数 /users/orders/789
@Get('orders/:orderId')
getOrder(@Param('orderId') orderId:string){
    return`查询订单${orderId}的详细信息`;
}
}
2.4 参数获取三大招
  1. 路径参数@Param('id')

// GET /product/iphone-15
@Get(':model')
getProduct(@Param('model') model: string) {
  return `正在查询${model}型号`;
}

查询参数@Query()

// GET /search?q=nestjs&page=2
@Get('search')
search(@Query() params: { q: string; page: number }) {
  return `搜索关键词:${params.q},第${params.page}页`;
}

请求体@Body()

// POST /login
@Post('login')
login(@Body() credentials: { username: string; password: string }) {
  // 实际开发中这里会调用验证服务
  return `用户${credentials.username}正在尝试登录`;
}
2.5 DTO模式(重要防御层)

场景:用户提交的数据就像未安检的行李

// 创建user.dto.ts
exportclassCreateUserDto{
@IsString()
@MinLength(5)
  username:string;
@IsEmail()
  email:string;
@IsStrongPassword()
  password:string;
}
// 在控制器中使用
@Post()
createUser(@Body() createUserDto: CreateUserDto){
// 自动验证通过后才会执行到这里
returnthis.userService.create(createUserDto);
}
2.6 常见问题现场诊断

Q:什么时候用@Param,什么时候用@Query?

  • 用@Param:/users/123 (标识具体资源)

  • 用@Query:/users?role=admin (过滤条件)

Q:DTO和普通的TypeScript接口有什么区别?

  • 接口:只是类型声明

  • DTO:携带验证规则,实际参与运行时校验


第三部分:服务层——你的业务「后厨团队」

3.1 服务层定位
  • 控制器 = 服务员(接收点单)

  • 服务层 = 厨师团队(实际烹饪)

  • 模块 = 餐厅部门划分(中厨/西厨)

3.2 创建标准服务(用户注册流程)
# 快速生成服务(附带测试文件)
nest generate service user

基础服务结构:

@Injectable()// 标记为可注入类
exportclassUserService{
// 模拟数据库
private users:any[]=[];
// 实际业务操作
asyncregister(userDto: CreateUserDto){
    // 检查用户名是否重复
    const exist =this.users.find(u => u.username === userDto.username);
    if(exist){
      thrownewConflictException('用户名已存在');
    }
    
    // 模拟入库操作
    const newUser ={
      id: Date.now(),
      ...userDto,
      createdAt:newDate()
    };
    this.users.push(newUser);
    
    return{
      success:true,
      data: _.omit(newUser,['password'])// 使用lodash过滤敏感字段
    };
}
}


3.3 控制器与服务联动
@Controller('users')
exportclassUserController{
// 依赖注入服务实例
constructor(privatereadonly userService: UserService){}
@Post('register')
asyncregister(@Body() dto: CreateUserDto){
    // 控制器只做参数传递
    returnawaitthis.userService.register(dto);
}
}
3.4 模块化拆分(数据库模块案例)
// database.module.ts
@Module({
  providers:[
    {
      provide:'DATABASE_CONNECTION',
      useFactory:async()=>{
        returnawaitcreateConnection({
          type:'mysql',
          host:'localhost',
          port:3306,
          username:'root',
          password:'root',
          database:'test'
        });
      }
    }
],
  exports:['DATABASE_CONNECTION']// 暴露给其他模块
})
exportclassDatabaseModule{}
// 在用户模块引入
@Module({
  imports:[DatabaseModule],// 导入数据库工具
  controllers:[UserController],
  providers:[UserService]
})
exportclassUserModule{}
3.5 异常处理艺术(错误分类)
// 自定义业务异常
exportclassInsufficientBalanceExceptionextendsHttpException{
constructor(){
    super('账户余额不足',402);// 特定状态码
}
}
// 在服务中使用
chargeUser(userId:number, amount:number){
const balance =this.getBalance(userId);
if(balance < amount){
    thrownewInsufficientBalanceException();
}
// 扣款逻辑...
}
// 全局过滤器(进阶用法)
@Catch(InsufficientBalanceException)
exportclassBalanceFilterimplementsExceptionFilter{
catch(exception: InsufficientBalanceException, host: ArgumentsHost){
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    
    response.status(402).json({
      code:402,
      message:'请先充值后再进行操作',
      rechargeUrl:'/payment'
    });
}
}


3.6 实战问答环节

Q:服务层必须用@Injectable装饰器吗?

  • 必须,这是Nest识别可注入类的标记

Q:什么时候应该创建新模块?

  • 当功能组件需要复用时(如数据库、第三方API)

  • 业务领域明确独立时(如用户模块、订单模块)

Q:为什么不用全局try-catch?

  • Nest的异常过滤器机制更统一

  • 可针对不同异常类型精细化处理


第四部分:管道——你的数据「安检通道」

4.1 管道核心作用
  • 验证

    :检查行李是否合规(数据格式)

  • 转换

    :把外币兑换成本地货币(数据格式化)

  • 过滤

    :没收违禁物品(清理不必要字段)

4.2 内置管道实战
// 单个路由使用
@Get(':id')
findOne(
@Param('id', ParseIntPipe) id:number// 自动转换数字
){
return`查询ID为${id}的用户`;
}
// 测试案例:
// 访问 /users/abc 将自动返回400错误
// 正确访问 /users/123 得到数字类型的123


4.3 自定义管道开发(手机号格式验证)
// phone.pipe.ts
@Injectable()
exportclassPhoneValidationPipeimplementsPipeTransform{
transform(value:any){
    const valid =/^1[3-9]\d{9}$/.test(value);
    if(!valid){
      thrownewBadRequestException('手机号格式错误');
    }
    return value.replace(/(\d{3})\d{4}(\d{4})/,'$1****$2');// 自动脱敏
}
}
// 在控制器中使用
@Post('bind-phone')
bindPhone(
@Body('phone', PhoneValidationPipe) phone:string
){
return`绑定成功:${phone}`;
}


4.4 全局管道配置(推荐方案)
// main.ts 启动文件
asyncfunctionbootstrap(){
const app =await NestFactory.create(AppModule);
// 全局启用数据验证
  app.useGlobalPipes(
    newValidationPipe({
      whitelist:true,          // 自动过滤DTO外的字段
      transform:true,          // 自动类型转换
      disableErrorMessages: process.env.NODE_ENV==='production'// 生产环境隐藏错误详情
    })
);
await app.listen(3000);
}


4.5 Class Validator深度集成(企业级方案)

安装必要包:

npm install class-validator class-transformer

DTO增强:

// create-user.dto.ts
exportclassCreateUserDto{
@IsString()
@Length(2,20,{ message:'用户名长度需在2-20字符之间'})
  username:string;
@IsEmail({},{ message:'必须是有效的邮箱格式'})
  email:string;
@Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/,{
    message:'密码必须包含字母和数字,至少8位'
})
  password:string;
@Type(()=> Number)// 配合class-transformer转换类型
@IsInt({ message:'年龄必须是整数'})
@Min(18,{ message:'未满18岁不得注册'})
  age:number;
}

错误响应示例:

// 当验证失败时自动返回
{
"statusCode":400,
"message":[
    "用户名长度需在2-20字符之间",
    "必须是有效的邮箱格式",
    "未满18岁不得注册"
],
"error":"Bad Request"
}
4.6 常见问题排雷

Q:管道和中间件有什么区别?

  • 中间件:更底层,处理HTTP请求/响应(跨域、日志)

  • 管道:专注处理请求参数(路由参数、查询参数、请求体)

Q:为什么要用class-transformer?

  • 处理原始请求数据到类实例的转换

  • 解决嵌套对象、数组等复杂结构的类型转换

Q:生产环境为什么要禁用错误详情?

  • 防止敏感信息泄露(如数据库字段结构)

  • 避免给攻击者提供线索


第五部分:守卫——你的系统「安检门禁」

5.1 守卫核心职责
  • 身份核验

    :检查登机牌(是否登录)

  • 权限确认

    :检查舱位等级(用户角色)

  • 访问控制

    :拦截危险人员(黑名单IP)

5.2 创建JWT守卫(实战认证方案)
// jwt.guard.ts
@Injectable()
exportclassJwtAuthGuardimplementsCanActivate{
constructor(private jwtService: JwtService){}
canActivate(context: ExecutionContext):boolean{
    const request = context.switchToHttp().getRequest();
    const token =this.extractToken(request);
    
    if(!token){
      thrownewUnauthorizedException('请先登录');
    }
    try{
      const payload =this.jwtService.verify(token);
      request.user = payload;// 注入用户信息到请求对象
      returntrue;
    }catch{
      thrownewUnauthorizedException('登录凭证已过期');
    }
}
privateextractToken(request: Request):string|null{
    return request.headers.authorization?.split(' ')[1]||null;
}
}
5.3 角色权限守卫(RBAC实现)
// roles.decorator.ts(自定义装饰器)
exportconstRoles=(...roles:string[])=>SetMetadata('roles', roles);
// roles.guard.ts
@Injectable()
exportclassRolesGuardimplementsCanActivate{
constructor(private reflector: Reflector){}
canActivate(context: ExecutionContext):boolean{
    const requiredRoles =this.reflector.get<string[]>(
      'roles',
      context.getHandler()
    );
    if(!requiredRoles)returntrue;
    const{ user }= context.switchToHttp().getRequest();
    return requiredRoles.some(role => user.roles?.includes(role));
}
}
// 控制器使用示例
@Post('admin/reports')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin','superadmin')
generateReport(){
return'生成管理员专属报表';
}
5.4 守卫注册方式(灵活配置)
// 全局注册(main.ts)
app.useGlobalGuards(newJwtAuthGuard());
// 控制器级别
@Controller('orders')
@UseGuards(JwtAuthGuard)
exportclassOrderController{}
// 路由方法级别
@Get('secret')
@UseGuards(RolesGuard)
getSecretData(){}


5.5 第三方登录集成(Github OAuth示例)
// github.strategy.ts(Passport集成)
@Injectable()
exportclassGithubStrategyextendsPassportStrategy(Strategy,'github'){
constructor(){
    super({
      clientID: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_SECRET,
      callbackURL:'/auth/github/callback',
      scope:['user:email']
    });
}
asyncvalidate(accessToken:string, _refreshToken:string, profile:any){
    return{
      provider:'github',
      uid: profile.id,
      email: profile.emails[0].value,
      username: profile.username
    };
}
}
// 在控制器中使用
@Get('auth/github')
@UseGuards(AuthGuard('github'))
githubLogin(){}
@Get('auth/github/callback')
@UseGuards(AuthGuard('github'))
githubCallback(@Req() req){
returnthis.authService.handleOAuthLogin(req.user);
}


5.6 安全防护常见问题

Q:守卫和中间件的执行顺序?

  • 中间件 -> 守卫 -> 拦截器 -> 管道 -> 控制器方法

Q:如何实现权限分级管理?

// 角色定义建议
enum UserRole {
TOURIST=1,    // 游客
USER=10,       // 普通用户
EDITOR=20,     // 内容编辑
ADMIN=99       // 系统管理员
}
// 权限检查时使用数字比对
hasPermission(userRole:number, requiredRole:number){
return userRole >= requiredRole;
}

Q:如何处理多设备登录问题?

// 生成带设备类型的JWT
createToken(userId:string, deviceType:string){
returnthis.jwtService.sign({
    sub: userId,
    device: deviceType // web/mobile/iot
});
}
// 在守卫中检查设备合法性
if(payload.device !=='web'){
thrownewForbiddenException('该设备无访问权限');
}


第六部分:拦截器——你的数据「物流中心」

6.1 拦截器核心能力(快递分拣系统比喻)
  • 请求预处理

    :包裹分类贴标签(添加全局请求头)

  • 响应格式化

    :统一包装盒规格(标准化响应结构)

  • 异常捕获

    :破损包裹拦截(错误日志记录)

  • 性能监控

    :运输时效统计(接口耗时计算)

6.2 创建基础拦截器(响应标准化)
// response.interceptor.ts
@Injectable()
exportclassResponseInterceptorimplementsNestInterceptor{
intercept(context: ExecutionContext, next: CallHandler): Observable<any>{
    const ctx = context.switchToHttp();
    const response = ctx.getResponse();
    
    // 统一设置响应头
    response.setHeader('X-Powered-By','NestJS');
    return next.handle().pipe(
      map(data =>({
        code:200,
        success:true,
        timestamp:newDate().toISOString(),
        data: _.isObject(data)? data :{ result: data }
      }))
    );
}
}
// 使用效果:
// 原始返回: 'Hello World'
// 处理后返回: 
// {
//   code: 200,
//   success: true,
//   timestamp: "2023-08-20T08:00:00Z",
//   data: { result: "Hello World" }
// }


6.3 多场景实战应用

案例一:API耗时监控

intercept(context: ExecutionContext, next: CallHandler){
const start = Date.now();
return next.handle().pipe(
    tap(()=>{
      const duration = Date.now()- start;
      console.log(`接口耗时:${duration}ms`);
    })
);
}

案例二:敏感数据脱敏

map(data => {
  if (data?.phone) {
    data.phone = data.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  }
  return data;
})

案例三:请求日志记录

const request = context.switchToHttp().getRequest();
logger.info(`[${request.method}] ${request.url} - Body: ${JSON.stringify(request.body)}`);
return next.handle().pipe(
  catchError(err => {
    logger.error(`请求失败:${err.stack}`);
    return throwError(() => err);
  })
);


6.4 全局拦截器配置
// main.ts
app.useGlobalInterceptors(
  new ResponseInterceptor(),
  new TimeoutInterceptor(5000) // 请求超时设置
);

6.5 高阶应用:缓存拦截器
// cache.interceptor.ts
@Injectable()
exportclassCacheInterceptorimplementsNestInterceptor{
private cache =newMap<string, any>();
intercept(context: ExecutionContext, next: CallHandler){
    const request = context.switchToHttp().getRequest();
    const cacheKey = request.originalUrl;
    if(request.method !=='GET')return next.handle();
    const cached =this.cache.get(cacheKey);
    if(cached){
      returnof(cached);// 直接返回缓存
    }
    return next.handle().pipe(
      tap(response =>{
        this.cache.set(cacheKey, response);
        setTimeout(()=>this.cache.delete(cacheKey),60000);// 60秒后清除
      })
    );
}
}


6.6 拦截器使用原则
  1. 职责单一:每个拦截器专注一个任务

  2. 执行顺序:

请求 -> 全局拦截器 -> 控制器拦截器 -> 路由方法拦截器


  1. 性能注意:避免在拦截器中做重型操作

6.7 常见问题解答

Q:拦截器 vs 中间件 vs 过滤器?

  • 中间件:处理原始HTTP请求(跨域、压缩)

  • 拦截器:处理控制器输入输出(业务相关)

  • 过滤器:捕获未处理的异常(兜底处理)

Q:如何跳过某些路由的拦截器?

// 使用自定义元数据装饰器
exportconstSkipInterceptor=()=>SetMetadata('skipInterceptor',true);
// 在拦截器中判断
const shouldSkip =this.reflector.get<boolean>('skipInterceptor', context.getHandler());
if(shouldSkip)return next.handle()

Q:如何处理异步操作?

// 超时控制示例
intercept(context: ExecutionContext, next: CallHandler){
return next.handle().pipe(
    timeout(5000),// 5秒超时
    catchError(err =>{
      if(err instanceofTimeoutError){
        thrownewRequestTimeoutException();
      }
      returnthrowError(()=> err);
    })
);
}


第七部分:中间件——你的请求「预处理车间」

7.1 中间件定位
  • 执行阶段

    :控制器之前处理原始请求

  • 典型场景

    • 请求日志记录

    • 跨域头设置

    • 用户身份预校验

    • 请求体压缩处理

7.2 创建日志中间件(实战演示)
// logger.middleware.ts
import{ Request, Response, NextFunction }from'express';
exportfunctionLoggerMiddleware(req: Request, res: Response, next: NextFunction){
const start = Date.now();
  res.on('finish',()=>{
    const duration = Date.now()- start;
    console.log(
      `[${new Date().toISOString()}] ${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms)`
    );
});
next();// 必须调用next继续传递
}

7.3 中间件注册方式(三种绑定策略)
// 全局中间件(main.ts)
app.use(LoggerMiddleware);
// 模块中间件(app.module.ts)
exportclassAppModuleimplementsNestModule{
configure(consumer: MiddlewareConsumer){
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path:'user*', method: RequestMethod.ALL});// 匹配/user开头的所有路由
}
}
// 路由排除法
consumer
.apply(AuthMiddleware)
.exclude(
    { path:'auth/login', method: RequestMethod.POST},
    { path:'docs', method: RequestMethod.GET}
)
.forRoutes('*');

7.4 文件上传处理(Multer集成方案)
// 安装依赖
npm install @nestjs/platform-express multer @types/multer
// 文件上传控制器
@Post('avatar')
@UseInterceptors(FileInterceptor('file'))
uploadAvatar(
@UploadedFile() file: Express.Multer.File,
@Body() userId:string
){
returnthis.userService.updateAvatar(userId, file);
}
// 配置文件存储(自定义配置)
const storage = multer.diskStorage({
destination:(req, file, cb)=>{
    cb(null,'./uploads');
},
filename:(req, file, cb)=>{
    const ext = path.extname(file.originalname);
    cb(null,`${Date.now()}${ext}`);
}
});
// 在模块中注册
@Module({
  imports:[
    MulterModule.register({
      storage: storage,
      limits:{ fileSize:1024*1024*5}// 5MB限制
    })
]
})
exportclassFileModule{}

7.5 第三方中间件集成(安全防护)
// 安装安全中间件
npm install helmet compression
// main.ts配置
import*as helmet from'helmet';
import*as compression from'compression';
asyncfunctionbootstrap(){
const app =await NestFactory.create(AppModule);
// 安全头设置
  app.use(helmet({
    contentSecurityPolicy:false// 根据需求配置
}));
// 响应压缩
  app.use(compression({ level:6}));
await app.listen(3000);
}
7.6 中间件开发准则
  1. 功能纯净

    :避免在一个中间件中做多件不相关的事

  2. 性能优先

    :不要阻塞事件循环(避免同步耗时操作)

  3. 错误处理:必须捕获并传递错误

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('服务器开小差了!');
});
7.7 常见问题破解

Q:中间件和过滤器的区别?

  • 中间件:处理请求/响应周期中的原始数据

  • 过滤器:处理控制器抛出的异常

Q:如何实现CSRF防护?

// 安装csrf防护
npm install csurf @types/csurf
// 使用中间件
import*as csurf from'csurf';
app.use(csurf());
app.use((req, res, next)=>{
  res.cookie('XSRF-TOKEN', req.csrfToken());
next();
});
// 前端需要在请求头携带
headers:{
'X-XSRF-TOKEN':getCookie('XSRF-TOKEN')
}

Q:处理大文件上传优化策略?

// 分片上传中间件
app.post('/upload',(req, res)=>{
const chunk = req.body.chunk;
const index = req.body.index;
const total = req.body.total;
// 将分片暂存到临时目录
  fs.writeFileSync(`./temp/${index}`, chunk);
if(index === total){
    // 合并所有分片
    mergeFiles(total);
}
  res.sendStatus(200);
});


第八部分:微服务——你的业务「集团分部」

8.1 微服务核心特征(连锁餐厅)
  • 独立运营

    :每家分店自主管理(服务自治)

  • 专业分工

    :中央厨房负责食材供应(服务拆分)

  • 统一协调

    :总部调度各分店资源(服务发现)

  • 容错机制

    :某分店停业不影响整体(熔断降级)

8.2 创建基础微服务(TCP通信示例)
# 创建用户服务(独立项目)
nest new user-service --strict
# 创建订单服务(独立项目)
nest new order-service --strict

用户服务配置:

// main.ts
asyncfunctionbootstrap(){
const app =await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
      options:{ host:'localhost', port:3001}
    }
);
await app.listen();
}

订单服务调用配置:

// order.module.ts
@Module({
  imports:[
    ClientsModule.register([
      {
        name:'USER_SERVICE',
        transport: Transport.TCP,
        options:{ host:'localhost', port:3001}
      }
    ])
]
})

8.3 跨服务通信模式(实战演示)

场景:创建订单时验证用户

// order.service.ts
@Injectable()
exportclassOrderService{
constructor(
    @Inject('USER_SERVICE')private userClient: ClientProxy
){}
asynccreateOrder(userId:string, items:string[]){
    // 调用用户服务验证
    const userValid =awaitthis.userClient
      .send<boolean>('validateUser', userId)
      .toPromise();
    if(!userValid){
      thrownewBadRequestException('用户不存在');
    }
    // 创建订单逻辑...
    return{ orderId: Date.now().toString()};
}
}
// user.controller.ts(用户服务)
@MessagePattern('validateUser')
validateUser(@Payload() userId:string){
returnthis.userService.exists(userId);// 返回布尔值
}

8.4 消息队列集成(RabbitMQ案例)
// 安装依赖
npm install @nestjs/microservices amqplib amqp-connection-manager
// 邮件服务配置
const app =await NestFactory.createMicroservice<MicroserviceOptions>(
  AppModule,
{
    transport: Transport.RMQ,
    options:{
      urls:['amqp://localhost:5672'],
      queue:'email_queue',
      queueOptions:{ durable:true}
    }
}
);
// 订单服务发送邮件通知
this.emailClient.emit('send_order_email',{
  email:'user@example.com',
  orderId:'12345'
});

8.5 网关统一入口(API网关模式)
// api-gateway项目配置
@Controller()
exportclassApiController{
constructor(
    @Inject('USER_SERVICE')private userClient: ClientProxy,
    @Inject('ORDER_SERVICE')private orderClient: ClientProxy
){}
@Get('user/:id/orders')
asyncgetUserOrders(@Param('id') userId:string){
    // 并行调用两个服务
    const[user, orders]=awaitPromise.all([
      this.userClient.send('getUser', userId).toPromise(),
      this.orderClient.send('getUserOrders', userId).toPromise()
    ]);
    return{...user, orders };
}
}

8.6 分布式系统常见问题

Q:如何保证数据一致性?

// 使用Saga模式示例
asynccreateOrder(){
try{
    // 阶段1:扣减库存
    await inventoryClient.emit('reduce_stock', items);
    
    // 阶段2:创建订单
    const order =awaitthis.create(orderData);
    
    // 阶段3:扣款
    await paymentClient.emit('deduct_balance', userId);
    
    return order;
}catch(error){
    // 补偿操作
    await inventoryClient.emit('rollback_stock', items);
    await paymentClient.emit('rollback_balance', userId);
    throw error;
}
}

Q:如何实现服务发现?

// 使用Consul配置示例
transport: Transport.CONSUL,
options:{
  host:'consul-server',
  port:8500,
  service:{
    name:'user-service',
    check:{
      interval:'10s',
      http:'http://localhost:3001/health'
    }
}
}

Q:如何监控微服务健康?

// 健康检查端点
@Get('health')
@HealthCheck()
checkHealth(){
returnthis.health.check([
    ()=>this.db.ping().then(()=> HealthCheckResult.healthy()),
    ()=>this.cache.ping().then(()=> HealthCheckResult.healthy())
]);
}

第九部分:MySQL集成——你的数据「智能仓库」

9.1 ORM核心概念(仓库管理员比喻)
  • 实体(Entity) = 货物清单(数据结构定义)

  • 仓库(Repository) = 货架管理员(数据存取操作)

  • 迁移(Migration) = 仓库改造记录(数据结构变更)

9.2 环境配置(.env标准方案)
# .env
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=nest_demo
DB_SYNC=false # 生产环境必须关闭!
9.3 实体定义规范(用户模型案例)
// user.entity.ts
@Entity()
exportclassUser{
@PrimaryGeneratedColumn()
  id:number;
@Column({ length:30, comment:'登录账号'})
  username:string;
@Column({
    select:false,// 查询时默认排除
    comment:'加密后的密码'
})
  password:string;
@Column({
    type:'enum',
    enum: UserRole,
    default: UserRole.USER
})
  role: UserRole;
@CreateDateColumn()
  createdAt: Date;
@UpdateDateColumn()
  updatedAt: Date;
}
9.4 数据库模块注册(动态配置)
// database.module.ts
@Module({
  imports:[
    TypeOrmModule.forRootAsync({
      imports:[ConfigModule],
      useFactory:(config: ConfigService)=>({
        type:'mysql',
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
        username: config.get('DB_USER'),
        password: config.get('DB_PASSWORD'),
        database: config.get('DB_NAME'),
        entities:[__dirname +'/../**/*.entity{.ts,.js}'],
        synchronize: config.get('DB_SYNC'),// 仅开发环境开启
        logging:['error','warn']// 生产环境建议关闭
      }),
      inject:[ConfigService]
    })
]
})
exportclassDatabaseModule{}
9.5 数据操作实战(CRUD演示)
// user.service.ts
@Injectable()
exportclassUserService{
constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>
){}
// 创建用户
asynccreate(userDto: CreateUserDto){
    const user =this.userRepository.create({
      ...userDto,
      password: bcrypt.hashSync(userDto.password,10)
    });
    returnawaitthis.userRepository.save(user);
}
// 分页查询
asyncpaginate(page =1, limit =10){
    const[data, total]=awaitthis.userRepository.findAndCount({
      skip:(page -1)* limit,
      take: limit,
      order:{ id:'DESC'}
    });
    
    return{
      data,
      meta:{ total, page, last_page: Math.ceil(total / limit)}
    };
}
// 事务操作
asynctransferMoney(fromId:number, toId:number, amount:number){
    awaitthis.userRepository.manager.transaction(async manager =>{
      const from =await manager.findOne(User,{ where:{ id: fromId }});
      const to =await manager.findOne(User,{ where:{ id: toId }});
      if(from.balance < amount)thrownewError('余额不足');
      
      await manager.update(User, fromId,{ balance: from.balance - amount });
      await manager.update(User, toId,{ balance: to.balance + amount });
    });
}
}
9.6 复杂查询构建(QueryBuilder实战)
// 多表联查示例
asyncgetUserWithOrders(userId:number){
returnthis.userRepository
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.orders','orders')
    .where('user.id = :id',{ id: userId })
    .andWhere('orders.status = :status',{ status:'paid'})
    .orderBy('orders.createdAt','DESC')
    .getOne();
}
// 原生SQL执行
asynccountActiveUsers(){
returnthis.userRepository.query(`
    SELECT COUNT(*) as count 
    FROM users 
    WHERE last_login_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
  `);
}
9.7 性能优化方案
  1. 索引优化:

@Index('IDX_USERNAME', ['username'], { unique: true })
@Entity()
export class User { ... }
  1. 查询缓存:

const users =awaitthis.userRepository.find({
  where:{ role:'admin'},
  cache:{
    id:'admin_users',
    milliseconds:60000// 1分钟缓存
}
});
  1. 连接池配置:

// database.module.ts
options:{
...,
  extra:{
    connectionLimit:20,// 最大连接数
    waitForConnections:true,
    queueLimit:0
}
}
9.8 数据迁移管理(生产环境必须)
# 生成迁移文件
npx typeorm migration:create ./migrations/InitSchema
# 执行迁移
npx typeorm migration:run
# 回滚迁移
npx typeorm migration:revert

迁移文件示例:

export classInitSchemaimplementsMigrationInterface{
publicasyncup(queryRunner: QueryRunner):Promise<void>{
    await queryRunner.query(`
      CREATE TABLE users (
        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(30) NOT NULL UNIQUE,
        password VARCHAR(100) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      ) ENGINE=InnoDB;
    `);
}
publicasyncdown():Promise<void>{
    await queryRunner.dropTable('users');
}
}
9.9 安全注意事项
  1. SQL注入防护

// 危险!
query(`SELECT * FROM users WHERE name = '${name}'`);
// 安全✅
query('SELECT * FROM users WHERE name = ?', [name]);

    • 始终使用参数化查询

    • 避免直接拼接SQL语句

  1. 敏感数据处理

// 密码加密处理
asyncfunctionhashPassword(password:string){
return bcrypt.hash(password,10);
}
// 敏感字段排除
@Entity()
exportclassUser{
@Column({ select:false})
  password:string;
}
9.10 常见问题解答

Q:实体和DTO有什么区别?

  • 实体:描述数据库表结构(与数据库直接交互)

  • DTO:定义接口传输格式(请求/响应结构)

Q:如何高效处理批量插入?

const users =[...];// 大量用户数据
awaitthis.userRepository
.createQueryBuilder()
.insert()
.into(User)
.values(users)
.orIgnore()// 忽略重复
.execute();


Q:事务处理的正确方式?

  • 使用transaction方法包裹原子操作

  • 避免在事务中执行长时间操作

  • 注意事务隔离级别设置


第十部分:测试与部署——你的系统「质检&物流」

10.1 测试金字塔模型(质量检测体系)
- 单元测试(70%):单个函数/类测试
- 集成测试(20%):模块间协作测试
- E2E测试(10%):完整业务流程测试
10.2 单元测试实战(用户服务案例)
// user.service.spec.ts
describe('UserService',()=>{
let service: UserService;
let mockRepository: jest.Mocked<Repository<User>>;
beforeEach(async()=>{
    // 模拟数据库操作
    mockRepository ={
      findOne: jest.fn(),
      save: jest.fn().mockImplementation((user)=> user)
    }asany;
    const module: TestingModule =await Test.createTestingModule({
      providers:[
        UserService,
        { provide:getRepositoryToken(User), useValue: mockRepository }
      ]
    }).compile();
    service = module.get<UserService>(UserService);
});
it('创建用户时应加密密码',async()=>{
    const dto ={ username:'test', password:'123456'};
    await service.create(dto);
    
    expect(mockRepository.save).toBeCalledWith(
      expect.objectContaining({
        password: expect.not.stringMatching(dto.password)
      })
    );
});
});
10.3 E2E测试配置(完整流程验证)
// app.e2e-spec.ts
describe('用户模块 (e2e)',()=>{
let app: INestApplication;
beforeAll(async()=>{
    const moduleFixture =await Test.createTestingModule({
      imports:[AppModule],
    }).compile();
    app = moduleFixture.createNestApplication();
    await app.init();
});
it('/user/register (POST)',()=>{
    returnrequest(app.getHttpServer())
      .post('/user/register')
      .send({
        username:'e2e_test',
        password:'Test@123',
        email:'e2e@test.com'
      })
      .expect(201)
      .expect(res =>{
        expect(res.body.data.username).toBe('e2e_test');
        expect(res.body.data.password).toBeUndefined();
      });
});
afterAll(async()=>{
    await app.close();
});
});


10.4 Docker容器化(生产部署标准)
# Dockerfile 三阶段构建
# 阶段一:构建环境
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 阶段二:生产依赖
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
# 阶段三:最终镜像
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY .env.prod .
EXPOSE 3000
CMD ["node", "dist/main.js"]
10.5 部署优化策略(生产环境配置)
# .env.prod 生产配置
NODE_ENV=production
DB_HOST=mysql-prod
DB_PORT=3306
DB_USER=prod_user
DB_PASSWORD=${DB_PASSWORD}# 从安全仓库注入
DB_NAME=prod_db
DB_SYNC=false
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DB_PASSWORD=${DB_PASSWORD}
    depends_on:
      - mysql
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: prod_db
    volumes:
      - mysql_data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password
volumes:
  mysql_data:
10.6 监控与维护(生产必备)
// 健康检查端点
@Get('health')
@HealthCheck()
checkHealth(){
returnthis.health.check([
    ()=>this.db.ping().then(()=> HealthCheckResult.healthy()),
    ()=>this.redis.ping().then(()=> HealthCheckResult.healthy())
]);
}
// 日志切割配置(PM2)
module.exports ={
  apps:[{
    name:'nest-app',
    script:'dist/main.js',
    instances:'max',
    autorestart:true,
    watch:false,
    max_memory_restart:'1G',
    error_file:'/var/log/nest-app/error.log',
    out_file:'/var/log/nest-app/out.log',
    log_date_format:'YYYY-MM-DD HH:mm:ss',
    merge_logs:true,
    env_production:{
      NODE_ENV:'production'
    }
}]
};
10.7 持续集成实战(GitHub Actions)
# .github/workflows/deploy.yml
name: Deploy Production
on:
push:
    tags:
      -'v*'
jobs:
build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    -name: Checkout code
      uses: actions/checkout@v3
    -name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version:18
    -name: Install Dependencies
      run: npm ci
    -name: Build Project
      run: npm run build
    -name: Run Tests
      run: npm run test:ci
    -name: Build Docker Image
      run: docker build -t ${{ secrets.DOCKER_USER }}/nest-app:${{ github.ref_name }} .
    -name: Push to Docker Hub
      run:|
        echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin
        docker push ${{ secrets.DOCKER_USER }}/nest-app:${{ github.ref_name }}
    -name: SSH Deploy
      uses: appleboy/ssh-action@v0.1.10
      with:
        host: ${{ secrets.PROD_HOST }}
        username: ${{ secrets.PROD_USER }}
        key: ${{ secrets.PROD_SSH_KEY }}
        script:|
          docker pull ${{ secrets.DOCKER_USER }}/nest-app:${{ github.ref_name }}
          docker stop nest-app || true
          docker rm nest-app || true
          docker run -d \
            --name nest-app \
            --env-file .env.prod \
            -p 3000:3000 \
            ${{ secrets.DOCKER_USER }}/nest-app:${{ github.ref_name }}
10.8 生产环境安全清单

1. [强制] 禁用Swagger文档(生产环境)

2. [推荐] 配置HTTPS证书(Let's Encrypt)

3. [重要] 定期备份数据库(每日全量+增量)

4. [必须] 使用防火墙限制端口访问

5. [建议] 实施速率限制(防止暴力破解)

10.9 故障排查指南
# 查看容器日志
docker logs nest-app --tail100-f
# 进入容器调试
dockerexec-it nest-app sh
# 性能分析
node--inspect=0.0.0.0:9229 dist/main.js
# 使用Chrome DevTools连接调试
# 内存泄漏检测
NODE_OPTIONS=--max-old-space-size=4096node dist/main.js

全系列总结路线图

1. 项目初始化 -> 基础架构搭建

2. 路由定义 -> 业务功能开发

3. 数据库集成 -> 数据持久化

4. 鉴权体系 -> 安全保障

5. 测试覆盖 -> 质量保障

6. 容器化 -> 部署标准化

7. 监控告警 -> 运维保障

8. 持续交付 -> 自动化流程



上一篇:Javascript实现多类继承

评论列表
发表评论
称呼
邮箱
网址
验证码(*)
热评文章
相关阅读