Design of API framework for modular development based on Gin

1, Introduction to gin project

The gin framework is very simple for people who write go language. It is a bit similar to the flash framework in python. You need to find a third-party package for everything, and then create a directory structure according to your own experience. For people without project experience, it is really not as good as the same type of beego framework. It already has a clear directory structure. Sometimes we can say that gin is just a package, Not a frame. The degree of freedom of assembly is relatively flexible, which also highlights the importance of our developers' experience. How to better build a gin API project is a difficult task.

The following is my back-end experience, using the idea of mvc to build a basic gin API framework. For your reference, you feel better 👍

2, Dependent packages that need to be installed

  • gin framework package

    go get -u github.com/gin-gonic/gin
    
  • gorm database package

    go get -u gorm.io/gorm
    go get -u gorm.io/driver/mysql
    
  • Packet for data verification

    go get github.com/go-playground/validator
    
  • token authenticated package

    go get -u github.com/dgrijalva/jwt-go
    
  • Log management pack

    go get -u github.com/sirupsen/logrus
    go get -u github.com/lestrrat-go/file-rotatelogs
    go get -u github.com/rifflock/lfshook
    
  • Package of configuration file

    go get -u github.com/spf13/viper
    

3, Project profile

  • 1. In config / application Configuration parameters required to create the project in the YML file

    server:
      port: 9000
    # Database configuration
    datasource:
      driverName: mysql
      host: localhost
      port: "3306"
      database: gin_admin_api
      username: root
      password: 123456
      charset: utf8mb4
      loc: Asia/Shanghai
    
  • 2. In main Go defines an initialization configuration file

    // Initialize configuration
    func InitConfig() {
    	workDir, _ := os.Getwd()
    	viper.SetConfigName("application")
    	viper.SetConfigType("yml")
    	viper.AddConfigPath(path.Join(workDir, "config"))
    	// Or use full path
    	//viper.AddConfigPath(path.Join(workDir, "config/application.yml"))
    	err := viper.ReadInConfig()
    	if err != nil {
    		fmt.Print("Error getting configuration file")
    		panic(err)
    	}
    }
    
  • 3, call the initialization configuration file in the init function.

    func init() {
    	InitConfig()
    }
    
  • 4. Test the configuration file successfully

    func main() {
    	router := gin.Default()
    	router.GET("/", func(c *gin.Context) {
    		c.JSON(http.StatusOK, gin.H{
    			"code": 1,
    		})
    	})
    	port := viper.GetString("server.port")
    	fmt.Println("Current port", port)
    	if port != "" {
    		router.Run(":" + port)
    	} else {
    		router.Run()
    	}
    }
    
  • 5. Or it can be added to the common/config file separately

    package common
    
    import (
    	"fmt"
    	"github.com/spf13/viper"
    	"os"
    	"path"
    )
    
    // Initialize configuration
    func InitConfig() {
    	workDir, _ := os.Getwd()
    	viper.SetConfigName("application")
    	viper.SetConfigType("yml")
    	viper.AddConfigPath(path.Join(workDir, "config"))
    	// Or use full path
    	//viper.AddConfigPath(path.Join(workDir, "config/application.yml"))
    	err := viper.ReadInConfig()
    	if err != nil {
    		fmt.Print("Error getting configuration file")
    		panic(err)
    	}
    }
    
    func init() {
    	InitConfig()
    }
    

    Borrow in main Go, the init function will be executed before initialization

    import (
    	...
      // This means that it is not required when compiling, but it is required when running. Without this line, the parameters cannot be obtained in the following mian function
    	_ "gin_admin_api/common" // gin_ admin_ The API is in go Module gin configured in module_ admin_ API, which is generally consistent with the project name
    	...
    )
    
    func main() {
    	...
    	port := viper.GetString("server.port")
    	fmt.Println("Current port", port)
    	...
    }
    

4, Initialize gorm database connection tool

  • 1. Configure database connection under common/database

    package common
    
    import (
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    	"github.com/spf13/viper"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"net/url"
    	"os"
    	"time"
    )
    
    var DB *gorm.DB
    
    func init() {
    	fmt.Println("Database connection")
    	InitDB()
    }
    
    func InitDB() *gorm.DB {
    	// Get parameters from configuration file
    	host := viper.GetString("datasource.host")
    	port := viper.GetString("datasource.port")
    	database := viper.GetString("datasource.database")
    	username := viper.GetString("datasource.username")
    	password := viper.GetString("datasource.password")
    	charset := viper.GetString("datasource.charset")
    	loc := viper.GetString("datasource.loc")
    	// String splicing
    	sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=true&loc=%s",
    		username,
    		password,
    		host,
    		port,
    		database,
    		charset,
    		url.QueryEscape(loc),
    	)
    	fmt.Println("Database connection:", sqlStr)
    	// Configure log output
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second,   // Cache log time
    			LogLevel:                  logger.Silent, // log level
    			IgnoreRecordNotFoundError: true,          // Ignore ErrRecordNotFound error for logger
    			Colorful:                  false,         // Disable color
    		},
    	)
    	db, err := gorm.Open(mysql.Open(sqlStr), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		fmt.Println("Failed to open database", err)
    		panic("Failed to open database" + err.Error())
    	}
    	DB = db
    	return DB
    }
    
    // TODO document address: https://gorm.io/zh_CN/docs/
    
  • 2. In model / account Go data model

    package model
    
    import (
    	"gorm.io/gorm"
    )
    
    type Account struct {
    	gorm.Model
    	UserName string `gorm:"type:varchar(50);column(username);not null;unique;comment:account number"`
    	Password string `gorm:"type:varchar(200);not null;comment:Account password"`
    	Mobile   string `gorm:"varchar(11);not null;unique;comment:phone number"`
    }
    
  • 3. In main Data model and database connection tool created by test in go

    func init()  {
      // Automatically synchronize data model to data table
    	common.DB.AutoMigrate(&model.Account{})
    }
    
  • 4. Check the data table of the database. By default, an s will be added here to indicate the plural number. If you want to rename the table name, you can refer to the following code

    // In the entity class file of the data model
    
    // Custom table name
    func (Account) TableName() string {
    	return "account"
    }
    

