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(®isterDto) 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