1. Module division and stratification

1. Project organization

According to the normal MVC pattern, modules must be divided into layers first and then. For example, a module for user verification is normally divided into three layers: Model, Controller and Service. The model layer declares the data or information to be operated, such as database model. Contorller is responsible for the control of the specific business module process. It calls the interface of the Service layer to control the business process, and the Service layer is the specific processing logic, for example:

├── Bass exercises ─ ├─ bass exercises ─ bass exercises ─ bass Exercises ─ bass Exercises ─ bass Exercises └ ─ ─ userService. GoCopy the code

However, for personal reasons, I decided to use the convention of dividing modules and then layering to organize the project

├─ Model exercises // Model Exercises, │ ├─ ├─ userAuth │ ├─ userAuth │ ├─ userAuth │ ├─ userAuth │ ├─ userAuth │ ├─ userAuth Including the controller, the service and the type │ └ ─ ─ cotroller. Go │ └ ─ ─ service. Go │ └ ─ ─ the go / / used to store data type │ └ ─ ─ util. Go / / Used to hold tool methods that apply to the moduleCopy the code

2. Add file processing methods and configuration files

(1) the json processing

1, install,

Import ("encoding/json") import ("encoding/json")Copy the code

2. Encapsulate some common methods

To facilitate json handling, we will create a tool folder in the root directory, create a new dataHanding folder in it to hold the data handling methods, and create a JSON folder under it to hold the JSON handling methods

