Go Web learning notes (Gin and Gorm)

With the recommendation of my senior, I will use go to develop the back-end! I've gone through the basic grammar of go before. Now I'm learning the Gin and Gorm framework. I'd like to make a record and hope it will be helpful to you. Of course, because I'm a novice to go, I have something wrong to write. Thank you!

By the way, here's the basic grammar of Go Chinese official documents

Note: most of this article is based on https://www.topgoer.com/gin%E6%A1%86%E6%9E%B6 The learning notes written on this website, and others are the materials and videos found on the Internet. Thank you for the opinions and codes provided by the boss!!

Gin framework

Write hello world with go native https package

package main

import (
	"fmt"
	"net/http" //Reference http package
)

func sayHello(w http.ResponseWriter, r *http.Request)  {
	_, _ = fmt.Fprintf(w, "<h1>Hello Golang!</h1>")
	// b, _ := ioutil.ReadFile("./hello.txt") //ioutil this function can read text files
	// _, _ = fmt.Fprintf(w, string(b))
}

func main() {
	http.HandleFunc("/hello", sayHello) //visit http://localhost:8080/hello This url
	err := http.ListenAndServe(":8080",nil) //Listen to port 8080. You don't need to write in the url (because the default is 8080)
	if err != nil{
		fmt.Printf("ERROR:%v\n", err)
		return
	}
}

Notes: Download Gin framework command line input: go get - u GitHub com/Gin-gonic/Gin


GET request

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default() // Return to the default routing engine

	//When the specified user accesses / hello with a GET request, the sayhello function will be executed
	r.GET("/hello", func(c *gin.Context) { //It must be * gin Parameters of type context
		c.JSON(200,gin.H{
			"message":"Hello golang!",
		})
	})

	//Start service
	r.Run(":9090") //Change the default port number, and be careful to add: "!!!
	//r.Run()
}

Notes: Restful style requests: GET: GET, POST: create, DELETE: DELETE, PUT: update


POSTMan installation

There are many tutorials on the Internet. I won't mention more here. Let's mention some important points:
1. First of all, the software needs a ladder, including downloading and login registration
2. It is recommended to download the software version
3. After downloading, create a new work space and start the operation

Note: because the template framework needs to read an html file, it does not separate the front and back ends. Personally, I think it is relatively backward. I won't learn it here


How to obtain parameters and files

API acquisition

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
)

func main() {
	r := gin.Default()
	r.GET("/user/:name/*action", func(c *gin.Context) { //Crawl the url through anonymous functions
		name := c.Param("name") //Get the name in the url
		action := c.Param("action")
		//Intercept/
		action = strings.Trim(action, "/") //Get the last parameter in the url
		c.String(http.StatusOK, name+" is "+action)
	})
	//The default is to listen to port 8080
	r.Run(":8000")
}


Get parameters from URL (get method)
Use Query() to get parameters
Use DefaultQuery() to set the default parameters

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/user", func(c *gin.Context) {
		//Specify default values
		//http://localhost:8080/user Will print out the default value
		name := c.DefaultQuery("name", "Little dinosaur")
		c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
	})
	r.Run()
}

When name does not pass in the get parameter, it returns the default small dinosaur:


Of course, parameters can also be passed in:

Get parameters from the form (generally in Post mode)

The form parameters can be obtained through the PostForm() method, which resolves the parameters in x-www-form-urlencoded or from data format by default
(there is also no front and rear end separation here, which is not recommended)
html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8080/form" method="post" action="application/x-www-form-urlencoded">
        user name:<input type="text" name="username" placeholder="Please enter your user name">  <br>
        dense&nbsp;&nbsp;&nbsp;Code:<input type="password" name="userpassword" placeholder="Please enter your password">  <br>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

go stage

package main

//
import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.POST("/form", func(c *gin.Context) {
        types := c.DefaultPostForm("type", "post")
        username := c.PostForm("username")
        password := c.PostForm("userpassword")
        c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))
    })
    r.Run()
}

Upload files (single or multiple)
html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
    Upload file:<input type="file" name="file" >
    <input type="submit" value="Submit">
</form>
</body>
</html>

Use the FormFile() function to get the uploaded file
SaveUploadedFile is used to save

