Hello everyone, I am fry fish, this is the address of this project: github.com/eddycjy/go-… If you have any questions, please feel free to communicate with us.

thinking

First of all, before starting an initial project, everyone should think about it

  • The text configuration of the program is written in the code, okay?

  • Is the API error code hardcoded in the program appropriate?

  • Everyone goes to Open db handle, there is no unified management, ok?

  • Get public parameters like paging, everybody write their own logic, okay?

Obviously, in more formal projects, the answer to these questions is no. In order to solve these problems, we choose a library for reading and writing configuration files. Viper is popular at present.

But this series uses go-INI/INI, its Chinese document. You have to read the documentation briefly, and then finish the rest.

In this paper, the target

  • Write a simple API error code package.
  • Complete a Demo example.
  • Explain the knowledge involved in Demo.

Introduce and initialize the project

Initialize the project directory

After initializing a Go-gin-example project in the previous section, we need to add the following directory structure:

Go - gin - example / ├ ─ ─ the conf ├ ─ ─ middleware ├ ─ ─ models ├ ─ ─ PKG ├ ─ ─ routers └ ─ ─ the runtimeCopy the code
  • Conf: Stores configuration files
  • Middleware: Application middleware
  • Models: Applies the database model
  • PKG: third-party package
  • Routers process routing logic
  • Runtime: indicates the running data of an application

Add Go Modules Replace

Open the go.mod file and add the replace configuration item as follows:

Module github.com/EDDYCJY/go-gin-example go 1.13 require (...) replace ( github.com/EDDYCJY/go-gin-example/pkg/setting => ~/go-application/go-gin-example/pkg/setting github.com/EDDYCJY/go-gin-example/conf => ~/go-application/go-gin-example/pkg/conf github.com/EDDYCJY/go-gin-example/middleware => ~/go-application/go-gin-example/middleware github.com/EDDYCJY/go-gin-example/models => ~/go-application/go-gin-example/models github.com/EDDYCJY/go-gin-example/routers => ~/go-application/go-gin-example/routers )Copy the code

You may not understand why we need to add the replace configuration item. First of all, you need to see that we are using the complete external module reference path (github.com/EDDYCJY/go-gin-example/xxx), and this module has not been pushed to the remote, so it cannot be downloaded. Therefore, it needs to be specified with replace to read the local module path, which can solve the local module read problem.

Note: You will need to add replace (I won’t remind you) to the go.mod file every time you add a local app directory. If you miss it, you will get an error when compiling and the module will not be found.

Initial project database

Create a new blog database with the encoding utF8_general_CI. Under the blog database, create the following table

1. Label table

CREATE TABLE `blog_tag` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT ' ' COMMENT 'Label name',
  `created_on` int(10) unsigned DEFAULT '0' COMMENT 'Creation time',
  `created_by` varchar(100) DEFAULT ' ' COMMENT 'Founder',
  `modified_on` int(10) unsigned DEFAULT '0' COMMENT 'Modification time',
  `modified_by` varchar(100) DEFAULT ' ' COMMENT 'Modifier',
  `deleted_on` int(10) unsigned DEFAULT '0',
  `state` tinyint(3) unsigned DEFAULT '1' COMMENT 'State 0 is disabled, state 1 is enabled',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Article Tag Management';
Copy the code

2. Article table

CREATE TABLE `blog_article` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `tag_id` int(10) unsigned DEFAULT '0' COMMENT 'tag ID',
  `title` varchar(100) DEFAULT ' ' COMMENT 'Article Title',
  `desc` varchar(255) DEFAULT ' ' COMMENT 'brief',
  `content` text,
  `created_on` int(11) DEFAULT NULL,
  `created_by` varchar(100) DEFAULT ' ' COMMENT 'Founder',
  `modified_on` int(10) unsigned DEFAULT '0' COMMENT 'Modification time',
  `modified_by` varchar(255) DEFAULT ' ' COMMENT 'Modifier',
  `deleted_on` int(10) unsigned DEFAULT '0',
  `state` tinyint(3) unsigned DEFAULT '1' COMMENT 'State 0 is disabled and state 1 is enabled',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Article Management';
Copy the code

3. Certification form

