思考

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

  • 程序的文本配置写在代码中,好吗?
  • API 的错误码硬编码在程序中,合适吗?
  • db 句柄谁都去Open,没有统一管理,好吗?
  • 获取分页等公共参数,谁都自己写一套逻辑,好吗?

显然在较正规的项目中,这些问题的答案都是不可以,为了解决这些问题,我们挑选一款读写配置文件的库,目前比较火的有 viper,有兴趣你未来可以简单了解一下,没兴趣的话等以后接触到再说。

但是本系列选用 go-ini/ini ,它的 中文文档。大家是必须需要要简单阅读它的文档,再接着完成后面的内容。

本文目标

  • 编写一个简单的 API 错误码包。
  • 完成一个 Demo 示例。
  • 讲解 Demo 所涉及的知识点。

介绍和初始化项目

初始化项目目录

在前一章节中,我们初始化了一个 go-gin-example 项目,接下来我们需要继续新增如下目录结构:

  1. go-gin-example/
  2. ├── conf
  3. ├── middleware
  4. ├── models
  5. ├── pkg
  6. ├── routers
  7. └── runtime
  • conf:用于存储配置文件
  • middleware:应用中间件
  • models:应用数据库模型
  • pkg:第三方包
  • routers 路由逻辑处理
  • runtime:应用运行时数据

image.png

添加 Go Modules Replace

打开 go.mod 文件,新增 replace 配置项,如下:

  1. module github.com/EDDYCJY/go-gin-example
  2. go 1.17
  3. require (
  4. github.com/360EntSecGroup-Skylar/excelize v1.4.1
  5. github.com/PuerkitoBio/purell v1.1.1 // indirect
  6. github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
  7. github.com/astaxie/beego v1.12.3
  8. github.com/boombuler/barcode v1.0.1
  9. github.com/dgrijalva/jwt-go v3.2.0+incompatible
  10. github.com/gin-gonic/gin v1.7.4
  11. github.com/go-ini/ini v1.63.2
  12. )

image.png

可能你会不理解为什么要特意跑来加 replace 配置项,首先你要看到我们使用的是完整的外部模块引用路径(github.com/EDDYCJY/go-gin-example/xxx),而这个模块还没推送到远程,是没有办法下载下来的,因此需要用 replace 将其指定读取本地的模块路径,这样子就可以解决本地模块读取的问题。

注:后续每新增一个本地应用目录,你都需要主动去 go.mod 文件里新增一条 replace(我不会提醒你),如果你漏了,那么编译时会出现报错,找不到那个模块。

初始项目数据库

