从 Spring Boot 到 NestJS:项目快速上手指南

本文档专为具有 Spring Boot 开发经验的开发者编写,旨在通过类比和循序渐进的实战指导,帮助你快速熟悉并参与到当前 NestJS 项目的开发中。

第一阶段:全局宏观认知

1. 架构思想的无缝平移

NestJS 几乎完美复刻了 Spring Boot 的架构哲学:

  • IoC/DI (控制反转与依赖注入):默认单例,通过构造函数注入。
  • AOP (面向切面编程):拦截器、守卫、异常过滤器一应俱全。
  • 模块化 (Module):按业务划分模块,避免代码耦合。

2. 核心装饰器(注解)对照表

Spring Boot NestJS 作用说明
@RestController @Controller() 标记控制器,定义路由前缀
@Service / @Component @Injectable() 标记为 Provider,交由 IoC 容器管理
@Autowired (无需显式注解) 直接在类的 constructor 中声明即可自动注入
@GetMapping("/path") @Get('path') GET 请求路由映射
@PostMapping @Post() POST 请求路由映射
@RequestBody @Body() 获取请求体 JSON 数据
@RequestParam @Query() 获取 URL 查询参数 (?key=value)
@PathVariable @Param() 获取路径参数 (/api/:id)

第二阶段:熟悉项目目录结构

当前项目的结构与标准的 MVC 分层高度一致:

src/
├── main.ts                   # 等同于 Application.java,项目启动入口
├── app.module.ts             # 根模块,等同于 @SpringBootApplication + 全局配置
├── config/                   # 配置文件目录(等同于 application.yml)
├── common/                   # 通用工具类、拦截器、全局异常处理等
│   ├── base/                 # 基础 Controller/Service 类(可继承复用)
│   ├── decorators/           # 自定义装饰器(自定义注解)
│   ├── interceptors/         # 拦截器(如统一返回值包装 transform.interceptor.ts)
│   └── filters/              # 异常过滤器(全局异常捕获)
├── module/                   # 业务模块目录
│   ├── ota/                  # OTA核心业务模块
│   │   ├── xxx.controller.ts # 控制层
│   │   ├── xxx.service.ts    # 业务逻辑层
│   │   ├── xxx.entity.ts     # 数据库实体类 (等同于 @Entity)
│   │   ├── xxx.module.ts     # 模块组装文件 (必须将Controller和Service注册在此)
│   │   └── dto/              # 数据传输对象 (入参/出参校验)

第三阶段:ORM 与数据库操作 (TypeORM)

本项目使用 TypeORM,体验上非常接近 JPAHibernate

1. 实体定义 (Entity)

类似 JPA,通过装饰器映射表结构:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity('sys_user') // @Table(name = "sys_user")
export class User {
  @PrimaryGeneratedColumn() // @Id @GeneratedValue
  id: number;

  @Column({ name: 'user_name', length: 50 }) // @Column
  userName: string;
}

2. 数据库操作 (Repository)

类似于 JpaRepository,在 Service 中注入 Repository 即可进行 CRUD:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  // 等同于 @Autowired private UserRepository userRepository;
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async findUser(id: number) {
    return await this.userRepository.findOne({ where: { id } });
  }
}

第四阶段:实战演练(手把手写一个接口)

假设我们需要在 src/module/ota/ 下新建一个获取航班列表的接口。请遵循单一职责最小开发单元的规则。

Step 1: 定义入参 DTO (flight-query.dto.ts)

等同于 Java 的 Request 对象。使用 class-validator 进行校验(类似 @Valid + javax.validation)。

import { IsString, IsNotEmpty } from 'class-validator';

export class FlightQueryDto {
  @IsString()
  @IsNotEmpty({ message: '出发地不能为空' })
  depCity: string;

  @IsString()
  @IsNotEmpty({ message: '目的地不能为空' })
  arrCity: string;
}

Step 2: 编写 Service (flight.service.ts)

实现核心业务逻辑。注意:优先使用卫语句(early return),复杂逻辑请拆分

import { Injectable } from '@nestjs/common';
import { FlightQueryDto } from './dto/flight-query.dto';
import { ObjectUtils } from 'src/common/utils/object.util'; // 假设有这个工具类

@Injectable()
export class FlightService {
  async getFlightList(query: FlightQueryDto) {
    // 卫语句:优先拦截异常情况
    if (ObjectUtils.isEmpty(query.depCity) || ObjectUtils.isEmpty(query.arrCity)) {
      throw new Error('城市参数缺失'); // 全局 Filter 会捕获并格式化抛出
    }

    // 业务逻辑处理...
    return [{ flightNo: 'CZ3201', dep: query.depCity, arr: query.arrCity }];
  }
}

Step 3: 编写 Controller (flight.controller.ts)

接收请求,调用 Service。

import { Controller, Get, Query } from '@nestjs/common';
import { FlightService } from './flight.service';
import { FlightQueryDto } from './dto/flight-query.dto';

@Controller('ota/flight') // 相当于 @RequestMapping("/ota/flight")
export class FlightController {
  // 构造函数注入 Service
  constructor(private readonly flightService: FlightService) {}

  @Get('list') // 相当于 @GetMapping("/list")
  async getList(@Query() query: FlightQueryDto) {
    return await this.flightService.getFlightList(query);
  }
}

Step 4: 在 Module 中注册(⚠️最容易忘的一步)

必须将新建的 Controller 和 Service 注册到对应的 Module 中,否则 IoC 容器无法识别。

import { Module } from '@nestjs/common';
import { FlightController } from './flight.controller';
import { FlightService } from './flight.service';

@Module({
  controllers: [FlightController],
  providers: [FlightService], // 相当于放入 Spring 容器
  exports: [FlightService], // 如果其他模块需要用 FlightService,必须导出
})
export class FlightModule {}

第五阶段:团队编码规范与避坑指南

结合本项目的规范要求,在编码时请严格遵守以下几点:

  1. 异步处理 (Async/Await):Node.js 是单线程的,所有涉及 I/O(查库、HTTP请求、读写文件)的方法都必须返回 Promise,并在调用时使用 await
  2. 导入规范:使用 import { xxx } from '...' 的方式导入,将相似的导入放在类的最上面,严禁在类中直接使用 xx.xxx.xxx 方式引用。
  3. 判空规范:优先使用 ObjectUtils.isEmpty() 进行非空判断,而不是直接使用 == null
  4. 异常处理:除了打印日志,必须抛出异常(不要吞掉异常),以便被全局异常过滤器处理。
  5. 代码整洁度
    • 优先使用卫语句 (Guard Clause / early return) 减少嵌套。
    • 单个方法内保证有效代码在 15行 内。
    • 复杂问题拆解为更小模块,复杂的校验逻辑应从 Service 抽离到独立的 Validator 类中。
    • 当有常量时,优先抽出来作为常量进行调用。
  6. 面向对象与可维护性
    • 按最小开发单元生成代码,每个类功能要单一。
    • 避免重复代码,建议使用继承、多态等机制复用。
    • 避免出现内部类。
  7. 性能规范:考虑代码执行效率(如:不要循环插入数据库,应使用批量插入)。
  8. 并发与状态一致性:高并发场景下,倾向使用“Redis分布式锁 + 数据库唯一索引 + 条件更新(CAS)”组合方案。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