DTO 与数据格式转换
DTO 与数据格式转换
DTO(Data Transfer Object)是一种设计模式,用于在不同层之间传输数据。在 Koa2 中,DTO 常用于规范化前后端交互的数据格式,确保数据的完整性和一致性。数据格式转换则是将原始数据转换为符合业务需求的格式,比如将数据库查询结果转换为前端需要的 JSON 结构。
DTO 的作用与实现
DTO 的核心作用是解耦数据层与业务层,避免直接暴露数据库模型。例如,从数据库查询的用户信息可能包含敏感字段(如密码),而 DTO 可以过滤这些字段,仅返回必要的数据。
// 数据库模型
interface UserModel {
id: number;
username: string;
password: string;
email: string;
createdAt: Date;
}
// DTO 结构
interface UserDTO {
id: number;
username: string;
email: string;
}
// 转换函数
function toUserDTO(user: UserModel): UserDTO {
return {
id: user.id,
username: user.username,
email: user.email,
};
}
在 Koa2 中,可以通过中间件或工具函数实现 DTO 转换:
import Koa from 'koa';
const app = new Koa();
app.use(async (ctx) => {
const userFromDB: UserModel = {
id: 1,
username: 'test',
password: '123456',
email: 'test@example.com',
createdAt: new Date(),
};
ctx.body = toUserDTO(userFromDB); // 返回过滤后的 DTO
});
数据格式转换的场景
数据格式转换不仅限于字段过滤,还包括:
- 字段重命名:将数据库的
created_at
转为驼峰式的createdAt
。 - 类型转换:将
Date
对象转为 ISO 字符串。 - 嵌套结构展开:将关联表的数据合并为一个对象。
// 示例:处理日期和嵌套数据
interface PostModel {
id: number;
title: string;
content: string;
created_at: Date;
author: UserModel;
}
interface PostDTO {
id: number;
title: string;
content: string;
createdAt: string;
author: {
name: string;
email: string;
};
}
function toPostDTO(post: PostModel): PostDTO {
return {
id: post.id,
title: post.title,
content: post.content,
createdAt: post.created_at.toISOString(),
author: {
name: post.author.username,
email: post.author.email,
},
};
}
使用类验证器实现 DTO 校验
在 Koa2 中,可以结合 class-validator
库实现 DTO 的校验。例如,验证用户注册时的输入:
import { IsEmail, IsString, MinLength } from 'class-validator';
class CreateUserDTO {
@IsString()
@MinLength(3)
username: string;
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
}
// 在中间件中使用
import { validate } from 'class-validator';
app.use(async (ctx) => {
const dto = new CreateUserDTO();
Object.assign(dto, ctx.request.body);
const errors = await validate(dto);
if (errors.length > 0) {
ctx.status = 400;
ctx.body = { errors };
return;
}
// 继续处理业务逻辑
});
复杂数据结构的转换
对于复杂数据结构(如分页查询结果),DTO 可以统一响应格式:
interface PaginatedResult<T> {
data: T[];
total: number;
page: number;
pageSize: number;
}
// 分页查询用户列表
async function getUsers(page: number, pageSize: number): Promise<PaginatedResult<UserDTO>> {
const [users, total] = await UserModel.findAndCount({
skip: (page - 1) * pageSize,
take: pageSize,
});
return {
data: users.map(toUserDTO),
total,
page,
pageSize,
};
}
性能优化与缓存
频繁的数据转换可能影响性能,可以通过缓存机制优化。例如,使用 Map
缓存已转换的 DTO:
const userCache = new Map<number, UserDTO>();
function getCachedUserDTO(user: UserModel): UserDTO {
if (userCache.has(user.id)) {
return userCache.get(user.id)!;
}
const dto = toUserDTO(user);
userCache.set(user.id, dto);
return dto;
}
错误处理与日志记录
在转换过程中,可能因数据异常导致错误。可以通过 try-catch 捕获并记录日志:
app.use(async (ctx) => {
try {
const user = await getUserFromDB(ctx.params.id);
ctx.body = toUserDTO(user);
} catch (error) {
ctx.app.emit('error', error, ctx); // 触发错误事件
ctx.status = 500;
}
});
// 全局错误监听
app.on('error', (err, ctx) => {
console.error('DTO conversion error:', err);
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:数据访问层的抽象设计
下一篇:统一异常处理机制