CREATE TABLE `blog_auth` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT ' ' COMMENT 'account',
  `password` varchar(50) DEFAULT ' ' COMMENT 'password',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `blog`.`blog_auth` (`id`, `username`, `password`) VALUES (null, 'test'.'test123456');

Copy the code

Write the project configuration package

In the go-gin-example application directory, pull the go-ini/ INI dependency package as follows:

$go get -u github.com/go-ini/ini go: Finding github.com/go-ini/ini v1.48.0 go: Complete downloading github.com/go-ini/ini v1.48.0 GO: fully github.com/go-ini/ini v1.48.0Copy the code

Next we need to write the basic application configuration file, create the app.ini file under the conf directory of go-gin-example and write:

#debug or release
RUN_MODE = debug

[app]
PAGE_SIZE = 10
JWT_SECRET = 23347The $040412[server] HTTP_PORT = 8000 READ_TIMEOUT = 60 WRITE_TIMEOUT = 60 [database] TYPE = mysql USER = database account PASSWORD = database PASSWORD# 127.0.0.1:3306HOST = database IP address: database port number NAME = blog TABLE_PREFIX = blog_Copy the code

Create the setting module to call the configuration, create the setting directory under the PKG directory of go-gin-example (pay attention to the new replace configuration), create the setting. Go file, and write the contents:

package setting

import (
	"log"
	"time"

	"github.com/go-ini/ini"
)

var (
	Cfg*ini.File RunMode string HTTPPort int ReadTimeout time.Duration WriteTimeout time.Duration PageSize int JwtSecret string  ) funcinit() {
	var err error
	Cfg, err = ini.Load("conf/app.ini")
	iferr ! = nil { log.Fatalf("Fail to parse 'conf/app.ini': %v", err)
	}

	LoadBase()
	LoadServer()
	LoadApp()
}

func LoadBase() {
	RunMode = Cfg.Section("").Key("RUN_MODE").MustString("debug")
}

func LoadServer() {
	sec, err := Cfg.GetSection("server")
	iferr ! = nil { log.Fatalf("Fail to get section 'server': %v", err)
	}

	HTTPPort = sec.Key("HTTP_PORT").MustInt(8000)
	ReadTimeout = time.Duration(sec.Key("READ_TIMEOUT").MustInt(60)) * time.Second
	WriteTimeout =  time.Duration(sec.Key("WRITE_TIMEOUT").MustInt(60)) * time.Second	
}

func LoadApp() {
	sec, err := Cfg.GetSection("app")
	iferr ! = nil { log.Fatalf("Fail to get section 'app': %v", err)
	}

	JwtSecret = sec.Key("JWT_SECRET").MustString(! "" @) * #)! @U#@*! @! ")
	PageSize = sec.Key("PAGE_SIZE").MustInt(10)
}
Copy the code

Current directory structure:

Go - gin - example ├ ─ ─ the conf │ └ ─ ─ app. Ini ├ ─ ─. Mod ├ ─ ─. Sum ├ ─ ─ middleware ├ ─ ─ models ├ ─ ─ PKG │ └ ─ ─ setting. Go ├ ─ ─ Routers └ ─ ─ the runtimeCopy the code

Write API error code packages

Create e module of error code, create e directory under the PKG directory of go-gin-example (note the new replace configuration), create code.go and msg.go files, and write the contents:

1, the code. Go:

package e

const (
	SUCCESS = 200
	ERROR = 500
	INVALID_PARAMS = 400

	ERROR_EXIST_TAG = 10001
	ERROR_NOT_EXIST_TAG = 10002
	ERROR_NOT_EXIST_ARTICLE = 10003

	ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
	ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002
	ERROR_AUTH_TOKEN = 20003
	ERROR_AUTH = 20004
)
Copy the code

2, MSG. Go:

package e

var MsgFlags = map[int]string {
	SUCCESS : "ok",
	ERROR : "fail",
	INVALID_PARAMS : "Request parameter error",
	ERROR_EXIST_TAG : "The label name already exists",
	ERROR_NOT_EXIST_TAG : "This tag does not exist",
	ERROR_NOT_EXIST_ARTICLE : "This article does not exist",
	ERROR_AUTH_CHECK_TOKEN_FAIL : "Token authentication failed",
	ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token has timed out",
	ERROR_AUTH_TOKEN : "Token generation failed",
	ERROR_AUTH : "Token error",
}

