Go 项目实战:搭建高效的Gin Web目录结构

引言

在当今迅速迭代的软件开发领域,挑选合适的工具与框架对于项目能否顺利推进至关重要。Gin 框架,作为 Go 语言生态中备受青睐的 Web 开发框架,凭借其卓越的性能、简洁的设计以及丰富的功能特性,在众多选项中脱颖而出。本文旨在深入剖析如何在使用 Gin 框架的过程中,构建一个既高效又便于管理的项目架构,助力开发者打造既快速响应又易于维护的 Web 应用程序。

一、Gin 概述

引入官网的描述:Gin 是一个使用 Go 语言开发的 Web 框架。 它提供类似 Martini 的 API,但性能更佳,速度提升高达40倍。 如果你是性能和高效的追求者, 你会爱上 Gin。

对比 Beego 框架,Gin 框架采用了极简主义的方法,为追求简单和高性能,没有多余文件或目录,他甚至什么也没有,没有集成任何中间件,一个 main 文件即可启动一个 web 服务。

正因为如上所述,过分精简对于开发一个项目来说,前期的项目搭建工作就显得尤为重要。

二、项目结构设计

有过 Java 开发经验的伙伴应该了解,SpringBoot 遵循着 MVC 的设计理念,这一套设计理念一直沿用至今,他的优秀难以言喻,Gin 框架完全可以参照这个模式来做,如下是我个人设计的一套架构:

html 复制代码
├── /cmd  
│   └── main.go  
├── /config
│   └── config.go  
│   └── config.yaml
├── /docs
├── /internal
│   ├── /api
│   │   ├── v1
│   │   │   ├── /routes.go
│   ├── /app
│   │   ├── loader.go
│   │   ├── db.go
│   │   └── ...
│   ├── /controller
│   │   ├── user_controller.go
│   │   └── ...
│   ├── /middleware
│   │   ├── error.go
│   │   └── ...
│   ├── /models
│   │   ├── user_entity.go
│   │   └── ...  
│   ├── /repositories  
│   │   ├── user_repository.go
│   │   └── ...  
│   ├── /services  
│   │   ├── user_service.go
│   │   └── ...
│   └── /utils
├── /pkg
├── /scripts
├── /test
├── .env
├── go.mod
├── go.sum

三、目录职责

  • /cmd
    • 存放应用的入口文件。
    • main.go:是整个应用的入口,在这里启动应用。
  • /config
    • 存放应用的配置文件和配置加载逻辑。
    • config.go:包含配置加载和解析的逻辑。
    • config.yaml:应用的配置文件,通常包含数据库连接信息、服务器设置等。
  • /docs
    • 存放应用的文档,如API文档、用户手册等。
  • /internal
    • 存放应用的内部逻辑,这些代码不能被外部包所引入,可根据实际需求进而拆分目录。
    • api:包含应用中核心的业务路由等,即URL路径与控制器方法的映射。
    • app:包含应用的核心逻辑,如初始化、启动等。
    • controllers:包含控制器逻辑,处理请求并返回响应。
    • middleware:存放中间件代码,用于在请求处理流程中的特定阶段执行代码。
    • models:定义应用的数据模型,通常与数据库表结构对应。
    • repositories:实现数据访问逻辑,与数据库进行交互。
    • services:实现业务逻辑,调用repositories中的方法来处理业务需求。
    • utils:包含通用的工具函数,这些函数可以被多个包所共享。
  • /pkg
    • 存放第三方库,如第三方中间件、工具库等。
  • /scripts
    • 存放各种脚本,如项目部署脚本、测试脚本等。
  • /tests
    • 存放测试代码,包括单元测试、集成测试等。
    • 这里的目录结构可以根据需要自行组织,以支持不同类型的测试。

以上目录结构有助于清晰地分离应用的不同部分,使得代码更加模块化、易于理解和维护。同时,我也参照众多优秀开源项目的目录搭建思想,使其完美遵循了Go语言的最佳实践。

