04-Go Web开发

Go标准库net/http功能完整,第三方框架生态丰富。天生高并发,适合构建高性能Web服务。

标准库HTTP服务器

基础服务器

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

路由和处理器

// HandlerFunc
func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Home"))
}

// Handler接口
type MyHandler struct{}

func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Custom handler"))
}

// 注册
http.HandleFunc("/home", homeHandler)
http.Handle("/custom", &MyHandler{})

// 自定义ServeMux
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
mux.HandleFunc("/api/posts", postsHandler)

http.ListenAndServe(":8080", mux)

请求和响应

func handler(w http.ResponseWriter, r *http.Request) {
    // 请求方法
    method := r.Method  // GET, POST, PUT, DELETE
    
    // URL参数
    id := r.URL.Query().Get("id")
    page := r.URL.Query().Get("page")
    
    // 路径参数(需第三方路由)
    // vars := mux.Vars(r)
    // id := vars["id"]
    
    // 表单数据
    r.ParseForm()
    username := r.FormValue("username")
    
    // JSON请求体
    var data struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    json.NewDecoder(r.Body).Decode(&data)
    
    // 请求头
    token := r.Header.Get("Authorization")
    
    // 设置响应头
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)  // 200
    
    // JSON响应
    json.NewEncoder(w).Encode(map[string]string{
        "message": "success",
    })
}

Gin框架

轻量高性能Web框架,类似Express。

基本使用

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()  // 带Logger和Recovery中间件
    
    // GET
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    
    // 路径参数
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(200, gin.H{"id": id})
    })
    
    // 查询参数
    r.GET("/search", func(c *gin.Context) {
        query := c.DefaultQuery("q", "default")
        page := c.Query("page")
        c.JSON(200, gin.H{"query": query, "page": page})
    })
    
    // POST JSON
    r.POST("/users", func(c *gin.Context) {
        var user struct {
            Name string `json:"name" binding:"required"`
            Age  int    `json:"age" binding:"gte=0,lte=130"`
        }
        
        if err := c.ShouldBindJSON(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(201, user)
    })
    
    r.Run(":8080")
}

路由分组

r := gin.Default()

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

// API v2
v2 := r.Group("/api/v2")
{
    v2.GET("/users", listUsersV2)
}

中间件

// 自定义中间件
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        c.Next()  // 执行后续处理器
        
        duration := time.Since(start)
        status := c.Writer.Status()
        log.Printf("%s %s %d %v", c.Request.Method, c.Request.URL.Path, status, duration)
    }
}

// 全局中间件
r.Use(Logger())
r.Use(gin.Recovery())

// 路由级中间件
r.GET("/admin", AuthRequired(), adminHandler)

// 分组中间件
admin := r.Group("/admin", AuthRequired())
{
    admin.GET("/users", listUsers)
}

// 认证中间件示例
func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(401, gin.H{"error": "unauthorized"})
            c.Abort()  // 阻止后续处理器
            return
        }
        
        // 验证token
        userID, err := validateToken(token)
        if err != nil {
            c.JSON(401, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }
        
        c.Set("userID", userID)  // 传递给后续处理器
        c.Next()
    }
}

func protectedHandler(c *gin.Context) {
    userID := c.GetInt("userID")
    c.JSON(200, gin.H{"userID": userID})
}

数据库操作

database/sql(标准库)

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

// 连接
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// 配置连接池
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)

// 查询单行
var name string
var age int
err = db.QueryRow("SELECT name, age FROM users WHERE id = ?", 1).Scan(&name, &age)
if err == sql.ErrNoRows {
    // 未找到
}

// 查询多行
rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 18)
defer rows.Close()

for rows.Next() {
    var id int
    var name string
    rows.Scan(&id, &name)
    fmt.Println(id, name)
}

// 插入
result, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", "Alice", 25)
lastID, _ := result.LastInsertId()
affected, _ := result.RowsAffected()

// 事务
tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

_, err = tx.Exec("INSERT INTO users ...")
if err != nil {
    tx.Rollback()
    return err
}

