Files
easygoadmin-wiki/EasyGoAdmin_Gin_NaiveVue/zh/guide/dto.md
T
2026-10-25 17:24:27 +08:00

122 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3.3 契约层开发(dto)
:::tip 概述
dto 是模块的对外契约:json 标签即 API 字段名,binding/msg 标签给出校验规则与提示语,form 标签供 GET 参数绑定。契约一旦发布应保持稳定——前端与生成器都依赖它。
:::
## 文件位置与命名
```text
internal/model/dto/example.go (package dto,一模块一文件)
```
结构体按职责命名:`ExampleItem`(行结构)、`ExamplePageReq`(分页请求)、`ExampleDetailRes`(详情响应)……
## 行结构与类型别名
```go
// internal/model/dto/example.go
// ExampleItem 案例行数据
type ExampleItem struct {
Id int `json:"id"` // 主键ID
Name string `json:"name"` // 案例名称
Avatar string `json:"avatar"` // 案例图片(读侧已补全域名)
Type int `json:"type"` // 案例类型
Status int `json:"status"` // 案例状态(取值以字典 example_status 为准)
Sort int `json:"sort"` // 案例排序
CreateUser string `json:"createUser"` // 创建人
CreateTime *LocalTime `json:"createTime"` // 创建时间(*LocalTime,NULL 输出 null)
StatusText string `json:"statusText"` // 状态显示名(按字典 example_status 回填)
}
```
结果类型用别名收敛:
```go
type ExamplePageRes = PageData[ExampleItem] // 分页壳
type ExampleListRes = []ExampleItem // 平铺列表
type ExampleExportRes = string // 导出结果直接是文件 URL
```
## 请求结构与标签
```go
// internal/model/dto/example.go
type ExamplePageReq struct {
PageReq // 分页基础参数(内嵌,pageNo/pageSize 即刻可用)
Name string `json:"name" form:"name" dc:"案例名称"` // 模糊匹配,空串跳过
Type int `json:"type" form:"type" dc:"案例类型"` // 等值,零值跳过
Status int `json:"status" form:"status" dc:"案例状态"` // 等值,零值跳过
Keyword string `json:"keyword" form:"keyword" dc:"关键词"` // 多列 OR 关键词
}
type ExampleDetailReq struct {
Id int64 `json:"id" binding:"required" msg:"缺少ID" dc:"记录ID"`
}
```
| 标签 | 作用 |
|---|---|
| json | 对外 API 字段名(小驼峰) |
| form | GET/query 参数绑定名(与 json 同名) |
| binding | 校验规则(required / min / max / oneof 等) |
| msg | 校验失败提示语 |
| dc | 字段说明(供 Swagger/OpenAPI 文档生成) |
## 命名与语义规约
| 请求/响应 | 语义 |
|---|---|
| AddReq | **全量必填**:新增时业务字段都要给(required 标注) |
| UpdateReq | **全量表单语义**:提交全部业务字段,未改字段也回传原值,零值照样落库 |
| DeleteReq | 单条删除,`Id` required |
| BatchDeleteReq | `Ids []int64` + `binding:"min=1"` |
| StatusReq | 单列状态更新入口,只提交 ID 与目标状态 |
时间字段统一 `*time.Time`(模块内用 `*LocalTime` 包装输出格式);文件字段用 `*multipart.FileHeader`。
## 文件上传字段与解析逃生舱
```go
// internal/model/dto/example.go
type ExampleImportReq struct {
File *multipart.FileHeader `form:"file" binding:"required" msg:"请选择文件" dc:"xls/xlsx 文件"`
}
// UnmarshalBody 实现 multipart 表单解析逃生舱(跳过默认 JSON 解析)
func (r *ExampleImportReq) UnmarshalBody(c *gin.Context) error {
f, err := c.FormFile("file")
if err != nil {
return errors.New("请选择文件")
}
r.File = f
return nil
}
```
任何结构体只要实现 `UnmarshalBody(c *gin.Context) error` 接口,绑定引擎就跳过默认解析,完全交给结构体自己处理(文件上传的唯一正确姿势)。
## 响应侧可选能力
```go
// ResMsg 实现 response.MsgProvider:成功通道直接弹出该提示语
func (r *ExampleImportRes) ResMsg() string {
if r.Failed > 0 {
return fmt.Sprintf("成功导入 %d 条,失败 %d 条", r.Success, r.Failed)
}
return fmt.Sprintf("成功导入 %d 条数据", r.Success)
}
```
## 编写检查清单
- [ ] 每个字段都有 json + form 标签(form 与 json 同名)
- [ ] 请求字段有 dc 说明;校验字段有 binding + msg
- [ ] 时间用 `*LocalTime`,批量删除用 `Ids []int64`
- [ ] AddReq 全量 required,UpdateReq 全量不带 required 之外的过滤语义
- [ ] 文件字段实现 UnmarshalBody
## 下一步
→ [3.4 数据访问层(repository)](/zh/guide/repository.html)