四、实践

目录搭建好后,开始填充代码

下边简单实现集成数据库,配置路由,启动服务

1、配置config

在 config.yaml 文件下配置端口和数据库连接,这里选择 xorm:

yaml 复制代码
# 基础配置
app:
  port: 8080
database:
  driver: mysql
  source: root:123456@tcp(127.0.0.1:3306)/xxx_table?charset=utf8mb4&parseTime=True&loc=Local

在 config.go 下解析配置

go 复制代码
package config

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

type Config struct {
    App      AppConfig      `yaml:"app" mapstructure:"app"`
    Database DatabaseConfig `yaml:"database" mapstructure:"database"`
}

type AppConfig struct {
    Port int `mapstructure:"port"`
}

type DatabaseConfig struct {
    Driver string `yaml:"driver" mapstructure:"driver"`
    Source string `yaml:"source" mapstructure:"source"`
}

var Conf *Config

// LoadConfig 加载配置文件
func LoadConfig() error {

    // 设置配置文件路径和名称
    viper.AddConfigPath("./config")
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")

    // 读取配置文件
    err = viper.ReadInConfig()
    if err != nil {
        return fmt.Errorf("读取配置文件失败: %v", err)
    }

    // 将配置文件内容解析到 Conf 变量中
    Conf = &Config{}
    err = viper.Unmarshal(Conf)
    if err != nil {
        return fmt.Errorf("解析配置文件失败: %v", err)
    }

    return nil
}

2、配置init

数据库及其他的初始化统一放置到 app 目录下,在这里新建 loader.go 来初始化 mysql,但是为了之后方便管理,我们另单独创建 db.go 文件:

如需要加载其他如 redis,那就新建 redis.go 文件

go 复制代码
package app

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/go-xorm/xorm"
    log "github.com/sirupsen/logrus"
    "yourProject/config"
)

var Engine *xorm.Engine

// InitializeMySQL 数据库初始化
func InitializeMySQL() error {
    var err error
    // 创建数据库引擎
    Engine, err = xorm.NewEngine(config.Conf.Database.Driver, config.Conf.Database.Source)
    if err != nil {
        log.Error("数据库初始化失败: %v", err)
        return err
    }

    // 测试数据库连接
    if err = Engine.Ping(); err != nil {
        log.Error("数据库连接失败: %v", err)
        return err
    }

    return nil
}

app.go 中调用 InitializeMySQL()

go 复制代码
package app

import (
    "fmt"
)

// InitializeAll 初始化所有模块
func InitializeAll() error {
    err := InitializeMySQL()
    if err != nil {
        return fmt.Errorf("MySQL初始化错误: %v", err)
    }

    return nil
}

3、配置model

在 models 下新建 user_entity.go,注意:这个需要和数据库对应

go 复制代码
package models

type User struct {
    Id          int64  `xorm:"pk autoincr 'id'"`
    UserID      int64  `xorm:"not null 'user_id'"`
    Password    string `xorm:"varchar(50) not null 'password'"`
    UserName    string `xorm:"varchar(30) 'user_name'"`
    Email       string `xorm:"varchar(50) 'email'"`
    PhoneNumber int64  `xorm:"'phone_number'"`
    Sex         string `xorm:"char(1) 'sex'"`
    Remark      string `xorm:"varchar(500) 'remark'"`
}

// TableName 方法用于返回表名
func (u User) TableName() string {
    return "user"
}

4、配置controller

在 controllers 下新建 user_controllers.go

go 复制代码
package controllers

import (
    "your_project/internal/services"
    "github.com/gin-gonic/gin"
    "net/http"
)

type UserController struct {
    UserService *services.UserService
}

func NewUserController(UserService *services.UserService) *UserController {
    return &UserController{UserService: UserService}
}

func (uc *UserController) GetUsers(c *gin.Context) {
    users, err := uc.UserService.GetUsers()
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
            return
        }
    c.JSON(http.StatusOK, gin.H{"users": users})
}