func GetMsg(code int) string {
	msg, ok := MsgFlags[code]
	if ok {
		return msg
	}

	return MsgFlags[ERROR]
}
Copy the code

Writing toolkits

Create a new util directory under the PKG directory of go-gin-example (note the new replace configuration) and pull the com dependency package as follows:

go get -u github.com/unknwon/com
Copy the code

Write a method for obtaining paging page numbers

Create pagination.go under util and say:

package util

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

	"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func GetPage(c *gin.Context) int {
	result := 0
	page, _ := com.StrTo(c.Query("page")).Int()
    if page > 0 {
        result = (page - 1) * setting.PageSize
    }

    return result
}
Copy the code

Writing models init

Pull the gorM dependency package as follows:

go get -u github.com/jinzhu/gorm
Copy the code

Mysql driver dependencies as follows:

go get -u github.com/go-sql-driver/mysql
Copy the code

When finished, create models.go under the Models directory of Go-gin-example for initial use of models

package models

import (
	"log"
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"

	"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

var db *gorm.DB

type Model struct {
	ID int `gorm:"primary_key" json:"id"`
	CreatedOn int `json:"created_on"`
	ModifiedOn int `json:"modified_on"`
}

func init() {
	var (
		err error
		dbType, dbName, user, password, host, tablePrefix string
	)

	sec, err := setting.Cfg.GetSection("database")
	iferr ! = nil { log.Fatal(2,"Fail to get section 'database': %v", err)
	}

	dbType = sec.Key("TYPE").String()
	dbName = sec.Key("NAME").String()
	user = sec.Key("USER").String()
	password = sec.Key("PASSWORD").String()
	host = sec.Key("HOST").String()
	tablePrefix = sec.Key("TABLE_PREFIX").String()

	db, err = gorm.Open(dbType, fmt.Sprintf("%s:%s@tcp(%s)/%s? charset=utf8&parseTime=True&loc=Local", 
		user, 
		password, 
		host, 
		dbName))

	iferr ! = nil { log.Println(err) } gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {return tablePrefix + defaultTableName;
	}

	db.SingularTable(true)
	db.LogMode(true)
	db.DB().SetMaxIdleConns(10)
	db.DB().SetMaxOpenConns(100)
}

func CloseDB() {
	defer db.Close()
}
Copy the code

Write project startup and routing files

Now that the basics are in place, let’s start writing the Demo!

Write the Demo

Gogin-example creates main.go as a startup file (i.e. the main package). We will write a Demo to help you understand it.

package main

import (
    "fmt"
	  "net/http"

    "github.com/gin-gonic/gin"

	  "github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func main() {
	router := gin.Default()
    router.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "test",
		})
	})

	s := &http.Server{
		Addr:           fmt.Sprintf(":%d", setting.HTTPPort),
		Handler:        router,
		ReadTimeout:    setting.ReadTimeout,
		WriteTimeout:   setting.WriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	s.ListenAndServe()
}
Copy the code

Run the go run main.go command to check whether the command is displayed

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /test                     --> main.main.func1 (3 handlers)
Copy the code

Run curl 127.0.0.1:8000/test to check whether {“message”:”test”} is returned.

knowledge

So, let’s extend what Demo is all about!

The standard library
  • FMT: Implements formatted I/O similar to C languages printf and scanf. The formatting action (‘verb’) comes from C but is simpler
  • Net/HTTP: provides HTTP client and server implementations
Gin
  • gin.Default(): Returns Gin’stype Engine struct{... }, which containsRouterGroupIs equivalent to creating a routeHandlers, you can bind various routing rules, functions, and middleware
  • router.GET(…) {… }: Creates different HTTP methods to bind toHandlersIn addition, common Restful methods such as POST, PUT, DELETE, PATCH, OPTIONS, and HEAD are supported
  • gin.H{… }: Just onemap[string]interface{}
  • gin.Context:Contextisgin, which allows us to pass variables between middleware, manage flows, validate JSON requests, respond to JSON requests, and so onginContains a large number ofContextMethods, such as those we commonly useDefaultQuery,Query,DefaultPostForm,PostForm, etc.
1 & HTTP Server and ListenAndServe?

1, the HTTP Server:

