项目地址: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为起点进行修改,开始编码!