快速開始構建一個簡單專案

奇蹟師發表於2021-08-20

前言

所有專案都是從一個檔案,一個 main 中開始的,可以根據以下步驟進行一個專案的初始化
專案地址

1.建立一個專案,同時執行 Hello world

package main

import "fmt"

func main {
    fmt.Println("hello world")
}

為什麼要執行一個 hello world?
- 如果連 main 中的 hello 都執行不了還怎麼執行一個大專案,可能是環境沒配置好

2.準備資料夾

project
    |- configs // 配置檔案應用位置
    |- pkg     // 包括介面在內的程式碼放在該目錄中
    |- utils // 工具類可以放在該檔案中
    |- inits     // 系統初始化,和程式管理配置
    |- README.md // 專案說明檔案
    |- main.go     // 主函式

3.準備好需要的第三方包

  • 資料庫連線: github.com/jinzhu/gorm
  • 路由連線: github.com/gin-gonic/gin
  • 快取連線: github.com/gomodule/redigo/redis
  • 訊息佇列連線: github.com/apache/rocketmq-client-go/v2
  • 讀取配置: github.com/spf13/viper
  • 日誌處理: github.com/sirupsen/logrus
  • 錯誤處理: github.com/pkg/errors
  • 測試斷言工具: github.com/stretchr/testify/assert

3.準備好專案配置檔案,推薦使用 .toml (或者 .yaml檔案)

檔案位置

projcet
    |- configs
            |- local.toml檔案
[http]
    port=":8080"
    read_timeout=60
    write_timeout=60
[database]
    host="127.0.0.1"
    dbname="postgres"
    user="postgres"
    password="123456"
    sslmode="disable"
[redis]
    host="127.0.0.1:6379"
    password=""
    max_idle=10
    max_active=10
[rocketmq]
    server = [
    "127.0.0.1:9876"
     ]

4.配置 log 日誌 (logrus)

檔案地址

project 
    |- utils
        |- logger
            |- log.go
package logger

import (
    "github.com/sirupsen/logrus"
    "os"
)

var (
    Log = logrus.New()
)

func init() {
    InitLogger()
}

func InitLogger() {
    // 日誌格式化為JSON而不是預設的ASCII
    Log.SetFormatter(&logrus.JSONFormatter{})
    // 輸出stdout而不是預設的stderr,也可以是一個檔案
    Log.SetOutput(os.Stdout)
    // 只記錄嚴重或以上警告
    Log.SetLevel(logrus.WarnLevel)
}

// New 用來建立新的 log 日誌
func New() *logrus.Logger {
    return Log
}

5.配置需要初始化的讀取專案配置(viper)資料庫(gorm)連線,快取連線(redigo),http初始化(gin),訊息佇列連線(rocketmq)

1.讀取配置檔案 (config.go)

檔案地址

project 
    |- inits
        |- config.go
package inits

import (
    "github.com/catbugdemo/project_order/utils/logger"
    "github.com/pkg/errors"
    "github.com/spf13/viper"
    "sync"
)

var (
    config *viper.Viper
    mu     sync.RWMutex
    log    = logger.New()
)

func init() {
    InitConfig()
}

func InitConfig() {
    v := viper.New()
    // 初始化配置資訊
    v.SetConfigName("local")
    v.SetConfigType("toml")

    // TODO 專案名稱進行配置
    v.AddConfigPath("$GOPATH/src/github.com/catbugdemo/project_order/configs")
    v.AddConfigPath("../configs")

    if err := ReadInConfig(v); err != nil {
        log.Fatalf("讀取配置失敗:%+v\n", err)
    }
    // 當配置改變是重新執行
    v.WatchConfig()

    config = v
}

// ReadInConfig 通過讀寫鎖保證內容穩定內容
func ReadInConfig(v *viper.Viper) error {
    mu.RLock()
    defer mu.RUnlock()
    if err := v.ReadInConfig(); err != nil {
        return errors.WithStack(err)
    }
    return nil
}

// GetConfig 獲取配置
func GetConfig() *viper.Viper {
    mu.RLock()
    defer mu.RUnlock()
    return config
}

2.初始化資料庫 (db.go)

檔案地址

project 
    |- inits
        |- db.go
package init

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

    "github.com/pkg/errors"
    "time"
)

var (
    db       *gorm.DB
    dbConfig *DbConfig
)

type DbConfig struct {
    Host     string `toml:"host"`
    DbName   string `toml:"dbname"`
    User     string `toml:"user"`
    Password string `toml:"password"`
    Sslmode  string `toml:"disable"`
}

func init() {
    InitDbConfig()
    InitDB()
}

func InitDbConfig() {
    c := GetConfig()

    dbConfig = &DbConfig{
        Host:     c.GetString("database.host"),
        DbName:   c.GetString("database.dbname"),
        User:     c.GetString("database.user"),
        Password: c.GetString("database.password"),
        Sslmode:  c.GetString("database.sslmode"),
    }
}