/ tool/dataHanding/json/json. Go package JSONHandle/import/deserialize method (" encoding/json "" FMT" "OS") / / decoding json file for map func JsonFileToMap (fileUrl String) map[string]interface{} { Datamap := make(map[string]interface{}) // create decoder := Json.NewDecoder(srcFile) // Decode err := decoder.Decode(&datamap) if err! = nil {fmt.Println(" failed to decode,err=", err) return datamap} fmt.Println(" failed to decode ", err) Func jsonToMap(jsonStr String) map[string]interface{} {// Convert JSON data to byte data jsonbytes := []byte(jsonStr) dataMap := make(map[string]interface{}) err := json.Unmarshal(jsonbytes, &dataMap) if err ! Println(" deserialization failed,err=", Err)} fmt.Println(dataMap) return dataMap} // Convert JSON data to map slice func jsonToMapSlice(jsonStr string) []map[string]interface{} { jsonBytes := []byte(jsonStr) dataSlice := make([]map[string]interface{}, 0) err := json.Unmarshal(jsonBytes, &dataSlice) if err ! = nil {fmt.Println(" deserialization failed,err=", err)} fmt.Println(dataSlice) return dataSlice}Copy the code

(2) YAML file processing

1, install,

go get gopkg.in/yaml.v2
Copy the code

2, use,

// Define the attributes in the conf type, Struct {Service Serivce 'yaml:" Service "'} type Serivce struct {Url string 'yaml:" Url "' Host String 'yaml:"host"' Topics' []Topic 'yaml:" Subscriptions "'} // Read the YAMl configuration file, Func (conf * conf) getConf() * conf {// The absolute address should be yamlFile, err := ioutil.readfile ("conf.yaml") if err! = nil { fmt.Println(err.Error()) } err = yaml.Unmarshal(yamlFile, conf) if err ! = nil { fmt.Println(err.Error()) } return conf }Copy the code

(3).ini file processing

1, install,

go get gopkg.in/ini.v1
Copy the code

2. How to use it

Ini file name = myName [user] account = 123456Copy the code
CFG, err := ini.Load("my.ini") if err! = nil { fmt.Printf("Fail to read file: %v", err) os.exit (1)} The default partition can use empty strings to represent cfg.section ("").key ("name").string () cfg.section ("user").key ("account").string () // change a value and save it cfg.Section("").Key("name").SetValue("newName") cfg.SaveTo("my.ini.local")Copy the code

Reference: official website (for more operations)

(5) Add configuration files to distinguish different project environments

We can use various types of files to write configuration files in a project, and I have chosen the.ini file

1. Add godotenv to read. Env file

Anything that needs to be changed when you switch from development to production is extracted from the code into environment variables. However, in real development, setting environment variables can easily conflict if multiple projects are running on the same machine, so here we can use GodotenV to handle this problem

1.1 installation

go get github.com/joho/godotenv
Copy the code

1.2 the use of

We can load it manually or we can load it automatically

// Manually package main import (" FMT ""log" "OS ""github.com/joho/godotenv") func main() {err := godotenV.load () if err! = nil { log.Fatal(err) } fmt.Println("env: ", os.getenv ("env")) // By default, godotenv reads the. Env file in the root directory of the project, which uses the key = value format, one key/value pair per line. // Godotenv.load () can be loaded, and os.getenv ("key") can be read directly. // os.getenv is used to read environment variables: } / / automatic package main import (" FMT "" OS" _ "github.com/joho/godotenv/autoload" / / because the code does not explicitly used in godotenv library, you need to use air import, Func main() {fmt.println ("env: ", os.getenv ("env"))}Copy the code

Reference: official website

The. Env file is added to the 1.3 project

Env = dev /. Env = devCopy the code

1.4 Handling the case where. Env files are not packaged into executable files

// If the current configuration file does not exist, create a new one. For convenience, I decide to use method 2Copy the code

2. Add configuration files for different environments to the project

We usually in the actual development in different environments have different configuration, in order to facilitate switching different configuration, we can create a new folder under the root directory config under different environment to put our configuration files, I here only has built three file to store the development, production and testing environment configuration file

├ ─ ─ the config │ ├ ─ ─ dev. Ini │ ├ ─ ─ pro. Ini │ ├ ─ ─ test. The iniCopy the code

3. Determine how to load different configurations in different environments

Since we introduced godotenv above to read the.env file, it is now easy to know what environment configuration file we need to load, so let’s add some methods to handle it

/config/util.go package projectConfig import ( "bytes" "fmt" "github.com/joho/godotenv" "log" "os" "path/filepath" Func createEnv() {envConfig := "env = pro" buf := &bytes.buffer {} buf.writeString (envConfig) env, err := godotenv.Parse(buf) if err ! = nil { log.Fatal(err) } path := getAppPath() + "/.env" err = godotenv.Write(env, path) if err ! Func getEnv() string {// Load. Env File configuration err := godotenV.load (".env") // Obtain the running environment env := os.Getenv("ENV") if err ! Println = nil {FMT.Println(" config file failed to load, now create a config file ") env = "pro"} FMT.Println(" current environment development :", Func getAppPath() string {var _, b, _, _ = runtime.Caller(0) return filepath.Join(filepath.Dir(b), ".. / ")}Copy the code
/config/config.go package projectConfig import ( "fmt" "gopkg.in/ini.v1" ) func InitConfig() { baseConfig := GetBaseConfig ()} func getBaseConfig() interface{} {// Read configuration file path := getAppPath() + "/config/" + getEnv() + ".ini" BaseConfig := new(baseConfig) // baseConfigStruct := ini.MapTo(baseConfig, path) return baseConfig }Copy the code

4. If it’s too much trouble

If you don’t think you need this much environment configuration, you can also define a variable to determine which environment configuration is currently loaded

Ini // Example [BASE_CONFIG] ENV = dev HTTP_PORT = 8081 READ_TIMEOUT = 60 WRITE_TIMEOUT = 60Copy the code
/config/configStruct.go

package projectConfig

type BaseInfo struct {
	env string
}

type Config struct {
	BaseConfig `ini:"BASE_CONFIG"`
}

type BaseConfig struct {
	ENV           string `ini:"ENV"`
	HTTP_PORT     int    `ini:"HTTP_PORT"`
	READ_TIMEOUT  int    `ini:"READ_TIMEOUT"`
	WRITE_TIMEOUT int    `ini:"WRITE_TIMEOUT"`
}
Copy the code
/config/config.go package projectConfig import ( "fmt" "gopkg.in/ini.v1" ) func InitConfig() { getBaseConfig() } func GetBaseConfig () interface{} {baseInfo := baseInfo {env: "dev"} fmt.Println(baseInfo.env, "BaseConfig. Env ") // Read configuration file path := getAppPath() + "/config/" + baseinfo.env + ".ini" // No default value // config := &config {BaseConfig: BaseConfig{}} // Set the default value Config: = &config {BaseConfig: BaseConfig{ENV: "pro", HTTP_PORT: 8081, READ_TIMEOUT: 60, WRITE_TIMEOUT: 60,}} // mapping, everything can be so simple. err := ini.MapTo(config, path) if err ! Println(config, config.BaseConfig, "BaseConfig ") return config} = nil {fmt.Println(config, config.BaseConfig," err ") return config}Copy the code

Three, log processing

1, install,

// Log polling mechanism, (that is, the log periodically clean, save, Go get -u github.com/lestrrat-go/file-rotatelogs // lfShook removes color from any type of formatter when writing to a local file, Because the color code in the file doesn't look good. Go get the -u github.com/rifflock/lfshook / / GIN log middleware logrus use go get -u github.com/sirupsen/logrusCopy the code

2. Project configuration

(1) Add log writing URL and log name to project configuration file

Ini [BASE_CONFIG] ENV = dev HTTP_PORT = 8081 READ_TIMEOUT = 60 WRITE_TIMEOUT = 60 // Add [LOG] LOG_SAVE_URL =  logs/ LOG_SAVE_NAME = logCopy the code

(2) Added log processing middleware

/middleware/global/log/log.go package logMiddleware import ( "fmt" "github.com/gin-gonic/gin" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "go-server-template/config" "os" "path" "time" ) func LogerMiddleware(config *projectConfig.Config) gin.HandlerFunc { // Log file fileName := path.join (config.logconfig. LOG_SAVE_URL, config.logconfig. LOG_SAVE_NAME) // Write the file SRC, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) if err ! = nil { fmt.Println("err", Logrus.new () // Sets the log level logger.setLevel (logrus.debuglevel) // Sets the output logger.out = SRC // set Rotatelogs logWriter, err := rotatelogs.New(fileName+".%Y%m%d.log", WithLinkName(fileName) rotatelogs.WithMaxAge(7*24* time.hour), / / set log cutting time interval (1 day) rotatelogs. WithRotationTime (24 * time. The Hour),) writeMap: = lfshook. WriterMap {logrus. InfoLevel: logWriter, logrus.FatalLevel: logWriter, logrus.DebugLevel: logWriter, logrus.WarnLevel: logWriter, logrus.ErrorLevel: logWriter, logrus.PanicLevel: logWriter, } logger.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{ TimestampFormat: "The 2006-01-02 15:04:05", })) return func(c *gin.Context) {// startTime startTime := time.now () // request processing c.ext () // endTime endTime := time.now () // execution time LatencyTime := endTime.Sub(startTime) // Request Method reqMethod := C.equest. Method // Request route reqUrl := C.equest. RequestURI // Status code StatusCode := c.whiter.status () // Request IP clientIP := c.clientip () // Log format logger.withfields (logrus.fields {"status_code": statusCode, "latency_time": latencyTime, "client_ip": clientIP, "req_method": reqMethod, "req_uri": reqUrl, }).info ()} // Logs are recorded to MongoDB func LoggerToMongo() gin.HandlerFunc {return func(c *gin.Context) {}} // Logs are recorded to ES func LoggerToES() gin.HandlerFunc {return func(c *gin.Context) {}} // Logs are logged to MQ func LoggerToMQ() gin  func(c *gin.Context) { } }Copy the code

Reference: Reference link

(3) Introduction of logging middleware

/main.go package main import ( "fmt" "go-server-template/config" "go-server-template/middleware/global/log" "go-server-template/routers" "strconv" ) func main() { config := projectConfig.InitConfig() r := routers.InitRouter() // Using logging middleware r.U se (logMiddleware LogerMiddleware (config)) / / int string http_port: = strconv.Itoa(config.BaseConfig.HTTP_PORT) err := r.Run(":" + http_port) if err ! = nil {fmt.println (" Server failed to start!" )}}Copy the code

Add hot updates

I believe that all of you are very impatient to restart the project every time you modify it, so let’s happily introduce hot update function in the project

1. Comparison of schemes

Go’s common packages that can support hot update include Fresh and Realize, which should be similar. Because Fresh is more convenient, we choose Fresh as our hot update solution.

2, realize

1.1 installation

GO111MODULE=off go get github.com/oxequa/realize
Copy the code

There is a pitfall here which is if we follow the official documentation

go get github.com/oxequa/realize
Copy the code

This is what happens when you install it this way

In /urfave/[email protected]: Parsing go.mod: module declares its path as: github.com/urfave/cli/v2 but was required as: gopkg.in/urfave/cli.v2Copy the code

Such error, the reason is very simple, I will not go into detail, interested can go to check

1.2 the initialization

After the installation we can use the command to initialize

realize init
Copy the code

In this step, we can set the rules according to our own requirements, or we can run back to the car and customize the configuration file changes later

For details about configuration files, see this link: Configuration Files

Note: Name and path in schema are specified based on actual conditions. If your machine is a Mac, it is important to replace the kill process command with pkill, otherwise Gin will continue to do this when recompiling, causing hot updates to fail.

[GIN-debug] [ERROR] listen tcp :8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted
Copy the code

To start, we can use commands in the root directory

realize start
Copy the code

3, fresh

Same as above

GO111MODULE=off go get github.com/oxequa/realize
Copy the code

Then use it directly in the root directory

fresh
Copy the code

You can use it directly because Fresh automatically runs the main.go project