go file:

package main

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

func main() {
	r := gin.Default()
	// Limit the upload size of the form to 8MB, and the default is 32MB
	r.MaxMultipartMemory = 8 << 20
	r.POST("/upload", func(c *gin.Context) {
		file, err := c.FormFile("file")
		if err != nil {
			c.String(500, "Error uploading picture")
		}
		//c.JSON(200, gin.H{"message": file.Header.Context})
		c.SaveUploadedFile(file, file.Filename)
		c.String(http.StatusOK, file.Filename)
	})
	r.Run()
}

gin seems to have no function to limit the file size for the time being, so it's not very difficult for us to write one ourselves.
One thing to pay attention to is to change the original c.FormFile into c.request FormFile.

package main

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
		
	//Upload a single file
	r.POST("/upload", func(c *gin.Context) {
		_, headers, err := c.Request.FormFile("file")
		if err != nil {
			log.Printf("Error when try to get file: %v", err)
		}
		//headers.Size gets the file size
		if headers.Size > 1024*1024*2 { //Limit the size of 2MB
			c.String(200,"The file is too large")
			return
		//Get file type
		}else if headers.Header.Get("Content-Type") != "image/png" {
			c.String(200,"Upload only png picture")
			return
		}else{
			//Go is one of the methods to return json data; Use map (another way is to use structure)
			my_json := map[string]interface{}{ 
				"1":"success",
				"2":true,
			}
			c.JSON(200,my_json)
		}
		//headers. Header. Get ("content type") gets the type of uploaded file

		c.SaveUploadedFile(headers, "./video/"+headers.Filename)
		c.String(http.StatusOK, headers.Filename)
	})
	
	//Upload multiple files
	//r.POST("/upload", func(c *gin.Context) {
	//	form, err := c.MultipartForm()
	//	if err != nil {
	//		c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
	//	}
	//	//Get all pictures
	//	files := form.File["files"]
	//	//Traverse all pictures
	//	for _, file := range files {
	//		//Save one by one
	//		if err := c.SaveUploadedFile(file, file.Filename); err != nil {
	//			c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
	//			return
	//		}
	//	}
	//	c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
	//})
	r.Run()
}

Routing and routing groups

Routing: a method to request data through a path
Different request methods and background processing in routing

r.any() can handle any request
r.NoRoute() can customize 404 pages
Example:

package main

import (
	"github.com/gin-gonic/gin"
)
func main() {
	// Two middleware loggers () and recovery () are used by default
	r := gin.Default()
	r.NoRoute(func(c *gin.Context) {
		c.JSON(404, gin.H{"msg": "12138"})
	})
	r.Run()
}

Routing group

package main

import (
   "github.com/gin-gonic/gin"
   "fmt"
)

// helloWorld of gin

func main() {
   // 1. Create a route
   // Two middleware loggers () and recovery () are used by default
   r := gin.Default()
   // Routing group 1, processing GET requests
   v1 := r.Group("/v1")
   // {} is a writing standard
   {
      v1.GET("/login", login)
      v1.GET("submit", submit)
      
      //Routing groups can be nested
      //xx := v1.shopGroup("xx")
      //xx.GET("/oo",my_func)
      
   }
   v2 := r.Group("/v2")
   {
      v2.POST("/login", login)
      v2.POST("/submit", submit)
   }
   r.Run(":8000")
}

func login(c *gin.Context) {
   name := c.DefaultQuery("name", "jack")
   c.String(200, fmt.Sprintf("hello %s\n", name))
}

func submit(c *gin.Context) {
   name := c.DefaultQuery("name", "lily")
   c.String(200, fmt.Sprintf("hello %s\n", name))
}

design sketch:


Note: if you want to import functions in different folders, the first letter of the exported function needs to be capitalized

Parsing and binding of JSON data:

Notes: gin h{...} And map[string]interface {} {...} The effect is the same

package main

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

