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

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

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

第一阶段:全局宏观认知

1. 架构思想的无缝平移

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

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

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

Spring BootNestJS作用说明
@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")exportclassUser{@PrimaryGeneratedColumn()// @Id @GeneratedValueid:number;@Column({name:'user_name',length:50})// @ColumnuserName: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()exportclassUserService{// 等同于 @Autowired private UserRepository userRepository;constructor(@InjectRepository(User)privatereadonlyuserRepository:Repository<User>,){}asyncfindUser(id:number){returnawaitthis.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';exportclassFlightQueryDto{@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()exportclassFlightService{asyncgetFlightList(query:FlightQueryDto){// 卫语句:优先拦截异常情况if(ObjectUtils.isEmpty(query.depCity)||ObjectUtils.isEmpty(query.arrCity)){thrownewError('城市参数缺失');// 全局 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")exportclassFlightController{// 构造函数注入 Serviceconstructor(privatereadonlyflightService:FlightService){}@Get('list')// 相当于 @GetMapping("/list")asyncgetList(@Query()query:FlightQueryDto){returnawaitthis.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,必须导出})exportclassFlightModule{}

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

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

  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)”组合方案。