跳到主要内容
EN

Go Web 开发实践

14 分钟阅读

net/http 标准库

Go 的 net/http 标准库已经足够构建生产级 Web 服务,无需框架也能工作:

func main() {
    mux := http.NewServeMux()

    // Go 1.22 增强路由:支持方法匹配和路径参数
    mux.HandleFunc("GET /users/{id}", getUser)
    mux.HandleFunc("POST /users", createUser)
    mux.HandleFunc("DELETE /users/{id}", deleteUser)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
    log.Fatal(server.ListenAndServe())
}

func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")  // Go 1.22+
    json.NewEncoder(w).Encode(map[string]string{"id": id})
}

中间件模式

// 中间链式调用
func Chain(h http.Handler, middleware ...func(http.Handler) http.Handler) http.Handler {
    for i := len(middleware) - 1; i >= 0; i-- {
        h = middleware[i](h)
    }
    return h
}

// 日志中间件
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// 认证中间件
func Auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        ctx := context.WithValue(r.Context(), "userID", parseToken(token))
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Gin/Echo 框架

标准库适合简单服务,框架提供路由分组、参数绑定、验证等便捷功能。

Gin 示例

func main() {
    r := gin.Default()  // Logger + Recovery 中间件

    // 路由分组
    api := r.Group("/api/v1")
    {
        users := api.Group("/users")
        {
            users.GET("", listUsers)
            users.GET("/:id", getUser)
            users.POST("", createUser)
            users.PUT("/:id", updateUser)
            users.DELETE("/:id", deleteUser)
        }
    }

    r.Run(":8080")
}

type CreateUserReq struct {
    Name  string `json:"name" binding:"required,min=2,max=50"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"omitempty,gte=0,lte=150"`
}

func createUser(c *gin.Context) {
    var req CreateUserReq
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    // 处理业务...
    c.JSON(http.StatusCreated, gin.H{"id": "usr_123", "name": req.Name})
}

数据库交互

GORM

GORM 是 Go 生态最流行的 ORM,提供模型定义、关联、迁移等功能:

type User struct {
    ID        uint           `gorm:"primaryKey" json:"id"`
    Name      string         `gorm:"size:100;not null" json:"name"`
    Email     string         `gorm:"uniqueIndex;size:255" json:"email"`
    Orders    []Order        `gorm:"foreignKey:UserID" json:"orders,omitempty"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`  // 软删除
}

func setupDB() *gorm.DB {
    dsn := "user:pass@tcp(localhost:3306)/mydb?charset=utf8mb4&parseTime=True"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }
    db.AutoMigrate(&User{}, &Order{})
    return db
}

// 查询示例
func getUserWithOrders(db *gorm.DB, id uint) (*User, error) {
    var user User
    err := db.Preload("Orders", "status = ?", "active").
        First(&user, id).Error
    return &user, err
}

sqlx

当需要更精细的 SQL 控制时,sqlx 是更好的选择:

type User struct {
    ID    int    `db:"id"`
    Name  string `db:"name"`
    Email string `db:"email"`
}

func getUsers(db *sqlx.DB) ([]User, error) {
    users := []User{}
    err := db.Select(&users, "SELECT id, name, email FROM users WHERE status = ?", "active")
    return users, err
}

func getUserByID(db *sqlx.DB, id int) (*User, error) {
    var user User
    err := db.Get(&user, "SELECT id, name, email FROM users WHERE id = ?", id)
    return &user, err
}

// 命名参数
func createUser(db *sqlx.DB, user *User) error {
    _, err := db.NamedExec(
        "INSERT INTO users (name, email) VALUES (:name, :email)",
        user,
    )
    return err
}

Clean Architecture 实践

Clean Architecture 将应用分为多个同心层,依赖关系只能从外向内:

graph TD
    subgraph "Clean Architecture"
        E["Entities<br/>业务实体与规则<br/>最内层,无依赖"]
        U["Use Cases<br/>业务用例/服务层"]
        IA["Interface Adapters<br/>控制器、网关、Presenter"]
        FW["Frameworks & Drivers<br/>Web、DB、外部服务"]
    end
    FW --> IA --> U --> E

Go 项目结构

project/
├── cmd/
│   └── server/
│       └── main.go          # 入口
├── internal/
│   ├── domain/              # 实体层:业务模型
│   │   ├── user.go
│   │   └── order.go
│   ├── usecase/             # 用例层:业务逻辑
│   │   ├── user_service.go
│   │   └── order_service.go
│   ├── repository/          # 接口适配层:仓储接口
│   │   └── user_repository.go
│   ├── handler/             # 接口适配层:HTTP 处理器
│   │   └── user_handler.go
│   └── infrastructure/      # 框架层:具体实现
│       ├── mysql/
│       │   └── user_repo_impl.go
│       └── redis/
│           └── cache.go
├── pkg/                     # 可复用的公共包
└── go.mod

依赖倒置实现

// domain/user.go — 实体层,无外部依赖
type User struct {
    ID    string
    Name  string
    Email string
}

// repository/user_repository.go — 定义接口(用例层需要)
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*User, error)
    Save(ctx context.Context, user *User) error
}

// usecase/user_service.go — 用例层,依赖接口
type UserService struct {
    repo repository.UserRepository
}

func NewUserService(repo repository.UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    return s.repo.FindByID(ctx, id)
}

// infrastructure/mysql/user_repo_impl.go — 框架层,实现接口
type mysqlUserRepo struct {
    db *sqlx.DB
}

func NewMysqlUserRepo(db *sqlx.DB) repository.UserRepository {
    return &mysqlUserRepo{db: db}
}

优雅关停

生产环境必须实现优雅关停——停止接收新请求,等待进行中的请求完成后再退出:

func main() {
    server := &http.Server{Addr: ":8080", Handler: setupRouter()}

    // 启动服务
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutting down server...")

    // 给进行中的请求 15 秒完成时间
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Server forced to shutdown: %v", err)
    }
    log.Println("Server exited gracefully")
}

优雅关停流程:

sequenceDiagram
    participant OS as 操作系统
    participant App as 应用
    participant Server as HTTP Server

    OS->>App: SIGTERM
    App->>Server: Shutdown(ctx)
    Note over Server: 停止接受新连接<br/>等待进行中请求完成
    Server-->>App: 所有请求完成 / 超时
    App->>App: 关闭数据库连接<br/>刷新缓冲区
    App->>OS: Exit 0

Go Web 开发的核心思路:标准库优先,按需引入框架;依赖倒置保证可测试性;优雅关停保证生产稳定。

编辑此页

评论