应用结构 #

一、项目结构概述 #

良好的项目结构是可维护、可扩展应用的基础。

1.1 结构设计原则 #

text
┌─────────────────────────────────────────────────────────┐
│                  项目结构设计原则                        │
├─────────────────────────────────────────────────────────┤
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐    │
│  │ 清晰分层│  │ 职责单一│  │ 易于测试│  │ 易于扩展│    │
│  └─────────┘  └─────────┘  └─────────┘  └─────────┘    │
└─────────────────────────────────────────────────────────┘

二、简单项目结构 #

2.1 目录结构 #

text
simple-app/
├── main.go
├── handlers/
│   └── user.go
├── models/
│   └── user.go
├── go.mod
└── go.sum

2.2 代码示例 #

main.go

go
package main

import (
    "simple-app/handlers"
    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    
    e.GET("/users", handlers.GetUsers)
    e.GET("/users/:id", handlers.GetUser)
    e.POST("/users", handlers.CreateUser)
    
    e.Start(":8080")
}

handlers/user.go

go
package handlers

import (
    "net/http"
    "simple-app/models"
    "github.com/labstack/echo/v4"
)

func GetUsers(c echo.Context) error {
    users := models.GetAllUsers()
    return c.JSON(http.StatusOK, users)
}

func GetUser(c echo.Context) error {
    id := c.Param("id")
    user := models.GetUserByID(id)
    return c.JSON(http.StatusOK, user)
}

func CreateUser(c echo.Context) error {
    u := new(models.User)
    if err := c.Bind(u); err != nil {
        return err
    }
    models.CreateUser(u)
    return c.JSON(http.StatusCreated, u)
}

models/user.go

go
package models

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var users = []User{
    {ID: 1, Name: "张三"},
    {ID: 2, Name: "李四"},
}

func GetAllUsers() []User {
    return users
}

func GetUserByID(id string) *User {
    for _, u := range users {
        if string(rune(u.ID)) == id {
            return &u
        }
    }
    return nil
}

func CreateUser(u *User) {
    u.ID = len(users) + 1
    users = append(users, *u)
}

三、中型项目结构 #

3.1 目录结构 #

text
medium-app/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handlers/
│   │   ├── handler.go
│   │   ├── user.go
│   │   └── auth.go
│   ├── models/
│   │   └── user.go
│   ├── services/
│   │   ├── user.go
│   │   └── auth.go
│   ├── repositories/
│   │   └── user.go
│   └── config/
│       └── config.go
├── pkg/
│   ├── response/
│   │   └── response.go
│   └── middleware/
│       └── auth.go
├── config/
│   └── config.yaml
├── go.mod
└── go.sum

3.2 分层架构 #

text
┌─────────────────────────────────────────────┐
│              HTTP Request                    │
└─────────────────┬───────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────┐
│           Handler Layer (处理层)             │
│         接收请求、参数验证、返回响应          │
└─────────────────┬───────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────┐
│          Service Layer (业务层)              │
│           业务逻辑、数据处理                 │
└─────────────────┬───────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────┐
│        Repository Layer (数据访问层)         │
│           数据库操作、缓存操作               │
└─────────────────┬───────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────┐
│              Database/Cache                  │
└─────────────────────────────────────────────┘

3.3 代码实现 #

cmd/server/main.go

go
package main

import (
    "medium-app/internal/config"
    "medium-app/internal/handlers"
    "medium-app/pkg/middleware"
    "github.com/labstack/echo/v4"
)

func main() {
    cfg := config.Load()
    
    e := echo.New()
    
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    
    h := handlers.NewHandler(cfg)
    
    api := e.Group("/api")
    {
        api.GET("/users", h.GetUsers)
        api.GET("/users/:id", h.GetUser)
        api.POST("/users", h.CreateUser)
    }
    
    e.Start(":" + cfg.Port)
}

internal/config/config.go

go
package config

import (
    "github.com/spf13/viper"
)

type Config struct {
    Port     string
    Database DatabaseConfig
    Redis    RedisConfig
}

type DatabaseConfig struct {
    Host     string
    Port     int
    User     string
    Password string
    Name     string
}

type RedisConfig struct {
    Host string
    Port int
    DB   int
}

func Load() *Config {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("./config")
    
    if err := viper.ReadInConfig(); err != nil {
        panic(err)
    }
    
    return &Config{
        Port: viper.GetString("app.port"),
        Database: DatabaseConfig{
            Host:     viper.GetString("database.host"),
            Port:     viper.GetInt("database.port"),
            User:     viper.GetString("database.user"),
            Password: viper.GetString("database.password"),
            Name:     viper.GetString("database.name"),
        },
        Redis: RedisConfig{
            Host: viper.GetString("redis.host"),
            Port: viper.GetInt("redis.port"),
            DB:   viper.GetInt("redis.db"),
        },
    }
}

internal/handlers/handler.go

go
package handlers

import (
    "medium-app/internal/config"
    "medium-app/internal/services"
)