// Defines the structure of the received data
type Login struct {
	// binding:"required" modified field. If the received value is null, an error will be reported. It is a required field
	User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
	Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
	// 1. Create a route
	// Two middleware loggers () and recovery () are used by default
	r := gin.Default()
	// JSON binding
	r.POST("loginJSON", func(c *gin.Context) {
		// Declare received variables
		var json Login //Declare json as a struct of type Login
		// Automatically parse the data in the body of the request into the structure in json format
		if err := c.ShouldBindJSON(&json); err != nil {
		//c.ShouldBindUri(...) Is used to parse the parameters in the url
		//c.Bind(...) Is used to parse the parameters in the form
			// Return error message
			// gin.H encapsulates the tool for generating json data
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		// Determine whether the user name and password are correct
		if json.User != "root" || json.Pssword != "admin" {
			c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": "200"})
	})
	r.Run(":8000")
}

Note: the above structure definition uses the structure tag. Due to the limitation of go, the name in the structure can only start with uppercase. Therefore, we can use tag to customize the different parameter names passed in by the structure when requested by different methods
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"
For example, this sentence should be username when requesting with form
binding:"required" indicates that it is a necessary parameter. If the received value is null, an error will be reported. It is a required field.

redirect

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/index", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
    })
    r.Run()
}

Asynchronous and synchronous execution

The concept of go process is used here. If you don't know, you can go to Kangkang document

package main

import (
    "log"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    // 1. Create a route
    // Two middleware loggers () and recovery () are used by default
    r := gin.Default()
    // 1. Asynchronous
    r.GET("/long_async", func(c *gin.Context) {
        // We need a copy
        copyContext := c.Copy()
        // Asynchronous processing
        go func() {
            time.Sleep(3 * time.Second)
            log.Println("Asynchronous execution:" + copyContext.Request.URL.Path)
        }()
    })
    // 2. Synchronization
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(3 * time.Second)
        log.Println("Synchronous execution:" + c.Request.URL.Path)
    })

    r.Run(":8000")
}

effect:


middleware

Local Middleware
Functions used for processing between browsers and servers are generally functions that all websites need to use, such as login authentication, permission verification, data paging, logging, time-consuming statistics, etc.
The schematic diagram is similar to the following figure:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"time"
)

func hello(c *gin.Context){
	c.JSON(200,gin.H{
		"msg":"index",
	})
}

func m1(c *gin.Context){
	fmt.Println("Enter verification...")
	start := time.Now()
	c.Next() // Call the subsequent processing function (Hello)!!! important
	//c.Abort() / / prevent calling subsequent processing functions
	cost := time.Since(start)
	fmt.Printf("cost:%v\n", cost)
}

func main() {
	r := gin.Default()

	r.GET("/index", m1, hello)

	r.Run()
}

m1 above is a simple middleware function,
It is executed before the hello function, which can determine the logic of the hello function.
design sketch:

Global Middleware

Use r.use(...)

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"time"
)

func hello(c *gin.Context){
	c.JSON(200,gin.H{
		"msg":"index",
	})
}

func m1(c *gin.Context){
	fmt.Println("Enter verification...")
	start := time.Now()
	c.Next() // Call the subsequent processing function (Hello)!!! important
	//c.Abort() / / prevent calling subsequent processing functions
	cost := time.Since(start)
	fmt.Printf("cost:%v\n", cost)
}

func main() {
	r := gin.Default()
	//As long as the request is made, it must pass m1, which is a middleware (actually a function)
	r.Use(m1)

	r.GET("/index", hello)
	r.Run()
}

Easy to use middleware recommendation
website

Session control: (cookie s and sessions)

Search, set and delete cookie s:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func main() {
	// 1. Create a route
	// Two middleware loggers () and recovery () are used by default
	r := gin.Default()
	// The server should give the client a cookie
	r.GET("cookie", func(c *gin.Context) {
		// Get whether the client carries cookie s
		cookie, err := c.Cookie("key_cookie")
		if err != nil {
			cookie = "NotSet"
			// Set cookie s for clients
			//  maxAge int, in seconds
			// Path, the directory where the cookie is located
			// domain string, domain name
			//   Is secure smart accessed via https
			// Does httpOnly bool allow others to obtain their own cookie s through js
			c.SetCookie("key_cookie", "value_cookie", 60, "/",
				"localhost", false, true)
				
		//If you delete the cookie, you can directly set the duration to 0 or empty
		//c.SetCookie("key_cookie", "", 60, "/","localhost", false, true)
		//c.SetCookie("key_cookie", "value_cookie", 0, "/","localhost", false, true)
		}
		fmt.Printf("cookie The values are: %s\n", cookie)
	})
	r.Run(":8000")
}

