思考
首先,在一个初始项目开始前,大家都要思考一下
- 程序的文本配置写在代码中,好吗?
- API 的错误码硬编码在程序中,合适吗?
- db 句柄谁都去Open,没有统一管理,好吗?
- 获取分页等公共参数,谁都自己写一套逻辑,好吗?
显然在较正规的项目中,这些问题的答案都是不可以,为了解决这些问题,我们挑选一款读写配置文件的库,目前比较火的有 viper,有兴趣你未来可以简单了解一下,没兴趣的话等以后接触到再说。
但是本系列选用 go-ini/ini ,它的 中文文档。大家是必须需要要简单阅读它的文档,再接着完成后面的内容。
本文目标
- 编写一个简单的 API 错误码包。
- 完成一个 Demo 示例。
- 讲解 Demo 所涉及的知识点。
介绍和初始化项目
初始化项目目录
在前一章节中,我们初始化了一个 go-gin-example 项目,接下来我们需要继续新增如下目录结构:
go-gin-example/
├── conf
├── middleware
├── models
├── pkg
├── routers
└── runtime
- conf:用于存储配置文件
- middleware:应用中间件
- models:应用数据库模型
- pkg:第三方包
- routers 路由逻辑处理
- runtime:应用运行时数据
添加 Go Modules Replace
打开 go.mod 文件,新增 replace 配置项,如下:
module github.com/EDDYCJY/go-gin-example
go 1.17
require (
github.com/360EntSecGroup-Skylar/excelize v1.4.1
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/astaxie/beego v1.12.3
github.com/boombuler/barcode v1.0.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.7.4
github.com/go-ini/ini v1.63.2
)
可能你会不理解为什么要特意跑来加 replace 配置项,首先你要看到我们使用的是完整的外部模块引用路径(github.com/EDDYCJY/go-gin-example/xxx),而这个模块还没推送到远程,是没有办法下载下来的,因此需要用 replace 将其指定读取本地的模块路径,这样子就可以解决本地模块读取的问题。
注:后续每新增一个本地应用目录,你都需要主动去 go.mod 文件里新增一条 replace(我不会提醒你),如果你漏了,那么编译时会出现报错,找不到那个模块。
初始项目数据库
新建 blog 数据库,编码为 utf8_general_ci
,在 blog
数据库下,新建以下表
1、 标签表
-- ----------------------------
-- Table structure for blog_tag
-- ----------------------------
DROP TABLE IF EXISTS `blog_tag`;
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' COMMENT '删除时间',
`state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用、1为启用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章标签管理';
2、 文章表
-- ----------------------------
-- Table structure for blog_article
-- ----------------------------
DROP TABLE IF EXISTS `blog_article`;
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 COMMENT '内容',
`cover_image_url` varchar(255) 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(255) DEFAULT '' COMMENT '修改人',
`deleted_on` int(10) unsigned DEFAULT '0',
`state` tinyint(3) unsigned DEFAULT '1' COMMENT '删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章管理';
3、 认证表
-- ----------------------------
-- Table structure for blog_auth
-- ----------------------------
DROP TABLE IF EXISTS `blog_auth`;
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 AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `blog_auth` (`id`, `username`, `password`) VALUES ('1', 'test', 'test123');
编写项目配置包
在 go-gin-example 应用目录下,拉取 go-ini/ini 的依赖包,如下:
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
[app]
PageSize = 10
JwtSecret = 233
PrefixUrl = http://127.0.0.1:8000
RuntimeRootPath = runtime/
ImageSavePath = upload/images/
# MB
ImageMaxSize = 5
ImageAllowExts = .jpg,.jpeg,.png
ExportSavePath = export/
QrCodeSavePath = qrcode/
FontSavePath = fonts/
LogSavePath = logs/
LogSaveName = log
LogFileExt = log
TimeFormat = 20060102
[server]
#debug or release
RunMode = debug
HttpPort = 8000
ReadTimeout = 60
WriteTimeout = 60
[database]
Type = mysql
User = root
Password = 123456
Host = 127.0.0.1:3306
Name = blog
TablePrefix = blog_
[redis]
Host = 127.0.0.1:6379
Password =
MaxIdle = 30
MaxActive = 30
IdleTimeout = 200
建立调用配置的 setting 模块,在 go-gin-example 的 pkg目录下新建setting目录(注意新增 replace 配置),新建 setting.go 文件,写入内容:D:\Projects\Github\NoobWu\go-samples\go-gin-example\pkg\setting\setting.go
package setting
import (
"log"
"time"
"github.com/go-ini/ini"
)
type App struct {
JwtSecret string
PageSize int
PrefixUrl string
RuntimeRootPath string
ImageSavePath string
ImageMaxSize int
ImageAllowExts []string
ExportSavePath string
QrCodeSavePath string
FontSavePath string
LogSavePath string
LogSaveName string
LogFileExt string
TimeFormat string
}
var AppSetting = &App{}
type Server struct {
RunMode string
HttpPort int
ReadTimeout time.Duration
WriteTimeout time.Duration
}
var ServerSetting = &Server{}
type Database struct {
Type string
User string
Password string
Host string
Name string
TablePrefix string
}
var DatabaseSetting = &Database{}
type Redis struct {
Host string
Password string
MaxIdle int
MaxActive int
IdleTimeout time.Duration
}
var RedisSetting = &Redis{}
var cfg *ini.File
// Setup initialize the configuration instance
func Setup() {
var err error
cfg, err = ini.Load("conf/app.ini")
if err != nil {
log.Fatalf("setting.Setup, fail to parse 'conf/app.ini': %v", err)
}
mapTo("app", AppSetting)
mapTo("server", ServerSetting)
mapTo("database", DatabaseSetting)
mapTo("redis", RedisSetting)
AppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024
ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.Second
ServerSetting.WriteTimeout = ServerSetting.WriteTimeout * time.Second
RedisSetting.IdleTimeout = RedisSetting.IdleTimeout * time.Second
}
// mapTo map section
func mapTo(section string, v interface{}) {
err := cfg.Section(section).MapTo(v)
if err != nil {
log.Fatalf("Cfg.MapTo %s err: %v", section, err)
}
}
当前的目录结构:
go-gin-example
├── conf
│ └── app.ini
├── go.mod
├── go.sum
├── middleware
├── models
├── pkg
│ └── setting.go
├── routers
└── 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
package e
const (
SUCCESS = 200
ERROR = 500
INVALID_PARAMS = 400
ERROR_EXIST_TAG = 10001
ERROR_EXIST_TAG_FAIL = 10002
ERROR_NOT_EXIST_TAG = 10003
ERROR_GET_TAGS_FAIL = 10004
ERROR_COUNT_TAG_FAIL = 10005
ERROR_ADD_TAG_FAIL = 10006
ERROR_EDIT_TAG_FAIL = 10007
ERROR_DELETE_TAG_FAIL = 10008
ERROR_EXPORT_TAG_FAIL = 10009
ERROR_IMPORT_TAG_FAIL = 10010
ERROR_NOT_EXIST_ARTICLE = 10011
ERROR_CHECK_EXIST_ARTICLE_FAIL = 10012
ERROR_ADD_ARTICLE_FAIL = 10013
ERROR_DELETE_ARTICLE_FAIL = 10014
ERROR_EDIT_ARTICLE_FAIL = 10015
ERROR_COUNT_ARTICLE_FAIL = 10016
ERROR_GET_ARTICLES_FAIL = 10017
ERROR_GET_ARTICLE_FAIL = 10018
ERROR_GEN_ARTICLE_POSTER_FAIL = 10019
ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20004
ERROR_AUTH_TOKEN = 20003
ERROR_AUTH = 20004
ERROR_UPLOAD_SAVE_IMAGE_FAIL = 30001
ERROR_UPLOAD_CHECK_IMAGE_FAIL = 30002
ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003
)
2、 msg.go:F:\Projects\NoobWu\go-samples\go-gin-example\pkg\e\msg.go
package e
var MsgFlags = map[int]string{
SUCCESS: "ok",
ERROR: "fail",
INVALID_PARAMS: "请求参数错误",
ERROR_EXIST_TAG: "已存在该标签名称",
ERROR_EXIST_TAG_FAIL: "获取已存在标签失败",
ERROR_NOT_EXIST_TAG: "该标签不存在",
ERROR_GET_TAGS_FAIL: "获取所有标签失败",
ERROR_COUNT_TAG_FAIL: "统计标签失败",
ERROR_ADD_TAG_FAIL: "新增标签失败",
ERROR_EDIT_TAG_FAIL: "修改标签失败",
ERROR_DELETE_TAG_FAIL: "删除标签失败",
ERROR_EXPORT_TAG_FAIL: "导出标签失败",
ERROR_IMPORT_TAG_FAIL: "导入标签失败",
ERROR_NOT_EXIST_ARTICLE: "该文章不存在",
ERROR_ADD_ARTICLE_FAIL: "新增文章失败",
ERROR_DELETE_ARTICLE_FAIL: "删除文章失败",
ERROR_CHECK_EXIST_ARTICLE_FAIL: "检查文章是否存在失败",
ERROR_EDIT_ARTICLE_FAIL: "修改文章失败",
ERROR_COUNT_ARTICLE_FAIL: "统计文章失败",
ERROR_GET_ARTICLES_FAIL: "获取多个文章失败",
ERROR_GET_ARTICLE_FAIL: "获取单个文章失败",
ERROR_GEN_ARTICLE_POSTER_FAIL: "生成文章海报失败",
ERROR_AUTH_CHECK_TOKEN_FAIL: "Token鉴权失败",
ERROR_AUTH_CHECK_TOKEN_TIMEOUT: "Token已超时",
ERROR_AUTH_TOKEN: "Token生成失败",
ERROR_AUTH: "Token错误",
ERROR_UPLOAD_SAVE_IMAGE_FAIL: "保存图片失败",
ERROR_UPLOAD_CHECK_IMAGE_FAIL: "检查图片失败",
ERROR_UPLOAD_CHECK_IMAGE_FORMAT: "校验图片错误,图片格式或大小有问题",
}
// GetMsg get error information based on Code
func GetMsg(code int) string {
msg, ok := MsgFlags[code]
if ok {
return msg
}
return MsgFlags[ERROR]
}
编写工具包
在 go-gin-example
的 pkg
目录下新建 util
目录(注意新增 replace 配置),并拉取 com
的依赖包,如下:
$ go get -u github.com/unknwon/com
编写分页页码的获取方法
在 util
目录下新建 pagination.go
,写入内容:F:\Projects\NoobWu\go-samples\go-gin-example\pkg\util\pagination.go
package util
import (
"github.com/unknwon/com"
"github.com/gin-gonic/gin"
"github.com/EDDYCJY/go-gin-example/pkg/setting"
)
// GetPage get page parameters
func GetPage(c *gin.Context) int {
result := 0
page := com.StrTo(c.Query("page")).MustInt()
if page > 0 {
result = (page - 1) * setting.AppSetting.PageSize
}
return result
}
编写 models init
拉取 gorm
的依赖包,如下:
$ go get -u github.com/jinzhu/gorm
拉取 mysql
驱动的依赖包,如下:
$ go get -u github.com/go-sql-driver/mysql
完成后,在 go-gin-example
的 models
目录下新建 models.go
,用于 models
的初始化使用F:\Projects\NoobWu\go-samples\go-gin-example\models\models.go
package models
import (
"fmt"
"log"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/EDDYCJY/go-gin-example/pkg/setting"
"time"
)
var db *gorm.DB
type Model struct {
ID int `gorm:"primary_key" json:"id"`
CreatedOn int `json:"created_on"`
ModifiedOn int `json:"modified_on"`
DeletedOn int `json:"deleted_on"`
}
// Setup initializes the database instance
func Setup() {
var err error
db, err = gorm.Open(setting.DatabaseSetting.Type, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
setting.DatabaseSetting.User,
setting.DatabaseSetting.Password,
setting.DatabaseSetting.Host,
setting.DatabaseSetting.Name))
if err != nil {
log.Fatalf("models.Setup err: %v", err)
}
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return setting.DatabaseSetting.TablePrefix + defaultTableName
}
db.SingularTable(true)
db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
db.Callback().Delete().Replace("gorm:delete", deleteCallback)
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
}
// CloseDB closes database connection (unnecessary)
func CloseDB() {
defer db.Close()
}
// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
if !scope.HasError() {
nowTime := time.Now().Unix()
if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
if createTimeField.IsBlank {
createTimeField.Set(nowTime)
}
}
if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
if modifyTimeField.IsBlank {
modifyTimeField.Set(nowTime)
}
}
}
}
// updateTimeStampForUpdateCallback will set `ModifiedOn` when updating
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
if _, ok := scope.Get("gorm:update_column"); !ok {
scope.SetColumn("ModifiedOn", time.Now().Unix())
}
}
// deleteCallback will set `DeletedOn` where deleting
func deleteCallback(scope *gorm.Scope) {
if !scope.HasError() {
var extraOption string
if str, ok := scope.Get("gorm:delete_option"); ok {
extraOption = fmt.Sprint(str)
}
deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedOn")
if !scope.Search.Unscoped && hasDeletedOnField {
scope.Raw(fmt.Sprintf(
"UPDATE %v SET %v=%v%v%v",
scope.QuotedTableName(),
scope.Quote(deletedOnField.DBName),
scope.AddToVars(time.Now().Unix()),
addExtraSpaceIfExist(scope.CombinedConditionSql()),
addExtraSpaceIfExist(extraOption),
)).Exec()
} else {
scope.Raw(fmt.Sprintf(
"DELETE FROM %v%v%v",
scope.QuotedTableName(),
addExtraSpaceIfExist(scope.CombinedConditionSql()),
addExtraSpaceIfExist(extraOption),
)).Exec()
}
}
}
// addExtraSpaceIfExist adds a separator
func addExtraSpaceIfExist(str string) string {
if str != "" {
return " " + str
}
return ""
}
编写项目启动、路由文件
最基础的准备工作完成啦,让我们开始编写 Demo 吧!
编写 Demo
在 go-gin-example
下建立 main.go
作为启动文件(也就是 main
包),我们先写个Demo,帮助大家理解,写入文件内容:F:\Projects\NoobWu\go-samples\go-gin-example\main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/EDDYCJY/go-gin-example/models"
"github.com/EDDYCJY/go-gin-example/pkg/gredis"
"github.com/EDDYCJY/go-gin-example/pkg/logging"
"github.com/EDDYCJY/go-gin-example/pkg/setting"
"github.com/EDDYCJY/go-gin-example/routers"
"github.com/EDDYCJY/go-gin-example/pkg/util"
)
func init() {
setting.Setup()
models.Setup()
logging.Setup()
gredis.Setup()
util.Setup()
}
// @title Golang Gin API
// @version 1.0
// @description An example of gin
// @termsOfService https://github.com/EDDYCJY/go-gin-example
// @license.name MIT
// @license.url https://github.com/EDDYCJY/go-gin-example/blob/master/LICENSE
func main() {
gin.SetMode(setting.ServerSetting.RunMode)
routersInit := routers.InitRouter()
readTimeout := setting.ServerSetting.ReadTimeout
writeTimeout := setting.ServerSetting.WriteTimeout
endPoint := fmt.Sprintf(":%d", setting.ServerSetting.HttpPort)
maxHeaderBytes := 1 << 20
server := &http.Server{
Addr: endPoint,
Handler: routersInit,
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
MaxHeaderBytes: maxHeaderBytes,
}
log.Printf("[info] start http server listening %s", endPoint)
server.ListenAndServe()
// If you want Graceful Restart, you need a Unix system and download github.com/fvbock/endless
//endless.DefaultReadTimeOut = readTimeout
//endless.DefaultWriteTimeOut = writeTimeout
//endless.DefaultMaxHeaderBytes = maxHeaderBytes
//server := endless.NewServer(endPoint, routersInit)
//server.BeforeBegin = func(add string) {
// log.Printf("Actual pid is %d", syscall.Getpid())
//}
//
//err := server.ListenAndServe()
//if err != nil {
// log.Printf("Server err: %v", err)
//}
}
执行go run main.go
,查看命令行是否显示
PS F:\Projects\NoobWu\go-samples\go-gin-example> go run .\main.go
在本机执行curl 127.0.0.1:8000/test,检查是否返回{“message”:”test”}。
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:
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
}
- Addr:监听的 TCP 地址,格式为:8000
- Handler:http 句柄,实质为ServeHTTP,用于处理程序响应 HTTP 请求
- TLSConfig:安全传输层协议(TLS)的配置
- ReadTimeout:允许读取的最大时间
- ReadHeaderTimeout:允许读取请求头的最大时间
- WriteTimeout:允许写入的最大时间
- IdleTimeout:等待的最大时间
- MaxHeaderBytes:请求头的最大字节数
- ConnState:指定一个可选的回调函数,当客户端连接发生变化时调用
- ErrorLog:指定一个可选的日志记录器,用于接收程序的意外行为和底层系统错误;如果未设置或为nil则默认以日志包的标准日志记录器完成(也就是在控制台输出)
2、 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
中的参数,他们是相互影响的。
3、 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 信息在这里输出。
4、 为什么 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.
,是运行模式原因,并不难理解,已在配置文件的管控下 :-),运维人员随时就可以修改它的配置。
5、 Demo 的 router.GET
等路由规则可以不写在main包中吗?
我们发现router.GET等路由规则,在 Demo 中被编写在了main包中,感觉很奇怪,我们去抽离这部分逻辑!
在go-gin-example
下routers
目录新建router.go
文件,写入内容:F:\Projects\NoobWu\go-samples\go-gin-example\routers\router.go
package routers
import (
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/EDDYCJY/go-gin-example/docs"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"github.com/EDDYCJY/go-gin-example/middleware/jwt"
"github.com/EDDYCJY/go-gin-example/pkg/export"
"github.com/EDDYCJY/go-gin-example/pkg/qrcode"
"github.com/EDDYCJY/go-gin-example/pkg/upload"
"github.com/EDDYCJY/go-gin-example/routers/api"
"github.com/EDDYCJY/go-gin-example/routers/api/v1"
)
// InitRouter initialize routing information
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.StaticFS("/export", http.Dir(export.GetExcelFullPath()))
r.StaticFS("/upload/images", http.Dir(upload.GetImageFullPath()))
r.StaticFS("/qrcode", http.Dir(qrcode.GetQrCodeFullPath()))
r.POST("/auth", api.GetAuth)
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.POST("/upload", api.UploadImage)
apiv1 := r.Group("/api/v1")
apiv1.Use(jwt.JWT())
{
//获取标签列表
apiv1.GET("/tags", v1.GetTags)
//新建标签
apiv1.POST("/tags", v1.AddTag)
//更新指定标签
apiv1.PUT("/tags/:id", v1.EditTag)
//删除指定标签
apiv1.DELETE("/tags/:id", v1.DeleteTag)
//导出标签
r.POST("/tags/export", v1.ExportTag)
//导入标签
r.POST("/tags/import", v1.ImportTag)
//获取文章列表
apiv1.GET("/articles", v1.GetArticles)
//获取指定文章
apiv1.GET("/articles/:id", v1.GetArticle)
//新建文章
apiv1.POST("/articles", v1.AddArticle)
//更新指定文章
apiv1.PUT("/articles/:id", v1.EditArticle)
//删除指定文章
apiv1.DELETE("/articles/:id", v1.DeleteArticle)
//生成文章海报
apiv1.POST("/articles/poster/generate", v1.GenerateArticlePoster)
}
return r
}
修改 main.go
的文件内容:
package main
import (
"fmt"
"net/http"
"github.com/EDDYCJY/go-gin-example/routers"
"github.com/EDDYCJY/go-gin-example/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()
}
当前目录结构:
go-gin-example/
├── 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 为起点进行修改,开始编码!