Published on

golang项目架构文档

Authors
  • avatar
    Name
    刘十三
    Twitter

项目架构文档

分层架构 + 依赖注入(Uber FX),基于 Gin 的 RESTful API 服务。

目录


技术栈

类别技术说明
语言Go最新稳定版
WebGin最新
ORMGORM最新
DIUber FX最新
配置Viper最新
日志Zap最新
存储PostgreSQL(优先), Redis最新客户端
其他GORM Gen, Swagger, Prometheus, Kafka(可选), WebSocket-

项目结构

your_project/
├── cmd/                    # 入口
│   ├── admin/              # 管理后台
│   ├── mini/               # 客户端
│   ├── migration/          # 迁移
│   └── gorm_gen/           # GORM 代码生成
├── internal/               # 业务(不对外)
│   ├── admin/
│   │   ├── handler/ service/ router/ middleware/ types/ vars/
│   └── mini/
│       ├── handler/ service/ router/ middleware/ types/ vars/ websocket/
├── third_party/
│   └── go-crate/           # 三方 monorepo(Git Submodule)
│       ├── provide/        # 基础设施:gin, gorm, redis, jwt, logger, mail, prometheus, kafka, websocket, vars, es, etcd, casbin, mcp, eino, ali, grafana, minio, tusd, i18n
│       ├── utils/          # 工具:response, crypto, timee, apple, iap, expo, slicee, stringss
│       ├── go.mod          # 模块 liuxiaobo.pro/go-crate
│       └── go.sum
├── repo/                   # 数据访问
│   ├── query/              # GORM Gen 生成
│   ├── redis/
│   └── schemas/
├── config/
├── docs/                   # Swagger
├── deploy/
├── static/
├── go.mod                  # replace liuxiaobo.pro/go-crate => ./third_party/go-crate
└── Makefile

third_party 说明

  • 单一 Submodulethird_party/go-crate,模块名 liuxiaobo.pro/go-crate,内含 provide/utils/
  • 主项目引用go.modreplace liuxiaobo.pro/go-crate => ./third_party/go-crate
  • 导入liuxiaobo.pro/go-crate/provide/ginliuxiaobo.pro/go-crate/provide/gormliuxiaobo.pro/go-crate/utils/response 等。
  • 初始化git submodule update --init --recursive;更新:git submodule update --remote

架构与各层职责

HTTPRouterHandlerServiceRepositoryDB/Redis
  • Handler:参数绑定/校验、调 Service、统一响应;继承基础 handler,用 bindError/serviceError/success
  • Service:业务逻辑、调 Repository、事务;定义接口,方法首参 context.Context,业务错误用 *response.Response
  • Repository:GORM Gen / Redis,无业务逻辑。
  • Router:路由、中间件、分组(noauth/auth)。

Router 示例

func (r *Router) RegisterRoutes(engine *provideGin.Engine) {
	noauth := engine.Group(prefix)
	noauth.Use(provideGin.TraceMiddleware(), provideGin.LoggerMiddleware(r.logger), middleware.JwtMiddleware(r.jwt, true))
	noauth.GET("/user/:id", r.userHandler.Detail)

	auth := engine.Group(prefix)
	auth.Use(provideGin.TraceMiddleware(), provideGin.LoggerMiddleware(r.logger), middleware.JwtMiddleware(r.jwt, false))
	auth.GET("/user/:id", r.userHandler.Detail)
	auth.PUT("/user", r.userHandler.Update)
}

Handler 示例

type UserHandler struct {
	handler
	logger      logger.Logger
	userService service.UserService
}

func NewUserHandler(handler *handler, log logger.Logger, userService service.UserService) *UserHandler {
	return &UserHandler{handler: *handler, logger: log, userService: userService}
}

func (h *UserHandler) Detail(c *gin.Context) {
	var req types.UserDetailRequest
	if err := c.ShouldBindUri(&req); err != nil {
		h.bindError(c, err)
		return
	}
	data, err := h.userService.Detail(c, &req)
	if err != nil {
		h.serviceError(c, err)
		return
	}
	h.success(c, data)
}

Service 示例

type UserService interface {
	Detail(ctx context.Context, req *types.UserDetailRequest) (*types.UserDetailResponse, error)
}

type userService struct {
	query *query.Query
	logger logger.Logger
}

func NewUserService(query *query.Query, logger logger.Logger) UserService {
	return &userService{query: query, logger: logger}
}

func (s *userService) Detail(ctx context.Context, req *types.UserDetailRequest) (*types.UserDetailResponse, error) {
	user, err := s.query.User.WithContext(ctx).Where(s.query.User.ID.Eq(req.ID)).First()
	if err != nil {
		return nil, err
	}
	return new(types.UserDetailResponse).FromUser(user), nil
}