type Handler struct {
    UserService *services.UserService
    AuthService *services.AuthService
}

func NewHandler(cfg *config.Config) *Handler {
    return &Handler{
        UserService: services.NewUserService(cfg),
        AuthService: services.NewAuthService(cfg),
    }
}

internal/handlers/user.go

go
package handlers

import (
    "net/http"
    "medium-app/internal/models"
    "medium-app/pkg/response"
    "github.com/labstack/echo/v4"
)

func (h *Handler) GetUsers(c echo.Context) error {
    users, err := h.UserService.GetAll()
    if err != nil {
        return response.Error(c, http.StatusInternalServerError, err.Error())
    }
    return response.Success(c, users)
}

func (h *Handler) GetUser(c echo.Context) error {
    id := c.Param("id")
    user, err := h.UserService.GetByID(id)
    if err != nil {
        return response.Error(c, http.StatusNotFound, "用户不存在")
    }
    return response.Success(c, user)
}

func (h *Handler) CreateUser(c echo.Context) error {
    u := new(models.User)
    if err := c.Bind(u); err != nil {
        return response.Error(c, http.StatusBadRequest, "参数错误")
    }
    
    if err := h.UserService.Create(u); err != nil {
        return response.Error(c, http.StatusInternalServerError, err.Error())
    }
    
    return response.Success(c, u)
}

internal/services/user.go

go
package services

import (
    "medium-app/internal/config"
    "medium-app/internal/models"
    "medium-app/internal/repositories"
)

type UserService struct {
    repo *repositories.UserRepository
}

func NewUserService(cfg *config.Config) *UserService {
    return &UserService{
        repo: repositories.NewUserRepository(cfg),
    }
}

func (s *UserService) GetAll() ([]models.User, error) {
    return s.repo.FindAll()
}

func (s *UserService) GetByID(id string) (*models.User, error) {
    return s.repo.FindByID(id)
}

func (s *UserService) Create(u *models.User) error {
    return s.repo.Save(u)
}

internal/repositories/user.go

go
package repositories

import (
    "medium-app/internal/config"
    "medium-app/internal/models"
)

type UserRepository struct {
    db *config.DatabaseConfig
}

func NewUserRepository(cfg *config.Config) *UserRepository {
    return &UserRepository{
        db: &cfg.Database,
    }
}

func (r *UserRepository) FindAll() ([]models.User, error) {
    return []models.User{
        {ID: 1, Name: "张三"},
        {ID: 2, Name: "李四"},
    }, nil
}

func (r *UserRepository) FindByID(id string) (*models.User, error) {
    return &models.User{ID: 1, Name: "张三"}, nil
}

func (r *UserRepository) Save(u *models.User) error {
    return nil
}

pkg/response/response.go

go
package response

import (
    "net/http"
    "github.com/labstack/echo/v4"
)

type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

func Success(c echo.Context, data interface{}) error {
    return c.JSON(http.StatusOK, Response{
        Code:    0,
        Message: "success",
        Data:    data,
    })
}

func Error(c echo.Context, code int, message string) error {
    return c.JSON(code, Response{
        Code:    code,
        Message: message,
    })
}

四、企业级项目结构 #

4.1 目录结构 #

text
enterprise-app/
├── api/
│   └── openapi.yaml
├── cmd/
│   ├── server/
│   │   └── main.go
│   └── cli/
│       └── main.go
├── internal/
│   ├── domain/
│   │   ├── user/
│   │   │   ├── entity.go
│   │   │   ├── repository.go
│   │   │   └── service.go
│   │   └── auth/
│   │       ├── entity.go
│   │       ├── repository.go
│   │       └── service.go
│   ├── infrastructure/
│   │   ├── database/
│   │   │   ├── mysql.go
│   │   │   └── redis.go
│   │   └── logger/
│   │       └── logger.go
│   ├── interfaces/
│   │   ├── http/
│   │   │   ├── handler/
│   │   │   │   ├── user.go
│   │   │   │   └── auth.go
│   │   │   ├── middleware/
│   │   │   │   ├── auth.go
│   │   │   │   └── logging.go
│   │   │   └── router/
│   │   │       └── router.go
│   │   └── grpc/
│   │       └── user.go
│   └── app/
│       └── container.go
├── pkg/
│   ├── errors/
│   │   └── errors.go
│   ├── validator/
│   │   └── validator.go
│   └── utils/
│       └── utils.go
├── config/
│   ├── config.yaml
│   └── config.prod.yaml
├── deployments/
│   ├── docker/
│   │   ├── Dockerfile
│   │   └── docker-compose.yaml
│   └── k8s/
│       ├── deployment.yaml
│       └── service.yaml
├── scripts/
│   ├── build.sh
│   └── deploy.sh
├── docs/
│   └── swagger.yaml
├── go.mod
└── go.sum

4.2 领域驱动设计 #

