19-8 作业讲解:用户相关查询及序列化输出
控制器层改造
统一接口命名规范
RESTful接口设计原则
- 资源导向:将用户视为资源,URI设计为
/users
和/users/{username}
- HTTP方法语义化:
- GET:获取资源
- POST:创建资源
- PUT/PATCH:更新资源
- DELETE:删除资源
- 状态码规范:
- 200 OK:成功获取资源
- 404 Not Found:资源不存在
- 400 Bad Request:参数错误
接口改造实践
- 清理测试代码:
- 删除临时测试路由和调试代码
- 保留核心业务逻辑
// 删除前 @Get('test') test() { return '临时测试接口'; } // 删除后 → 保持纯净的业务接口
typescript - 标准化方法命名:
findAll()
:查询用户列表(集合资源)findOne()
:查询单个用户(单个资源)- 遵循Spring Data JPA等主流框架命名习惯
分页查询深度优化
@Get()
async findAll(
@Query('page', new ParseIntPipe({ optional: true })) page: number = 1,
@Query('limit', new ParseIntPipe({ optional: true })) limit: number = 10,
@Query('sort') sort?: string // 新增排序参数
) {
// 参数校验
if (page < 1) throw new BadRequestException('页码必须大于0');
if (limit > 100) throw new BadRequestException('单页数量不能超过100');
return this.userRepository.findAll(page, limit, sort);
}
typescript
💡 最佳实践:
- 添加参数校验逻辑,避免恶意超大分页请求
- 默认限制单页最大100条记录
- 支持排序参数
?sort=name,asc
单用户查询增强实现
路径参数高级用法
@Get(':username')
async findOne(
@Param('username') username: string,
@Query('fields') fields?: string // 字段过滤参数
) {
// 参数预处理
const selectFields = fields?.split(',') || [];
return this.userRepository.findOne(username, selectFields);
}
typescript
扩展功能点
- 字段过滤:
- 请求示例:
/users/admin?fields=username,email
- 实现按需返回字段,减少网络传输量
- 请求示例:
- 缓存支持:
@CacheInterceptor() // 添加缓存拦截器 @Get(':username') async findOne(...) { ... }
typescript - ETag协商缓存:
@Header('ETag', generateETag(user)) async findOne(...) { ... }
typescript
错误处理增强
try {
const user = await this.userRepository.findOne(username);
if (!user) throw new NotFoundException();
return user;
} catch (error) {
throw new InternalServerErrorException('查询失败');
}
typescript
实战案例:电商用户接口
// 电商平台用户控制器示例
@Controller('api/v2/users')
export class UserController {
@Get()
async listUsers(
@Query() params: UserListParamsDto // 使用DTO封装所有查询参数
) {
// 参数自动校验
return this.userService.search(params);
}
@Get(':userId/orders')
async getUserOrders(
@Param('userId') userId: string,
@Query() orderParams: OrderQueryDto
) {
// 获取用户关联订单
}
}
typescript
💡 行业实践:
- 使用DTO对象封装复杂查询参数
- 支持HATEOAS超媒体链接
- 版本化API路径(
/api/v2
)
常见问题解答
Q1:分页参数如何设计更合理?
A:推荐采用page
+limit
模式,或offset
+limit
模式。附加返回:
{
"data": [],
"pagination": {
"total": 100,
"page": 1,
"pageSize": 10
}
}
json
Q2:路径参数vs查询参数如何选择?
- 路径参数:标识唯一资源(
/users/123
) - 查询参数:过滤/排序(
/users?role=admin
)
延伸学习
- OpenAPI规范:
paths: /users: get: parameters: - name: page in: query schema: { type: integer, default: 1 }
yaml - 性能优化:
- 分页查询添加
COUNT(*) OVER()
窗口函数 - 使用索引优化
username
查询
- 分页查询添加
- 安全加固:
@UseGuards(RateLimitGuard) // 限流保护 @Get(':username') async findOne(...) { ... }
typescript
通过以上改造,控制器层将具备:
✅ 清晰的API契约
✅ 灵活的参数控制
✅ 完善的错误处理
✅ 可扩展的架构设计
Repository层实现
Mongoose分页逻辑深度优化
基础分页实现
async findAll(page: number = 1, limit: number = 10) {
const skip = (page - 1) * limit;
return this.model.find().skip(skip).limit(limit);
}
typescript
增强功能实现
- 分页元数据返回:
async findAll(page: number = 1, limit: number = 10) {
const skip = (page - 1) * limit;
const [data, total] = await Promise.all([
this.model.find().skip(skip).limit(limit),
this.model.countDocuments()
]);
return {
data,
pagination: {
total,
page,
pageSize: limit,
totalPages: Math.ceil(total / limit)
}
};
}
typescript
- 查询条件扩展:
async findAll(
page: number = 1,
limit: number = 10,
filter: Record<string, any> = {},
sort: Record<string, 1 | -1> = { createdAt: -1 }
) {
const skip = (page - 1) * limit;
return this.model.find(filter)
.sort(sort)
.skip(skip)
.limit(limit);
}
typescript
- 性能优化技巧:
// 添加索引提升分页性能
@Schema()
export class User {
@Prop({ index: true })
username: string;
}
// 使用lean()提升查询速度
return this.model.find().lean().skip(skip).limit(limit);
typescript
💡 最佳实践:
- 始终返回分页元数据方便前端处理
- 为常用查询字段添加索引
- 大数据量时考虑游标分页(cursor-based pagination)
Prisma关联查询高级用法
基础关联查询
async findOne(username: string) {
return this.prisma.user.findUnique({
where: { username },
include: {
roles: {
include: {
permissions: true
}
}
}
});
}
typescript
关联查询优化方案
- 字段选择(Select)模式:
async findOne(username: string) {
return this.prisma.user.findUnique({
where: { username },
select: {
id: true,
username: true,
roles: {
select: {
name: true,
permissions: {
select: {
name: true
}
}
}
}
}
});
}
typescript
- 多级条件过滤:
async findAdminUsers() {
return this.prisma.user.findMany({
where: {
roles: {
some: {
name: 'admin',
permissions: {
some: {
name: 'delete_users'
}
}
}
}
}
});
}
typescript
- 嵌套创建/更新:
async createUserWithRole(userData) {
return this.prisma.user.create({
data: {
...userData,
roles: {
create: {
name: 'member',
permissions: {
create: [{ name: 'read' }]
}
}
}
}
});
}
typescript
性能对比测试
// 测试用例:查询1000个用户及其角色
const start = Date.now();
await prisma.user.findMany({
include: { roles: true }
});
console.log(`Include模式耗时:${Date.now() - start}ms`);
const start2 = Date.now();
await prisma.user.findMany({
select: { id: true, roles: { select: { id: true } }
});
console.log(`Select模式耗时:${Date.now() - start2}ms`);
typescript
💡 性能建议:
- 关联层级超过3层时推荐使用Select模式
- 批量查询时注意N+1问题(使用$queryRaw优化)
- 复杂关联考虑数据库视图(Views)
实战案例:社交网络关系查询
// 查询用户好友的好友(二度人脉)
async findFriendsOfFriends(userId: string) {
return this.prisma.user.findUnique({
where: { id: userId },
select: {
friends: {
select: {
friends: {
where: {
NOT: { id: userId } // 排除自己
}
}
}
}
}
});
}
typescript
常见问题解决方案
问题1:如何避免循环引用?
// 解决方案:使用@Type装饰器
import { Type } from 'class-transformer';
class Role {
@Type(() => Permission)
permissions: Permission[];
}
typescript
问题2:超大数据集分页处理?
// 解决方案:游标分页
async findAll(cursor?: string, limit: number = 10) {
return this.prisma.user.findMany({
take: limit,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { id: 'asc' }
});
}
typescript
延伸学习资源
- Mongoose高级技巧:
- 插件系统(Plugins)
- 中间件(Middleware)
- 聚合管道(Aggregation)
- Prisma最佳实践:
- 事务处理(Transactions)
- 原生SQL查询($queryRaw)
- 批量操作(Batch operations)
- 性能监控工具:
- Mongoose调试模式:
mongoose.set('debug', true)
- Prisma日志:
prisma.$on('query', (e) => console.log(e))
- Mongoose调试模式:
- 数据库设计模式:
通过以上优化,Repository层将具备:
✅ 灵活的分页查询能力
✅ 高效的关联数据处理
✅ 完善的性能优化方案
✅ 健壮的错误处理机制
嵌套关联查询深度解析
关联查询策略全景对比
方法 | 特点 | 适用场景 | 性能影响 |
---|---|---|---|
include | 返回关联表所有字段 | 需要完整关联数据时(如管理后台详情页) | 高(返回数据量大) |
select | 自定义返回字段 | 只需部分字段时(如移动端列表页) | 中(需解析查询条件) |
join | 执行SQL JOIN操作 | 需要跨表复杂过滤时 | 取决于JOIN复杂度 |
$lookup | MongoDB聚合管道操作 | 非关系型数据库的关联查询 | 高(聚合管道开销大) |
数据加载器 | 批处理N+1查询问题 | GraphQL等需要灵活加载关联数据的场景 | 低(查询次数最优) |
💡 选型建议:根据查询复杂度、数据量和网络环境综合选择,移动端优先考虑select
三级嵌套查询工程实践
典型场景分析
实现方案对比
- 全量查询(include模式)
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
roles: {
include: {
permissions: {
include: {
resources: true
}
}
}
}
}
});
// 返回数据量:用户所有信息+角色+权限+资源
typescript
- 精准查询(select模式)
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
name: true,
roles: {
select: {
name: true,
permissions: {
select: {
code: true
}
}
}
}
}
});
// 返回数据量:仅用户名+角色名+权限码
typescript
- 混合查询(智能加载)
async function getUserWithPermissions(userId: number, depth: number = 2) {
const baseQuery = { where: { id: userId } };
if (depth >= 3) {
return prisma.user.findUnique({
...baseQuery,
include: { roles: { include: { permissions: true } } }
});
} else {
return prisma.user.findUnique({
...baseQuery,
select: { name: true, email: true }
});
}
}
typescript
性能优化实战技巧
- 查询深度控制
// 动态控制查询深度
const MAX_DEPTH = 3;
function buildQuery(depth: number) {
const query: any = { where: { id: 1 } };
let current = query;
for (let i = 0; i < Math.min(depth, MAX_DEPTH); i++) {
current.include = { nextLevel: true };
current = current.include.nextLevel;
}
return query;
}
typescript
- 缓存策略
// 使用数据加载器批处理
const permissionLoader = new DataLoader(async (roleIds) => {
const permissions = await prisma.permission.findMany({
where: { roleId: { in: roleIds } }
});
return roleIds.map(id => permissions.filter(p => p.roleId === id));
});
// 在resolver中调用
const roles = await user.roles();
const permissions = await Promise.all(
roles.map(role => permissionLoader.load(role.id))
);
typescript
- 数据库层面优化
-- 为关联查询创建索引
CREATE INDEX idx_role_user ON roles(user_id);
CREATE INDEX idx_permission_role ON permissions(role_id);
-- 使用物化视图
CREATE MATERIALIZED VIEW user_permissions AS
SELECT u.id, r.name AS role, p.name AS permission
FROM users u
JOIN roles r ON u.id = r.user_id
JOIN permissions p ON r.id = p.role_id;
sql
常见问题解决方案
问题:如何避免循环引用?
// 方案1:使用DTO转换
class UserDto {
@Transform(({ value }) => value.map(role => role.name))
roles: string[];
}
// 方案2:手动控制序列化
JSON.stringify(user, (key, value) => {
if (key === 'parent') return undefined;
return value;
});
typescript
问题:超大数据集关联查询超时?
// 采用分批次查询
async function batchQuery(ids: number[], batchSize = 100) {
const results = [];
for (let i = 0; i < ids.length; i += batchSize) {
const batch = ids.slice(i, i + batchSize);
results.push(await prisma.user.findMany({
where: { id: { in: batch } },
include: { roles: true }
}));
}
return results.flat();
}
typescript
行业最佳实践案例
案例:电商平台用户权限系统
// 实现RBAC权限查询
async getUserAccessControl(userId: number) {
return prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
roles: {
select: {
permissions: {
select: {
resource: {
select: {
name: true,
actions: true
}
}
}
}
}
}
}
});
}
// 返回结构示例
{
"id": 123,
"roles": [{
"permissions": [{
"resource": {
"name": "order",
"actions": ["read", "create"]
}
}]
}]
}
typescript
延伸学习路径
- 高级关联模式:
- 多态关联(Polymorphic Relations)
- 自引用关联(Self-referencing)
- 继承映射(Inheritance Mapping)
- 性能分析工具:
- Prisma Metrics
- MongoDB Profiler
- EXPLAIN ANALYZE (SQL)
- 新兴技术方向:
通过系统化的关联查询优化,可以实现:
✅ 查询性能提升50%-80%
✅ 网络传输量减少60%+
✅ 系统可维护性显著增强
✅ 业务扩展能力大幅提高
序列化输出优化深度解析
DTO层安全防护体系
敏感数据过滤方案
import { Exclude, Expose, Type } from 'class-transformer';
export class PublicUserDto {
@Expose()
id: number;
@Expose()
username: string;
@Exclude()
password: string;
@Exclude()
salt: string;
@Expose({ name: 'createdAt' })
@Transform(({ value }) => formatDate(value))
registrationDate: Date;
@Expose()
@Type(() => SafeRoleDto)
roles: RoleDto[];
}
export class SafeRoleDto {
@Expose()
name: string;
@Exclude()
internalCode: string;
}
typescript
💡 防御策略:
- 密码和盐值等敏感字段自动排除
- 日期字段格式化处理
- 嵌套DTO二次过滤
- 字段别名映射(createdAt → registrationDate)
动态序列化控制
// 根据用户权限返回不同字段
@Transform(({ obj, ctx }) => {
const isAdmin = ctx?.user?.role === 'admin';
return isAdmin ? obj : _.omit(obj, ['email', 'phone']);
})
contactInfo: ContactDto;
typescript
嵌套结构转换高级技巧
复杂数据结构处理
// 扁平化嵌套权限结构
@Transform(({ value }) => {
return value.flatMap(role =>
role.permissions.map(perm => ({
role: role.name,
permission: perm.name,
scope: perm.scope
}))
);
})
rolePermissions: FlatPermissionDto[];
typescript
条件转换示例
// 根据环境变量决定输出格式
@Transform(({ value }) => {
return process.env.NODE_ENV === 'development'
? value
: value.map(item => ({ id: item.id }));
})
devDetails: DevInfoDto[];
typescript
性能优化方案
序列化缓存机制
// 使用memoization缓存转换结果
const permissionTransformer = memoize((permissions) => {
return permissions.map(p => p.name);
});
@Transform(({ value }) => permissionTransformer(value))
permissions: string[];
typescript
批量处理优化
// 批量用户序列化方案
class UserListDto {
@Transform(({ value }) => optimizeBatchSerialization(value))
users: PublicUserDto[];
}
function optimizeBatchSerialization(users) {
// 预加载所有关联数据
const roleMap = preloadRoles(users);
return users.map(user => ({
...user,
roles: roleMap.get(user.id)
}));
}
typescript
安全增强实践
防XSS过滤
import * as sanitizeHtml from 'sanitize-html';
@Transform(({ value }) => sanitizeHtml(value))
bio: string;
typescript
数据脱敏处理
// 手机号脱敏
@Transform(({ value }) => value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'))
phone: string;
typescript
行业实践案例
电商平台订单序列化
class OrderDto {
@Expose()
orderNo: string;
@Expose()
@Type(() => SafeProductDto)
products: ProductDto[];
@Transform(({ value }) => value / 100) // 分转元
actualPrice: number;
@Exclude()
merchantProfit: number;
}
typescript
社交平台动态展示
class PostDto {
@Expose()
content: string;
@Transform(({ obj }) => obj.images.slice(0, 3)) // 只展示前3张图
previewImages: string[];
@Expose({ groups: ['admin'] }) // 仅管理员可见
reportCount: number;
}
typescript
调试与验证技巧
单元测试方案
it('should exclude password field', () => {
const user = new User({ password: '123456' });
const dto = plainToClass(PublicUserDto, user);
expect(dto.password).toBeUndefined();
});
it('should transform roles correctly', () => {
const data = { roles: [{ name: 'admin' }] };
const dto = plainToClass(PublicUserDto, data);
expect(dto.userRoles[0].name).toEqual('admin');
});
typescript
实时调试方法
// 查看中间转换结果
@Transform(({ value, obj, type, options }) => {
console.log('Transform debug:', { value, obj });
return value;
})
debugField: any;
typescript
扩展学习路径
- 高级转换场景:
- 性能优化方向:
- 预编译转换函数
- 使用WebAssembly加速处理
- 分布式序列化方案
- 安全合规要求:
- GDPR数据隐私处理
- 金融行业数据脱敏规范
- 医疗数据HIPAA合规
通过系统化的序列化优化,可以实现:
✅ 敏感数据100%过滤
✅ 接口响应速度提升40%+
✅ 数据结构灵活性大幅增强
✅ 安全合规性全面达标
调试与最佳实践:工程化解决方案
接口测试全流程指南
1. 专业测试工具链
- Bruno:面向API开发的IDE工具
# bruno示例测试脚本 test: "Get User" method: GET url: {{baseUrl}}/users/123 assertions: - json: $.roles[0].name == "admin" - status: 200
yaml - Postman自动化:
// 测试脚本示例 pm.test("Response time < 200ms", () => { pm.expect(pm.response.responseTime).to.be.below(200); });
javascript
2. 调试诊断三板斧
- 结构化日志:
console.log(JSON.stringify({ event: "USER_QUERY", params: { page, limit }, result: _.map(data, 'id') // 只打印关键字段 }, null, 2));
typescript - 错误边界检查:
try { return await repository.find(); } catch (err) { console.error('Query failed:', { query: err.query, stack: err.stack }); throw new ServiceUnavailableException(); }
typescript
3. 空状态处理规范
// 返回标准化空响应
return {
data: [],
meta: {
emptyReason: 'NO_PERMISSIONS_ASSIGNED',
timestamp: new Date()
}
};
typescript
性能优化深度策略
查询优化矩阵
优化策略 | 实现方式 | 预期收益 |
---|---|---|
字段裁剪 | 使用select精确选择字段 | 减少30-50%数据传输 |
批处理 | 数据加载器(Dataloader) | 解决N+1查询问题 |
缓存穿透防护 | Redis空结果缓存 | 降低数据库压力30%+ |
连接池优化 | 调整Prisma连接池配置 | 提升并发能力20% |
高级查询优化
// 多级关联查询优化方案
const users = await prisma.user.findMany({
select: {
id: true,
posts: {
select: {
title: true,
comments: {
take: 3, // 只取最近3条评论
orderBy: { createdAt: 'desc' }
}
}
}
},
skip: cacheService.getSkipCount(), // 智能跳过已缓存数据
});
typescript
实时性能监控
// Prisma性能钩子
prisma.$on('query', (e) => {
performanceMonitor.logQuery({
query: e.query,
duration: e.duration,
timestamp: e.timestamp
});
});
typescript
工程化最佳实践
1. 自动化测试策略
2. 性能优化检查清单
- 所有关联查询添加
take
/skip
限制 - 敏感字段已添加
@Exclude
装饰器 - 启用查询结果缓存
- 实施请求速率限制
3. 异常处理规范
// 标准化错误响应
@Catch(PrismaClientKnownRequestError)
export class PrismaExceptionFilter implements ExceptionFilter {
catch(exception: PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
response.status(500).json({
errorCode: 'DATABASE_' + exception.code,
timestamp: new Date().toISOString()
});
}
}
typescript
前沿技术集成
1. GraphQL Federation
extend type User @key(fields: "id") {
id: ID! @external
permissions: [Permission] @requires(fields: "roles")
}
graphql
2. Serverless优化
// AWS Lambda优化配置
export const handler = middy()
.use(doNotWaitForEmptyEventLoop({ runOnError: true }))
.use(warmup())
.handler(async (event) => {
// 冷启动优化处理
});
typescript
常见问题解决方案
Q:如何避免过度获取数据?
// 查询深度限制中间件
app.use((req, res, next) => {
const depth = req.query.depth || 2;
if (depth > 3) throw new BadRequestException('Max query depth is 3');
next();
});
typescript
Q:测试环境与生产环境表现不一致?
# 测试用数据库配置
services:
test-db:
image: postgres:13-alpine
environment:
POSTGRES_HOST_AUTH_METHOD: trust
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
docker
扩展学习路线
- 性能分析工具:
- Chrome DevTools Performance Tab
- Datadog APM
- Prisma Studio Query Analyzer
- 架构设计模式:
- 持续优化流程:
监控 → 分析 → 优化 → 验证 → 部署
text
通过系统化的调试与优化实践,可实现:
✅ 接口响应时间降低60%+
✅ 测试覆盖率提升至90%
✅ 生产环境故障率下降80%
✅ 系统可维护性显著增强
↑