項目地址:https://github.com/EDDYCJY/go...mysql
首先,在一個初始項目開始前,你們都要思考一下git
Open
,好嗎顯然在較正規的項目中,這些問題的答案都是不能夠github
爲了解決這些問題,咱們挑選一款讀寫配置文件的庫,本系列中選用go-ini/ini ,它的中文文檔。你們須要先簡單閱讀它的文檔,再接着完成後面的內容。golang
咱們還會編寫一個簡單的API錯誤碼包,而且完成一個Demo示例和講解知識點,便於後面的學習。sql
首先,咱們須要增長一個工做區(GOPATH)路徑用於咱們的Blog
項目。數據庫
將你新的工做區加入到/etc/profile
中的GOPATH
環境變量中, 並在新工做區中,創建bin
、pkg
、src
三個目錄。json
在src
目錄下建立gin-blog
目錄,初始的目錄結構:segmentfault
$GOPATH ├── bin ├── pkg └── src └── gin-blog
gin-blog/ ├── conf ├── middleware ├── models ├── pkg ├── routers └── runtime
新建blog
數據庫,編碼爲utf8_general_ci
緩存
在blog
數據庫下,新建如下表安全
一、 標籤表
CREATE TABLE `blog_tag` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT '' COMMENT '標籤名稱', `created_on` int(10) unsigned DEFAULT '0' COMMENT '建立時間', `created_by` varchar(100) DEFAULT '' COMMENT '建立人', `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改時間', `modified_by` varchar(100) DEFAULT '' COMMENT '修改人', `deleted_on` int(10) unsigned DEFAULT '0', `state` tinyint(3) unsigned DEFAULT '1' COMMENT '狀態 0爲禁用、1爲啓用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章標籤管理';
二、 文章表
CREATE TABLE `blog_article` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tag_id` int(10) unsigned DEFAULT '0' COMMENT '標籤ID', `title` varchar(100) DEFAULT '' COMMENT '文章標題', `desc` varchar(255) DEFAULT '' COMMENT '簡述', `content` text, `created_on` int(11) DEFAULT NULL, `created_by` varchar(100) DEFAULT '' COMMENT '建立人', `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改時間', `modified_by` varchar(255) DEFAULT '' COMMENT '修改人', `deleted_on` int(10) unsigned DEFAULT '0', `state` tinyint(3) unsigned DEFAULT '1' COMMENT '狀態 0爲禁用1爲啓用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章管理';
三、 認證表
CREATE TABLE `blog_auth` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(50) DEFAULT '' COMMENT '帳號', `password` varchar(50) DEFAULT '' COMMENT '密碼', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `blog`.`blog_auth` (`id`, `username`, `password`) VALUES (null, 'test', 'test123456');
拉取go-ini/ini
的依賴包
go get -u github.com/go-ini/ini
咱們須要編寫基礎的應用配置文件,在gin-blog
的conf
目錄下新建app.ini
文件,寫入內容:
#debug or release RUN_MODE = debug [app] PAGE_SIZE = 10 JWT_SECRET = 23347$040412 [server] HTTP_PORT = 8000 READ_TIMEOUT = 60 WRITE_TIMEOUT = 60 [database] TYPE = mysql USER = 數據庫帳號 PASSWORD = 數據庫密碼 #127.0.0.1:3306 HOST = 數據庫IP:數據庫端口號 NAME = blog TABLE_PREFIX = blog_
創建調用配置的setting
模塊,在gin-blog
的pkg
目錄下新建setting
目錄,新建setting.go
文件,寫入內容:
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 ) func init() { var err error Cfg, err = ini.Load("conf/app.ini") if err != 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") if err != 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") if err != nil { log.Fatalf("Fail to get section 'app': %v", err) } JwtSecret = sec.Key("JWT_SECRET").MustString("!@)*#)!@U#@*!@!)") PageSize = sec.Key("PAGE_SIZE").MustInt(10) }
當前的目錄結構:
gin-blog/ ├── conf │ └── app.ini ├── middleware ├── models ├── pkg │ └── setting │ └── setting.go ├── routers ├── runtime
創建錯誤碼的e
模塊,在gin-blog
的pkg
目錄下新建e
目錄,新建code.go
和msg.go
文件,寫入內容:
一、 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 )
二、 msg.go:
package e var MsgFlags = map[int]string { SUCCESS : "ok", ERROR : "fail", INVALID_PARAMS : "請求參數錯誤", ERROR_EXIST_TAG : "已存在該標籤名稱", ERROR_NOT_EXIST_TAG : "該標籤不存在", ERROR_NOT_EXIST_ARTICLE : "該文章不存在", ERROR_AUTH_CHECK_TOKEN_FAIL : "Token鑑權失敗", ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token已超時", ERROR_AUTH_TOKEN : "Token生成失敗", ERROR_AUTH : "Token錯誤", } func GetMsg(code int) string { msg, ok := MsgFlags[code] if ok { return msg } return MsgFlags[ERROR] }
在gin-blog
的pkg
目錄下新建util
目錄,
拉取com
的依賴包
go get -u github.com/Unknwon/com
在util
目錄下新建pagination.go
,寫入內容:
package util import ( "github.com/gin-gonic/gin" "github.com/Unknwon/com" "gin-blog/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 }
拉取gorm
的依賴包
go get -u github.com/jinzhu/gorm
拉取mysql
驅動的依賴包
go get -u github.com/go-sql-driver/mysql
完成後,在gin-blog
的models
目錄下新建models.go
,用於models
的初始化使用
package models import ( "log" "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "gin-blog/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") if err != 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)) if err != nil { log.Println(err) } gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string { return tablePrefix + defaultTableName; } db.SingularTable(true) db.DB().SetMaxIdleConns(10) db.DB().SetMaxOpenConns(100) } func CloseDB() { defer db.Close() }
最基礎的準備工做完成啦,讓咱們開始編寫Demo吧!
在gin-blog
下創建main.go
做爲啓動文件(也就是main
包),
咱們先寫個Demo,幫助你們理解,寫入文件內容:
package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" "gin-blog/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() }
執行go run main.go
,查看命令行是否顯示
[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)
在本機執行curl 127.0.0.1:8000/test
,檢查是否返回{"message":"test"}
。
那麼,咱們來延伸一下Demo所涉及的知識點!
一、 標準庫:
二、 Gin:
type Engine struct{...}
,裏面包含RouterGroup
,至關於建立一個路由Handlers
,能夠後期綁定各種的路由規則和函數、中間件等Handlers
中,也支持POST、PUT、DELETE、PATCH、OPTIONS、HEAD 等經常使用的Restful方法map[string]interface{}
Context
是gin
中的上下文,它容許咱們在中間件之間傳遞變量、管理流、驗證JSON請求、響應JSON請求等,在gin
中包含大量Context
的方法,例如咱們經常使用的DefaultQuery
、Query
、DefaultPostForm
、PostForm
等等三、 &http.Server
和ListenAndServe
?
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 }
:8000
ServeHTTP
,用於處理程序響應HTTP請求nil
則默認以日誌包的標準日誌記錄器完成(也就是在控制檯輸出)ListenAndServe:
func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }
開始監聽服務,監聽TCP網絡地址,Addr和調用應用程序處理鏈接上的請求。
咱們在源碼中看到Addr
是調用咱們在&http.Server
中設置的參數,所以咱們在設置時要用&
,咱們要改變參數的值,由於咱們ListenAndServe
和其餘一些方法須要用到&http.Server
中的參數,他們是相互影響的。
四、 http.ListenAndServe
和連載一的r.Run()
有區別嗎?
咱們看看r.Run
的實現:
func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine) return }
經過分析源碼,得知本質上沒有區別,同時也得知了啓動gin
時的監聽debug信息在這裏輸出。
五、 爲何Demo裏會有WARNING
?
首先咱們能夠看下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 }
你們能夠看到默認狀況下,已經附加了日誌、恢復中間件的引擎實例。而且在開頭調用了debugPrintWARNINGDefault()
,而它的實現就是輸出該行日誌
func debugPrintWARNINGDefault() { debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) }
而另一個Running in "debug" mode. Switch to "release" mode in production.
,是運行模式緣由,並不難理解,已在配置文件的管控下 :-),運維人員隨時就能夠修改它的配置。
六、 Demo的router.GET
等路由規則能夠不寫在main
包中嗎?
咱們發現router.GET
等路由規則,在Demo中被編寫在了main
包中,感受很奇怪,咱們去抽離這部分邏輯!
在gin-blog
下routers
目錄新建router.go
文件,寫入內容:
package routers import ( "github.com/gin-gonic/gin" "gin-blog/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 }
修改main.go
的文件內容:
package main import ( "fmt" "net/http" "gin-blog/routers" "gin-blog/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() }
當前目錄結構:
gin-blog/ ├── conf │ └── app.ini ├── main.go ├── middleware ├── models │ └── models.go ├── pkg │ ├── e │ │ ├── code.go │ │ └── msg.go │ ├── setting │ │ └── setting.go │ └── util │ └── pagination.go ├── routers │ └── router.go ├── runtime
重啓服務,執行curl 127.0.0.1:8000/test
查看是否正確返回。
下一節,咱們將以咱們的Demo爲起點進行修改,開始編碼!