Go: 电商 API(下):文档与部署

最后更新:2026-08-26

架构有了,安全有了,现在——让它上线。

从第一行代码到生产就绪:Swagger 文档、优雅关闭、Docker deploy、pprof 调优、Prometheus 监控。Bob 的电商 API 准备迎接真实用户了。

1. 你将学到


2. 故事:上线前夜

(1) 痛点:没有文档、没有监控、部署靠手动

Bob 的电商 API 功能完整了,但运营团队拒绝验收:

"运营说'没有 API 文档,前端没法对接。没有健康检查,我们不知道服务是否在运行。没有监控,挂了也不知道。部署还要手动 scp 二进制,太原始了。'"

上线 checklist:

TEXT 📖 仅展示
❌ API 文档 → 前端每次都要问"这个接口返回什么字段?"
❌ 优雅关闭 → kill 进程导致正在处理的订单丢失
❌ Docker 部署 → scp 二进制到服务器,手动启动
❌ 性能监控 → 不知道 API 慢在哪里

(2) 本课目标:生产就绪

TEXT 📖 仅展示
✅ Swagger 文档 → 前端自助查看
✅ Graceful Shutdown → 信号处理 + 等待连接关闭
✅ Docker Compose → 一条命令启动全栈
✅ pprof + Prometheus → 性能可视化

3. 完整实现

▶ 示例:Graceful Shutdown

⚙️ 前置安装:运行 go get github.com/mattn/go-sqlite3 ⚠️ 注意:go-sqlite3 需要 CGO,Windows 需安装 gcc(MinGW-w64),macOS/Linux 自带

GO 📖 仅展示
// cmd/server/main.go
package main

import (
    "context"
    "database/sql"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "ecommerce/internal/handler"
    "ecommerce/internal/repository"
    "ecommerce/internal/service"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "./ecommerce.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)

    // 依赖注入
    userRepo := repository.NewUserRepository(db)
    productRepo := repository.NewProductRepository(db)
    orderRepo := repository.NewOrderRepository(db)

    userSvc := service.NewUserService(userRepo)
    productSvc := service.NewProductService(productRepo)
    orderSvc := service.NewOrderService(orderRepo, productRepo, userRepo)

    userHandler := handler.NewUserHandler(userSvc)
    productHandler := handler.NewProductHandler(productSvc)
    orderHandler := handler.NewOrderHandler(orderSvc)

    mux := http.NewServeMux()
    userHandler.Register(mux)
    productHandler.Register(mux)
    orderHandler.Register(mux)

    // 健康检查端点
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
        if err := db.Ping(); err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            json.NewEncoder(w).Encode(map[string]string{"status": "not ready"})
            return
        }
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
        // 超时设置(防慢连接攻击)
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       60 * time.Second,
        ReadHeaderTimeout: 5 * time.Second,
    }

    // Graceful Shutdown
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        sig := <-sigCh
        log.Printf("收到信号 %v,正在关闭...", sig)

        // 给正在处理的请求最多 30 秒完成
        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()

        if err := server.Shutdown(ctx); err != nil {
            log.Printf("强制关闭: %v", err)
        }
    }()

    log.Println("电商 API 启动于 :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
    log.Println("服务已安全关闭")
}
逻辑代码 76 行(超过 40 行限制,仅展示)

▶ 示例:pprof + Prometheus 集成

⚙️ 前置安装:运行 go get github.com/prometheus/client_golang/prometheusgo get github.com/prometheus/client_golang/prometheus/promhttp

GO 📖 仅展示
package main

import (
    "fmt"
    "net/http"
    "net/http/pprof"
    "runtime"
    "strconv"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// Prometheus 指标
var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5},
        },
        []string{"method", "path"},
    )

    activeGoroutines = prometheus.NewGaugeFunc(
        prometheus.GaugeOpts{
            Name: "go_goroutines_active",
            Help: "Current number of goroutines",
        },
        func() float64 {
            return float64(runtime.NumGoroutine())
        },
    )
)