5、配置service

在 sevices 下新建 user_service.go

go 复制代码
package services

import (
    "your_project/internal/models"
    "your_project/internal/repositories"
    "github.com/go-xorm/xorm"
)

type UserService struct {
    userRepo *repositories.UserRepository
}

func NewUserService(engine *xorm.Engine) *UserService {
    return &UserService{userRepo: repositories.NewUserRepository(engine)}
}

func (us *UserService) GetUsers() ([]*models.User, error) {
    return us.userRepo.GetUsers()
}

6、配置repositorie

在 repositories 下新建 user_repo.go

go 复制代码
package repositories

import (
    "your_project/internal/models"
    "github.com/go-xorm/xorm"
)

type UserRepository struct {
    engine *xorm.Engine
}

func NewUserRepository(engine *xorm.Engine) *UserRepository {
    return &UserRepository{engine: engine}
}

// GetUsers 获取所有用户
func (r *UserRepository) GetUsers() ([]*models.User, error) {
    var users []*models.User
    err := r.engine.Table(models.User{}.TableName()).Find(&users)
    return users, err
}

7、配置api

routes.go 中设置路由,这里设置路由组,为方便日后迭代

go 复制代码
package v1

import (
    "github.com/gin-gonic/gin"
    "github.com/go-xorm/xorm"
    "your_project/internal/controllers"
    "your_project/internal/services"
)

func SetupRoutes(r *gin.Engine, engine *xorm.Engine) {
    // 定义用户路由组
    user := r.Group("/user")
    {
        // 创建 UserService 实例
        UserService := services.NewUserService(engine)
        // 创建 UserController 实例
        UserController := controllers.NewUserController(UserService)

        user.GET("/", UserController.GetUsers)
    }
}

8、配置main

go 复制代码
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    log "github.com/sirupsen/logrus"
    "your_project/config"
    "your_project/internal/api/v1"
    "your_project/internal/app"
)

func main() {
    // 加载配置文件
    err := config.LoadConfig()
    if err != nil {
        log.Error("配置文件加载错误: %v", err)
        return
    }

    // 初始化所有模块
    err = InitializeAll()
    if err != nil {
        log.Error("模块初始化错误: %v", err)
        return
    }

    r := gin.Default()
    v1.SetupRoutes(r, Engine)

    err = r.Run(fmt.Sprintf(":%d", config.Conf.App.Port))
    if err != nil {
        log.Error("服务启动错误: %v", err)
        return
    }
}

截至这里,基本的一个查询请求就已经构建好了

7、启动项目

cmd 目录下直接运行 main 函数,正常会输出如下信息:

shell 复制代码
Listening and serving HTTP on :8080

接着访问 http://localhost:8080/user 正常查询结果回显 json 如下:

json 复制代码
{
    "users": [
        {
            "Id": 1,
            "UserID": "000001",
            "Password": "123456",
            ...
        }
    ]
}
相关推荐
newerp15 小时前
Go net/http 标准库基础
后端·程序员·go
26519405112648016 小时前
01-整体架构与高可用
go
程序员爱钓鱼18 小时前
Go if 判断详解
前端·后端·go
名字还没想好☜1 天前
Go 的 unsafe.Pointer 实战:零拷贝 []byte↔string 转换与三条铁律
开发语言·后端·golang·go·unsafe
阿里云云原生1 天前
阿里云联合 Datadog,补齐 Go 可观测性最后短板
云原生·go
可观测性用观测云1 天前
零码改造!Go 语言应用上报观测云完整最佳实践
go
斐波那契数列1 天前
参考react 实现一个golang gui
go
程序员爱钓鱼2 天前
Go 运算符详解
后端·面试·go
angryshan2 天前
goLang配置断点流程
go·bug
名字还没想好☜3 天前
Go 的 sync.Cond 实战:用条件变量做等待/通知,比忙轮询省 CPU
开发语言·数据库·后端·golang·go