美食网站开发的目的和意义,聊城集团网站建设费用,大学生兼职网网站建设计划书,网站创建怎么做文章目录 **第一部分#xff1a;认识Nest.js与基础环境搭建****1.1 什么是Nest.js#xff1f;****1.2 环境准备****1.3 创建第一个项目****1.4 启动开发服务器****1.5 核心文件解读** **第二部分#xff1a;基础控制器与路由****2.1 控制器的作用****2.2 创建自定义控制器**… 文章目录 **第一部分认识Nest.js与基础环境搭建****1.1 什么是Nest.js****1.2 环境准备****1.3 创建第一个项目****1.4 启动开发服务器****1.5 核心文件解读** **第二部分基础控制器与路由****2.1 控制器的作用****2.2 创建自定义控制器****2.3 路由参数处理****2.4 状态码处理****2.5 完整示例****2.6 测试你的API** **关键概念总结****第三部分服务层与依赖注入****3.1 为什么需要服务层****3.2 创建第一个服务****3.3 依赖注入实战****3.4 模块Module的作用****3.5 完整流程测试****3.6 依赖注入原理图解** **核心概念对比****第四部分数据库集成TypeORM PostgreSQL****4.1 准备工作****4.2 配置数据库连接****4.3 创建第一个实体Entity****4.4 修改服务层操作数据库****4.5 修改用户模块****4.6 数据验证DTO****4.7 完整API测试****4.8 错误处理示例** **数据库操作关键API****第五部分身份认证JWT策略****5.1 认证方案概述****5.2 安装必要依赖****5.3 用户实体增强****5.4 配置JWT模块****5.5 实现认证服务****5.6 创建策略守卫****5.7 实现认证控制器****5.8 保护路由****5.9 测试认证流程****5.10 安全增强建议** **认证核心组件****第六部分异常处理与日志****6.1 异常处理的重要性****6.2 创建自定义异常过滤器****6.3 全局注册过滤器****6.4 使用内置HTTP异常****6.5 日志记录配置****6.6 集成Winston日志****6.7 请求日志中间件****6.8 错误追踪集成Sentry示例****6.9 测试验证****6.10 最佳实践建议** **异常处理核心机制****第七部分单元测试与E2E测试****7.1 测试金字塔模型****7.2 初始化测试环境****7.3 服务层单元测试****7.4 控制器层测试****7.5 端到端测试E2E****7.6 运行与解读测试****7.7 测试最佳实践** **测试类型对比****第八部分部署与生产环境优化****8.1 Docker容器化部署****8.2 环境变量配置****8.3 性能优化策略****8.4 健康检查与监控****8.5 日志收集方案****8.6 部署验证****8.7 生产环境检查清单** **部署架构示意图****第九部分微服务架构进阶****9.1 微服务核心概念****9.2 创建基础微服务****9.3 实现gRPC通信****9.4 RabbitMQ消息队列集成****9.5 分布式事务处理Saga模式示例****9.6 微服务通信模式对比****9.7 服务发现与负载均衡****9.8 测试微服务通信****9.9 生产环境注意事项** **微服务架构核心组件****第十部分前端集成与全栈实践****10.1 Swagger API文档集成****10.2 前端项目配置以React为例****10.3 实现登录认证流程****10.4 前端路由保护React示例****10.5 全栈调试技巧****10.6 部署联调配置** **全栈开发关键点总结****项目完整工作流** 第一部分认识Nest.js与基础环境搭建
1.1 什么是Nest.js
Node.js后端框架类似Express/Koa但更结构化使用TypeScript构建也支持JavaScript结合了面向对象编程OOP 函数式编程FP内置依赖注入、模块化架构适合构建高效、可靠且易维护的服务端应用
1.2 环境准备
步骤1安装Node.js
前往官网下载LTS版本建议v18安装完成后验证node -v # 显示版本号
npm -v # 显示版本号步骤2安装Nest CLI
npm install -g nestjs/cli1.3 创建第一个项目
nest new my-first-project选择包管理器推荐使用npm或yarn
目录结构说明
my-first-project/
├── src/
│ ├── app.controller.ts # 控制器处理HTTP请求
│ ├── app.service.ts # 服务业务逻辑
│ ├── app.module.ts # 根模块组织应用结构
│ └── main.ts # 应用入口文件1.4 启动开发服务器
cd my-first-project
npm run start:dev访问 http://localhost:3000 看到Hello World!
1.5 核心文件解读
main.ts应用入口
import { NestFactory } from nestjs/core;
import { AppModule } from ./app.module;async function bootstrap() {const app await NestFactory.create(AppModule); // 创建应用实例await app.listen(3000); // 监听端口
}
bootstrap();app.controller.ts示例控制器
import { Controller, Get } from nestjs/common;
import { AppService } from ./app.service;Controller() // 控制器装饰器
export class AppController {constructor(private readonly appService: AppService) {} // 依赖注入Get() // 处理GET请求getHello(): string {return this.appService.getHello();}
}第二部分基础控制器与路由
2.1 控制器的作用
负责处理客户端请求通过装饰器定义路由路径调用服务层处理业务逻辑返回HTTP响应
2.2 创建自定义控制器
步骤1生成新控制器
nest generate controller user
# 或简写 nest g co user生成文件 src/user/user.controller.ts
步骤2基础路由示例
// user.controller.ts
import { Controller, Get } from nestjs/common;Controller(users) // 定义路由前缀 /users
export class UserController {Get() // 处理 /users 的GET请求findAll(): string {return All users;}Get(profile) // 处理 /users/profile 的GET请求getProfile(): string {return User profile;}
}2.3 路由参数处理
示例1路径参数
Get(:id) // 匹配 /users/123 形式的请求
findOne(Param(id) id: string): string {return User ID: ${id};
}示例2查询参数
Get(search)
search(Query(name) name: string): string {return Searching for: ${name};
}示例3POST请求体
Post()
create(Body() userData: any): string {return Created user: ${JSON.stringify(userData)};
}2.4 状态码处理
默认GET返回200POST返回201手动指定状态码
Post()
HttpCode(202) // 自定义状态码
createWithStatus() {return Created with custom status;
}2.5 完整示例
import { Controller, Get, Post, Param, Query, Body, HttpCode } from nestjs/common;Controller(users)
export class UserController {Get()findAll(): string {return User list;}Post()create(Body() user: { name: string }): string {return Created user: ${user.name};}Get(:id)findOne(Param(id) id: string): string {return User ID: ${id};}Get(search)search(Query(keyword) keyword: string): string {return Search keyword: ${keyword};}
}2.6 测试你的API 使用Postman或curl测试 # GET请求示例
curl http://localhost:3000/users
curl http://localhost:3000/users/123
curl http://localhost:3000/users/search?keywordjohn# POST请求示例
curl -X POST -H Content-Type: application/json -d {name:Alice} http://localhost:3000/users观察返回结果是否符合预期 关键概念总结
装饰器作用示例Controller定义控制器类并设置路由前缀Controller(users)Get处理GET请求Get(:id)Post处理POST请求Post()Param获取路径参数Param(id)Query获取查询参数Query(page)Body获取请求体数据Body() userDataHttpCode设置响应状态码HttpCode(204) 第三部分服务层与依赖注入
3.1 为什么需要服务层
遵循单一职责原则控制器只处理请求/响应集中存放业务逻辑方便代码复用和测试通过依赖注入实现解耦
3.2 创建第一个服务
步骤1生成服务文件
nest generate service user
# 或简写 nest g s user生成文件
src/user/user.service.ts服务类src/user/user.service.spec.ts测试文件
步骤2基础服务示例
// user.service.ts
import { Injectable } from nestjs/common;Injectable() // 标记为可注入的类
export class UserService {private users [{ id: 1, name: John },{ id: 2, name: Alice }];getAllUsers() {return this.users;}createUser(name: string) {const newUser { id: Date.now(), name };this.users.push(newUser);return newUser;}
}3.3 依赖注入实战
改造控制器user.controller.ts
import { Controller, Get, Post, Body } from nestjs/common;
import { UserService } from ./user.service;Controller(users)
export class UserController {constructor(private readonly userService: UserService) {} // 依赖注入Get()getAllUsers() {return this.userService.getAllUsers();}Post()createUser(Body(name) name: string) {return this.userService.createUser(name);}
}3.4 模块Module的作用
查看自动生成的 user.module.ts
import { Module } from nestjs/common;
import { UserService } from ./user.service;
import { UserController } from ./user.controller;Module({controllers: [UserController],providers: [UserService], // 注册服务
})
export class UserModule {}关键点
模块是Nest的组织单元providers 数组注册可注入的类通过Global()可以创建全局模块
3.5 完整流程测试 通过Postman发送请求 GET http://localhost:3000/users
POST http://localhost:3000/users
Body: { name: Bob }观察响应结果 // GET 响应
[{ id: 1, name: John },{ id: 2, name: Alice }
]// POST 响应
{ id: 1625641654845, name: Bob }3.6 依赖注入原理图解
---------------
| Controller |
|---------------| ---------------
| constructor( |--------| UserService |
| userService) | ---------------
---------------▲| 通过Module装饰器的providers数组注册
---------------
| Module |
|---------------|
| providers: [ |
| UserService |
| ] |
---------------核心概念对比
组件职责关键装饰器控制器Controller处理HTTP请求/响应Controller服务Service实现业务逻辑Injectable模块Module组织应用结构管理依赖关系Module
接下来计划讲解 第四部分数据库集成TypeORM PostgreSQL 包括
TypeORM基本配置实体Entity创建CRUD操作实现数据验证与DTO 第四部分数据库集成TypeORM PostgreSQL
4.1 准备工作 安装所需依赖 npm install nestjs/typeorm typeorm pgnestjs/typeorm: Nest的TypeORM集成包typeorm: ORM框架pg: PostgreSQL驱动 确保已安装PostgreSQL数据库本地或使用云服务
4.2 配置数据库连接
修改app.module.ts
import { TypeOrmModule } from nestjs/typeorm;Module({imports: [TypeOrmModule.forRoot({type: postgres,host: localhost, // 数据库地址port: 5432, // 数据库端口username: postgres, // 数据库用户名password: your_password, // 数据库密码database: nestdemo, // 数据库名称entities: [__dirname /**/*.entity{.ts,.js}], // 自动扫描实体文件synchronize: true, // 开发环境自动同步数据库结构生产环境禁用}),UserModule,],
})
export class AppModule {}4.3 创建第一个实体Entity
新建src/user/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from typeorm;Entity() // 标记为数据库实体
export class User {PrimaryGeneratedColumn() // 自增主键id: number;Column({ length: 50 }) // 字符串列长度限制50name: string;Column({ unique: true }) // 唯一约束email: string;Column({ default: () CURRENT_TIMESTAMP }) // 默认值createdAt: Date;
}4.4 修改服务层操作数据库
更新user.service.ts
import { Injectable } from nestjs/common;
import { InjectRepository } from nestjs/typeorm;
import { Repository } from typeorm;
import { User } from ./user.entity;Injectable()
export class UserService {constructor(InjectRepository(User) // 注入用户实体仓库private userRepository: RepositoryUser,) {}async findAll(): PromiseUser[] {return this.userRepository.find();}async create(userData: PartialUser): PromiseUser {const newUser this.userRepository.create(userData);return this.userRepository.save(newUser);}async findOne(id: number): PromiseUser | null {return this.userRepository.findOne({ where: { id } });}async remove(id: number): Promisevoid {await this.userRepository.delete(id);}
}4.5 修改用户模块
更新user.module.ts
import { Module } from nestjs/common;
import { TypeOrmModule } from nestjs/typeorm;
import { User } from ./user.entity;
import { UserService } from ./user.service;
import { UserController } from ./user.controller;Module({imports: [TypeOrmModule.forFeature([User])], // 注册实体到模块controllers: [UserController],providers: [UserService],
})
export class UserModule {}4.6 数据验证DTO 安装验证库 npm install class-validator class-transformer创建src/user/dto/create-user.dto.ts import { IsString, IsEmail, Length } from class-validator;export class CreateUserDto {IsString()Length(2, 50)name: string;IsEmail()email: string;
}更新控制器 import { Body, ValidationPipe } from nestjs/common;Post()
async create(Body(new ValidationPipe()) createUserDto: CreateUserDto, // 自动验证
) {return this.userService.create(createUserDto);
}4.7 完整API测试
# 创建用户
curl -X POST -H Content-Type: application/json \
-d {name:Lisa,email:lisaexample.com} \
http://localhost:3000/users# 获取所有用户
curl http://localhost:3000/users# 获取单个用户
curl http://localhost:3000/users/1# 删除用户
curl -X DELETE http://localhost:3000/users/14.8 错误处理示例
// 在控制器中处理查找异常
Get(:id)
async findOne(Param(id) id: string) {const user await this.userService.findOne(id);if (!user) {throw new NotFoundException(User not found); // 自动返回404}return user;
}数据库操作关键API
方法作用示例repository.find()获取所有记录userRepository.find()repository.findOne()获取单条记录findOne({ where: { id } })repository.save()创建/更新记录save(user)repository.delete()删除记录delete(id)
接下来计划讲解 第五部分身份认证JWT策略 包括
用户注册/登录实现Passport.js集成JWT签发与验证路由守卫使用 第五部分身份认证JWT策略
5.1 认证方案概述 #mermaid-svg-oXwqmVfd0hYp6Mhy {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .error-icon{fill:#552222;}#mermaid-svg-oXwqmVfd0hYp6Mhy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oXwqmVfd0hYp6Mhy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .marker.cross{stroke:#333333;}#mermaid-svg-oXwqmVfd0hYp6Mhy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oXwqmVfd0hYp6Mhy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .cluster-label text{fill:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .cluster-label span{color:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .label text,#mermaid-svg-oXwqmVfd0hYp6Mhy span{fill:#333;color:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .node rect,#mermaid-svg-oXwqmVfd0hYp6Mhy .node circle,#mermaid-svg-oXwqmVfd0hYp6Mhy .node ellipse,#mermaid-svg-oXwqmVfd0hYp6Mhy .node polygon,#mermaid-svg-oXwqmVfd0hYp6Mhy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oXwqmVfd0hYp6Mhy .node .label{text-align:center;}#mermaid-svg-oXwqmVfd0hYp6Mhy .node.clickable{cursor:pointer;}#mermaid-svg-oXwqmVfd0hYp6Mhy .arrowheadPath{fill:#333333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oXwqmVfd0hYp6Mhy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-oXwqmVfd0hYp6Mhy .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-oXwqmVfd0hYp6Mhy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oXwqmVfd0hYp6Mhy .cluster text{fill:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy .cluster span{color:#333;}#mermaid-svg-oXwqmVfd0hYp6Mhy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oXwqmVfd0hYp6Mhy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 提交凭证 2. 验证成功 3. 返回Token 4. 携带Token 5. 验证Token 客户端 登录接口 签发JWT 受保护资源 返回数据 5.2 安装必要依赖
npm install nestjs/passport passport passport-jwt nestjs/jwt bcrypt
npm install types/passport-jwt types/bcrypt --save-dev5.3 用户实体增强
// user.entity.ts
import { BeforeInsert } from typeorm;Entity()
export class User {// ...其他字段Column()password: string;BeforeInsert() // 自动加密密码async hashPassword() {this.password await bcrypt.hash(this.password, 10);}
}5.4 配置JWT模块
// auth.module.ts
import { JwtModule } from nestjs/jwt;Module({imports: [JwtModule.register({global: true,secret: your-secret-key, // 生产环境应使用环境变量signOptions: { expiresIn: 1h },}),],// ...
})
export class AuthModule {}5.5 实现认证服务
// auth.service.ts
import { compare } from bcrypt;Injectable()
export class AuthService {constructor(InjectRepository(User)private usersRepository: RepositoryUser,private jwtService: JwtService,) {}async validateUser(email: string, pass: string): Promiseany {const user await this.usersRepository.findOne({ where: { email } });if (user await compare(pass, user.password)) {const { password, ...result } user; // 移除密码字段return result;}return null;}async login(user: User) {const payload { email: user.email, sub: user.id };return {access_token: this.jwtService.sign(payload),};}
}5.6 创建策略守卫
// jwt.strategy.ts
import { ExtractJwt, Strategy } from passport-jwt;Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {constructor() {super({jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),ignoreExpiration: false,secretOrKey: your-secret-key,});}async validate(payload: any) {return { userId: payload.sub, email: payload.email };}
}5.7 实现认证控制器
// auth.controller.ts
Controller(auth)
export class AuthController {constructor(private authService: AuthService) {}Post(login)async login(Body() loginDto: LoginDto) {const user await this.authService.validateUser(loginDto.email,loginDto.password,);if (!user) {throw new UnauthorizedException(Invalid credentials);}return this.authService.login(user);}Post(register)async register(Body() createUserDto: CreateUserDto) {return this.authService.register(createUserDto);}
}5.8 保护路由
// user.controller.ts
Get(profile)
UseGuards(AuthGuard(jwt)) // 添加守卫
async getProfile(Request() req) {return req.user;
}5.9 测试认证流程
# 注册用户
curl -X POST -H Content-Type: application/json \
-d {name:test,email:testexample.com,password:123456} \
http://localhost:3000/auth/register# 登录获取Token
curl -X POST -H Content-Type: application/json \
-d {email:testexample.com,password:123456} \
http://localhost:3000/auth/login# 访问受保护资源
curl -H Authorization: Bearer your-token \
http://localhost:3000/users/profile5.10 安全增强建议
使用环境变量存储密钥实现密码强度验证添加刷新令牌机制设置合理的令牌有效期记录认证日志 认证核心组件
组件作用关键方法/装饰器JwtModuleJWT配置模块register()JwtStrategy验证请求携带的JWTvalidate()AuthGuard路由守卫UseGuards(AuthGuard())bcrypt密码哈希处理hash(), compare()nestjs/jwtJWT签发与验证sign(), verify() 第六部分异常处理与日志
6.1 异常处理的重要性 #mermaid-svg-BQsuqPeXmtf5E8gT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT .error-icon{fill:#552222;}#mermaid-svg-BQsuqPeXmtf5E8gT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BQsuqPeXmtf5E8gT .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-BQsuqPeXmtf5E8gT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BQsuqPeXmtf5E8gT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BQsuqPeXmtf5E8gT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BQsuqPeXmtf5E8gT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BQsuqPeXmtf5E8gT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BQsuqPeXmtf5E8gT .marker.cross{stroke:#333333;}#mermaid-svg-BQsuqPeXmtf5E8gT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BQsuqPeXmtf5E8gT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT .cluster-label text{fill:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT .cluster-label span{color:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT .label text,#mermaid-svg-BQsuqPeXmtf5E8gT span{fill:#333;color:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT .node rect,#mermaid-svg-BQsuqPeXmtf5E8gT .node circle,#mermaid-svg-BQsuqPeXmtf5E8gT .node ellipse,#mermaid-svg-BQsuqPeXmtf5E8gT .node polygon,#mermaid-svg-BQsuqPeXmtf5E8gT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BQsuqPeXmtf5E8gT .node .label{text-align:center;}#mermaid-svg-BQsuqPeXmtf5E8gT .node.clickable{cursor:pointer;}#mermaid-svg-BQsuqPeXmtf5E8gT .arrowheadPath{fill:#333333;}#mermaid-svg-BQsuqPeXmtf5E8gT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BQsuqPeXmtf5E8gT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BQsuqPeXmtf5E8gT .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-BQsuqPeXmtf5E8gT .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-BQsuqPeXmtf5E8gT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BQsuqPeXmtf5E8gT .cluster text{fill:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT .cluster span{color:#333;}#mermaid-svg-BQsuqPeXmtf5E8gT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BQsuqPeXmtf5E8gT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功 失败 客户端请求 应用处理 返回数据 异常过滤器 格式化错误响应 客户端接收 6.2 创建自定义异常过滤器
步骤1生成过滤器
nest generate filter common/exceptions/http-exception步骤2实现过滤器逻辑
// http-exception.filter.ts
import {ExceptionFilter,Catch,ArgumentsHost,HttpException,
} from nestjs/common;
import { Request, Response } from express;Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {catch(exception: HttpException, host: ArgumentsHost) {const ctx host.switchToHttp();const response ctx.getResponseResponse();const request ctx.getRequestRequest();const status exception.getStatus();response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),path: request.url,message: exception.message || Unknown error,});}
}6.3 全局注册过滤器
// main.ts
async function bootstrap() {const app await NestFactory.create(AppModule);app.useGlobalFilters(new HttpExceptionFilter()); // 注册全局过滤器await app.listen(3000);
}6.4 使用内置HTTP异常
// user.controller.ts
Get(:id)
async findOne(Param(id) id: string) {const user await this.userService.findOne(id);if (!user) {throw new NotFoundException(User ${id} not found);}return user;
}6.5 日志记录配置
启用默认日志
// main.ts
const app await NestFactory.create(AppModule, {logger: [log, error, warn, debug, verbose],
});自定义日志服务
// logger.service.ts
import { LoggerService } from nestjs/common;export class MyLogger implements LoggerService {log(message: string) {console.log([LOG] ${new Date().toISOString()} - ${message});}error(message: string, trace: string) {console.error([ERROR] ${new Date().toISOString()} - ${message}, trace);}warn(message: string) {console.warn([WARN] ${new Date().toISOString()} - ${message});}
}// main.ts
const app await NestFactory.create(AppModule, {logger: new MyLogger(),
});6.6 集成Winston日志
安装依赖
npm install winston types/winston创建日志配置文件
// logger/logger.config.ts
import { createLogger, format, transports } from winston;export const winstonConfig {level: info,format: format.combine(format.timestamp(),format.printf(({ timestamp, level, message }) {return ${timestamp} [${level}]: ${message};})),transports: [new transports.Console(),new transports.File({ filename: logs/error.log, level: error }),new transports.File({ filename: logs/combined.log }),],
};创建NestJS适配器
// logger/winston.logger.ts
import { LoggerService } from nestjs/common;
import { createLogger, Logger } from winston;
import { winstonConfig } from ./logger.config;export class WinstonLogger implements LoggerService {private logger: Logger;constructor() {this.logger createLogger(winstonConfig);}log(message: string) {this.logger.info(message);}error(message: string, trace: string) {this.logger.error(${message} - ${trace});}warn(message: string) {this.logger.warn(message);}debug(message: string) {this.logger.debug(message);}verbose(message: string) {this.logger.verbose(message);}
}6.7 请求日志中间件
// logger/request.logger.middleware.ts
import { Injectable, NestMiddleware } from nestjs/common;
import { Request, Response, NextFunction } from express;Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {use(req: Request, res: Response, next: NextFunction) {const start Date.now();const { method, originalUrl } req;res.on(finish, () {const duration Date.now() - start;const { statusCode } res;console.log([${method}] ${originalUrl} - ${statusCode} (${duration}ms),);});next();}
}// app.module.ts
export class AppModule implements NestModule {configure(consumer: MiddlewareConsumer) {consumer.apply(RequestLoggerMiddleware).forRoutes(*);}
}6.8 错误追踪集成Sentry示例
安装依赖
npm install sentry/node sentry/tracing配置Sentry
// sentry.config.ts
import * as Sentry from sentry/node;Sentry.init({dsn: your-dsn-here,tracesSampleRate: 1.0,
});集成到异常过滤器
// http-exception.filter.ts
catch(exception: HttpException, host: ArgumentsHost) {const ctx host.switchToHttp();const request ctx.getRequestRequest();Sentry.captureException(exception, {extra: {path: request.url,method: request.method,body: request.body,},});// ...原有处理逻辑
}6.9 测试验证
# 触发404错误
curl http://localhost:3000/nonexistent# 查看日志文件
tail -f logs/error.log# 预期输出示例
2023-08-20T10:00:00.000Z [ERROR] 404 - Cannot GET /nonexistent6.10 最佳实践建议 使用不同日志级别 verbose: 详细调试信息debug: 调试信息log: 常规日志warn: 警告信息error: 错误信息 日志文件管理 使用logrotate进行日志轮换敏感信息过滤按日期分割日志文件 生产环境注意事项 禁用synchronize选项设置适当的日志级别使用集中式日志系统ELK/Splunk 异常处理核心机制
组件作用关键方法/装饰器ExceptionFilter捕获并处理异常Catch()HttpException预定义的HTTP异常new BadRequestException()LoggerService日志接口log(), error()Middleware记录请求信息implements NestMiddleware 第七部分单元测试与E2E测试
7.1 测试金字塔模型 #mermaid-svg-GHatQrOmM6iHPPTF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GHatQrOmM6iHPPTF .error-icon{fill:#552222;}#mermaid-svg-GHatQrOmM6iHPPTF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GHatQrOmM6iHPPTF .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-GHatQrOmM6iHPPTF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GHatQrOmM6iHPPTF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GHatQrOmM6iHPPTF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GHatQrOmM6iHPPTF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GHatQrOmM6iHPPTF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GHatQrOmM6iHPPTF .marker.cross{stroke:#333333;}#mermaid-svg-GHatQrOmM6iHPPTF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GHatQrOmM6iHPPTF .pieCircle{stroke:black;stroke-width:2px;opacity:0.7;}#mermaid-svg-GHatQrOmM6iHPPTF .pieTitleText{text-anchor:middle;font-size:25px;fill:black;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-GHatQrOmM6iHPPTF .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#333;font-size:17px;}#mermaid-svg-GHatQrOmM6iHPPTF .legend text{fill:black;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-GHatQrOmM6iHPPTF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 70% 20% 10% 测试类型分布 单元测试 集成测试 E2E测试 7.2 初始化测试环境
项目已内置测试配置
test/ 目录存放测试相关配置jest.config.jsJest测试框架配置package.json 脚本{scripts: {test: jest,test:watch: jest --watch,test:cov: jest --coverage}
}7.3 服务层单元测试
测试目标UserService 创建测试文件user.service.spec.ts
import { Test, TestingModule } from nestjs/testing;
import { getRepositoryToken } from nestjs/typeorm;
import { User } from ./user.entity;
import { UserService } from ./user.service;// 模拟Repository
const mockRepository {find: jest.fn(),findOne: jest.fn(),save: jest.fn(),delete: jest.fn(),
};describe(UserService, () {let service: UserService;beforeEach(async () {const module: TestingModule await Test.createTestingModule({providers: [UserService,{provide: getRepositoryToken(User),useValue: mockRepository,},],}).compile();service module.getUserService(UserService);});afterEach(() {jest.clearAllMocks();});describe(findAll, () {it(应返回用户数组, async () {const mockUsers [{ id: 1, name: Test }];mockRepository.find.mockResolvedValue(mockUsers);const result await service.findAll();expect(result).toEqual(mockUsers);expect(mockRepository.find).toHaveBeenCalledTimes(1);});});describe(create, () {it(应成功创建用户, async () {const newUser { name: New };mockRepository.save.mockResolvedValue({ id: 1, ...newUser });const result await service.create(newUser);expect(result).toHaveProperty(id, 1);expect(mockRepository.save).toHaveBeenCalledWith(newUser);});});
});7.4 控制器层测试
测试目标UserController 创建测试文件user.controller.spec.ts
import { Test, TestingModule } from nestjs/testing;
import { UserController } from ./user.controller;
import { UserService } from ./user.service;describe(UserController, () {let controller: UserController;const mockUserService {findAll: jest.fn().mockResolvedValue([{ id: 1 }]),create: jest.fn().mockImplementation((dto) Promise.resolve({ id: 1, ...dto })),};beforeEach(async () {const module: TestingModule await Test.createTestingModule({controllers: [UserController],providers: [{provide: UserService,useValue: mockUserService,},],}).compile();controller module.getUserController(UserController);});it(GET /users 应返回用户列表, async () {await expect(controller.getAllUsers()).resolves.toEqual([{ id: 1 }]);expect(mockUserService.findAll).toHaveBeenCalled();});it(POST /users 应创建新用户, async () {const dto { name: Test };await expect(controller.createUser(dto)).resolves.toEqual({id: 1,...dto,});expect(mockUserService.create).toHaveBeenCalledWith(dto);});
});7.5 端到端测试E2E
创建测试文件app.e2e-spec.ts
import { Test, TestingModule } from nestjs/testing;
import { INestApplication } from nestjs/common;
import * as request from supertest;
import { AppModule } from ../src/app.module;describe(AppController (e2e), () {let app: INestApplication;beforeAll(async () {const moduleFixture: TestingModule await Test.createTestingModule({imports: [AppModule],}).compile();app moduleFixture.createNestApplication();await app.init();});afterAll(async () {await app.close();});it(/ (GET), () {return request(app.getHttpServer()).get(/).expect(200).expect(Hello World!);});describe(用户模块, () {it(GET /users 应返回空数组, () {return request(app.getHttpServer()).get(/users).expect(200).expect([]);});it(POST /users 应创建用户, async () {const response await request(app.getHttpServer()).post(/users).send({ name: E2E Test }).expect(201);expect(response.body).toHaveProperty(id);expect(response.body.name).toBe(E2E Test);});});
});7.6 运行与解读测试
执行测试命令
# 运行全部测试
npm run test# 开发时监听模式
npm run test:watch# 生成覆盖率报告
npm run test:cov覆盖率报告示例
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 92 | 85 | 90 | 92 | user.service | 100 | 100 | 100 | 100 | user.controller| 95 | 80 | 90 | 95 | 32
----------------|---------|----------|---------|---------|-------------------7.7 测试最佳实践 测试命名规范 describe(场景描述, () {it(应完成特定行为, () {// 测试逻辑});
});测试三部曲 准备Arrange设置测试数据和模拟执行Act调用被测试方法断言Assert验证结果 测试隔离原则 每个测试用例独立运行使用beforeEach/afterEach重置状态避免测试间的依赖关系 测试数据库策略 // 使用测试专用数据库
TypeOrmModule.forRoot({database: test_db,synchronize: true,
})测试类型对比
测试类型测试范围执行速度维护成本适合场景单元测试单个类/方法快低核心业务逻辑验证集成测试模块间交互中中服务层与数据库交互E2E测试完整系统流程慢高用户操作流程验证 第八部分部署与生产环境优化
8.1 Docker容器化部署
步骤1创建Dockerfile
# 使用Node.js官方镜像
FROM node:18-alpine# 设置工作目录
WORKDIR /app# 复制依赖文件
COPY package*.json ./# 安装依赖生产环境不装devDependencies
RUN npm install --onlyproduction# 复制项目文件
COPY . .# 构建项目如果需要编译TypeScript
RUN npm run build# 暴露端口
EXPOSE 3000# 启动命令
CMD [npm, run, start:prod]步骤2创建docker-compose.yml
version: 3.8services:app:build: .ports:- 3000:3000environment:- NODE_ENVproductiondepends_on:- dbdb:image: postgres:15environment:POSTGRES_USER: postgresPOSTGRES_PASSWORD: yoursecurepasswordPOSTGRES_DB: nestprodvolumes:- pgdata:/var/lib/postgresql/dataports:- 5432:5432volumes:pgdata:步骤3构建并启动容器
docker-compose up -d --build8.2 环境变量配置
步骤1安装配置模块
npm install nestjs/config步骤2创建.env文件
# .env.production
DATABASE_HOSTdb
DATABASE_PORT5432
DATABASE_USERpostgres
DATABASE_PASSWORDyoursecurepassword
DATABASE_NAMEnestprod
JWT_SECRETprod_secret_key步骤3更新app.module.ts
import { ConfigModule } from nestjs/config;Module({imports: [ConfigModule.forRoot({envFilePath: .env.${process.env.NODE_ENV},isGlobal: true,}),TypeOrmModule.forRootAsync({imports: [ConfigModule],useFactory: (config: ConfigService) ({type: postgres,host: config.get(DATABASE_HOST),port: config.get(DATABASE_PORT),username: config.get(DATABASE_USER),password: config.get(DATABASE_PASSWORD),database: config.get(DATABASE_NAME),entities: [__dirname /**/*.entity{.ts,.js}],synchronize: false, // 生产环境必须关闭}),inject: [ConfigService],}),],
})8.3 性能优化策略
1. 启用压缩
// main.ts
import compression from compression;async function bootstrap() {const app await NestFactory.create(AppModule);app.use(compression()); // 添加GZIP压缩
}2. 集群模式利用多核CPU
// main.ts
import { clusterize } from nestjs/clusterize;async function bootstrap() {await clusterize({workers: process.env.NODE_ENV production ? max : 1,bootstrap: async () {const app await NestFactory.create(AppModule);await app.listen(3000);},});
}3. 缓存策略
// 安装缓存模块
npm install cache-manager nestjs/cache-manager// app.module.ts
import { CacheModule } from nestjs/cache-manager;Module({imports: [CacheModule.register({ttl: 60, // 缓存时间秒max: 1000, // 最大缓存数isGlobal: true,}),],
})8.4 健康检查与监控
1. 添加终止信号处理
// main.ts
async function bootstrap() {const app await NestFactory.create(AppModule);// 优雅关闭process.on(SIGTERM, () {app.close().then(() {console.log(Application closed);process.exit(0);});});
}2. 集成健康检查
npm install nestjs/terminus// health.controller.ts
import { Controller, Get } from nestjs/common;
import { HealthCheckService, HealthCheck } from nestjs/terminus;Controller(health)
export class HealthController {constructor(private health: HealthCheckService) {}Get()HealthCheck()check() {return this.health.check([]);}
}3. Prometheus监控可选
npm install nestjs/metrics prom-client// metrics.module.ts
import { Module } from nestjs/common;
import { PrometheusModule } from nestjs/metrics;Module({imports: [PrometheusModule.register()],
})
export class MetricsModule {}8.5 日志收集方案
1. 生产环境日志配置
// winston.config.prod.ts
export const winstonConfig {transports: [new transports.File({ filename: logs/error.log, level: error,maxsize: 1024 * 1024 * 10, // 10MBmaxFiles: 7 }),new transports.File({filename: logs/combined.log,maxsize: 1024 * 1024 * 50, // 50MBmaxFiles: 14})]
};2. 日志查询命令
# 查看实时日志
tail -f logs/combined.log# 根据时间过滤日志
grep 2023-08-20T10 logs/error.log8.6 部署验证
# 检查容器状态
docker ps -a# 查看应用日志
docker logs container_id# 测试健康检查端点
curl http://localhost:3000/health# 压力测试安装wrk
wrk -t12 -c400 -d30s http://localhost:3000/users8.7 生产环境检查清单 禁用synchronize: true 使用HTTPS加密通信 配置防火墙规则 设置自动备份策略 实施速率限制 定期安全扫描 监控CPU/内存使用 设置报警阈值 部署架构示意图 #mermaid-svg-aJgjblI880zN9xvZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-aJgjblI880zN9xvZ .error-icon{fill:#552222;}#mermaid-svg-aJgjblI880zN9xvZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aJgjblI880zN9xvZ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-aJgjblI880zN9xvZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aJgjblI880zN9xvZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aJgjblI880zN9xvZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aJgjblI880zN9xvZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aJgjblI880zN9xvZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aJgjblI880zN9xvZ .marker.cross{stroke:#333333;}#mermaid-svg-aJgjblI880zN9xvZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aJgjblI880zN9xvZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aJgjblI880zN9xvZ .cluster-label text{fill:#333;}#mermaid-svg-aJgjblI880zN9xvZ .cluster-label span{color:#333;}#mermaid-svg-aJgjblI880zN9xvZ .label text,#mermaid-svg-aJgjblI880zN9xvZ span{fill:#333;color:#333;}#mermaid-svg-aJgjblI880zN9xvZ .node rect,#mermaid-svg-aJgjblI880zN9xvZ .node circle,#mermaid-svg-aJgjblI880zN9xvZ .node ellipse,#mermaid-svg-aJgjblI880zN9xvZ .node polygon,#mermaid-svg-aJgjblI880zN9xvZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aJgjblI880zN9xvZ .node .label{text-align:center;}#mermaid-svg-aJgjblI880zN9xvZ .node.clickable{cursor:pointer;}#mermaid-svg-aJgjblI880zN9xvZ .arrowheadPath{fill:#333333;}#mermaid-svg-aJgjblI880zN9xvZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aJgjblI880zN9xvZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aJgjblI880zN9xvZ .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-aJgjblI880zN9xvZ .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-aJgjblI880zN9xvZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aJgjblI880zN9xvZ .cluster text{fill:#333;}#mermaid-svg-aJgjblI880zN9xvZ .cluster span{color:#333;}#mermaid-svg-aJgjblI880zN9xvZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aJgjblI880zN9xvZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端 负载均衡器 容器实例1 容器实例2 PostgreSQL集群 备份存储 日志收集系统 监控仪表盘 第九部分微服务架构进阶
9.1 微服务核心概念 #mermaid-svg-jvXy0v6hmyJCnJwd {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd .error-icon{fill:#552222;}#mermaid-svg-jvXy0v6hmyJCnJwd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jvXy0v6hmyJCnJwd .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-jvXy0v6hmyJCnJwd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jvXy0v6hmyJCnJwd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jvXy0v6hmyJCnJwd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jvXy0v6hmyJCnJwd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jvXy0v6hmyJCnJwd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jvXy0v6hmyJCnJwd .marker.cross{stroke:#333333;}#mermaid-svg-jvXy0v6hmyJCnJwd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jvXy0v6hmyJCnJwd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd .cluster-label text{fill:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd .cluster-label span{color:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd .label text,#mermaid-svg-jvXy0v6hmyJCnJwd span{fill:#333;color:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd .node rect,#mermaid-svg-jvXy0v6hmyJCnJwd .node circle,#mermaid-svg-jvXy0v6hmyJCnJwd .node ellipse,#mermaid-svg-jvXy0v6hmyJCnJwd .node polygon,#mermaid-svg-jvXy0v6hmyJCnJwd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jvXy0v6hmyJCnJwd .node .label{text-align:center;}#mermaid-svg-jvXy0v6hmyJCnJwd .node.clickable{cursor:pointer;}#mermaid-svg-jvXy0v6hmyJCnJwd .arrowheadPath{fill:#333333;}#mermaid-svg-jvXy0v6hmyJCnJwd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jvXy0v6hmyJCnJwd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jvXy0v6hmyJCnJwd .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-jvXy0v6hmyJCnJwd .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-jvXy0v6hmyJCnJwd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jvXy0v6hmyJCnJwd .cluster text{fill:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd .cluster span{color:#333;}#mermaid-svg-jvXy0v6hmyJCnJwd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jvXy0v6hmyJCnJwd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端 API网关 用户服务 订单服务 支付服务 用户数据库 订单数据库 支付数据库 9.2 创建基础微服务
步骤1安装依赖
npm install nestjs/microservices步骤2创建用户服务TCP通信
// user-service/src/main.ts
import { NestFactory } from nestjs/core;
import { MicroserviceOptions, Transport } from nestjs/microservices;
import { UserModule } from ./user.module;async function bootstrap() {const app await NestFactory.createMicroserviceMicroserviceOptions(UserModule,{transport: Transport.TCP,options: {host: localhost,port: 3001,},},);await app.listen();
}
bootstrap();步骤3定义用户服务接口
// shared/user.interface.ts
export interface User {id: number;name: string;
}export interface FindUserRequest {id: number;
}export interface CreateUserRequest {name: string;
}9.3 实现gRPC通信
步骤1定义proto文件
// proto/user.proto
syntax proto3;package user;service UserService {rpc FindUser (FindUserRequest) returns (User) {}rpc CreateUser (CreateUserRequest) returns (User) {}
}message FindUserRequest {int32 id 1;
}message CreateUserRequest {string name 1;
}message User {int32 id 1;string name 2;
}步骤2配置gRPC服务端
// user-service/src/main.ts
{transport: Transport.GRPC,options: {package: user,protoPath: join(__dirname, proto/user.proto),url: localhost:50051,},
}步骤3实现gRPC客户端
// api-gateway/src/user.client.ts
Client({transport: Transport.GRPC,options: {package: user,protoPath: join(__dirname, proto/user.proto),url: localhost:50051,},
})
client: ClientGrpc;private userService: UserService;onModuleInit() {this.userService this.client.getServiceUserService(UserService);
}Get(users/:id)
async findUser(Param(id) id: number) {return this.userService.findUser({ id });
}9.4 RabbitMQ消息队列集成
步骤1安装依赖
npm install nestjs/microservices amqplib amqp-connection-manager步骤2配置消息生产者
// order-service/src/order.service.ts
Injectable()
export class OrderService {constructor(Inject(RABBITMQ_CLIENT) private readonly client: ClientProxy,) {}async createOrder(orderData: CreateOrderDto) {// 发送创建订单事件this.client.emit(order_created, orderData);return { status: processing };}
}步骤3配置消息消费者
// payment-service/src/payment.consumer.ts
Controller()
export class PaymentController {EventPattern(order_created)async handleOrderCreated(data: CreateOrderDto) {// 处理支付逻辑console.log(Processing payment for order:, data);// 发送支付完成事件this.client.emit(payment_processed, {orderId: data.id,status: paid,});}
}9.5 分布式事务处理Saga模式示例
// 订单创建Saga流程
async createOrderSaga(orderData) {try {// 1. 创建订单Pending状态const order await this.orderService.createPendingOrder(orderData);// 2. 扣减库存await this.inventoryService.reserveStock(order.items);// 3. 处理支付const payment await this.paymentService.processPayment(order);// 4. 确认订单await this.orderService.confirmOrder(order.id);return order;} catch (error) {// 补偿操作await this.orderService.cancelOrder(order.id);await this.inventoryService.releaseStock(order.items);throw error;}
}9.6 微服务通信模式对比
模式协议适用场景优点缺点请求-响应HTTP/REST简单查询操作简单易用同步阻塞gRPCHTTP/2高性能内部通信高效、强类型需要proto定义消息队列AMQP异步任务处理解耦、可靠架构复杂度增加事件驱动Pub/Sub实时数据更新实时性高消息顺序需处理
9.7 服务发现与负载均衡
使用Consul示例配置
// 服务注册
import { ConsulService } from nestjs/consul;Module({providers: [{provide: CONSUL_CLIENT,useFactory: () {return new ConsulService({host: consul-server,port: 8500,});},},],
})客户端负载均衡
Client({transport: Transport.TCP,options: {serviceName: user-service,loadBalancer: new RoundRobinLoadBalancer(),discoverer: new ConsulDiscoverer({host: consul-server,port: 8500,}),},
})9.8 测试微服务通信
// 测试gRPC服务
describe(UserService (gRPC), () {let client: UserServiceClient;beforeAll(async () {const packageDefinition await loadPackageDefinition(loadSync(join(__dirname, proto/user.proto)));const proto packageDefinition.user as any;client new proto.UserService(localhost:50051,credentials.createInsecure());});it(should return user details, (done) {client.findUser({ id: 1 }, (err, response) {expect(response).toEqual({ id: 1, name: Test User });done();});});
});9.9 生产环境注意事项 服务监控 使用Prometheus Grafana监控服务指标实现健康检查端点 容错处理 // 断路器模式
Get(users/:id)
UseFilters(CircuitBreakerFilter)
async getUser(Param(id) id: string) {return this.userService.findUser(id);
}日志追踪 集成OpenTelemetry实现分布式追踪使用唯一请求ID串联日志 安全策略 服务间TLS加密通信JWT认证传递速率限制 微服务架构核心组件
组件作用常用工具API网关请求路由、聚合、认证Kong, NestJS网关服务注册中心服务发现与管理Consul, etcd, Zookeeper配置中心统一管理配置Spring Cloud Config消息中间件异步通信RabbitMQ, Kafka分布式追踪系统请求链路追踪Jaeger, Zipkin 第十部分前端集成与全栈实践
10.1 Swagger API文档集成
步骤1安装依赖
npm install nestjs/swagger swagger-ui-express步骤2配置Swagger模块
// main.ts
import { SwaggerModule, DocumentBuilder } from nestjs/swagger;async function bootstrap() {const app await NestFactory.create(AppModule);const config new DocumentBuilder().setTitle(NestJS API).setDescription(全栈开发接口文档).setVersion(1.0).addBearerAuth() // 启用JWT认证.build();const document SwaggerModule.createDocument(app, config);SwaggerModule.setup(api-docs, app, document);await app.listen(3000);
}步骤3添加接口注释
// user.controller.ts
ApiOperation({ summary: 获取用户列表 })
ApiResponse({ status: 200, description: 返回用户数组 })
Get()
getAllUsers() {return this.userService.findAll();
}// create-user.dto.ts
export class CreateUserDto {ApiProperty({ example: 张三, description: 用户姓名 })IsString()name: string;
}访问文档http://localhost:3000/api-docs 10.2 前端项目配置以React为例
步骤1创建React应用
npx create-react-app nest-client
cd nest-client步骤2配置代理解决跨域
// package.json
{proxy: http://localhost:3000
}步骤3安装axios
npm install axios10.3 实现登录认证流程
// src/api/auth.js
import axios from axios;export const login async (credentials) {const response await axios.post(/auth/login, credentials);localStorage.setItem(access_token, response.data.access_token);return response.data;
};export const getProfile async () {return axios.get(/users/profile, {headers: {Authorization: Bearer ${localStorage.getItem(access_token)}}});
};10.4 前端路由保护React示例
// src/components/PrivateRoute.js
import { Navigate } from react-router-dom;const PrivateRoute ({ children }) {const isAuthenticated !!localStorage.getItem(access_token);return isAuthenticated ? children : Navigate to/login /;
};10.5 全栈调试技巧
调试工具组合 #mermaid-svg-s3mTXcXoPouYxfcg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-s3mTXcXoPouYxfcg .error-icon{fill:#552222;}#mermaid-svg-s3mTXcXoPouYxfcg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s3mTXcXoPouYxfcg .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-s3mTXcXoPouYxfcg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s3mTXcXoPouYxfcg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s3mTXcXoPouYxfcg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s3mTXcXoPouYxfcg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s3mTXcXoPouYxfcg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s3mTXcXoPouYxfcg .marker.cross{stroke:#333333;}#mermaid-svg-s3mTXcXoPouYxfcg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s3mTXcXoPouYxfcg .pieCircle{stroke:black;stroke-width:2px;opacity:0.7;}#mermaid-svg-s3mTXcXoPouYxfcg .pieTitleText{text-anchor:middle;font-size:25px;fill:black;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-s3mTXcXoPouYxfcg .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#333;font-size:17px;}#mermaid-svg-s3mTXcXoPouYxfcg .legend text{fill:black;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-s3mTXcXoPouYxfcg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 45% 30% 20% 5% 调试工具使用率 浏览器开发者工具 Postman VS Code调试器 其他 常见问题排查 跨域问题 // 后端启用CORS
app.enableCors({origin: http://localhost:3001,methods: GET,HEAD,PUT,PATCH,POST,DELETE,credentials: true,
});认证失效处理 // 前端axios拦截器
axios.interceptors.response.use(response response,error {if (error.response.status 401) {window.location /login;}return Promise.reject(error);}
);10.6 部署联调配置
生产环境配置示例
# nginx配置
server {listen 80;server_name yourdomain.com;location /api {proxy_pass http://backend:3000;proxy_set_header Host $host;}location / {root /var/www/client;try_files $uri $uri/ /index.html;}
}全栈开发关键点总结
层级技术栈关注重点前端React/Vue Axios状态管理、路由守卫网关层Nginx负载均衡、HTTPS配置后端NestJS TypeORM业务逻辑、数据库优化基础设施Docker PostgreSQL容器编排、备份策略 项目完整工作流 #mermaid-svg-YhVU1KNEysE9XWUz {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-YhVU1KNEysE9XWUz .error-icon{fill:#552222;}#mermaid-svg-YhVU1KNEysE9XWUz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YhVU1KNEysE9XWUz .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-YhVU1KNEysE9XWUz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YhVU1KNEysE9XWUz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YhVU1KNEysE9XWUz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YhVU1KNEysE9XWUz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YhVU1KNEysE9XWUz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YhVU1KNEysE9XWUz .marker.cross{stroke:#333333;}#mermaid-svg-YhVU1KNEysE9XWUz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YhVU1KNEysE9XWUz .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YhVU1KNEysE9XWUz text.actortspan{fill:black;stroke:none;}#mermaid-svg-YhVU1KNEysE9XWUz .actor-line{stroke:grey;}#mermaid-svg-YhVU1KNEysE9XWUz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-YhVU1KNEysE9XWUz .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-YhVU1KNEysE9XWUz #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-YhVU1KNEysE9XWUz .sequenceNumber{fill:white;}#mermaid-svg-YhVU1KNEysE9XWUz #sequencenumber{fill:#333;}#mermaid-svg-YhVU1KNEysE9XWUz #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-YhVU1KNEysE9XWUz .messageText{fill:#333;stroke:#333;}#mermaid-svg-YhVU1KNEysE9XWUz .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YhVU1KNEysE9XWUz .labelText,#mermaid-svg-YhVU1KNEysE9XWUz .labelTexttspan{fill:black;stroke:none;}#mermaid-svg-YhVU1KNEysE9XWUz .loopText,#mermaid-svg-YhVU1KNEysE9XWUz .loopTexttspan{fill:black;stroke:none;}#mermaid-svg-YhVU1KNEysE9XWUz .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YhVU1KNEysE9XWUz .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-YhVU1KNEysE9XWUz .noteText,#mermaid-svg-YhVU1KNEysE9XWUz .noteTexttspan{fill:black;stroke:none;}#mermaid-svg-YhVU1KNEysE9XWUz .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YhVU1KNEysE9XWUz .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YhVU1KNEysE9XWUz .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YhVU1KNEysE9XWUz .actorPopupMenu{position:absolute;}#mermaid-svg-YhVU1KNEysE9XWUz .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-YhVU1KNEysE9XWUz .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YhVU1KNEysE9XWUz .actor-man circle,#mermaid-svg-YhVU1KNEysE9XWUz line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-YhVU1KNEysE9XWUz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户 前端 后端 数据库 发起请求 API调用 查询数据 返回数据 JSON响应 渲染界面 用户 前端 后端 数据库 至此Nest.js全栈开发系列教程已全部完成建议学习者通过以下方式巩固知识
开发一个完整博客系统包含用户/文章/评论模块尝试部署到云平台AWS/Aliyun参与开源Nest.js项目探索NestJS官方高级特性CLI插件、自定义装饰器等