2. Go 基础回顾:结构体、标签、接口与错误类型

在构建 REST API 服务时,结构体用于定义请求/响应模型,标签提供元数据(如字段名、校验规则),接口抽象行为,错误类型则统一处理异常。本章快速回顾这四个核心概念,为后续参数校验与错误处理打下基础。

2.1 结构体 (struct) 与标签 (tag)

结构体是 Go 中聚合不同类型字段的复合类型,常用于映射 JSON 请求体或数据库记录。标签(Tag)是附加在字段后的字符串,通过反射机制在运行时读取,广泛用于序列化、校验和 ORM 映射。

2.1.1 结构体定义与标签语法

type CreateUserRequest struct {
    Username string `json:"username" validate:"required,min=3,max=20"`
    Email    string `json:"email"    validate:"required,email"`
    Age      int    `json:"age"      validate:"gte=18,lte=120"`
    Role     string `json:"role"     validate:"oneof=admin user"`
}

标签格式:key:"value",多个 key 用空格分隔。常用标签包括:

标签 Key 用途 示例
json JSON 序列化/反序列化字段名 json:"username"
validate 参数校验规则(如 validator 库) validate:"required,email"
form 表单/URL 查询参数绑定 form:"page"
xml XML 序列化字段名 xml:"item"
gorm ORM 列名/约束 gorm:"column:user_name"

2.1.2 标签读取与反射

标签通过 reflect 包在运行时读取,这是校验库和序列化库的核心机制:

import "reflect"

func readTag(s interface{}) {
    t := reflect.TypeOf(s).Elem()
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        jsonTag := field.Tag.Get("json")
        validateTag := field.Tag.Get("validate")
        fmt.Printf("Field: %s, json: %s, validate: %s\n", field.Name, jsonTag, validateTag)
    }
}
💡 提示: 标签中的 key 是大小写敏感的,通常使用小写。自定义标签 key 应避免与标准库冲突,建议使用项目前缀,如 myapp:"..."。

2.2 接口 (interface) 的概念

Go 的接口是隐式实现的——只要类型实现了接口的所有方法,就自动满足该接口。这种“鸭子类型”风格让代码更灵活,是依赖注入、中间件和策略模式的基础。

2.2.1 接口定义与实现

// 定义校验器接口
type Validator interface {
    Validate() error
}

// 结构体实现接口
type User struct {
    Name  string
    Email string
}

func (u User) Validate() error {
    if u.Name == "" {
        return errors.New("name is required")
    }
    if !strings.Contains(u.Email, "@") {
        return errors.New("invalid email")
    }
    return nil
}

// 使用接口
func process(v Validator) error {
    return v.Validate()
}

2.2.2 接口在 REST API 中的典型应用

  • 校验器接口:所有请求 DTO 实现 Validate() error,统一校验入口。
  • 存储层接口:定义 UserRepository 接口,支持 mock 测试和切换实现。
  • 中间件接口:Handler 或 Middleware 接口抽象 HTTP 处理逻辑。
  • 错误处理接口:自定义错误类型实现 Error() string 和状态码方法。
📌 关键点: 接口应保持小而专注(单一职责),推荐每个接口只包含 1-3 个方法。Go 标准库中的 io.Reader、io.Writer 就是最佳范例。

2.3 错误 (error) 类型基础

Go 的 error 是一个内置接口:

type error interface {
    Error() string
}

任何实现了 Error() string 方法的类型都可以作为错误值。这是 Go 错误处理的核心抽象。

2.3.1 创建错误的常用方式

方式 代码示例 适用场景
errors.New errors.New("not found") 简单静态错误消息
fmt.Errorf fmt.Errorf("user %d not found", id) 带格式化参数的动态错误
自定义结构体 type AppError struct { Code int; Msg string } 携带额外上下文(状态码、内部错误)
哨兵错误 var ErrNotFound = errors.New("not found") 用 == 比较的错误常量

2.3.2 自定义错误类型示例

type APIError struct {
    HTTPStatus int    `json:"-"`
    Code       string `json:"code"`
    Message    string `json:"message"`
    Err        error  `json:"-"` // 内部原始错误
}

func (e *APIError) Error() string {
    return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}

func (e *APIError) Unwrap() error {
    return e.Err
}

// 使用
func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, &APIError{
            HTTPStatus: 400,
            Code:       "INVALID_ID",
            Message:    "用户 ID 必须为正整数",
        }
    }
    // ...
}
⚠️ 注意: 不要忽略错误!Go 中未处理的错误会导致不可预知的行为。在 REST API 中,每个可能返回 error 的调用都应被检查,并转换为统一的 API 错误响应。

2.4 核心概念关系图

下图展示了结构体、标签、接口和错误类型在 REST API 请求处理中的协作关系:

结构体 · 标签 · 接口 · 错误 协作关系 HTTP 请求 请求结构体 + JSON/Validate 标签 Validator 接口 Validate() error 自定义错误类型 APIError / ValidationError HTTP 响应 JSON 错误/成功 客户端 📘 流程说明: 1. 客户端请求 → 结构体绑定(JSON 标签映射字段) 2. 结构体实现 Validator 接口 → 调用 Validate() 校验(validate 标签规则) 3. 校验失败 → 返回自定义错误类型 → 统一格式化为 JSON 错误响应

2.5 本章小结

  • 结构体 + 标签:定义数据模型,标签提供元数据驱动校验和序列化。
  • 接口:隐式实现,抽象行为(如校验、存储),便于测试和扩展。
  • 错误类型:error 接口是基础,自定义错误可携带 HTTP 状态码、错误码等上下文。
  • 三者结合:结构体承载数据 → 标签描述规则 → 接口定义行为 → 错误类型统一异常路径。
🎯 实践建议: 在项目初期就定义好基础的错误类型和校验接口,后续所有 handler 和 service 层统一使用,避免散落各处的 fmt.Errorf 和重复校验逻辑。