func init() {
    prometheus.MustRegister(httpRequestsTotal)
    prometheus.MustRegister(httpRequestDuration)
    prometheus.MustRegister(activeGoroutines)
}

// Prometheus 中间件
func prometheusMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        // 包装 ResponseWriter 获取状态码
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(wrapped, r)

        duration := time.Since(start)
        httpRequestsTotal.WithLabelValues(
            r.Method, r.URL.Path, strconv.Itoa(wrapped.statusCode),
        ).Inc()
        httpRequestDuration.WithLabelValues(
            r.Method, r.URL.Path,
        ).Observe(duration.Seconds())
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

// 在 main 中注册 pprof 和 Prometheus 端点
func registerMonitoring(mux *http.ServeMux) {
    // pprof 端点
    mux.HandleFunc("/debug/pprof/", pprof.Index)
    mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
    mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

    // Prometheus 指标
    mux.Handle("/metrics", promhttp.Handler())
}

func main() {
    mux := http.NewServeMux()
    registerMonitoring(mux)
    fmt.Println("Monitoring server on :8080")
    http.ListenAndServe(":8080", prometheusMiddleware(mux))
}
逻辑代码 78 行(超过 40 行限制,仅展示)

▶ 示例:Docker 部署

DOCKERFILE
# Dockerfile(Multi-stage Build)
FROM golang:1.22-alpine AS builder
WORKDIR /app

# 依赖缓存
COPY go.mod go.sum ./
RUN go mod download

# 源码 + 编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# === 运行阶段 ===
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata

COPY --from=builder /app/server /server

EXPOSE 8080
EXPOSE 6060  # pprof

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

CMD ["/server"]
YAML
# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8080:8080"
      - "6060:6060"   # pprof
    environment:
      - DB_PATH=/data/ecommerce.db
      - GIN_MODE=release
    volumes:
      - data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  data:
  prometheus_data:
  grafana_data:
YAML
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'ecommerce-api'
    static_configs:
      - targets: ['api:8080']
    metrics_path: '/metrics'

▶ 示例:Swagger 文档

GO
// 用注释生成 OpenAPI 文档(配合 swag 工具)

// Package handler handles HTTP requests.
//
// 电商 API
//
//     Schemes: http
//     Host: localhost:8080
//     BasePath: /api/v1
//     Version: 1.0.0
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
// swagger:meta
package handler

import "ecommerce/pkg/model"

// User 用户信息
// swagger:model
type User struct {
    ID    int    `json:"id"`
    Email string `json:"email"`
    Name  string `json:"name"`
}

// RegisterUser 用户注册
// swagger:route POST /api/v1/register users registerUser
//
// 注册新用户
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
//     Responses:
//       201: User
//       400: ErrorResponse
//       409: ErrorResponse
func (h *UserHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
    // handler implementation
}

// swagger:parameters registerUser
type RegisterUserParams struct {
    // in: body
    Body model.RegisterRequest
}

// ErrorResponse 错误响应
// swagger:response
type ErrorResponse struct {
    // in: body
    Body struct {
        Error string `json:"error"`
    }
}
▶ 试一试
BASH
# 安装 swag CLI
$ go install github.com/swaggo/swag/cmd/swag@latest

# 生成文档
$ swag init -g cmd/server/main.go

# 访问文档
# http://localhost:8080/swagger/index.html

(4) 上线检查清单