新建 blog 数据库,编码为 utf8_general_ci ,在 blog 数据库下,新建以下表
1、 标签表

  1. -- ----------------------------
  2. -- Table structure for blog_tag
  3. -- ----------------------------
  4. DROP TABLE IF EXISTS `blog_tag`;
  5. CREATE TABLE `blog_tag` (
  6. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  7. `name` varchar(100) DEFAULT '' COMMENT '标签名称',
  8. `created_on` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  9. `created_by` varchar(100) DEFAULT '' COMMENT '创建人',
  10. `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
  11. `modified_by` varchar(100) DEFAULT '' COMMENT '修改人',
  12. `deleted_on` int(10) unsigned DEFAULT '0' COMMENT '删除时间',
  13. `state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用、1为启用',
  14. PRIMARY KEY (`id`)
  15. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章标签管理';

2、 文章表

  1. -- ----------------------------
  2. -- Table structure for blog_article
  3. -- ----------------------------
  4. DROP TABLE IF EXISTS `blog_article`;
  5. CREATE TABLE `blog_article` (
  6. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  7. `tag_id` int(10) unsigned DEFAULT '0' COMMENT '标签ID',
  8. `title` varchar(100) DEFAULT '' COMMENT '文章标题',
  9. `desc` varchar(255) DEFAULT '' COMMENT '简述',
  10. `content` text COMMENT '内容',
  11. `cover_image_url` varchar(255) DEFAULT '' COMMENT '封面图片地址',
  12. `created_on` int(10) unsigned DEFAULT '0' COMMENT '新建时间',
  13. `created_by` varchar(100) DEFAULT '' COMMENT '创建人',
  14. `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
  15. `modified_by` varchar(255) DEFAULT '' COMMENT '修改人',
  16. `deleted_on` int(10) unsigned DEFAULT '0',
  17. `state` tinyint(3) unsigned DEFAULT '1' COMMENT '删除时间',
  18. PRIMARY KEY (`id`)
  19. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章管理';

3、 认证表

  1. -- ----------------------------
  2. -- Table structure for blog_auth
  3. -- ----------------------------
  4. DROP TABLE IF EXISTS `blog_auth`;
  5. CREATE TABLE `blog_auth` (
  6. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  7. `username` varchar(50) DEFAULT '' COMMENT '账号',
  8. `password` varchar(50) DEFAULT '' COMMENT '密码',
  9. PRIMARY KEY (`id`)
  10. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  11. INSERT INTO `blog_auth` (`id`, `username`, `password`) VALUES ('1', 'test', 'test123');

编写项目配置包

在 go-gin-example 应用目录下,拉取 go-ini/ini 的依赖包,如下:

  1. PS D:\Projects\Github\NoobWu\go-samples\go-gin-example> go get -u github.com/go-ini/ini

接下来我们需要编写基础的应用配置文件,在 go-gin-example 的 conf 目录下新建 app.ini 文件,写入内容:
D:\Projects\Github\NoobWu\go-samples\go-gin-example\conf\app.ini

  1. [app]
  2. PageSize = 10
  3. JwtSecret = 233
  4. PrefixUrl = http://127.0.0.1:8000
  5. RuntimeRootPath = runtime/
  6. ImageSavePath = upload/images/
  7. # MB
  8. ImageMaxSize = 5
  9. ImageAllowExts = .jpg,.jpeg,.png
  10. ExportSavePath = export/
  11. QrCodeSavePath = qrcode/
  12. FontSavePath = fonts/
  13. LogSavePath = logs/
  14. LogSaveName = log
  15. LogFileExt = log
  16. TimeFormat = 20060102
  17. [server]
  18. #debug or release
  19. RunMode = debug
  20. HttpPort = 8000
  21. ReadTimeout = 60
  22. WriteTimeout = 60
  23. [database]
  24. Type = mysql
  25. User = root
  26. Password = 123456
  27. Host = 127.0.0.1:3306
  28. Name = blog
  29. TablePrefix = blog_
  30. [redis]
  31. Host = 127.0.0.1:6379
  32. Password =
  33. MaxIdle = 30
  34. MaxActive = 30
  35. IdleTimeout = 200

建立调用配置的 setting 模块,在 go-gin-example 的 pkg目录下新建setting目录(注意新增 replace 配置),新建 setting.go 文件,写入内容:
D:\Projects\Github\NoobWu\go-samples\go-gin-example\pkg\setting\setting.go

  1. package setting
  2. import (
  3. "log"
  4. "time"
  5. "github.com/go-ini/ini"
  6. )
  7. type App struct {
  8. JwtSecret string
  9. PageSize int
  10. PrefixUrl string
  11. RuntimeRootPath string
  12. ImageSavePath string
  13. ImageMaxSize int
  14. ImageAllowExts []string
  15. ExportSavePath string
  16. QrCodeSavePath string
  17. FontSavePath string
  18. LogSavePath string
  19. LogSaveName string
  20. LogFileExt string
  21. TimeFormat string
  22. }
  23. var AppSetting = &App{}
  24. type Server struct {
  25. RunMode string
  26. HttpPort int
  27. ReadTimeout time.Duration
  28. WriteTimeout time.Duration
  29. }
  30. var ServerSetting = &Server{}
  31. type Database struct {
  32. Type string
  33. User string
  34. Password string
  35. Host string
  36. Name string
  37. TablePrefix string
  38. }
  39. var DatabaseSetting = &Database{}
  40. type Redis struct {
  41. Host string
  42. Password string
  43. MaxIdle int
  44. MaxActive int
  45. IdleTimeout time.Duration
  46. }
  47. var RedisSetting = &Redis{}
  48. var cfg *ini.File
  49. // Setup initialize the configuration instance
  50. func Setup() {
  51. var err error
  52. cfg, err = ini.Load("conf/app.ini")
  53. if err != nil {
  54. log.Fatalf("setting.Setup, fail to parse 'conf/app.ini': %v", err)
  55. }
  56. mapTo("app", AppSetting)
  57. mapTo("server", ServerSetting)
  58. mapTo("database", DatabaseSetting)
  59. mapTo("redis", RedisSetting)
  60. AppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024
  61. ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.Second
  62. ServerSetting.WriteTimeout = ServerSetting.WriteTimeout * time.Second
  63. RedisSetting.IdleTimeout = RedisSetting.IdleTimeout * time.Second
  64. }
  65. // mapTo map section
  66. func mapTo(section string, v interface{}) {
  67. err := cfg.Section(section).MapTo(v)
  68. if err != nil {
  69. log.Fatalf("Cfg.MapTo %s err: %v", section, err)
  70. }
  71. }

当前的目录结构:

  1. go-gin-example
  2. ├── conf
  3. └── app.ini
  4. ├── go.mod
  5. ├── go.sum
  6. ├── middleware
  7. ├── models
  8. ├── pkg
  9. └── setting.go
  10. ├── routers
  11. └── runtime

编写 API 错误码包

建立错误码的e模块,在 go-gin-example 的pkg目录下新建 e 目录(注意新增 replace 配置),新建code.go和msg.go文件,写入内容:

1、 code.go:
F:\Projects\NoobWu\go-samples\go-gin-example\pkg\e\code.go

  1. package e
  2. const (
  3. SUCCESS = 200
  4. ERROR = 500
  5. INVALID_PARAMS = 400
  6. ERROR_EXIST_TAG = 10001
  7. ERROR_EXIST_TAG_FAIL = 10002
  8. ERROR_NOT_EXIST_TAG = 10003
  9. ERROR_GET_TAGS_FAIL = 10004
  10. ERROR_COUNT_TAG_FAIL = 10005
  11. ERROR_ADD_TAG_FAIL = 10006
  12. ERROR_EDIT_TAG_FAIL = 10007
  13. ERROR_DELETE_TAG_FAIL = 10008
  14. ERROR_EXPORT_TAG_FAIL = 10009
  15. ERROR_IMPORT_TAG_FAIL = 10010
  16. ERROR_NOT_EXIST_ARTICLE = 10011
  17. ERROR_CHECK_EXIST_ARTICLE_FAIL = 10012
  18. ERROR_ADD_ARTICLE_FAIL = 10013
  19. ERROR_DELETE_ARTICLE_FAIL = 10014
  20. ERROR_EDIT_ARTICLE_FAIL = 10015
  21. ERROR_COUNT_ARTICLE_FAIL = 10016
  22. ERROR_GET_ARTICLES_FAIL = 10017
  23. ERROR_GET_ARTICLE_FAIL = 10018
  24. ERROR_GEN_ARTICLE_POSTER_FAIL = 10019
  25. ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
  26. ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20004
  27. ERROR_AUTH_TOKEN = 20003
  28. ERROR_AUTH = 20004
  29. ERROR_UPLOAD_SAVE_IMAGE_FAIL = 30001
  30. ERROR_UPLOAD_CHECK_IMAGE_FAIL = 30002
  31. ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003
  32. )

2、 msg.go:
F:\Projects\NoobWu\go-samples\go-gin-example\pkg\e\msg.go

  1. package e
  2. var MsgFlags = map[int]string{
  3. SUCCESS: "ok",
  4. ERROR: "fail",
  5. INVALID_PARAMS: "请求参数错误",
  6. ERROR_EXIST_TAG: "已存在该标签名称",
  7. ERROR_EXIST_TAG_FAIL: "获取已存在标签失败",
  8. ERROR_NOT_EXIST_TAG: "该标签不存在",
  9. ERROR_GET_TAGS_FAIL: "获取所有标签失败",
  10. ERROR_COUNT_TAG_FAIL: "统计标签失败",
  11. ERROR_ADD_TAG_FAIL: "新增标签失败",
  12. ERROR_EDIT_TAG_FAIL: "修改标签失败",
  13. ERROR_DELETE_TAG_FAIL: "删除标签失败",
  14. ERROR_EXPORT_TAG_FAIL: "导出标签失败",
  15. ERROR_IMPORT_TAG_FAIL: "导入标签失败",
  16. ERROR_NOT_EXIST_ARTICLE: "该文章不存在",
  17. ERROR_ADD_ARTICLE_FAIL: "新增文章失败",
  18. ERROR_DELETE_ARTICLE_FAIL: "删除文章失败",
  19. ERROR_CHECK_EXIST_ARTICLE_FAIL: "检查文章是否存在失败",
  20. ERROR_EDIT_ARTICLE_FAIL: "修改文章失败",
  21. ERROR_COUNT_ARTICLE_FAIL: "统计文章失败",
  22. ERROR_GET_ARTICLES_FAIL: "获取多个文章失败",
  23. ERROR_GET_ARTICLE_FAIL: "获取单个文章失败",
  24. ERROR_GEN_ARTICLE_POSTER_FAIL: "生成文章海报失败",
  25. ERROR_AUTH_CHECK_TOKEN_FAIL: "Token鉴权失败",
  26. ERROR_AUTH_CHECK_TOKEN_TIMEOUT: "Token已超时",
  27. ERROR_AUTH_TOKEN: "Token生成失败",
  28. ERROR_AUTH: "Token错误",
  29. ERROR_UPLOAD_SAVE_IMAGE_FAIL: "保存图片失败",
  30. ERROR_UPLOAD_CHECK_IMAGE_FAIL: "检查图片失败",
  31. ERROR_UPLOAD_CHECK_IMAGE_FORMAT: "校验图片错误,图片格式或大小有问题",
  32. }
  33. // GetMsg get error information based on Code
  34. func GetMsg(code int) string {
  35. msg, ok := MsgFlags[code]
  36. if ok {
  37. return msg
  38. }
  39. return MsgFlags[ERROR]
  40. }

编写工具包

go-gin-examplepkg 目录下新建 util目录(注意新增 replace 配置),并拉取 com 的依赖包,如下:

  1. $ go get -u github.com/unknwon/com

编写分页页码的获取方法

util 目录下新建 pagination.go,写入内容:
F:\Projects\NoobWu\go-samples\go-gin-example\pkg\util\pagination.go

  1. package util
  2. import (
  3. "github.com/unknwon/com"
  4. "github.com/gin-gonic/gin"
  5. "github.com/EDDYCJY/go-gin-example/pkg/setting"
  6. )
  7. // GetPage get page parameters
  8. func GetPage(c *gin.Context) int {
  9. result := 0
  10. page := com.StrTo(c.Query("page")).MustInt()
  11. if page > 0 {
  12. result = (page - 1) * setting.AppSetting.PageSize
  13. }
  14. return result
  15. }

编写 models init

拉取 gorm 的依赖包,如下:

  1. $ go get -u github.com/jinzhu/gorm

拉取 mysql 驱动的依赖包,如下:

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

完成后,在 go-gin-examplemodels 目录下新建 models.go ,用于 models 的初始化使用
F:\Projects\NoobWu\go-samples\go-gin-example\models\models.go

  1. package models
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/jinzhu/gorm"
  6. _ "github.com/jinzhu/gorm/dialects/mysql"
  7. "github.com/EDDYCJY/go-gin-example/pkg/setting"
  8. "time"
  9. )
  10. var db *gorm.DB
  11. type Model struct {
  12. ID int `gorm:"primary_key" json:"id"`
  13. CreatedOn int `json:"created_on"`
  14. ModifiedOn int `json:"modified_on"`
  15. DeletedOn int `json:"deleted_on"`
  16. }
  17. // Setup initializes the database instance
  18. func Setup() {
  19. var err error
  20. db, err = gorm.Open(setting.DatabaseSetting.Type, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
  21. setting.DatabaseSetting.User,
  22. setting.DatabaseSetting.Password,
  23. setting.DatabaseSetting.Host,
  24. setting.DatabaseSetting.Name))
  25. if err != nil {
  26. log.Fatalf("models.Setup err: %v", err)
  27. }
  28. gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
  29. return setting.DatabaseSetting.TablePrefix + defaultTableName
  30. }
  31. db.SingularTable(true)
  32. db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
  33. db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
  34. db.Callback().Delete().Replace("gorm:delete", deleteCallback)
  35. db.DB().SetMaxIdleConns(10)
  36. db.DB().SetMaxOpenConns(100)
  37. }
  38. // CloseDB closes database connection (unnecessary)
  39. func CloseDB() {
  40. defer db.Close()
  41. }
  42. // updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating
  43. func updateTimeStampForCreateCallback(scope *gorm.Scope) {
  44. if !scope.HasError() {
  45. nowTime := time.Now().Unix()
  46. if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
  47. if createTimeField.IsBlank {
  48. createTimeField.Set(nowTime)
  49. }
  50. }
  51. if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
  52. if modifyTimeField.IsBlank {
  53. modifyTimeField.Set(nowTime)
  54. }
  55. }
  56. }
  57. }
  58. // updateTimeStampForUpdateCallback will set `ModifiedOn` when updating
  59. func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
  60. if _, ok := scope.Get("gorm:update_column"); !ok {
  61. scope.SetColumn("ModifiedOn", time.Now().Unix())
  62. }
  63. }
  64. // deleteCallback will set `DeletedOn` where deleting
  65. func deleteCallback(scope *gorm.Scope) {
  66. if !scope.HasError() {
  67. var extraOption string
  68. if str, ok := scope.Get("gorm:delete_option"); ok {
  69. extraOption = fmt.Sprint(str)
  70. }
  71. deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedOn")
  72. if !scope.Search.Unscoped && hasDeletedOnField {
  73. scope.Raw(fmt.Sprintf(
  74. "UPDATE %v SET %v=%v%v%v",
  75. scope.QuotedTableName(),
  76. scope.Quote(deletedOnField.DBName),
  77. scope.AddToVars(time.Now().Unix()),
  78. addExtraSpaceIfExist(scope.CombinedConditionSql()),
  79. addExtraSpaceIfExist(extraOption),
  80. )).Exec()
  81. } else {
  82. scope.Raw(fmt.Sprintf(
  83. "DELETE FROM %v%v%v",
  84. scope.QuotedTableName(),
  85. addExtraSpaceIfExist(scope.CombinedConditionSql()),
  86. addExtraSpaceIfExist(extraOption),
  87. )).Exec()
  88. }
  89. }
  90. }
  91. // addExtraSpaceIfExist adds a separator
  92. func addExtraSpaceIfExist(str string) string {
  93. if str != "" {
  94. return " " + str
  95. }
  96. return ""
  97. }

编写项目启动、路由文件

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

编写 Demo

go-gin-example 下建立 main.go 作为启动文件(也就是 main 包),我们先写个Demo,帮助大家理解,写入文件内容:
F:\Projects\NoobWu\go-samples\go-gin-example\main.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "github.com/gin-gonic/gin"
  7. "github.com/EDDYCJY/go-gin-example/models"
  8. "github.com/EDDYCJY/go-gin-example/pkg/gredis"
  9. "github.com/EDDYCJY/go-gin-example/pkg/logging"
  10. "github.com/EDDYCJY/go-gin-example/pkg/setting"
  11. "github.com/EDDYCJY/go-gin-example/routers"
  12. "github.com/EDDYCJY/go-gin-example/pkg/util"
  13. )
  14. func init() {
  15. setting.Setup()
  16. models.Setup()
  17. logging.Setup()
  18. gredis.Setup()
  19. util.Setup()
  20. }
  21. // @title Golang Gin API
  22. // @version 1.0
  23. // @description An example of gin
  24. // @termsOfService https://github.com/EDDYCJY/go-gin-example
  25. // @license.name MIT
  26. // @license.url https://github.com/EDDYCJY/go-gin-example/blob/master/LICENSE
  27. func main() {
  28. gin.SetMode(setting.ServerSetting.RunMode)
  29. routersInit := routers.InitRouter()
  30. readTimeout := setting.ServerSetting.ReadTimeout
  31. writeTimeout := setting.ServerSetting.WriteTimeout
  32. endPoint := fmt.Sprintf(":%d", setting.ServerSetting.HttpPort)
  33. maxHeaderBytes := 1 << 20
  34. server := &http.Server{
  35. Addr: endPoint,
  36. Handler: routersInit,
  37. ReadTimeout: readTimeout,
  38. WriteTimeout: writeTimeout,
  39. MaxHeaderBytes: maxHeaderBytes,
  40. }
  41. log.Printf("[info] start http server listening %s", endPoint)
  42. server.ListenAndServe()
  43. // If you want Graceful Restart, you need a Unix system and download github.com/fvbock/endless
  44. //endless.DefaultReadTimeOut = readTimeout
  45. //endless.DefaultWriteTimeOut = writeTimeout
  46. //endless.DefaultMaxHeaderBytes = maxHeaderBytes
  47. //server := endless.NewServer(endPoint, routersInit)
  48. //server.BeforeBegin = func(add string) {
  49. // log.Printf("Actual pid is %d", syscall.Getpid())
  50. //}
  51. //
  52. //err := server.ListenAndServe()
  53. //if err != nil {
  54. // log.Printf("Server err: %v", err)
  55. //}
  56. }

执行go run main.go,查看命令行是否显示

  1. PS F:\Projects\NoobWu\go-samples\go-gin-example> go run .\main.go

在本机执行curl 127.0.0.1:8000/test,检查是否返回{“message”:”test”}。

  1. Invoke-WebRequest -Uri "http://127.0.0.1:8080/test" -UseBasicParsing

知识点

那么,我们来延伸一下 Demo 所涉及的知识点!

标准库

  • fmt:实现了类似 C 语言 **printf****scanf** 的格式化 I/O。格式化动作(‘**verb**’)源自 C 语言但更简单
  • net/http:提供了 HTTP 客户端和服务端的实现

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{}
  • gin.Context:Context是gin中的上下文,它允许我们在中间件之间传递变量、管理流、验证 JSON 请求、响应 JSON 请求等,在gin中包含大量Context的方法,例如我们常用的DefaultQuery、Query、DefaultPostForm、PostForm等等

&http.Server 和 ListenAndServe?

1、http.Server:

  1. type Server struct {
  2. Addr string
  3. Handler Handler
  4. TLSConfig *tls.Config
  5. ReadTimeout time.Duration
  6. ReadHeaderTimeout time.Duration
  7. WriteTimeout time.Duration
  8. IdleTimeout time.Duration
  9. MaxHeaderBytes int
  10. ConnState func(net.Conn, ConnState)
  11. ErrorLog *log.Logger
  12. }
  • Addr:监听的 TCP 地址,格式为:8000
  • Handler:http 句柄,实质为ServeHTTP,用于处理程序响应 HTTP 请求
  • TLSConfig:安全传输层协议(TLS)的配置
  • ReadTimeout:允许读取的最大时间
  • ReadHeaderTimeout:允许读取请求头的最大时间
  • WriteTimeout:允许写入的最大时间
  • IdleTimeout:等待的最大时间
  • MaxHeaderBytes:请求头的最大字节数
  • ConnState:指定一个可选的回调函数,当客户端连接发生变化时调用
  • ErrorLog:指定一个可选的日志记录器,用于接收程序的意外行为和底层系统错误;如果未设置或为nil则默认以日志包的标准日志记录器完成(也就是在控制台输出)

2、 ListenAndServe:

  1. func (srv *Server) ListenAndServe() error {
  2. addr := srv.Addr
  3. if addr == "" {
  4. addr = ":http"
  5. }
  6. ln, err := net.Listen("tcp", addr)
  7. if err != nil {
  8. return err
  9. }
  10. return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
  11. }

开始监听服务,监听 TCP 网络地址,Addr 和调用应用程序处理连接上的请求。

我们在源码中看到 Addr 是调用我们在 &http.Server 中设置的参数,因此我们在设置时要用 & ,我们要改变参数的值,因为我们 **ListenAndServe** 和其他一些方法需要用到 &http.Server 中的参数,他们是相互影响的。

3、 http.ListenAndServe连载一r.Run() 有区别吗?

我们看看r.Run的实现:

  1. func (engine *Engine) Run(addr ...string) (err error) {
  2. defer func() { debugPrintError(err) }()
  3. address := resolveAddress(addr)
  4. debugPrint("Listening and serving HTTP on %s\n", address)
  5. err = http.ListenAndServe(address, engine)
  6. return
  7. }

通过分析源码,得知本质上没有区别,同时也得知了启动 gin 时的监听 debug 信息在这里输出。

4、 为什么 Demo 里会有WARNING?
首先我们可以看下Default()的实现

  1. // Default returns an Engine instance with the Logger and Recovery middleware already attached.
  2. func Default() *Engine {
  3. debugPrintWARNINGDefault()
  4. engine := New()
  5. engine.Use(Logger(), Recovery())
  6. return engine
  7. }

大家可以看到默认情况下,已经附加了日志、恢复中间件的引擎实例。并且在开头调用了debugPrintWARNINGDefault(),而它的实现就是输出该行日志

  1. func debugPrintWARNINGDefault() {
  2. debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
  3. `)
  4. }