text
┌─────────────────────────────────────────────────────────┐
│                    Interfaces Layer                      │
│         HTTP Handler / gRPC Service / CLI               │
├─────────────────────────────────────────────────────────┤
│                    Application Layer                     │
│              Use Cases / Application Services            │
├─────────────────────────────────────────────────────────┤
│                      Domain Layer                        │
│          Entities / Value Objects / Domain Services      │
├─────────────────────────────────────────────────────────┤
│                  Infrastructure Layer                    │
│         Database / Cache / Message Queue / Logger        │
└─────────────────────────────────────────────────────────┘

五、路由组织 #

5.1 路由分组 #

go
func setupRoutes(e *echo.Echo, h *Handler) {
    api := e.Group("/api/v1")
    
    api.GET("/health", h.Health)
    
    users := api.Group("/users")
    {
        users.GET("", h.GetUsers)
        users.GET("/:id", h.GetUser)
        users.POST("", h.CreateUser)
        users.PUT("/:id", h.UpdateUser)
        users.DELETE("/:id", h.DeleteUser)
    }
    
    auth := api.Group("/auth")
    {
        auth.POST("/login", h.Login)
        auth.POST("/register", h.Register)
        auth.POST("/logout", h.Logout)
    }
    
    protected := api.Group("")
    protected.Use(middleware.JWT([]byte("secret")))
    {
        protected.GET("/profile", h.GetProfile)
        protected.PUT("/profile", h.UpdateProfile)
    }
}

5.2 路由文件分离 #

internal/interfaces/http/router/router.go

go
package router

import (
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func Setup(e *echo.Echo, handlers *Handlers) {
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.CORS())
    
    setupUserRoutes(e, handlers.User)
    setupAuthRoutes(e, handlers.Auth)
}

func setupUserRoutes(e *echo.Echo, h *UserHandler) {
    g := e.Group("/api/v1/users")
    g.GET("", h.List)
    g.GET("/:id", h.Get)
    g.POST("", h.Create)
    g.PUT("/:id", h.Update)
    g.DELETE("/:id", h.Delete)
}

func setupAuthRoutes(e *echo.Echo, h *AuthHandler) {
    g := e.Group("/api/v1/auth")
    g.POST("/login", h.Login)
    g.POST("/register", h.Register)
}

六、依赖注入 #

6.1 手动注入 #

go
func main() {
    db := database.New()
    redis := cache.New()
    
    userRepo := repositories.NewUserRepository(db)
    userSvc := services.NewUserService(userRepo, redis)
    userHandler := handlers.NewUserHandler(userSvc)
    
    e := echo.New()
    e.GET("/users", userHandler.List)
    e.Start(":8080")
}

6.2 使用Wire #

安装Wire:

bash
go install github.com/google/wire/cmd/wire@latest

wire.go

go
//go:build wireinject

package main

import (
    "github.com/google/wire"
    "github.com/labstack/echo/v4"
)

func InitializeApp() *echo.Echo {
    wire.Build(
        database.New,
        cache.New,
        repositories.NewUserRepository,
        services.NewUserService,
        handlers.NewUserHandler,
        NewEcho,
    )
    return nil
}

func NewEcho(h *handlers.UserHandler) *echo.Echo {
    e := echo.New()
    e.GET("/users", h.List)
    return e
}

生成代码:

bash
wire

七、配置管理 #

7.1 多环境配置 #

text
config/
├── config.yaml
├── config.dev.yaml
├── config.test.yaml
└── config.prod.yaml

7.2 配置加载 #

go
func LoadConfig() *Config {
    env := os.Getenv("APP_ENV")
    if env == "" {
        env = "dev"
    }
    
    viper.SetConfigName("config." + env)
    viper.SetConfigType("yaml")
    viper.AddConfigPath("./config")
    
    if err := viper.ReadInConfig(); err != nil {
        panic(err)
    }
    
    var cfg Config
    if err := viper.Unmarshal(&cfg); err != nil {
        panic(err)
    }
    
    return &cfg
}

八、最佳实践 #

8.1 目录命名规范 #

目录 用途
cmd 程序入口
internal 私有代码
pkg 公共代码
api API定义
config 配置文件
deployments 部署配置
docs 文档
scripts 脚本

8.2 文件命名规范 #

文件 用途
main.go 程序入口
handler.go HTTP处理
service.go 业务逻辑
repository.go 数据访问
model.go 数据模型
middleware.go 中间件
router.go 路由配置

8.3 代码组织建议 #

  1. 单一职责:每个文件只做一件事
  2. 依赖注入:通过参数传递依赖
  3. 接口抽象:面向接口编程
  4. 错误处理:统一错误处理
  5. 日志规范:统一日志格式

九、总结 #

项目结构要点:

结构 适用场景
简单结构 小项目、学习项目
中型结构 中等项目、团队协作
企业结构 大型项目、微服务

关键原则:

  1. 分层清晰:Handler → Service → Repository
  2. 职责单一:每个模块只负责一件事
  3. 依赖注入:便于测试和替换
  4. 配置管理:支持多环境

准备好学习路由系统了吗?让我们进入下一章!

最后更新:2026-03-28