NestJS数据脱敏与敏感数据处理实战
NestJS数据脱敏与敏感数据处理实战
学习目标:掌握数据安全的重要性、敏感数据处理方案、class-transformer 使用、自定义拦截器、自定义装饰器。
一、数据安全需求背景
1.1 为什么需要数据脱敏
code
数据安全的重要性:
│
├── 数据类型安全
│ ├── 类型校验
│ ├── 格式校验
│ └── 范围校验
│
├── 业务层面安全
│ ├── 敏感数据不能响应给前端
│ ├── 密码不能明文存储
│ └── 个人隐私数据保护
│
└── 常见敏感数据
├── password:用户密码
├── phone:手机号码
├── idCard:身份证号码
├── bankCard:银行卡号
└── address:详细地址1.2 敏感数据处理的两层考虑
code
敏感数据处理两层考虑:
│
├── 第一层:入库加密
│ ├── 密码不能明文存储
│ ├── 使用 bcrypt 加密
│ ├── 使用 MD5 或 SHA256 哈希
│ └── 对应:NestJS 鉴权相关章节
│
└── 第二层:响应脱敏
├── 敏感数据不能响应给前端
├── 删除或隐藏敏感字段
├── 数据序列化时过滤
└── 对应:本节内容1.3 数据脱敏常见方案
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 手动删除字段 | 实现简单 | 代码重复、维护性差 | |
| Helper 函数 | 复用性好 | 需要手动调用 | |
| 拦截器处理 | 自动应用、集中管理 | 需要额外配置 | |
| 装饰器标记 | 优雅、可维护 | 需要定义 DTO |
二、class-transformer 简介
2.1 class-transformer 是什么
code
class-transformer 简介:
│
├── 作用
│ ├── 对象与类实例之间的转换
│ ├── 属性映射和转换
│ ├── 敏感数据过滤
│ └── 数据序列化和反序列化
│
├── 常用装饰器
│ ├── @Expose():暴露属性
│ ├── @Exclude():排除属性
│ ├── @Type():类型转换
│ └── @Transform():自定义转换
│
├── 常用方法
│ ├── plainToInstance():对象转类实例
│ ├── instanceToPlain():类实例转对象
│ └── serialize():序列化为字符串
│
└── 安装
└── npm install class-transformer class-validator2.2 @Exclude 装饰器使用
typescript
// src/modules/user/dto/user.dto.ts
import { Exclude, Expose } from 'class-transformer';
export class UserDto {
@Expose()
id: number;
@Expose()
username: string;
@Expose()
email: string;
@Exclude() // 排除敏感字段
password: string;
@Expose()
createdAt: Date;
}2.3 @Exclude 装饰器原理
code
@Exclude() 装饰器原理:
│
├── 标记属性
│ └── 在属性上添加 @Exclude() 装饰器
│
├── 序列化时处理
│ ├── class-transformer 检查装饰器
│ ├── 发现有 @Exclude() 标记
│ └── 自动过滤该属性
│
└── 结果
└── 响应数据中不包含 password 字段三、使用 ClassSerializerInterceptor
3.1 ClassSerializerInterceptor 简介
typescript
// NestJS 内置的序列化拦截器
import { ClassSerializerInterceptor } from '@nestjs/common';
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor) // 应用拦截器
export class UserController {
@Get()
async getUsers(): Promise<UserDto[]> {
// 返回的数据会自动应用 @Exclude() 装饰器
return this.userService.getUsers();
}
}3.2 ClassSerializerInterceptor 的局限性
code
ClassSerializerInterceptor 局限性:
│
├── 问题:只对类实例生效
│ ├── 生效:返回 UserDto 实例
│ └── 不生效:返回普通 JavaScript 对象
│
├── 原因
│ ├── 拦截器检查是否为类实例
│ ├── 如果是普通对象,直接返回
│ └── 不会应用装饰器
│
└── 解决方案
├── 确保返回类实例(手动 new)
└── 创建自定义拦截器(推荐)3.3 问题示例
typescript
// 问题:返回普通对象,@Exclude() 不生效
@Controller('courses')
@UseInterceptors(ClassSerializerInterceptor)
export class CourseController {
@Get()
async getCourses() {
const data = await this.courseService.getCourses();
// data 是普通对象,不是类实例
return data.map((item) => ({
id: item.id,
name: item.name,
// ... 手动结构化,不是类实例
}));
}
}
// 解决:返回类实例
@Controller('courses')
@UseInterceptors(ClassSerializerInterceptor)
export class CourseController {
@Get()
async getCourses(): Promise<CourseDto[]> {
const data = await this.courseService.getCourses();
// 手动转换为类实例
return data.map((item) => plainToInstance(CourseDto, item));
}
}四、创建响应数据 DTO
4.1 使用 AI 工具生成 DTO
code
使用 Copilot 生成 DTO:
│
├── 第一步:准备 JSON 数据
│ └── 从 Postman 复制响应数据
│
├── 第二步:编写 Prompt
│ └── 根据以下 JSON 数据生成 TypeScript 类
│ 1. 使用 class-validator 和 class-transformer
│ 2. 类名为 {ClassName}
│ 3. 嵌套子类使用 @Type() 进行转换
│ 4. 输出完整的类属性定义
│ 5. 嵌套子类先定义,再定义父类
│
├── 第三步:粘贴 JSON 数据
│ └── 将 JSON 数据粘贴到 Prompt 后面
│
├── 第四步:执行生成
│ └── AI 自动生成完整的 DTO 类
│
└── 第五步:调整和优化
├── 调整类定义顺序
├── 添加 @Exclude() 装饰器
└── 添加 @Expose() 装饰器4.2 Prompt 模板
code
Prompt 模板:
根据以下 JSON 数据生成 TypeScript 类:
1. 使用 class-validator 和 class-transformer 装饰器
2. 类名为 {ClassName}Dto
3. 嵌套子类中使用 class-transformer 的 @Type() 方法进行 transform 转换
4. 输出完整的类的属性定义,不能有 null
5. 嵌套的子类先定义,后再定义父类
JSON 数据:
{粘贴 JSON 数据}4.3 生成的 DTO 示例
typescript
// src/modules/course/dto/public-get-courses.dto.ts
import { Exclude, Expose, Type } from 'class-transformer';
/**
* 用户 DTO(嵌套子类)
*/
export class UserDto {
@Expose()
id: number;
@Expose()
username: string;
@Expose()
email: string;
@Exclude() // 排除敏感字段
password: string;
@Expose()
createdAt: Date;
}
/**
* 课程 DTO(嵌套子类)
*/
export class CourseDto {
@Expose()
id: number;
@Expose()
title: string;
@Expose()
content: string;
@Expose()
@Type(() => UserDto)
author: UserDto;
}
/**
* 公开课程响应 DTO(父类)
*/
export class PublicGetCoursesDto {
@Expose()
id: number;
@Expose()
name: string;
@Expose()
@Type(() => CourseDto)
courses: CourseDto[];
}五、创建自定义序列化拦截器
5.1 使用 NestJS CLI 创建拦截器
bash
# 创建拦截器(扁平结构,无测试文件)
nest g itc common/interceptors/serialize --no-spec --flat5.2 自定义 SerializeInterceptor 实现
typescript
// src/common/interceptors/serialize.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToInstance } from 'class-transformer';
/**
* 序列化拦截器
* 自动将响应数据转换为 DTO 类实例
*/
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
constructor(
private readonly dto: any,
private readonly excludeExtraneousValues: boolean = false,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) =>
plainToInstance(this.dto, data, {
excludeExtraneousValues: this.excludeExtraneousValues,
}),
),
);
}
}5.3 plainToInstance 方法详解
code
plainToInstance 方法详解:
│
├── 作用
│ ├── 将普通对象转换为类实例
│ ├── 应用 class-transformer 装饰器
│ └── 执行属性映射和转换
│
├── 参数
│ ├── classType:目标类
│ ├── plain:普通对象
│ └── options:配置选项
│
├── 常用选项
│ ├── excludeExtraneousValues:是否排除未标记的属性
│ ├── enableCircularCheck:启用循环引用检查
│ ├── enableImplicitConversion:启用隐式类型转换
│ └── version:版本控制
│
└── excludeExtraneousValues 详解
├── true:只处理有 @Expose() 的属性
│ ├── 必须显式标记 @Expose()
│ └── 未标记的属性不显示
│
└── false:处理所有属性,排除 @Exclude() 的属性
├── 默认暴露所有属性
└── 只排除 @Exclude() 标记的属性5.4 excludeExtraneousValues 对比
| 选项 | true | false |
|---|---|---|
| 行为 | 只显示 @Expose() 的属性 | 显示所有属性,排除 @Exclude() |
| 安全性 | 高(显式控制) | 中(隐式控制) |
| 配置方式 | 每个属性加 @Expose() | 敏感属性加 @Exclude() |
| 推荐度 |
六、创建自定义装饰器
6.1 自定义装饰器实现
typescript
// src/common/decorators/serialize.decorator.ts
import { UseInterceptors } from '@nestjs/common';
import { SerializeInterceptor } from '../interceptors/serialize.interceptor';
/**
* 类构造器接口
*/
interface ClassConstructor {
new (...args: any[]): any;
}
/**
* 序列化装饰器
* @param dto DTO 类
* @param excludeExtraneousValues 是否排除未标记的属性(默认 false)
*/
export function Serialize(
dto: ClassConstructor,
excludeExtraneousValues: boolean = false,
) {
return UseInterceptors(new SerializeInterceptor(dto, excludeExtraneousValues));
}
/**
* 严格序列化装饰器
* 只显示有 @Expose() 标记的属性
* @param dto DTO 类
*/
export function SerializeStrict(dto: ClassConstructor) {
return UseInterceptors(new SerializeInterceptor(dto, true));
}6.2 装饰器使用示例
typescript
// src/modules/course/course.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { Serialize, SerializeStrict } from '@/common/decorators/serialize.decorator';
import { PublicGetCoursesDto } from './dto/public-get-courses.dto';
import { CourseService } from './course.service';
@Controller('courses')
export class CourseController {
constructor(private readonly courseService: CourseService) {}
/**
* 方式一:使用 @Serialize() 装饰器
* 排除 @Exclude() 标记的属性
*/
@Get()
@Serialize(PublicGetCoursesDto)
async getCourses() {
return this.courseService.getCourses();
}
/**
* 方式二:使用 @SerializeStrict() 装饰器
* 只显示 @Expose() 标记的属性
*/
@Get('strict')
@SerializeStrict(PublicGetCoursesDto)
async getCoursesStrict() {
return this.courseService.getCourses();
}
}6.3 两种装饰器对比
code
@Serialize() vs @SerializeStrict():
│
├── @Serialize(dto, false)
│ ├── 行为:显示所有属性,排除 @Exclude()
│ ├── 配置:敏感属性加 @Exclude()
│ ├── 优点:配置简单
│ └── 缺点:可能遗漏敏感属性
│
└── @SerializeStrict(dto)(推荐)
├── 行为:只显示 @Expose() 的属性
├── 配置:需要暴露的属性加 @Expose()
├── 优点:显式控制、更安全
└── 缺点:配置稍多七、完整实战示例
7.1 项目结构
code
project/
├── src/
│ ├── common/
│ │ ├── decorators/
│ │ │ └── serialize.decorator.ts
│ │ └── interceptors/
│ │ └── serialize.interceptor.ts
│ ├── modules/
│ │ └── course/
│ │ ├── dto/
│ │ │ ├── public-get-courses.dto.ts
│ │ │ └── get-courses-by-type.dto.ts
│ │ ├── course.controller.ts
│ │ ├── course.service.ts
│ │ └── course.module.ts
│ └── main.ts
└── package.json7.2 完整 SerializeInterceptor 实现
typescript
// src/common/interceptors/serialize.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToInstance } from 'class-transformer';
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
constructor(
private readonly dto: any,
private readonly excludeExtraneousValues: boolean = false,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) =>
plainToInstance(this.dto, data, {
excludeExtraneousValues: this.excludeExtraneousValues,
}),
),
);
}
}7.3 完整 Serialize 装饰器实现
typescript
// src/common/decorators/serialize.decorator.ts
import { UseInterceptors } from '@nestjs/common';
import { SerializeInterceptor } from '../interceptors/serialize.interceptor';
interface ClassConstructor {
new (...args: any[]): any;
}
/**
* 序列化装饰器
*/
export function Serialize(
dto: ClassConstructor,
excludeExtraneousValues: boolean = false,
) {
return UseInterceptors(new SerializeInterceptor(dto, excludeExtraneousValues));
}
/**
* 严格序列化装饰器
*/
export function SerializeStrict(dto: ClassConstructor) {
return UseInterceptors(new SerializeInterceptor(dto, true));
}7.4 完整 DTO 实现
typescript
// src/modules/course/dto/public-get-courses.dto.ts
import { Exclude, Expose, Type } from 'class-transformer';
export class UserDto {
@Expose()
id: number;
@Expose()
username: string;
@Expose()
email: string;
@Exclude() // 排除敏感字段
password: string;
}
export class CourseDto {
@Expose()
id: number;
@Expose()
title: string;
@Expose()
@Type(() => UserDto)
author: UserDto;
}
export class PublicGetCoursesDto {
@Expose()
id: number;
@Expose()
name: string;
@Expose()
@Type(() => CourseDto)
courses: CourseDto[];
}7.5 完整 Controller 实现
typescript
// src/modules/course/course.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { Serialize, SerializeStrict } from '@/common/decorators/serialize.decorator';
import { PublicGetCoursesDto } from './dto/public-get-courses.dto';
import { CourseService } from './course.service';
import { GetCoursesByTypeDto } from './dto/get-courses-by-type.dto';
@Controller('courses')
export class CourseController {
constructor(private readonly courseService: CourseService) {}
/**
* 获取课程列表(排除敏感数据)
* GET /courses
*/
@Get()
@Serialize(PublicGetCoursesDto)
async getCourses() {
return this.courseService.getCourses();
}
/**
* 根据分类查询课程(严格模式)
* GET /courses/by-type
*/
@Get('by-type')
@SerializeStrict(PublicGetCoursesDto)
async getCoursesByType(@Query() dto: GetCoursesByTypeDto) {
return this.courseService.getCoursesByType(dto);
}
}八、Postman 测试示例
8.1 测试一:@Serialize 装饰器验证
code
请求配置:
│
├── Method: GET
├── URL: http://localhost:3000/courses
│
└── 预期响应:
[
{
"id": 11,
"name": "推荐内容",
"courses": [
{
"id": 4,
"title": "Vue3 项目实战",
"author": {
"id": 1,
"username": "admin",
"email": "admin@example.com"
// password 字段已被排除
}
}
]
}
]8.2 测试二:@SerializeStrict 装饰器验证
code
请求配置:
│
├── Method: GET
├── URL: http://localhost:3000/courses/by-type
│
└── 预期响应:
[
{
"id": 11,
"name": "推荐内容",
"courses": [...]
// 只显示 @Expose() 标记的属性
}
]
注意:
├── 如果 DTO 中没有 @Expose() 标记
└── 响应为空对象:{}8.3 测试三:验证敏感数据已排除
code
验证步骤:
│
├── 第一步:发起请求
│ └── GET /courses
│
├── 第二步:检查响应数据
│ ├── 包含:id, username, email
│ └── 不包含:password
│
└── 第三步:确认数据脱敏成功
└── password 字段已成功排除九、常见问题与解决方案
9.1 @Exclude 不生效问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| @Exclude() 不生效 | 返回的是普通对象,不是类实例 | 使用 plainToInstance 转换 |
| 装饰器未应用 | 未应用拦截器 | 添加 @Serialize 装饰器 |
| 属性仍然显示 | excludeExtraneousValues 设置错误 | 检查装饰器配置 |
9.2 plainToInstance 使用问题
typescript
// 错误:直接返回普通对象
return data;
// 正确:转换为类实例
return plainToInstance(Dto, data, {
excludeExtraneousValues: false,
});9.3 嵌套对象转换问题
typescript
// 错误:嵌套对象未添加 @Type() 装饰器
export class CourseDto {
@Expose()
author: UserDto; // 不会被转换
}
// 正确:添加 @Type() 装饰器
export class CourseDto {
@Expose()
@Type(() => UserDto) // 指定类型
author: UserDto;
}十、最佳实践总结
10.1 数据脱敏最佳实践
code
数据脱敏最佳实践:
│
├── 使用 @SerializeStrict() 装饰器
│ ├── 显式控制暴露的属性
│ ├── 更安全的数据控制
│ └── 避免遗漏敏感属性
│
├── DTO 设计规范
│ ├── 敏感属性加 @Exclude()
│ ├── 需暴露属性加 @Expose()
│ └── 嵌套对象加 @Type()
│
├── 拦截器使用
│ ├── Controller 级别应用
│ ├── 方法级别应用
│ └── 全局应用(谨慎)
│
└── 测试验证
├── 单元测试
├── 集成测试
└── 安全审计10.2 DTO 设计最佳实践
code
DTO 设计最佳实践:
│
├── 分离关注点
│ ├── 数据库 DTO:数据库操作
│ ├── 响应 DTO:接口响应
│ └── 请求 DTO:接口请求
│
├── 命名规范
│ ├── PublicXxxDto:公开响应 DTO
│ ├── CreateXxxDto:创建请求 DTO
│ └── UpdateXxxDto:更新请求 DTO
│
├── 类定义顺序
│ ├── 子类先定义
│ └── 父类后定义
│
└── 装饰器使用
├── @Expose():暴露属性
├── @Exclude():排除属性
└── @Type():嵌套类型10.3 安全性最佳实践
code
安全性最佳实践:
│
├── 敏感数据处理
│ ├── 密码加密存储(bcrypt)
│ ├── 响应时排除敏感字段
│ └── 日志中过滤敏感信息
│
├── 数据验证
│ ├── 入参验证(class-validator)
│ ├── 类型校验
│ └── 格式校验
│
├── 权限控制
│ ├── 角色权限验证
│ ├── 接口访问控制
│ └── 数据访问控制
│
└── 审计日志
├── 操作日志
├── 访问日志
└── 异常日志十一、命令速查表
11.1 class-transformer 装饰器速查
| 装饰器 | 作用 | 示例 |
|---|---|---|
| @Expose() | 暴露属性 | @Expose() id: number; |
| @Exclude() | 排除属性 | @Exclude() password: string; |
| @Type() | 类型转换 | @Type(() => UserDto) author: UserDto; |
| @Transform() | 自定义转换 | @Transform(({ value }) => value.toUpperCase()) |
11.2 plainToInstance 选项速查
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| excludeExtraneousValues | boolean | false | 排除未标记的属性 |
| enableCircularCheck | boolean | false | 启用循环引用检查 |
| enableImplicitConversion | boolean | false | 启用隐式类型转换 |
| version | number | - | 版本控制 |
11.3 NestJS CLI 命令速查
| 命令 | 说明 |
|---|---|
nest g itc <path> | 创建拦截器 |
nest g itc <path> --no-spec | 创建拦截器(无测试) |
nest g itc <path> --flat | 创建拦截器(扁平结构) |
十二、扩展应用场景
12.1 多版本响应 DTO
typescript
// 支持版本控制的 DTO
export class UserDto {
@Expose({ since: 1, until: 2 })
name: string;
@Expose({ since: 2 })
fullName: string;
@Exclude()
password: string;
}
// 使用时指定版本
plainToInstance(UserDto, data, { version: 1 });12.2 自定义转换函数
typescript
// 使用 @Transform() 自定义转换
export class UserDto {
@Expose()
@Transform(({ value }) => value.toUpperCase())
username: string;
@Expose()
@Transform(({ value }) => `***${value.slice(-4)}`)
phone: string; // 手机号脱敏:138****1234
}12.3 条件暴露属性
typescript
// 根据条件暴露属性
export class UserDto {
@Expose()
id: number;
@Expose({ groups: ['admin'] })
email: string;
@Expose({ groups: ['user'] })
username: string;
}
// 使用时指定分组
plainToInstance(UserDto, data, { groups: ['admin'] });十三、学习要点总结
13.1 核心知识点
code
本文核心要点:
│
├── 数据脱敏的重要性
│ ├── 业务层面安全
│ ├── 敏感数据保护
│ └── 常见敏感数据类型
│
├── class-transformer 使用
│ ├── @Exclude() 排除属性
│ ├── @Expose() 暴露属性
│ ├── @Type() 类型转换
│ └── plainToInstance() 方法
│
├── 自定义拦截器
│ ├── SerializeInterceptor 实现
│ ├── excludeExtraneousValues 选项
│ └── plainToInstance 转换
│
├── 自定义装饰器
│ ├── @Serialize() 装饰器
│ ├── @SerializeStrict() 装饰器
│ └── 简化使用方式
│
├── 使用 AI 工具生成 DTO
│ ├── Copilot prompt 编写
│ ├── 类定义顺序
│ └── 调整和优化
│
└── 最佳实践
├── 使用 @SerializeStrict()
├── 显式控制暴露属性
└── 测试验证13.2 重要程度标注
| 知识点 | 重要程度 | 说明 |
|---|---|---|
| @Exclude() 装饰器 | 必须掌握 | 敏感数据排除 |
| plainToInstance 方法 | 必须掌握 | 对象转换核心 |
| 自定义拦截器 | 必须掌握 | 序列化处理 |
| @Expose() 装饰器 | 重要 | 显式控制暴露 |
| AI 工具生成 DTO | 了解 | 提高效率 |
13.3 学习路径规划
code
学习路径规划:
│
├── 第一阶段:理解概念(1 天)
│ ├── 理解数据脱敏的重要性
│ ├── 理解 class-transformer 原理
│ └── 理解拦截器工作机制
│
├── 第二阶段:实践操作(2-3 天)
│ ├── 创建响应 DTO
│ ├── 实现自定义拦截器
│ └── 实现自定义装饰器
│
└── 第三阶段:深入应用(持续)
├── 多版本响应 DTO
├── 自定义转换函数
└── 条件暴露属性十四、完整代码清单
14.1 SerializeInterceptor 完整代码
typescript
// src/common/interceptors/serialize.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToInstance } from 'class-transformer';
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
constructor(
private readonly dto: any,
private readonly excludeExtraneousValues: boolean = false,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) =>
plainToInstance(this.dto, data, {
excludeExtraneousValues: this.excludeExtraneousValues,
}),
),
);
}
}14.2 Serialize 装饰器完整代码
typescript
// src/common/decorators/serialize.decorator.ts
import { UseInterceptors } from '@nestjs/common';
import { SerializeInterceptor } from '../interceptors/serialize.interceptor';
interface ClassConstructor {
new (...args: any[]): any;
}
export function Serialize(
dto: ClassConstructor,
excludeExtraneousValues: boolean = false,
) {
return UseInterceptors(new SerializeInterceptor(dto, excludeExtraneousValues));
}
export function SerializeStrict(dto: ClassConstructor) {
return UseInterceptors(new SerializeInterceptor(dto, true));
}14.3 PublicGetCoursesDto 完整代码
typescript
// src/modules/course/dto/public-get-courses.dto.ts
import { Exclude, Expose, Type } from 'class-transformer';
export class UserDto {
@Expose()
id: number;
@Expose()
username: string;
@Expose()
email: string;
@Exclude()
password: string;
}
export class CourseDto {
@Expose()
id: number;
@Expose()
title: string;
@Expose()
@Type(() => UserDto)
author: UserDto;
}
export class PublicGetCoursesDto {
@Expose()
id: number;
@Expose()
name: string;
@Expose()
@Type(() => CourseDto)
courses: CourseDto[];
}14.4 CourseController 完整代码
typescript
// src/modules/course/course.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { Serialize, SerializeStrict } from '@/common/decorators/serialize.decorator';
import { PublicGetCoursesDto } from './dto/public-get-courses.dto';
import { CourseService } from './course.service';
import { GetCoursesByTypeDto } from './dto/get-courses-by-type.dto';
@Controller('courses')
export class CourseController {
constructor(private readonly courseService: CourseService) {}
@Get()
@Serialize(PublicGetCoursesDto)
async getCourses() {
return this.courseService.getCourses();
}
@Get('by-type')
@SerializeStrict(PublicGetCoursesDto)
async getCoursesByType(@Query() dto: GetCoursesByTypeDto) {
return this.courseService.getCoursesByType(dto);
}
}重要提示:数据脱敏是后端开发的重要安全措施,掌握 class-transformer 的使用、理解 plainToInstance 方法、学会创建自定义拦截器和装饰器,对实际项目开发非常重要!推荐使用 @SerializeStrict() 装饰器,显式控制暴露的属性,确保数据安全!
扩展建议:可以尝试实现多版本响应 DTO、自定义转换函数、条件暴露属性等功能,进一步掌握 class-transformer 的高级应用!