而另外一个 Running in "debug" mode. Switch to "release" mode in production. ,是运行模式原因,并不难理解,已在配置文件的管控下 :-),运维人员随时就可以修改它的配置。

5、 Demo 的 router.GET等路由规则可以不写在main包中吗?
我们发现router.GET等路由规则,在 Demo 中被编写在了main包中,感觉很奇怪,我们去抽离这部分逻辑!
go-gin-examplerouters目录新建router.go文件,写入内容:
F:\Projects\NoobWu\go-samples\go-gin-example\routers\router.go

  1. package routers
  2. import (
  3. "net/http"
  4. "github.com/gin-gonic/gin"
  5. _ "github.com/EDDYCJY/go-gin-example/docs"
  6. "github.com/swaggo/gin-swagger"
  7. "github.com/swaggo/gin-swagger/swaggerFiles"
  8. "github.com/EDDYCJY/go-gin-example/middleware/jwt"
  9. "github.com/EDDYCJY/go-gin-example/pkg/export"
  10. "github.com/EDDYCJY/go-gin-example/pkg/qrcode"
  11. "github.com/EDDYCJY/go-gin-example/pkg/upload"
  12. "github.com/EDDYCJY/go-gin-example/routers/api"
  13. "github.com/EDDYCJY/go-gin-example/routers/api/v1"
  14. )
  15. // InitRouter initialize routing information
  16. func InitRouter() *gin.Engine {
  17. r := gin.New()
  18. r.Use(gin.Logger())
  19. r.Use(gin.Recovery())
  20. r.StaticFS("/export", http.Dir(export.GetExcelFullPath()))
  21. r.StaticFS("/upload/images", http.Dir(upload.GetImageFullPath()))
  22. r.StaticFS("/qrcode", http.Dir(qrcode.GetQrCodeFullPath()))
  23. r.POST("/auth", api.GetAuth)
  24. r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  25. r.POST("/upload", api.UploadImage)
  26. apiv1 := r.Group("/api/v1")
  27. apiv1.Use(jwt.JWT())
  28. {
  29. //获取标签列表
  30. apiv1.GET("/tags", v1.GetTags)
  31. //新建标签
  32. apiv1.POST("/tags", v1.AddTag)
  33. //更新指定标签
  34. apiv1.PUT("/tags/:id", v1.EditTag)
  35. //删除指定标签
  36. apiv1.DELETE("/tags/:id", v1.DeleteTag)
  37. //导出标签
  38. r.POST("/tags/export", v1.ExportTag)
  39. //导入标签
  40. r.POST("/tags/import", v1.ImportTag)
  41. //获取文章列表
  42. apiv1.GET("/articles", v1.GetArticles)
  43. //获取指定文章
  44. apiv1.GET("/articles/:id", v1.GetArticle)
  45. //新建文章
  46. apiv1.POST("/articles", v1.AddArticle)
  47. //更新指定文章
  48. apiv1.PUT("/articles/:id", v1.EditArticle)
  49. //删除指定文章
  50. apiv1.DELETE("/articles/:id", v1.DeleteArticle)
  51. //生成文章海报
  52. apiv1.POST("/articles/poster/generate", v1.GenerateArticlePoster)
  53. }
  54. return r
  55. }