type Server struct {
    Addr    string
    Handler Handler
    TLSConfig *tls.Config
    ReadTimeout time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
}
Copy the code
  • Addr: indicates the MONITORED TCP address in the format of: 8000
  • Handler: indicates the HTTP handleServeHTTPIs used by handlers in response to HTTP requests
  • TLSConfig: configures the Secure Transport Layer protocol (TLS)
  • ReadTimeout: indicates the maximum read time
  • ReadHeaderTimeout: Maximum time allowed to read request headers
  • WriteTimeout: maximum write time
  • IdleTimeout: indicates the maximum waiting time
  • MaxHeaderBytes: The maximum number of bytes in the request header
  • ConnState: Specifies an optional callback function to be called when the client connection changes
  • ErrorLog: Specifies an optional logger to receive unexpected behavior and underlying system errors from the program; If not set ornilDefaults to the standard logger for the log package (that is, output to the console)

2, ListenAndServe:

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    iferr ! = nil {return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
Copy the code

Start listening for the service, listening for the TCP network address, Addr, and calling the application to process requests on the connection.

We see in the source code that Addr is the parameter that we set in &http.Server, so we need to use & when we set it, and we need to change the value of the parameter, because ListenAndServe and some other methods need the parameters in &http.Server, and they interact.

ListenAndServe is there any difference between ListenAndServe and serial 1’s R.run ()?

Let’s look at an implementation of R.run:

func (engine *Engine) Run(addr ... string) (err error) { deferfunc() { debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}
Copy the code

Through the analysis of the source code, we know that there is no difference in essence, and we also know that the debugging information of listening when gin is started is output here.

4. Why are there warnings in Demo?

First we can look at the implementation of Default()

// Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine {  debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery())return engine
}
Copy the code

As you can see, engine instances of logging, recovery middleware have been attached by default. The debugPrintWARNINGDefault() is called at the beginning, and its implementation is to print the line log

func debugPrintWARNINGDefault() {
	debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`)
}
Copy the code

The other Running in “debug” mode. Switch to “release” mode in production. It is not difficult to understand and under the control of configuration file :-), operation and maintenance personnel can modify its configuration at any time.

5. Can router.GET and other routing rules of Demo not be written in the main package?

Router.get and other routing rules are written in the main package in the Demo.

Create a router.go file in the Go-gin-Example directory and write the following information to the router:

package routers

import (
    "github.com/gin-gonic/gin"
    
    "github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func InitRouter() *gin.Engine {
    r := gin.New()

    r.Use(gin.Logger())

    r.Use(gin.Recovery())

    gin.SetMode(setting.RunMode)

    r.GET("/test", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "test",})})return r
}
Copy the code

Modify the contents of main.go file:

package main

import (
	"fmt"
	"net/http"

	"github.com/EDDYCJY/go-gin-example/routers"
	"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func main() {
	router := routers.InitRouter()

	s := &http.Server{
		Addr:           fmt.Sprintf(":%d", setting.HTTPPort),
		Handler:        router,
		ReadTimeout:    setting.ReadTimeout,
		WriteTimeout:   setting.WriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	s.ListenAndServe()
}
Copy the code

Current directory structure:

Go - gin - example / ├ ─ ─ the conf │ └ ─ ─ app. Ini ├ ─ ─ main. Go ├ ─ ─ middleware ├ ─ ─ models │ └ ─ ─ models. Go ├ ─ ─ PKG │ ├ ─ ─ e │ │ ├ ─ ─ Code. Go │ │ └ ─ ─ MSG. Go │ ├ ─ ─ setting │ │ └ ─ ─ setting. Go │ └ ─ ─ util │ └ ─ ─ pagination. Go ├ ─ ─ routers │ └ ─ ─ the router. Go ├ ─ ─ runtimeCopy the code

To restart the service, run the curl 127.0.0.1:8000/test command to check whether the command output is correct.

In the next section, we’ll use our Demo as a starting point to make changes and start coding!

reference

Sample code for this series

  • go-gin-example

?

If you have any questions or mistakes, welcome to raise questions or give correction opinions on issues. If you like or are helpful to you, welcome Star, which is a kind of encouragement and promotion for the author.

My blog

Learn to fry fish Go: github.com/eddycjy/blo…

My official account