概述
本节对用户模块进行整体改造,将原先测试性质的 Controller 方法重构为符合 RESTful 规范的接口,同时统一 Repository 层的实现、完善 Prisma 关联查询,并通过 class-transformer 实现响应数据的序列化输出。
核心改造路径:接口定义 → Repository 实现 → 关联查询优化 → 序列化输出。
接口层改造
RESTful 方法签名定义
在 UserController 中定义规范的接口方法:
// user.controller.ts
import { Controller, Get, Query, Param, UseInterceptors } from '@nestjs/common';
import { ParseIntPipe } from '@nestjs/common';
import { UserSerialize } from '../dto/public-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
@UseInterceptors(ClassSerializerInterceptor)
findAll(
@Query('page', new ParseIntPipe({ optional: true })) page?: number,
@Query('limit', new ParseIntPipe({ optional: true })) limit?: number,
) {
return this.userService.findAll(page, limit);
}
@Get(':username')
@UseInterceptors(ClassSerializerInterceptor)
findOne(@Param('username') username: string) {
return this.userService.findOne(username);
}
}
typescript
要点说明:
| 参数 | 类型 | 是否必传 | 说明 |
|---|---|---|---|
page | number | 否 | 页码,通过 optional: true 允许不传 |
limit | number | 否 | 每页条数,同样可选 |
username | string | 是 | 路由参数,用于查询单个用户 |
Repository 层统一实现
项目采用多种 ORM 共存的策略(Prisma、Mongoose、TypeORM),需要确保各 Repository 实现统一的接口。
Prisma Repository 实现
// user.prisma.repository.ts
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class UserPrismaRepository implements IUserRepository {
constructor(private readonly prismaClient: PrismaClient) {}
async findAll(page: number = 1, limit: number = 10) {
const skip = (page - 1) * limit;
return this.prismaClient.user.findMany({
skip,
take: limit,
});
}
async findOne(username: string) {
return this.prismaClient.user.findUnique({
where: { username },
});
}
}
typescript
分页计算公式:skip = (page - 1) * limit,Prisma 使用 skip + take 组合实现分页。
抽象层封装
// user.repository.ts
export abstract class UserRepository {
abstract findAll(page?: number, limit?: number): Promise<any[]>;
abstract findOne(username: string): Promise<any>;
}
typescript
Prisma 关联查询:三层嵌套 Include
用户查询需要关联角色和权限数据,Prisma 使用 include 实现嵌套关联查询:
// 用户 → 角色关联 → 角色权限 → 权限详情
async findOne(username: string) {
return this.prismaClient.user.findUnique({
where: { username },
include: {
userRole: {
include: {
role: {
include: {
rolePermission: {
include: {
permission: true,
},
},
},
},
},
},
},
});
}
typescript
Include vs Select 对比
| 特性 | include | select |
|---|---|---|
| 字段范围 | 返回关联表全部字段 | 仅返回指定字段 |
| 适用场景 | 字段较少的关联表 | 字段较多需精确控制 |
| 嵌套支持 | 支持多层嵌套 | 同样支持 |
| 类型推断 | 自动推断完整类型 | 仅推断选中字段类型 |
嵌套三层 include 的结构关系:
User
└── userRole[] (用户-角色关联表)
└── role (角色表)
└── rolePermission[] (角色-权限关联表)
└── permission (权限表)
text
序列化输出:class-transformer
PublicUserDto 定义
使用 @Expose() 和 @Transform() 装饰器控制响应数据结构:
// public-user.dto.ts
import { Expose, Transform, Type } from 'class-transformer';
export class PublicUserDto {
@Expose({ name: 'roles' })
@Transform(({ value }) =>
value?.map((item: any) => ({
name: item.role?.name,
permissions: item.role?.rolePermission?.map((rp: any) => rp.permission),
})),
)
userRole: any;
}
typescript
关键点:
@Expose({ name: 'roles' })—— 将数据库字段userRole重命名为roles输出@Transform()—— 对嵌套数据进行结构化转换value参数接收原始数据库查询结果
序列化结果对比
序列化前(原始数据库结构):
{
"userRole": [
{
"role": {
"name": "admin",
"rolePermission": []
}
}
]
}
json
序列化后(API 响应结构):
{
"roles": [
{
"name": "admin",
"permissions": []
}
]
}
json
关键知识点总结
| 知识点 | 说明 |
|---|---|
ParseIntPipe({ optional: true }) | 允许查询参数不传,同时保持类型转换 |
Prisma findUnique | 按唯一字段查询单条记录,替代 findMany |
三层嵌套 include | User → UserRole → Role → RolePermission → Permission |
@Expose({ name }) | 为响应字段设置别名 |
@Transform() | 对查询结果进行自定义结构化转换 |
↑