5, Using routing packet to realize routing management in gin

  • 1. Create a route folder, which is responsible for collecting routes under all controllers

    package route
    
    import (
    	"gin_admin_api/controller/account"
    	"gin_admin_api/controller/login"
    	"gin_admin_api/controller/register"
    	"gin_admin_api/middleware"
    	"github.com/gin-gonic/gin"
    )
    
    func CollectRoute(router *gin.Engine) {
    	// When creating account routing packets, first ignore the existence of middleware
    	accountGroup := router.Group("/account", middleware.AuthMiddleWare())
    	account.AccountRouter(accountGroup)
    	// Logged in route
    	loginGroup := router.Group("/login")
    	login.LoginRouter(loginGroup)
     
    	registerGroup := router.Group("/register")
    	register.RegisterRouter(registerGroup)
    }
    
  • 2. For example, the route of login

    package login
    
    import (
    	"github.com/gin-gonic/gin"
    )
    
    func LoginRouter(router *gin.RouterGroup) {
    	router.POST("/", Login)
    }
    
  • 3. In main Use routing groups in go

    func main() {
    	router := gin.Default()
    	// Register routing group
    	route.CollectRoute(router)
      ...
    }
    

6, User registration using data verification

  • 1. Create a dto file under the controller, which is specially used to receive the data transmitted from the front end

    package dto
    
    import (
    	"fmt"
    	"gin_admin_api/model"
    	"github.com/go-playground/validator"
    	"unicode/utf8"
    )
    var valildate *validator.Validate
    
    func init() {
    	valildate = validator.New()
    	valildate.RegisterValidation("checkName", CheckNameFunc)
    }
    
    //Define the registered structure (the data structure to be sent by the front end)
    type RegisterDto struct {
    	UserName string `validate:"required,checkName" json:"username"`
    	Password string `validate:"required" json:"password"`
    }
    
    // User defined verifier verification user name
    func CheckNameFunc(f validator.FieldLevel) bool {
    	count := utf8.RuneCountInString(f.Field().String())
    	if count >= 2 && count <= 12 {
    		return true
    	} else {
    		return false
    	}
    }
    
    // Define the method of verifying data
    func ValidatorRegister(account RegisterDto) error {
    	err := valildate.Struct(account)
    	if err != nil {
    		// Output check error (validator.ValidationErrors) is an assertion
    		for _, e := range err.(validator.ValidationErrors)[:1] {
    			fmt.Println("Error field:", e.Field())
    			fmt.Println("Wrong value:", e.Value())
    			fmt.Println("FALSE tag:", e.Tag())
    		}
    		return err
    	} else {
    		return nil
    	}
    }
    
  • 2. In the controller, the data transmitted from the front end is inserted into the database

    // User registration account
    func Register(c *gin.Context) {
    	// 1. Obtain the data transmitted from the front end
    	var registerDto dto.RegisterDto
    	err := c.Bind(&registerDto)
    	if err != nil {
    		response.Fail(c, "Error parsing the data passed by the front end")
    		return
    	}
    	// 2. Verify the data transmitted from the front end
    	err = dto.ValidatorRegister(registerDto)
    	if err != nil {
    		response.Fail(c, "Data verification error")
    		return
    	}
    	// 3. Insert data into the database
    	newPassword, err := utils.GeneratePassword(registerDto.Password)
    	if err != nil {
    		response.Fail(c, "Password encryption error")
    		return
    	}
      // 4. Data structure assembled into data model
    	account := model.Account{
    		UserName: registerDto.UserName,
    		Password: newPassword,
    	}
    	tx := common.DB.Create(&account)
    	fmt.Println(tx.RowsAffected, tx.Error)
    	if tx.RowsAffected > 0 {
    		response.Success(c, nil)
    	} else {
    		response.Fail(c, "Insert data error")
    	}
    }
    
  • 3. For password encryption and decryption, please refer to the methods in utils

  • 4. Check whether the database is inserted successfully

7, About the use of Middleware

  • 1. Login middleware can refer to the article Link address
  • 2. Cross domain middleware is relatively fixed. You can directly Baidu or refer to my baidu data
  • 3. Refer to the documentation for log processing Link address

Keywords: Go

Added by alpinec on Fri, 18 Feb 2022 14:08:56 +0200