Become a sponsor

概述
dto 是模块的对外契约:json 标签即 API 字段名,binding/msg 标签给出校验规则与提示语,form 标签供 GET 参数绑定。契约一旦发布应保持稳定——前端与生成器都依赖它。
internal/model/dto/example.go (package dto,一模块一文件)结构体按职责命名:ExampleItem(行结构)、ExamplePageReq(分页请求)、ExampleDetailRes(详情响应)……
// 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 回填)
}结果类型用别名收敛:
type ExamplePageRes = PageData[ExampleItem] // 分页壳
type ExampleListRes = []ExampleItem // 平铺列表
type ExampleExportRes = string // 导出结果直接是文件 URL// 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。
// 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 接口,绑定引擎就跳过默认解析,完全交给结构体自己处理(文件上传的唯一正确姿势)。
// 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)
}*LocalTime,批量删除用 Ids []int64