签入版本

This commit is contained in:
小蚂蚁云
2026-10-25 17:24:27 +08:00
parent 48911cc08f
commit fdbeae092a
1010 changed files with 30682 additions and 95274 deletions
+121
View File
@@ -0,0 +1,121 @@
# 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)