err = tx.Commit()

GORM(ORM)

import "gorm.io/gorm"
import "gorm.io/driver/mysql"

// 连接
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

// 定义模型
type User struct {
    ID        uint           `gorm:"primaryKey"`
    Name      string         `gorm:"size:100;not null"`
    Email     string         `gorm:"uniqueIndex"`
    Age       int
    CreatedAt time.Time
    UpdatedAt time.Time
}

// 自动迁移
db.AutoMigrate(&User{})

// 创建
user := User{Name: "Alice", Email: "alice@example.com", Age: 25}
db.Create(&user)

// 查询
var user User
db.First(&user, 1)                    // 主键查询
db.Where("name = ?", "Alice").First(&user)
db.Where("age > ?", 18).Find(&users)

// 更新
db.Model(&user).Update("age", 26)
db.Model(&user).Updates(User{Age: 26, Email: "new@example.com"})

// 删除
db.Delete(&user)

// 链式调用
db.Where("age > ?", 18).
   Order("created_at desc").
   Limit(10).
   Find(&users)

RESTful API示例

package main

import (
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

type User struct {
    gorm.Model
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    r := gin.Default()
    
    // GET /api/users
    r.GET("/api/users", func(c *gin.Context) {
        var users []User
        db.Find(&users)
        c.JSON(200, users)
    })
    
    // GET /api/users/:id
    r.GET("/api/users/:id", func(c *gin.Context) {
        var user User
        if err := db.First(&user, c.Param("id")).Error; err != nil {
            c.JSON(404, gin.H{"error": "User not found"})
            return
        }
        c.JSON(200, user)
    })
    
    // POST /api/users
    r.POST("/api/users", func(c *gin.Context) {
        var user User
        if err := c.ShouldBindJSON(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        db.Create(&user)
        c.JSON(201, user)
    })
    
    // PUT /api/users/:id
    r.PUT("/api/users/:id", func(c *gin.Context) {
        var user User
        if err := db.First(&user, c.Param("id")).Error; err != nil {
            c.JSON(404, gin.H{"error": "User not found"})
            return
        }
        
        if err := c.ShouldBindJSON(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        db.Save(&user)
        c.JSON(200, user)
    })
    
    // DELETE /api/users/:id
    r.DELETE("/api/users/:id", func(c *gin.Context) {
        if err := db.Delete(&User{}, c.Param("id")).Error; err != nil {
            c.JSON(404, gin.H{"error": "User not found"})
            return
        }
        c.JSON(204, nil)
    })
    
    r.Run(":8080")
}

中间件模式

CORS

func CORS() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
        
        c.Next()
    }
}

r.Use(CORS())

限流

import "golang.org/x/time/rate"

func RateLimiter(r rate.Limit, b int) gin.HandlerFunc {
    limiter := rate.NewLimiter(r, b)
    
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.JSON(429, gin.H{"error": "rate limit exceeded"})
            c.Abort()
            return
        }
        c.Next()
    }
}

r.Use(RateLimiter(10, 100))  // 每秒10个请求

HTTP客户端

import "net/http"

// GET请求
resp, err := http.Get("https://api.example.com/data")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

// POST JSON
data := map[string]string{"key": "value"}
jsonData, _ := json.Marshal(data)

resp, err := http.Post("https://api.example.com",
    "application/json",
    bytes.NewBuffer(jsonData))

// 自定义请求
client := &http.Client{
    Timeout: 10 * time.Second,
}

req, err := http.NewRequest("PUT", url, body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token")

resp, err := client.Do(req)

WebSocket

import "github.com/gorilla/websocket"

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true  // 允许跨域
    },
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
    // 升级HTTP到WebSocket
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    defer conn.Close()
    
    for {
        // 读取消息
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            break
        }
        
        // 处理消息
        log.Printf("Received: %s", message)
        
        // 发送消息
        err = conn.WriteMessage(messageType, message)
        if err != nil {
            break
        }
    }
}