Request: http://localhost:8000/cookie View cookies after

Notes: the disadvantages of cookies: 1 Unsafe, plaintext, 2 Increase bandwidth consumption, 3 Can be disabled, 4 Cookie s have an upper limit

Setting, finding and deleting of session (the value is set to nil)

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

func main() {
	// Initialize a cookie storage object
	// Something very secret should be your own key, as long as it is not known by others
	var store = cookie.NewStore([]byte("something-very-secret"))
	r := gin.Default()
	//Using middleware, store is the storage engine created before and can be replaced by other engines
	//mysession is the name that will be stored in the cookie on the browser. The server uses this name to find the corresponding session
	r.Use(sessions.Sessions("mysession", store))
	r.GET("/save", func(c *gin.Context) {
		session := sessions.Default(c)
		v := session.Get("count")
		var count int
		if v == nil {
			count = 0
		}else {
			count = v.(int)
			count++
		}
		session.Set("count",count)
		session.Save()
		c.JSON(200, gin.H{"now the count":count})
	})
	r.GET("/get", func(c *gin.Context) {
		session := sessions.Default(c)
		v := session.Get("count")
		c.JSON(200, gin.H{"the count":v})
	})
	err := r.Run()
	if err != nil {
		return
	}
}

design sketch:


There is no session package in gin, so we need to import other session packages ourselves

Parameter verification

Structural experience certificate:
Write the requirements after the object that defines the structure

package main

