目前做web api项目的语言主要有php、java、python以及go等,每种语言都有很多框架来方便我们快速构建项目,这就是框架存在的意义,有了框架,我们就不必每个项目都重写很多不必要的代码,只需要专注业务逻辑,有人说有了框架,接下来程序员只需要面向过程写代码就可以了,这话一定程度上是对,但是,如果要考虑系统业务代码的扩展性、复用性以及可维护性,在实际编码的过程中,还是要坚持OOP编程思想,及时冗余代码,以免积重难返。
1 web框架梳理
language | framework |
---|---|
PHP | laravel,yii,lumen,ci、zend、symfony |
Java | SSH, Springboot |
python | djongo、flask |
go | gin、beego、echo、Iris |
这是目前比较常见的框架,java相关的没接触过,其他的都有接触,其实本质上都是一样的,mvc,组件化等等,如果深入研究透彻一款,其他的只是语言实现的区别。
2 包管理工具
这是现代编程语言必备的工具,要不然管理各种依赖包及其版本,肯定是灾难。
language | package tools |
---|---|
js | npm |
java | maven |
php | composer |
python | pip3 |
ruby | rubygem |
go | go.mod (开启GO111MODULE) |
3 Gin框架
3.1 初始化test项目
go mod init test
//go.mod文件
module test
go 1.15
3.2 install gin framework
go get
//go.mod 文件
module test
go 1.15
require (
github.com/gin-gonic/gin v1.6.3 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/ugorji/go v1.1.13 // indirect
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
从go.mod文件中看到gin 1.6.3版本以及其他核心库信息,同时生成了go.sum文件,其记录每个依赖库的版本和哈希值,目的在于分布式包管理的一致性和安全性。
3.3 创建服务并启动
package main
import "github.com/gin-gonic/gin"
import "net/http"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "hello word")
})
r.Run() // listen and serve on 0.0.0.0:8080
}
//启动服务
$ 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 /ping --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
3.4 参数接收
方法 | 获取 |
---|---|
GET | c.DefaultQuery(“name”, “Guest”) 、c.Query(“name”) |
POST | name := c.PostForm(“name”) |
File | file, _ := c.FormFile(“file”) |
3.5 定义路由及路由分组
router := gin.Default()
router.get("/ping", func(c *gin.Context) {})
v1 := r.Group("/v1")
{
v1.GET("/getuser", func(c *gin.Context) {
c.String(http.StatusOK, "jack")
})
}
//访问方式
http://localhost:8080/v1/getuser
//返回
jack
3.6 中间件
中间件技术能有效解决很多问题,想想laravel框架的中间件就知道,比如token验证、返回值格式化等
gin默认启动方式下,包含Logger、Recovery中间件
func Default() *Engine {
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
//定义一个在请求响应header中写入请求id的中间件
func RequestIdMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("X-Request-GinId", uuid.NewV4().String())
c.Next()
}
}
//使用
r := gin.Default()
r.Use(RequestIdMiddleware())
//在请求头里就会有X-Request-GinId项
X-Request-Ginid: c5d2d11e-891b-4299-8021-cb9298129b3f
3.7 日志收集
使用gin.DefaultWriter 或者 logrus日志库,当然你可以自定义日志格式,满足业务需求
logfile, err := os.Create("./gin_http.log")
if err != nil {
fmt.Println("Could not create log file")
}
gin.SetMode(gin.DebugMode)
gin.DefaultWriter = io.MultiWriter(logfile)
//log file
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] GET /v1/getuser --> main.main.func2 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
如果使用logrus日志库,请参考相关文档【logrus 日志库】,使用很方便,简捷。
3.8 代码即时生效工具Air
一般的,这种工具都是开发时使用,例如beego里的bee,gin的air等
go get -u github.com/cosmtrek/air
在项目目录touch .air.conf,并写入以下参考配置文件
# [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件
# 工作目录
# 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下
root = "."
tmp_dir = "tmp"
[build]
# 只需要写你平常编译使用的shell命令。你也可以使用 `make`
cmd = "go build -o ./tmp/main.exe ."
# 由`cmd`命令得到的二进制文件名
bin = "tmp/main.exe"
# 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release
full_bin = "SET APP_ENV=dev & SET APP_USER=air & .\tmp\main.exe"
# 监听以下文件扩展名的文件.
include_ext = ["go", "tpl", "tmpl", "html"]
# 忽略这些文件扩展名或目录
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# 监听以下指定目录的文件
include_dir = []
# 排除以下文件
exclude_file = []
# 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间
delay = 1000 # ms
# 发生构建错误时,停止运行旧的二进制文件。
stop_on_error = true
# air的日志文件名,该日志文件放置在你的`tmp_dir`中
log = "air_errors.log"
[log]
# 显示日志时间
time = true
[color]
# 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
# 退出时删除tmp目录
clean_on_exit = true
利用air命令启动项目,后续代码的修改,会自动编译生效。
3.9 模型绑定校验
gin/binding 内置模型绑定实现,将请求数据提取到合适的绑定器,Gin 主要提供了两组绑定方法 Must bind 与 Should bind 。前者使用框架自带的处理机制,基本上验证不通过,就会被终止或抛出特定的错误页面。后者存在绑定错误,这个错误会被返回, 需要开发者去处理相应的请求和错误,具备灵活性。两者都提供了Bind, BindJSON, BindXML, BindQuery, BindYAML等绑定方法,可以根据场景判定使用。
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
//endpoint中,绑定query参数
var person Person
if c.BindQuery(&person) == nil {
}
3.10 模板渲染
新建templates目录,并新建index.html文件
<html>
<h1>
{{.title}}
</h1>
</html>
//define router
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
})
http://localhost:8080/index
即可显示模板信息 Main website
现代的模板都是公共的header,footer,中间嵌套不同页面的不同内容,分目录组织模板文件是绝对必要的,当然现在基本上不用从头写了,直接找现成的模板改造使用。
3.11 多服务启动
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
r1 := gin.New()
r1.Use(gin.Recovery())
r1.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return r1
}
func router02() http.Handler {
r2 := gin.New()
r2.Use(gin.Recovery())
r2.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return r2
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
3.12 测试
包net/http/httptest是进行api接口测试首选
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, "pong", w.Body.String())
3.13 服务的不停服启停
router := gin.Default()
router.GET("/", handler)
// [...]
endless.ListenAndServe(":4242", router)
或者
manners:一个Go HTTP服务器,能优雅的关闭
graceful:Graceful是一个go的包,支持优雅地关闭http.Handler服务器
grace:对Go服务器进行优雅的重启和零停机部署
参考文档
1 go mod
2 go 基本知识点总结
3 go 中文文档