Migration 示例

func main() {
	configFile := flag.String("f", "config.yaml", "配置文件路径")
	flag.Parse()
	app := fx.New(
		fx.Supply(*configFile),
		fx.Provide(config.NewViper, config.NewConfig, logger.NewLogger, gorm.NewDB),
		fx.Invoke(registerLifecycle),
	)
	if err := app.Start(context.Background()); err != nil {
		log.Fatal("启动应用失败: ", err)
	}
}

func registerLifecycle(lc fx.Lifecycle, cfg *config.Config, log logger.Logger, db *gorm.DB) {
	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error {
			log.Info("开始迁移数据库...")
			if err := db.AutoMigrate(schemas.AutoMigrateModels...); err != nil {
				log.Error("数据库迁移失败", zap.Error(err))
				return err
			}
			log.Info("数据库迁移成功")
			return nil
		},
	})
}

GORM Gen 示例

func main() {
	configFile := flag.String("f", "config.yaml", "配置文件路径")
	flag.Parse()
	app := fx.New(
		fx.Supply(*configFile),
		fx.Provide(config.NewViper, logger.NewLogger, gorm.NewDB),
		fx.Invoke(registerLifecycle),
	)
	if err := app.Start(context.Background()); err != nil {
		log.Fatal("启动应用失败: ", err)
	}
}

func registerLifecycle(lc fx.Lifecycle, log logger.Logger, db *gorm.DB) {
	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error {
			log.Info("开始生成代码...")
			g := gen.NewGenerator(gen.Config{
				OutPath: "../../repo/query",
				Mode:    gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
			})
			g.UseDB(db.DB)
			g.ApplyBasic(schemas.AutoMigrateModels...)
			g.Execute()
			log.Info("代码生成成功")
			return nil
		},
	})
}

Config 示例

type Config struct {
	ServerMini  HttpServer `mapstructure:"server_mini"`
	ServerAdmin HttpServer `mapstructure:"server_admin"`
	App         struct {
		Env  string `mapstructure:"env"`
		Name string `mapstructure:"name"`
	} `mapstructure:"app"`
	Database struct {
		Driver string `mapstructure:"driver"`
		DSN    string `mapstructure:"dsn"`
	} `mapstructure:"database"`
	Redis struct {
		DSN string `mapstructure:"dsn"`
		DB  int    `mapstructure:"db"`
	} `mapstructure:"redis"`
	Jwt struct {
		Secret string `mapstructure:"secret"`
	} `mapstructure:"jwt"`
}

func NewViper(configFile string) (*viper.Viper, error) {
	if configFile != "" {
		return NewViperFromFile(configFile)
	}
	return nil, fmt.Errorf("未指定配置文件路径")
}

func NewConfig(configFile string) (*Config, error) {
	if configFile != "" {
		return NewConfigFromFile(configFile)
	}
	return nil, fmt.Errorf("未指定配置文件路径")
}

入口示例 (Admin)

func main() {
	configFile := flag.String("f", "", "配置文件路径")
	flag.Parse()

	if *configFile == "" {
		printBuildInfo()
		os.Exit(0)
	}

	app := fx.New(
		fx.Supply(*configFile),
		fx.Provide(
			config.NewViper,
			config.NewConfig,
			logger.NewLogger,
			gin.NewEngine,
			gorm.NewDB,
			query.NewQuery,
			redis.NewRedis,
			redisrepo.NewRedisRepo,
			jwt.NewJWT,
			casbin.NewCasbin,
		),
		service.Module,
		handler.Module,
		router.Module,
		fx.Invoke(registerLifecycle),
	)

	app.Run()
}

func registerLifecycle(lc fx.Lifecycle,
	cfg *config.Config,
	log logger.Logger,
	engine *gin.Engine,
	db *gorm.DB,
	router *router.Router,
	cronManager *cron.CronManager,
) {
	addr := fmt.Sprintf("%s:%d", cfg.ServerAdmin.Host, cfg.ServerAdmin.Port)
	srv := &http.Server{
		Addr:           addr,
		Handler:        engine.Engine,
		ReadTimeout:    cfg.ServerAdmin.ReadTimeout,
		WriteTimeout:   cfg.ServerAdmin.WriteTimeout,
		MaxHeaderBytes: cfg.ServerAdmin.MaxHeaderBytes,
	}

	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error {
			if err := cronManager.Start(ctx); err != nil {
				log.Error("启动定时任务管理器失败", zap.Error(err))
				return err
			}
			log.Info("HTTP 服务器启动", zap.String("addr", addr))
			router.RegisterRoutes(engine)
			go func() {
				if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
					log.Fatal("HTTP 服务器启动失败", zap.Error(err))
				}
			}()
			return nil
		},
		OnStop: func(ctx context.Context) error {
			log.Info("正在关闭 HTTP 服务器...")
			shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
			defer cancel()
			if err := srv.Shutdown(shutdownCtx); err != nil {
				log.Error("HTTP 服务器关闭失败", zap.Error(err))
				return err
			}
			if err := cronManager.Stop(ctx); err != nil {
				log.Error("停止定时任务管理器失败", zap.Error(err))
			}
			if err := db.Close(); err != nil {
				log.Error("数据库连接关闭失败", zap.Error(err))
				return err
			}
			return nil
		},
	})
}