import (
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

//Person ..
type Person struct {
    //Cannot be empty and greater than 10
    Age      int       `form:"age" binding:"required,gt=10"`
    Name     string    `form:"name" binding:"required"`
    Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

func main() {
    r := gin.Default()
    r.GET("/5lmh", func(c *gin.Context) {
        var person Person
        if err := c.ShouldBind(&person); err != nil {
            c.String(500, fmt.Sprint(err))
            return
        }
        c.String(200, fmt.Sprintf("%#v", person))
    })
    r.Run()
}

Custom function validation

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

type Users struct {
	Name   string `form:"name" json:"name" validate:"required,CustomValidationErrors"`//Include custom functions
	Age   uint8 `form:"age" json:"age" validate:"required,gt=18"`
	Passwd   string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
	Code   string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {
	// Test incoming data
	users := &Users{
		Name:      "admin",
		Age:        121,
		Passwd:       "126783",
		Code:            "123456",
	}
	validate := validator.New()
	//Register custom functions
	_=validate.RegisterValidation("CustomValidationErrors", CustomValidationErrors)
	err := validate.Struct(users)
	if err != nil {
		for _, err := range err.(validator.ValidationErrors) {
			fmt.Println(err)//Key: 'Users.Name' Error:Field validation for 'Name' failed on the 'CustomValidationErrors' tag
			return
		}
	}
	return
}

func CustomValidationErrors(fl validator.FieldLevel) bool {
	return fl.Field().String() != "admin"
}

CustomValidationErrors is a self-defined function that can customize filter parameters

Note: be sure to see that the tag behind the structure adds validate instead of binding. This is caused by the problem of the validator version. This problem has been bothering me for a long time. Be sure to see that the validator version is v10, and v9 and v8 may have problems

Here's another big man's summary of how to use this package: website

Other common functions

Log

package main

import (
	"io"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	gin.DisableConsoleColor()

	// Logging to a file.
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)

	// If you need to write the log to both the file and the console, use the following code.
	// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	r.Run()
}

design sketch:

Verification Code

///Updating ing



Gorm framework

Notes: English go object relational mapping

Gorm installation

Command line input:

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

basic operation

Take mysql as an example

package main

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type Gorm_test struct {
	ID     int    //Note: if ID is defined, it will be defaulted as primary key
	Name   string
	Gender string
	Hobby  string
}

func main() {
	mysql_conn := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", "root", "1234", "127.0.0.1", 3306, "mydatabase")

	//Assigned to global variable Db No: = is assigned
	db, err := gorm.Open("mysql", mysql_conn)
	if err != nil {
		panic(err)
	}
	fmt.Println("Database initialization succeeded......")

	//Create a table and associate the structure with the data table
	db.AutoMigrate(&Gorm_test{})

	//Create data row
	u1 := Gorm_test{1, "Little dinosaur", "male", "piano"}
	db.Create(&u1) //I suggest adding&
	fmt.Println("Data added successfully")

	//query
	var u Gorm_test
	//Use the First function to query the First
	db.First(&u) //Query the first one and assign it to u. note that if you want to modify the structure, you must pass the pointer!
	fmt.Printf("u:%#v\n", u)

	//Update data
	db.Model(&u).Update("hobby", "Percussion")
 
	fmt.Println("Successfully changed data")

	//delete
	db.Delete(&u)
	fmt.Println("Data deleted successfully")


}

design sketch:

Note: gorm will create a database when we associate the structure. Even if the name of our structure is lowercase and followed by s

Query plus

package main

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type Gorm_test struct {
	ID     int    //Note: if ID is defined, it will be defaulted as primary key
	Name   string
	Gender string
	Hobby  string
}

func main() {
	mysql_conn := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", "root", "1234", "127.0.0.1", 3306, "mydatabase")

	//Assigned to global variable Db No: = is assigned
	db, err := gorm.Open("mysql", mysql_conn)
	if err != nil {
		panic(err)
	}
	fmt.Println("Database initialization succeeded......")

	//Create a table and associate the structure with the data table
	db.AutoMigrate(&Gorm_test{})

	var test Gorm_test
	db.First(&test, 4) //According to the primary key (currently ID), if it is empty, it will return: {0} only the first sign condition can be found
	fmt.Println(test)

	var test1 []Gorm_test
	db.Where("name = ?", "zlz").Find(&test1) //Query all matching records and save them in test1 array. Haishu must add &!!!
	fmt.Println(test1)
}

Note: when no data can be queried, you can specify the field name behind the structure, similar to: ID int `gorm:"column:ID"

Other Go knowledge

Real time loading tool Air

Let me quote the article of the boss directly here: website

Go package management tool Go Mod

See this for specific operation: website

Points to note:
You need main under the home folder go
Each time you create a new project, you can enter from the command line:
go mod init your folder name
go mod tidy
Then I can automatically download the package for you

Configure Go environment on cloud server liunx pagoda panel

Share big guy's website: website

Go's project structure and guide package

Generally, we use Go mod to manage packages

A project usually has only one go mod

For example, if my project is called sisipai, first create a large folder, and then create two small folders called article and quku, as well as a main Go as the main document of the project

There is also a main in article and quku respectively Go file
The first line inside is

//Be careful to capitalize
package Article //This bag indicating Article
//and
package Quku //Indicates that it's Quku's bag

Note: in article and quku, you cannot use idea or go mod,go.sum (a project usually has only one go.mod)

Then switch back to the main folder of Sipai and write

package main
//Then you can import
import (
	"sisipai/Article"
	"sisipai/Qupu"
)

If an error is reported, don't panic. Enter the following in the sisipai folder command line:

go mod init sispai
go mod tidy

You can find your bag by yourself!
Run successfully




The Go project runs on the server side

If the server is Linux and you want to put files directly on it, you can compile the files that Linux can run locally and then put them directly on the server.
Method: enter the following on the command line:

set GOARCH=amd64
set GOOS=linux
go build .

Then it will generate a file with the same name as your project without suffix, which is even a file that can be executed by linux.
Drag it to your server and give it permission to run:

chmod +x [Your project name]

You can add ". /" to run!

Port problem
Many novice Xiaobai (such as me) didn't know how to make their go file access it after running on the server at the beginning. Today, let's solve this problem.

First of all, your port is like this: (if you want to run on port 8080)

_ = r.Run("0.0.0.0:8080")

Then don't forget to turn on the firewall and release port 8080
Just go on! You can try curl 127.0.0.1:8080 port test, or you can try through the public IP access of your server.

Go language setting cross domain access

website
Cross domain is realized by using cors middleware to change the header. Generally, https should be added to the domain name

Wait ing for perfection

Keywords: Go Front-end

Added by andrewcb on Sat, 29 Jan 2022 07:45:42 +0200