修改 main.go 的文件内容:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/EDDYCJY/go-gin-example/routers"
  6. "github.com/EDDYCJY/go-gin-example/pkg/setting"
  7. )
  8. func main() {
  9. router := routers.InitRouter()
  10. s := &http.Server{
  11. Addr: fmt.Sprintf(":%d", setting.HTTPPort),
  12. Handler: router,
  13. ReadTimeout: setting.ReadTimeout,
  14. WriteTimeout: setting.WriteTimeout,
  15. MaxHeaderBytes: 1 << 20,
  16. }
  17. s.ListenAndServe()
  18. }

当前目录结构:

  1. go-gin-example/
  2. ├── conf
  3. └── app.ini
  4. ├── main.go
  5. ├── middleware
  6. ├── models
  7. └── models.go
  8. ├── pkg
  9. ├── e
  10. ├── code.go
  11. └── msg.go
  12. ├── setting
  13. └── setting.go
  14. └── util
  15. └── pagination.go
  16. ├── routers
  17. └── router.go
  18. ├── runtime

重启服务,执行 curl 127.0.0.1:8000/test查看是否正确返回。

下一节,我们将以我们的 Demo 为起点进行修改,开始编码!

参考

本系列示例代码

原文链接

https://eddycjy.com/posts/go/gin/2018-02-11-api-01/