Gin实践 连载二 搭建Blog API's(一)

EDDYCJY · · 14976 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Gin搭建Blog API's (一)

介绍和初始化项目

初始工作区

首先,我们需要增加一个工作区(GOPATH)路径用于我们的Blog项目。

将你新的工作区加入到/etc/profile中的GOPATH环境变量中, 并在新工作区中,建立binpkgsrc三个目录。

src目录下创建gin-blog目录,初始的目录结构:

$GOPATH ├── bin ├── pkg └── src └── gin-blog

初始化项目目录

gin-blog/ ├── conf ├── middleware ├── models ├── pkg ├── routers └── runtime
  • conf:用于存储配置文件
  • middleware:应用中间件
  • models:应用数据库模型
  • pkg:第三方包
  • routers 路由逻辑处理
  • runtime 应用运行时数据

初始项目数据库

新建blog数据库,编码为utf8_general_ci

blog数据库下,新建以下表

1、 标签表

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 '修改人', `state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用、1为启用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章标签管理';

2、文章表

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 '修改人', `state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用1为启用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章管理';

3、认证表

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'); 

首先,在一个初始项目开始前,大家都要思考一下

  1. 把各种的程序配置写在代码中真的好吗
  2. 将API的错误码硬编在程序中合适吗

等等

显然在较正规的项目中,这两个问题的答案都是不可以

为了解决这2个问题,我们挑选一款读写配置文件的库,本系列中选用无闻的go-ini/ini ,它的中文文档。大家需要先简单阅读它的文档,再接着完成后面的内容。

并且我们会编写一个简单的API错误码包。

编写项目配置包

拉取go-ini/ini的依赖包

go get -u github.com/go-ini/ini

我们需要编写基础的应用配置文件,在gin-blogconf目录下新建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-blogpkg目录下新建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.Fatal(2, "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.Fatal(2, "Fail to get section 'server': %v", err) } RunMode = Cfg.Section("").Key("RUN_MODE").MustString("debug") 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.Fatal(2, "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

编写API错误码包

建立错误码的e模块,在gin-blogpkg目录下新建e目录,新建code.gomsg.go文件,写入内容:

  1. 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 )
  1. 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] }

最基础的准备工作完成啦,让我们开始编写Demo吧!

编写工具包

gin-blogpkg目录下新建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 }

编写models init

拉取gorm的依赖包

go get -u github.com/jinzhu/gorm

拉取mysql驱动的依赖包

go get -u github.com/go-sql-driver/mysql

完成后,在gin-blogmodels目录下新建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() }

编写项目启动、路由文件

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所涉及的知识点!

  1. 标准库:
  • fmt:实现了类似C语言printf和scanf的格式化I/O。格式化动作('verb')源自C语言但更简单
  • net/http:提供了HTTP客户端和服务端的实现
  1. Gin:
  • gin.Default():返回Gin的type Engine struct{...},里面包含RouterGroup,相当于创建一个路由Handlers,可以后期绑定各类的路由规则和函数、中间件等
  • router.GET(...){...}:创建不同的HTTP方法绑定到Handlers中,也支持POST、PUT、DELETE、PATCH、OPTIONS、HEAD 等常用的Restful方法
  • gin.H{...}:就是一个map[string]interface{}
  1. 为什么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.,是运行模式原因,并不难理解,已在配置文件的管控下 :-),运维人员随时就可以修改它的配置。

  1. Demo的router.GET等路由规则可以不写在main包中吗?

我们发现router.GET等路由规则,在Demo中被编写在了main包中,感觉很奇怪,我们去抽离这部分逻辑!

gin-blogrouters目录新建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为起点进行修改,开始编码!

参考

本系列示例代码


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:EDDYCJY

查看原文:Gin实践 连载二 搭建Blog API's(一)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

14976 次点击  ∙  1 赞  
加入收藏 微博
被以下专栏收入,发现更多相似内容
4 回复  |  直到 2021-11-24 14:18:49
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传