func InitDB() {

    params := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s password=%s",
        dbConfig.Host,
        dbConfig.User,
        dbConfig.DbName,
        dbConfig.Sslmode,
        dbConfig.Password,
    )

    open, err := gorm.Open("postgres", params)
    if err != nil {
        log.Fatalf("連線資料庫失敗:%+v", errors.WithStack(err))
    }
    if err = open.DB().Ping(); err != nil {
        log.Fatalf("ping 資料庫失敗:%+v", errors.WithStack(err))
    }

    // 設定表不為複數
    open.SingularTable(true)
    // 設定列印日誌
    open.LogMode(true)
    // 設定可重複使用連線的最長時間
    open.DB().SetConnMaxLifetime(10 * time.Second)

    db = open
}

// 獲取資料庫
func DB() *gorm.DB {
    return db
}

3.初始化快取 (redis.go)

檔案地址

project 
    |- inits
        |- redis.go
package init

import (
    "github.com/gomodule/redigo/redis"
    "github.com/pkg/errors"
    "time"
)

var (
    pool        *redis.Pool
    redisConfig *RedisConfig
)

type RedisConfig struct {
    Host      string `toml:"host"`
    Password  string `toml:"passowrd"`
    MaxIdle   int    `toml:"max_idle"`
    MaxActive int    `toml:"max_active"`
    Db        string `toml:"db"`
}

func init() {
    InitRedisConfig()
    InitRedisPool()
}

// InitRedisConfig 初始化快取配置
func InitRedisConfig() {
    c := GetConfig()

    redisConfig = &RedisConfig{
        Host:      c.GetString("redis.host"),
        Password:  c.GetString("redis.password"),
        MaxIdle:   c.GetInt("redis.max_idle"),
        MaxActive: c.GetInt("redis.max_active"),
    }
}

// InitRedisPool 初始化快取池
func InitRedisPool() {
    pool = &redis.Pool{
        //最大閒置連線
        MaxIdle: redisConfig.MaxIdle,
        //最大活動數
        MaxActive: redisConfig.MaxActive,
        //資料庫連線
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", redisConfig.Host)
            if err != nil {
                c.Close()
                log.Printf("fail to dial redis: %+v\n", errors.WithStack(err))
                return nil, err
            }
            //密碼認證
            if redisConfig.Password != "" {
                if _, err = c.Do("AUTH", redisConfig.Password); err != nil {
                    c.Close()
                    log.Printf("fail to auth redis: %+v\n", errors.WithStack(err))
                    return nil, err
                }
            }
            //redis 快取資料庫認證
            if redisConfig.Db != "" {
                if _, err = c.Do("SELECT", redisConfig.Db); err != nil {
                    c.Close()
                    log.Printf("fail to SELECT DB redis: %+v\n", errors.WithStack(err))
                    return nil, err
                }
            }
            return c, err
        },
        //測試連線是否正常
        TestOnBorrow: func(c redis.Conn, t time.Time) error {
            _, err := c.Do("PING")
            if err != nil {
                c.Close()
                log.Printf("fail to ping redis: %+v\n", err)
                return err
            }
            return nil
        },
    }
}

// Get 獲取快取連線
func Get() redis.Conn {
    return pool.Get()
}

4.介面連線 (http.go)

檔案地址

project 
    |- inits
        |- http.go
``
package inits

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

var (
    ginConfig *GinConfig
)

type GinConfig struct {
    Port         string `toml:"port"`
    ReadTimeout  int    `toml:"read_timeout"`
    WriteTimeout int    `toml:"write_timeout"`
}

func InitHttp() {
    InitGinConfig()
    InitGin()
}

func InitGinConfig() {
    c := GetConfig()
    ginConfig = &GinConfig{
        Port:         c.GetString("http.port"),
        ReadTimeout:  c.GetInt("http.read_timeout"),
        WriteTimeout: c.GetInt("http.write_timeout"),
    }
}

func InitGin() {
    router := gin.New()

    router.Use(gin.Logger(), gin.Recovery())

    router.GET("/version/", func(c *gin.Context) {
        c.JSON(200, gin.H{"version": "v1.0.0"})
    })

    s := &http.Server{
        Addr:         ginConfig.Port,
        Handler:      router,
        ReadTimeout:  time.Duration(ginConfig.ReadTimeout) * time.Second,
        WriteTimeout: time.Duration(ginConfig.WriteTimeout) * time.Second,
    }

    if err := s.ListenAndServe(); err != nil {
        if err == http.ErrServerClosed {
            log.Println("http: Server Close:%+v", errors.WithStack(err))
        }
        log.Fatalf("http開啟監聽服務失敗:%+v", errors.WithStack(err))
    }

}

5. 訊息佇列連線 (rocketmq.go)

檔案地址

project 
    |- inits
        |- rocketmq.go

6.簡單編寫 main.go

檔案地址

project 
    |- main.go
package main

import (
    "fmt"
    "github.com/catbugdemo/project_order/inits"
)

func main() {

    go inits.InitHttp()

    select {}
}

8.單元測試每個方法是否正確

9.開啟專案

  1. 執行 main.go
  2. 測試 version 埠 localhost:8080/version

結語

  • 推薦第一次自己嘗試跟著寫,第二次自己寫一個新的作為自己的 project_order,第三次直接使用
  • 感謝閱讀
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章