http.HandleFunc("/ws", wsHandler)

gRPC

高性能RPC框架,基于HTTP/2和Protocol Buffers。

Protocol Buffers定义

// user.proto
syntax = "proto3";

package user;

service UserService {
    rpc GetUser(GetUserRequest) returns (User);
    rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}

message User {
    int64 id = 1;
    string name = 2;
    string email = 3;
}

message GetUserRequest {
    int64 id = 1;
}

message ListUsersRequest {
    int32 page = 1;
    int32 page_size = 2;
}

message ListUsersResponse {
    repeated User users = 1;
}

服务端实现

// 生成代码
// protoc --go_out=. --go-grpc_out=. user.proto

type server struct {
    pb.UnimplementedUserServiceServer
}

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    // 查询数据库
    user := &pb.User{
        Id:    req.Id,
        Name:  "Alice",
        Email: "alice@example.com",
    }
    return user, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &server{})
    s.Serve(lis)
}

客户端调用

conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()

client := pb.NewUserServiceClient(conn)

// 调用
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
if err != nil {
    log.Fatal(err)
}

fmt.Println(user.Name)

配置管理

Viper

统一配置管理,支持多种格式。

import "github.com/spf13/viper"

func initConfig() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    
    // 读取配置
    if err := viper.ReadInConfig(); err != nil {
        log.Fatal(err)
    }
    
    // 环境变量
    viper.AutomaticEnv()
    
    // 默认值
    viper.SetDefault("port", 8080)
}

// 使用
port := viper.GetInt("port")
dbHost := viper.GetString("database.host")

YAML配置示例

# config.yaml
server:
  port: 8080
  host: 0.0.0.0

database:
  host: localhost
  port: 3306
  user: root
  password: secret
  dbname: mydb

redis:
  addr: localhost:6379
  password: ""
  db: 0

日志

标准库log

import "log"

log.Println("info message")
log.Printf("formatted: %d", 42)
log.Fatal("fatal error")   // 打印后exit(1)
log.Panic("panic message") // 打印后panic

// 自定义logger
logger := log.New(os.Stdout, "[APP] ", log.LstdFlags|log.Lshortfile)
logger.Println("custom log")

logrus(结构化日志)

import "github.com/sirupsen/logrus"

log := logrus.New()

// 设置格式
log.SetFormatter(&logrus.JSONFormatter{})

// 设置级别
log.SetLevel(logrus.InfoLevel)

// 使用
log.WithFields(logrus.Fields{
    "user_id": 123,
    "action":  "login",
}).Info("User logged in")

log.WithError(err).Error("Failed to process")

zap(高性能)

import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("user login",
    zap.String("username", "alice"),
    zap.Int("user_id", 123),
)

// 开发环境
logger, _ := zap.NewDevelopment()

测试

单元测试

// add_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

// 表格驱动测试
func TestAddTable(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -2, -3},
        {"zero", 0, 0, 0},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("got %d, want %d", got, tt.want)
            }
        })
    }
}

HTTP测试

func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/api/users", nil)
    w := httptest.NewRecorder()
    
    handler(w, req)
    
    if w.Code != 200 {
        t.Errorf("got %d, want 200", w.Code)
    }
}

// Gin测试
func TestGinRoute(t *testing.T) {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })
    
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/ping", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

性能优化

JSON序列化

// 标准库encoding/json
json.Marshal(data)    // 通用,性能一般

// jsoniter(快2-3倍)
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary

json.Marshal(data)

// easyjson(代码生成,快5-10倍)
// 需要生成代码:easyjson -all user.go
user.MarshalJSON()

连接池复用

// HTTP客户端连接池
var client = &http.Client{
    Timeout: 10 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     90 * time.Second,
    },
}

部署

构建

# 编译
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

# 减小体积
go build -ldflags="-s -w" -o app .

# upx压缩(可选)
upx -9 app

Docker

# 多阶段构建
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .

EXPOSE 8080
CMD ["./app"]

核心: Go天生适合Web开发,标准库强大,性能优秀。