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",
            ...
        }
    ]
}
相关推荐
Vespeng1 小时前
Go 项目实战:如何优雅的处理日志
go
DemonAvenger1 小时前
实战:从零仿NSQ打造高性能消息队列 - Go开发者的进阶之路
分布式·架构·go
我是前端小学生7 小时前
一文了解go语言中的select
go
forever231 天前
Gin 框架全面解析与实战指南
go·gin
Amazing_snack1 天前
设计模式--单例模式(Singleton)【Go】
单例模式·设计模式·go
yz1.2 天前
GolangTCP通信解决粘包问题
网络·go·tcp
zhuyasen2 天前
Go错误码规范化指南:构建优雅的HTTP & gRPC错误处理体系
后端·go
梦兮林夕2 天前
04 学会 Gin 中间件,这些操作你也能随心所欲!
go·gin
forever232 天前
go实现配置热加载小工具
go