第一部分:认识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(动手验证)
步骤分解:
安装脚手架:
npm i -g @nestjs/cli创建项目:
nest new hello-world核心文件结构:
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 参数获取三大招
路径参数:
@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 得到数字类型的1234.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 拦截器使用原则
职责单一:每个拦截器专注一个任务
执行顺序:
请求 -> 全局拦截器 -> 控制器拦截器 -> 路由方法拦截器
性能注意:避免在拦截器中做重型操作
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 中间件开发准则
功能纯净
:避免在一个中间件中做多件不相关的事
性能优先
:不要阻塞事件循环(避免同步耗时操作)
错误处理:必须捕获并传递错误
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 性能优化方案
索引优化:
@Index('IDX_USERNAME', ['username'], { unique: true })
@Entity()
export class User { ... }查询缓存:
const users =awaitthis.userRepository.find({
where:{ role:'admin'},
cache:{
id:'admin_users',
milliseconds:60000// 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 安全注意事项
SQL注入防护:
// 危险!
query(`SELECT * FROM users WHERE name = '${name}'`);
// 安全✅
query('SELECT * FROM users WHERE name = ?', [name]);始终使用参数化查询
避免直接拼接SQL语句
敏感数据处理:
// 密码加密处理
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实现多类继承