“我直接把数据库模型返回给前端,结果用户看到了
password_hash……”
在 Go 开发中,我们经常需要:
- 从数据库读数据
- 处理业务逻辑
- 把结果返回给 API 客户端(比如前端或 App)
但如果你图省事,直接把数据库模型(比如 User)原样返回,那就像穿着睡衣去开董事会——功能是有了,但风险也来了!
今天,我们就来聊聊 如何用 DTO(Data Transfer Object)安全又优雅地传递数据,让你的 API 既干净又专业。
🤔 什么是 DTO?
DTO = Data Transfer Object,中文叫“数据传输对象”。
说白了,它就是一个专门用来传数据的 struct,和你的数据库模型、业务模型分开。
❌ 错误示范:直接暴露数据库模型
type User struct {
ID int
Name string
Email string
Password string // 😱 前端拿到密码??
}
然后直接 json.Marshal(user) 返回?
危险!冗余!不灵活!
✅ 正确姿势:为 API 单独定义 DTO
第一步:定义 API 专用的响应结构
type UserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
// 注意:没有 Password!
}
这就像给用户发“定制菜单”,只上他们能吃的菜,厨房的秘密(比如盐放了几克)不用透露。
第二步:手动转换(简单场景)
func toUserResponse(u User) UserResponse {
return UserResponse{
ID: u.ID,
Name: u.Name,
Email: u.Email,
}
}
虽然要多写几行,但清晰、可控、安全!
🧩 进阶技巧:用标签控制字段可见性
有时候,同一个用户信息,不同接口需要不同字段。比如:
- 公开列表:只显示
name - 个人主页:显示
name + email - 管理后台:显示全部(除密码)
这时候,可以用 多个 DTO:
// 公开用
type PublicUser struct {
Name string `json:"name"`
}
// 本人用
type ProfileUser struct {
Name string `json:"name"`
Email string `json:"email"`
}
不同角色,不同视图——这才是专业的 API 设计!
⚙️ 自动化转换?小心“魔法”陷阱!
有些库(比如 copier、mapstructure)能自动 copy 字段,看起来很爽:
copier.Copy(&dto, &user)
但问题来了:
- 字段名不一致怎么办?
- 时间格式要转成 RFC3339?
- 某些字段要加业务逻辑(比如
IsVIP)?
自动 ≠ 智能。
在关键路径上,显式转换反而更可靠、更容易测试。
就像做饭:全自动料理机方便,但米其林大厨还是亲手调味。
🛡️ DTO 的三大好处
| 问题 | DTO 怎么解决 |
|---|---|
| 暴露敏感字段 | 只定义需要的字段 |
| 接口耦合数据库 | 解耦,DB 改了不影响 API |
| 返回格式混乱 | 每个接口有明确 contract |
DTO 不是“多此一举”,而是对 API 负责的表现。
💡 小贴士:命名规范让代码更清晰
- 数据库模型:
User - 请求 DTO:
CreateUserRequest/UpdateUserRequest - 响应 DTO:
UserResponse/UserSummary - 内部业务模型:
UserDomain(可选)
这样一看就知道这个 struct 是干啥的!
🎯 总结:DTO = 专业 API 的起点
别小看一个小小的 struct。
好的 DTO 设计,能让你的 API 更安全、更稳定、更易维护。
下次写接口前,先问自己一句:
“我是在返回‘数据’,还是在暴露‘内部细节’?”
答案会告诉你:该写 DTO 了!
Happy Coding! 🚀
