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’s
type Engine struct{... }
, which containsRouterGroup
Is equivalent to creating a routeHandlers
, you can bind various routing rules, functions, and middleware - router.GET(…) {… }: Creates different HTTP methods to bind to
Handlers
In addition, common Restful methods such as POST, PUT, DELETE, PATCH, OPTIONS, and HEAD are supported - gin.H{… }: Just one
map[string]interface{}
- gin.Context:
Context
isgin
, which allows us to pass variables between middleware, manage flows, validate JSON requests, respond to JSON requests, and so ongin
Contains a large number ofContext
Methods, 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 handle
ServeHTTP
Is 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 or
nil
Defaults 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…