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", ... } ] }