分层架构 + 依赖注入(Uber FX),基于 Gin 的 RESTful API 服务。
| 类别 | 技术 | 说明 |
|---|
| 语言 | Go | 最新稳定版 |
| Web | Gin | 最新 |
| ORM | GORM | 最新 |
| DI | Uber 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
- 单一 Submodule:
third_party/go-crate,模块名 liuxiaobo.pro/go-crate,内含 provide/ 与 utils/。 - 主项目引用:
go.mod 中 replace liuxiaobo.pro/go-crate => ./third_party/go-crate。 - 导入:
liuxiaobo.pro/go-crate/provide/gin、liuxiaobo.pro/go-crate/provide/gorm、liuxiaobo.pro/go-crate/utils/response 等。 - 初始化:
git submodule update --init --recursive;更新:git submodule update --remote。
HTTP → Router → Handler → Service → Repository → DB/Redis
- Handler:参数绑定/校验、调 Service、统一响应;继承基础
handler,用 bindError/serviceError/success。 - Service:业务逻辑、调 Repository、事务;定义接口,方法首参
context.Context,业务错误用 *response.Response。 - Repository:GORM Gen / Redis,无业务逻辑。
- Router:路由、中间件、分组(noauth/auth)。
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)
}
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
}
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
},
})
}
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
},
})
}
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("未指定配置文件路径")
}
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
},
})
}
- FX:
fx.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))。 - 生命周期:
registerLifecycle 里 lc.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>
go run cmd/mini/mini.go -f config/dev.yaml
| 命令 | 说明 |
|---|
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 tidy | go mod tidy |
make init-submodule | 初始化 submodule |
make update-submodule | 更新 submodule |
{
"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"]
}
]
}
- 业务代码仅在
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。