9.1 工程化名词
- 分层架构(Layered Architecture)
- 把代码按职责切成 handler(HTTP)→ service(业务)→ repository(数据)三层,单向依赖,各层可独立测试。
- DTO / Entity
- DTO 是对外的请求/响应模型;Entity 是数据库模型。分开可避免把内部字段暴露给客户端。
- viper
- Go 主流配置库,支持 YAML/JSON/env/远程配置,能热加载、多来源合并、优先级覆盖。
- zap
- Uber 出品的高性能结构化日志库,零分配、JSON 输出,便于被 ELK/Loki 采集分析。
- 优雅关机(Graceful Shutdown)
- 收到退出信号后停止接收新请求,等待在途请求处理完再退出,避免连接被强制切断。
- Swagger / OpenAPI
- 用注释生成 API 文档与在线调试页(swag + gin-swagger),前后端协作利器。
- 依赖装配(Wiring)
- 在入口把各层实例串起来,可手写,也可用 wire/fx 等 DI 框架自动生成。
9.2 推荐项目结构
myapp/
├── cmd/server/main.go # 入口
├── internal/
│ ├── config/config.go # viper 加载
│ ├── handler/user.go # HTTP 层:绑定、调 service、响应
│ ├── service/user.go # 业务逻辑
│ ├── repository/user.go # 数据访问
│ ├── model/user.go # Entity
│ ├── dto/user.go # 请求/响应 DTO
│ ├── middleware/ # 中间件集合
│ └── router/router.go # 路由装配
├── pkg/ # 可复用工具(resp/errcode/log)
├── configs/config.yaml
├── go.mod
└── Dockerfile
9.3 viper 配置管理
# configs/config.yaml
server:
port: 8080
mode: release
database:
dsn: "postgres://user:pass@localhost:5432/app"
jwt:
secret: "${JWT_SECRET}" # 敏感项走环境变量
type Config struct {
Server struct{ Port int; Mode string }
Database struct{ DSN string }
JWT struct{ Secret string }
}
func Load(path string) (*Config, error) {
v := viper.New()
v.SetConfigFile(path)
v.AutomaticEnv() // 环境变量覆盖
if err := v.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
9.4 zap 结构化日志中间件
func ZapLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
logger.Info("request",
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("latency", time.Since(start)),
zap.String("ip", c.ClientIP()),
)
}
}
结构化 > 拼字符串
zap 的 zap.String("k", v) 输出 JSON 键值,方便 Loki/ELK 按字段检索与告警。别用 fmt.Sprintf 把一切塞进一行文本,那样几乎没法做聚合分析。
9.5 优雅关机
func main() {
cfg, _ := config.Load("configs/config.yaml")
gin.SetMode(cfg.Server.Mode)
r := router.Setup(cfg)
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Server.Port),
Handler: r,
}
// 后台启动
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %v", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down...")
// 给在途请求 10 秒处理时间
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("forced shutdown: %v", err)
}
log.Println("server exited")
}
9.6 Swagger 接口文档
# 安装工具
$ go install github.com/swaggo/swag/cmd/swag@latest
$ go get github.com/swaggo/gin-swagger github.com/swaggo/files
// 在 handler 上写注释
// @Summary 获取用户
// @Tags user
// @Param id path int true "用户ID"
// @Success 200 {object} dto.UserResp
// @Router /users/{id} [get]
func (h *UserHandler) Get(c *gin.Context) { /* ... */ }
// 生成 docs 后挂载
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// 访问 http://localhost:8080/swagger/index.html
执行 swag init 扫描注释生成 docs/,即可得到在线可调试的 API 文档。
9.7 小结
- 分层 handler/service/repository 单向依赖,DTO 与 Entity 分离。
- viper 管配置、敏感项走环境变量;zap 输出结构化日志便于采集。
- 优雅关机用
srv.Shutdown(ctx),给在途请求留处理时间。 - Swagger 从注释生成文档,前后端协作零成本。
- 最后一章:实战与部署——完整项目、httptest 测试、Docker 与部署清单。