依赖注入

  • FXfx.Provide 构造函数、fx.Module 模块、fx.Invoke 启动、fx.Lifecycle 启停。
  • 模块:各层定义 var Module = fx.Module("name", fx.Provide(NewXxx, ...)),入口里 opts = append(opts, handler.Module, service.Module, router.Module, fx.Invoke(registerLifecycle))
  • 生命周期registerLifecyclelc.Append(fx.Hook{OnStart: 注册路由并 ListenAndServe, OnStop: Shutdown + db.Close})

开发规范

  • 命名:包小写单数;接口大驼峰(UserService);实现小驼峰(userService);文件小写下划线(user_handler.go)。
  • 错误:统一用 utils/response(Success/Error/BindError/ErrNotFound 等);Service 返回 *response.Response;Handler 用 serviceError 处理。
  • 类型:请求/响应在 types/,Request/Response 后缀;提供 FromXxx 做模型→响应转换。
  • 中间件:JWT 等放 internal/{module}/middleware/;路由组上挂 RequestLog、Trace、Logger、JwtMiddleware。
  • 三方:仅通过 liuxiaobo.pro/go-crate/provide/...liuxiaobo.pro/go-crate/utils/... 引用 go-crate,勿相对路径。

快速开始

git clone --recurse-submodules <repo>
# 或 git clone <repo> && git submodule update --init --recursive

go run cmd/mini/mini.go -f config/dev.yaml
# 或 make build-mini && ./bin/mini -f config/dev.yaml

Makefile

命令说明
make a / build-mini构建 mini
make b / build-admin构建 admin
make c / build-mcp构建 mcp
make fmt格式化(swag fmt + gofumpt + go fmt)
make swag-mini / swag-admin生成 Swagger
make tidygo mod tidy
make init-submodule初始化 submodule
make update-submodule更新 submodule

VS Code 调试 (launch.json) 示例

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Mini Server",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${workspaceFolder}/cmd/mini/mini.go",
      "output": "${workspaceFolder}/__debug_mini",
      "args": ["-f", "${workspaceFolder}/config/dev.yaml"]
    },
    {
      "name": "Admin Server",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${workspaceFolder}/cmd/admin/admin.go",
      "output": "${workspaceFolder}/__debug_admin",
      "args": ["-f", "${workspaceFolder}/config/dev.yaml"]
    },
    {
      "name": "Gorm Gen",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${workspaceFolder}/cmd/gorm_gen/gorm_gen.go",
      "output": "${workspaceFolder}/__debug_gorm_gen",
      "args": ["-f", "${workspaceFolder}/config/dev.yaml"]
    },
    {
      "name": "Migration",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${workspaceFolder}/cmd/migration/migration.go",
      "output": "${workspaceFolder}/__debug_migration",
      "args": ["-f", "${workspaceFolder}/config/dev.yaml"]
    },
    {
      "name": "MCP Server",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${workspaceFolder}/cmd/mcp/mcp.go",
      "output": "${workspaceFolder}/__debug_mcp",
      "args": ["-f", "${workspaceFolder}/config/dev.yaml"]
    }
  ]
}

AI 开发指南

  • 业务代码仅在 internal/{module}/ 下;Handler/Service/Router/Types/Middleware 分目录。
  • 包名/接口/实现/文件命名见上文开发规范。
  • fx.Module + NewXxx 构造函数;导入仅用 liuxiaobo.pro/go-crate/provide/...liuxiaobo.pro/go-crate/utils/...
  • Handler 继承基础 handler;Service 定义接口并返回业务错误;Types 带 Request/Response 与 FromXxx。
  • 错误与响应统一用 response 包;为接口方法加 Swagger 注释(@Summary、@Tags、@Param、@Success、@Router)。
  • 三方:仅 third_party/go-crate 一个 submodule;克隆后 init,更新时 update --remote。