TEXT 📖 仅展示
✅ Graceful Shutdown — kill 进程后等待请求完成
✅ Health Check — /health 和 /ready 端点
✅ pprof — /debug/pprof/ 性能分析
✅ Prometheus — /metrics 指标暴露
✅ Swagger — API 文档自动生成
✅ Docker — Multi-stage build,镜像 < 20 MB
✅ docker-compose — 一条命令启动全栈
✅ ReadTimeout / WriteTimeout — 防慢连接攻击
✅ DB 连接池 — MaxOpenConns=25, MaxIdleConns=10
✅ server.Shutdown — SIGINT/SIGTERM 处理
💡 提示: 生产环境还需要:(1) 反向代理(Nginx / Caddy)处理 TLS 和域名;(2) CI/CD 管道自动构建和部署;(3) 日志集中管理(如 Loki 或 Elasticsearch);(4) 告警规则(如 5xx 错误率 > 1% 时告警)。这些超出了本课范围,但建议部署前准备好。


❓ 常见问题

Q Graceful Shutdown 为什么重要?
A 在收到 SIGTERM(如 K8s 停止 Pod)时,server.Shutdown 会等待所有活跃 HTTP 请求处理完成(最长等待设定的 timeout),然后关闭 listener。如果直接 kill 进程,正在处理的请求会中断——导致订单数据不一致。
Q Server 超时设置有哪些?
A (1) ReadTimeout:读取整个请求(包括 Body)的超时;(2) WriteTimeout:发送响应的超时;(3) IdleTimeout:Keep-Alive 连接的空闲超时;(4) ReadHeaderTimeout:读取请求头的超时。建议全部设置——防止慢连接攻击。
Q pprof 在生产环境安全吗?
A pprof 端点不应该对外暴露。方案:(1) 通过内部端口(如 :6060)提供,不与业务端口共用;(2) 用 auth 中间件保护 /debug/pprof/ 路径;(3) 只在 staging 环境开启。生产环境建议在需要时临时开启采集 profile。
Q Prometheus metrics 怎么暴露?
Apromhttp.Handler()/metrics 端点暴露指标。Prometheus 服务定期 scrape 该端点。Grafana 连接 Prometheus 数据源,创建可视化面板。标准指标:请求数、延迟分布、错误率、goroutine 数、GC 次数。
Q OpenAPI 文档怎么自动生成?
Aswaggo/swag 工具——在 handler 代码中写特定格式的注释,运行 swag init 生成 docs/ 目录。用 swaggo/http-swagger 将文档挂载到 /swagger/ 端点。注释格式:// swagger:route // swagger:model // swagger:parameters
Q docker-compose 多服务怎么编排?
A 定义多个 service(api、prometheus、grafana),用 depends_on 控制启动顺序,用 volumes 持久化数据。docker-compose up -d 一条命令启动所有服务。生产环境用 docker stack 或 K8s。
Q 健康检查设计 3 种粒度?
A (1) /health(存活)——最简单的检查,返回 200 表示进程运行;(2) /ready(就绪)——检查依赖(数据库、缓存)是否可达;(3) /status(详细)——返回所有依赖的状态和延迟。

📖 小节


📝 作业

  1. 基础题(难度⭐):为本课的电商 API 添加 Graceful Shutdown 和超时设置。用 curl 测试 /health/ready 端点。用 kill -SIGTERM <pid> 测试优雅关闭。

  2. 进阶题(难度⭐⭐):实现完整的 Docker 化部署。要求:(1) Dockerfile multi-stage build;(2) docker-compose.yml(API + Prometheus + Grafana);(3) HEALTHCHECK;(4) pprof 端点(仅内部端口);(5) 验证 docker-compose up 后所有服务正常启动。

  3. 挑战题(难度⭐⭐⭐):实现 Prometheus 监控 + Grafana 可视化面板。要求:(1) 在 API 中添加 Prometheus 中间件(请求数、延迟、错误率);(2) 添加业务指标(如订单创建数、注册用户数);(3) docker-compose 包含 Prometheus + Grafana;(4) 导入/创建 Grafana dashboard 展示 QPS、P99 延迟、错误率、goroutine 数;(5) 压测后用 Grafana 验证指标正确。

🎉 恭喜!你完成了全部 30 课 Go 教程!从 Hello World 到生产级电商 API,你已经掌握了 Go 后端开发的完整技能栈。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