└── README.md /README.md: -------------------------------------------------------------------------------- 1 | - [安装与配置](#install) 2 | - [框架架构](#arch) 3 | - [生命周期](#life-circle) 4 | - [Context](#context) 5 | - [路由](#router) 6 | - [基本路由](#basic-router) 7 | - [路由参数](#router-param) 8 | - [路由群组](#router-group) 9 | - [控制器](#controller) 10 | - [请求](#request) 11 | - [请求头](#request-header) 12 | - [Cookies](#request-cookie) 13 | - [上传文件](#upload) 14 | - [响应](#response) 15 | - [响应头](#response-header) 16 | - [附加Cookie](#response-cookie) 17 | - [字符串响应](#response-string) 18 | - [JSON响应](#response-json) 19 | - [视图响应](#response-view) 20 | - [文件下载](#response-file) 21 | - [重定向](#response-redirect) 22 | - [同步异步](#sync-async) 23 | - [视图](#view) 24 | - [传参](#view-param) 25 | - [视图组件](#view-unit) 26 | - [中间件](#middleware) 27 | - [分类使用](#middleware-use) 28 | - [创建中间件](#middleware-create) 29 | - [中间件参数](#middleware-param) 30 | - [数据库](#db) 31 | - [Mongodb](#db-mongodb) 32 | - [Mysql](#db-mysql) 33 | - [ORM](#orm) 34 | - [扩展包](#extensions) 35 | - [常用方法](#functions) 36 | - [gin]() 37 | - [Context]() 38 | 39 | 40 | ### 安装与配置 41 | 安装: 42 | 43 | ```sh 44 | $ go get gopkg.in/gin-gonic/gin.v1 45 | ``` 46 | ` 47 | 注意:确保 GOPATH GOROOT 已经配置 48 | ` 49 | 50 | 导入: 51 | ```go 52 | import "gopkg.in/gin-gonic/gin.v1" 53 | ``` 54 | 55 | 56 | 57 | 58 | ### 框架架构 59 | 60 | 61 | - HTTP 服务器 62 | 63 | **1.默认服务器** 64 | 65 | ``` 66 | router.Run() 67 | ``` 68 | 69 | **2.HTTP 服务器** 70 | 71 | 除了默认服务器中 `router.Run()` 的方式外,还可以用 `http.ListenAndServe()`,比如 72 | 73 | ```go 74 | func main() { 75 | router := gin.Default() 76 | http.ListenAndServe(":8080", router) 77 | } 78 | ``` 79 | 或者自定义 HTTP 服务器的配置: 80 | 81 | ```go 82 | func main() { 83 | router := gin.Default() 84 | 85 | s := &http.Server{ 86 | Addr: ":8080", 87 | Handler: router, 88 | ReadTimeout: 10 * time.Second, 89 | WriteTimeout: 10 * time.Second, 90 | MaxHeaderBytes: 1 << 20, 91 | } 92 | s.ListenAndServe() 93 | } 94 | ``` 95 | 96 | **3.HTTP 服务器替换方案** 97 | 想无缝重启、停机吗? 以下有几种方式: 98 | 99 | 我们可以使用 [fvbock/endless](https://github.com/fvbock/endless) 来替换默认的 `ListenAndServe`。但是 windows 不能使用。 100 | 101 | ```go 102 | router := gin.Default() 103 | router.GET("/", handler) 104 | // [...] 105 | endless.ListenAndServe(":4242", router) 106 | ``` 107 | 108 | 除了 endless 还可以用manners: 109 | 110 | [manners](https://github.com/braintree/manners) 兼容windows 111 | 112 | ``` 113 | manners.ListenAndServe(":8888", r) 114 | ``` 115 | 116 | 如果你使用的 golang 版本大于 1.8 版本, 那么可以用 http.Server 内置的 Shutdown 方法来实现优雅的关闭服务, 一个简单的示例代码如下: 117 | 118 | ``` 119 | srv := http.Server{ 120 | Addr: ":8080", 121 | Handler: router, 122 | } 123 | 124 | go func() { 125 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 126 | log.Fatalf("listen: %s\n", err) 127 | } 128 | } 129 | 130 | // 其他代码, 等待关闭信号 131 | ... 132 | 133 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 134 | defer cancel() 135 | if err := srv.Shutdown(ctx); err != nil { 136 | log.Fatal("Server Shutdown: ", err) 137 | } 138 | log.Println("Server exiting") 139 | ``` 140 | 141 | 完整的代码见 [graceful-shutdown](https://github.com/gin-gonic/gin/blob/master/examples/graceful-shutdown/graceful-shutdown/server.go). 142 | 143 | 144 | 145 | - 生命周期 146 | 147 | 148 | 149 | - Context 150 | 151 | 152 | 153 | ### 路由 154 | 155 | 156 | - 基本路由 157 | gin 框架中采用的路由库是 httprouter。 158 | 159 | 160 | ```go 161 | // 创建带有默认中间件的路由: 162 | // 日志与恢复中间件 163 | router := gin.Default() 164 | //创建不带中间件的路由: 165 | //r := gin.New() 166 | 167 | router.GET("/someGet", getting) 168 | router.POST("/somePost", posting) 169 | router.PUT("/somePut", putting) 170 | router.DELETE("/someDelete", deleting) 171 | router.PATCH("/somePatch", patching) 172 | router.HEAD("/someHead", head) 173 | router.OPTIONS("/someOptions", options) 174 | ``` 175 | 176 | 177 | 178 | - 路由参数 179 | 180 | api 参数通过Context的Param方法来获取 181 | 182 | ``` 183 | router.GET("/string/:name", func(c *gin.Context) { 184 | name := c.Param("name") 185 | fmt.Println("Hello %s", name) 186 | }) 187 | ``` 188 | 189 | URL 参数通过 DefaultQuery 或 Query 方法获取 190 | 191 | ``` 192 | // url 为 http://localhost:8080/welcome?name=ningskyer时 193 | // 输出 Hello ningskyer 194 | // url 为 http://localhost:8080/welcome时 195 | // 输出 Hello Guest 196 | router.GET("/welcome", func(c *gin.Context) { 197 | name := c.DefaultQuery("name", "Guest") //可设置默认值 198 | // 是 c.Request.URL.Query().Get("lastname") 的简写 199 | lastname := c.Query("lastname") 200 | fmt.Println("Hello %s", name) 201 | }) 202 | ``` 203 | 表单参数通过 PostForm 方法获取 204 | ``` 205 | //form 206 | router.POST("/form", func(c *gin.Context) { 207 | type := c.DefaultPostForm("type", "alert")//可设置默认值 208 | msg := c.PostForm("msg") 209 | title := c.PostForm("title") 210 | fmt.Println("type is %s, msg is %s, title is %s", type, msg, title) 211 | }) 212 | ``` 213 | 214 | 215 | - 路由群组 216 | 217 | ```go 218 | someGroup := router.Group("/someGroup") 219 | { 220 | someGroup.GET("/someGet", getting) 221 | someGroup.POST("/somePost", posting) 222 | } 223 | ``` 224 | 225 | 226 | 227 | ### 控制器 228 | 229 | 230 | 231 | - 数据解析绑定 232 | 233 | 模型绑定可以将请求体绑定给一个类型,目前支持绑定的类型有 JSON, XML 和标准表单数据 (foo=bar&boo=baz)。 234 | 要注意的是绑定时需要给字段设置绑定类型的标签。比如绑定 JSON 数据时,设置 `json:"fieldname"`。 235 | 使用绑定方法时,Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动推断,而用 BindWith 方法。 236 | 你也可以指定某字段是必需的。如果一个字段被 `binding:"required"` 修饰而值却是空的,请求会失败并返回错误。 237 | 238 | ```go 239 | // Binding from JSON 240 | type Login struct { 241 | User string `form:"user" json:"user" binding:"required"` 242 | Password string `form:"password" json:"password" binding:"required"` 243 | } 244 | 245 | func main() { 246 | router := gin.Default() 247 | 248 | // 绑定JSON的例子 ({"user": "manu", "password": "123"}) 249 | router.POST("/loginJSON", func(c *gin.Context) { 250 | var json Login 251 | 252 | if c.BindJSON(&json) == nil { 253 | if json.User == "manu" && json.Password == "123" { 254 | c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) 255 | } else { 256 | c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) 257 | } 258 | } 259 | }) 260 | 261 | // 绑定普通表单的例子 (user=manu&password=123) 262 | router.POST("/loginForm", func(c *gin.Context) { 263 | var form Login 264 | // 根据请求头中 content-type 自动推断. 265 | if c.Bind(&form) == nil { 266 | if form.User == "manu" && form.Password == "123" { 267 | c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) 268 | } else { 269 | c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) 270 | } 271 | } 272 | }) 273 | // 绑定多媒体表单的例子 (user=manu&password=123) 274 | router.POST("/login", func(c *gin.Context) { 275 | var form LoginForm 276 | // 你可以显式声明来绑定多媒体表单: 277 | // c.BindWith(&form, binding.Form) 278 | // 或者使用自动推断: 279 | if c.Bind(&form) == nil { 280 | if form.User == "user" && form.Password == "password" { 281 | c.JSON(200, gin.H{"status": "you are logged in"}) 282 | } else { 283 | c.JSON(401, gin.H{"status": "unauthorized"}) 284 | } 285 | } 286 | }) 287 | // Listen and serve on 0.0.0.0:8080 288 | router.Run(":8080") 289 | } 290 | ``` 291 | 292 | 293 | ### 请求 294 | 295 | 296 | - 请求头 297 | 298 | 299 | 300 | - 请求参数 301 | 302 | 303 | 304 | - Cookies 305 | 306 | 307 | 308 | - 上传文件 309 | 310 | ``` 311 | router.POST("/upload", func(c *gin.Context) { 312 | 313 | file, header , err := c.Request.FormFile("upload") 314 | filename := header.Filename 315 | fmt.Println(header.Filename) 316 | out, err := os.Create("./tmp/"+filename+".png") 317 | if err != nil { 318 | log.Fatal(err) 319 | } 320 | defer out.Close() 321 | _, err = io.Copy(out, file) 322 | if err != nil { 323 | log.Fatal(err) 324 | } 325 | }) 326 | ``` 327 | 328 | 329 | ### 响应 330 | 331 | 332 | - 响应头 333 | 334 | 335 | 336 | - 附加Cookie 337 | 338 | 339 | 340 | - 字符串响应 341 | 342 | ``` 343 | c.String(http.StatusOK, "some string") 344 | ``` 345 | 346 | 347 | - JSON/XML/YAML响应 348 | 349 | ``` 350 | r.GET("/moreJSON", func(c *gin.Context) { 351 | // You also can use a struct 352 | var msg struct { 353 | Name string `json:"user" xml:"user"` 354 | Message string 355 | Number int 356 | } 357 | msg.Name = "Lena" 358 | msg.Message = "hey" 359 | msg.Number = 123 360 | // 注意 msg.Name 变成了 "user" 字段 361 | // 以下方式都会输出 : {"user": "Lena", "Message": "hey", "Number": 123} 362 | c.JSON(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123}) 363 | c.XML(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123}) 364 | c.YAML(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123}) 365 | c.JSON(http.StatusOK, msg) 366 | c.XML(http.StatusOK, msg) 367 | c.YAML(http.StatusOK, msg) 368 | }) 369 | 370 | ``` 371 | 372 | 373 | - 视图响应 374 | 375 | 先要使用 LoadHTMLTemplates() 方法来加载模板文件 376 | 377 | ```go 378 | func main() { 379 | router := gin.Default() 380 | //加载模板 381 | router.LoadHTMLGlob("templates/*") 382 | //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") 383 | //定义路由 384 | router.GET("/index", func(c *gin.Context) { 385 | //根据完整文件名渲染模板,并传递参数 386 | c.HTML(http.StatusOK, "index.tmpl", gin.H{ 387 | "title": "Main website", 388 | }) 389 | }) 390 | router.Run(":8080") 391 | } 392 | ``` 393 | 394 | 模板结构定义 395 | 396 | ```html 397 | 398 |

399 | {{ .title }} 400 |

401 | 402 | ``` 403 | 不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径 404 | 405 | ```go 406 | router.LoadHTMLGlob("templates/**/*") 407 | router.GET("/posts/index", func(c *gin.Context) { 408 | c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ 409 | "title": "Posts", 410 | }) 411 | c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ 412 | "title": "Users", 413 | }) 414 | 415 | } 416 | ``` 417 | 418 | templates/posts/index.tmpl 419 | ```html 420 | 421 | {{ define "posts/index.tmpl" }} 422 |

423 | {{ .title }} 424 |

425 | 426 | {{ end }} 427 | 428 | gin也可以使用自定义的模板引擎,如下 429 | 430 | ```go 431 | import "html/template" 432 | 433 | func main() { 434 | router := gin.Default() 435 | html := template.Must(template.ParseFiles("file1", "file2")) 436 | router.SetHTMLTemplate(html) 437 | router.Run(":8080") 438 | } 439 | ``` 440 | 441 | 442 | - 文件响应 443 | 444 | ``` 445 | //获取当前文件的相对路径 446 | router.Static("/assets", "./assets") 447 | // 448 | router.StaticFS("/more_static", http.Dir("my_file_system")) 449 | //获取相对路径下的文件 450 | router.StaticFile("/favicon.ico", "./resources/favicon.ico") 451 | 452 | ``` 453 | 454 | 455 | - 重定向 456 | 457 | ``` 458 | r.GET("/redirect", func(c *gin.Context) { 459 | //支持内部和外部的重定向 460 | c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/") 461 | }) 462 | ``` 463 | 464 | 465 | - 同步异步 466 | 467 | goroutine 机制可以方便地实现异步处理 468 | 469 | ```go 470 | func main() { 471 | r := gin.Default() 472 | //1. 异步 473 | r.GET("/long_async", func(c *gin.Context) { 474 | // goroutine 中只能使用只读的上下文 c.Copy() 475 | cCp := c.Copy() 476 | go func() { 477 | time.Sleep(5 * time.Second) 478 | 479 | // 注意使用只读上下文 480 | log.Println("Done! in path " + cCp.Request.URL.Path) 481 | }() 482 | }) 483 | //2. 同步 484 | r.GET("/long_sync", func(c *gin.Context) { 485 | time.Sleep(5 * time.Second) 486 | 487 | // 注意可以使用原始上下文 488 | log.Println("Done! in path " + c.Request.URL.Path) 489 | }) 490 | 491 | // Listen and serve on 0.0.0.0:8080 492 | r.Run(":8080") 493 | } 494 | ``` 495 | 496 | 497 | ### 视图 498 | 499 | 500 | - 传参 501 | 502 | 503 | 504 | - 视图组件 505 | 506 | 507 | 508 | ### 中间件 509 | 510 | 511 | - 分类使用方式 512 | ``` 513 | // 1.全局中间件 514 | router.Use(gin.Logger()) 515 | router.Use(gin.Recovery()) 516 | 517 | // 2.单路由的中间件,可以加任意多个 518 | router.GET("/benchmark", MyMiddelware(), benchEndpoint) 519 | 520 | // 3.群组路由的中间件 521 | authorized := router.Group("/", MyMiddelware()) 522 | // 或者这样用: 523 | authorized := router.Group("/") 524 | authorized.Use(MyMiddelware()) 525 | { 526 | authorized.POST("/login", loginEndpoint) 527 | } 528 | ``` 529 | 530 | 531 | - 自定义中间件 532 | 533 | ```go 534 | //定义 535 | func Logger() gin.HandlerFunc { 536 | return func(c *gin.Context) { 537 | t := time.Now() 538 | 539 | // 在gin上下文中定义变量 540 | c.Set("example", "12345") 541 | 542 | // 请求前 543 | 544 | c.Next()//处理请求 545 | 546 | // 请求后 547 | latency := time.Since(t) 548 | log.Print(latency) 549 | 550 | // access the status we are sending 551 | status := c.Writer.Status() 552 | log.Println(status) 553 | } 554 | } 555 | //使用 556 | func main() { 557 | r := gin.New() 558 | r.Use(Logger()) 559 | 560 | r.GET("/test", func(c *gin.Context) { 561 | //获取gin上下文中的变量 562 | example := c.MustGet("example").(string) 563 | 564 | // 会打印: "12345" 565 | log.Println(example) 566 | }) 567 | 568 | // 监听运行于 0.0.0.0:8080 569 | r.Run(":8080") 570 | } 571 | ``` 572 | 573 | 574 | 575 | - 中间件参数 576 | 577 | - 内置中间件 578 | 1.简单认证BasicAuth 579 | 580 | ```go 581 | // 模拟私有数据 582 | var secrets = gin.H{ 583 | "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, 584 | "austin": gin.H{"email": "austin@example.com", "phone": "666"}, 585 | "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, 586 | } 587 | 588 | func main() { 589 | r := gin.Default() 590 | 591 | // 使用 gin.BasicAuth 中间件,设置授权用户 592 | authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ 593 | "foo": "bar", 594 | "austin": "1234", 595 | "lena": "hello2", 596 | "manu": "4321", 597 | })) 598 | 599 | // 定义路由 600 | authorized.GET("/secrets", func(c *gin.Context) { 601 | // 获取提交的用户名(AuthUserKey) 602 | user := c.MustGet(gin.AuthUserKey).(string) 603 | if secret, ok := secrets[user]; ok { 604 | c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) 605 | } else { 606 | c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) 607 | } 608 | }) 609 | 610 | // Listen and serve on 0.0.0.0:8080 611 | r.Run(":8080") 612 | } 613 | ``` 614 | 615 | 2. 616 | 617 | 618 | ## 数据库 619 | 620 | 621 | - Mongodb 622 | 623 | Golang常用的Mongodb驱动为 mgo.v2, [查看文档](http://godoc.org/gopkg.in/mgo.v2) 624 | 625 | mgo 使用方式如下: 626 | 627 | ``` 628 | //定义 Person 结构,字段须为首字母大写 629 | type Person struct { 630 | Name string 631 | Phone string 632 | } 633 | 634 | router.GET("/mongo", func(context *gin.Context){ 635 | //可本地可远程,不指定协议时默认为http协议访问,此时需要设置 mongodb 的nohttpinterface=false来打开httpinterface。 636 | //也可以指定mongodb协议,如 "mongodb://127.0.0.1:27017" 637 | var MOGODB_URI = "127.0.0.1:27017" 638 | //连接 639 | session, err := mgo.Dial(MOGODB_URI) 640 | //连接失败时终止 641 | if err != nil { 642 | panic(err) 643 | } 644 | //延迟关闭,释放资源 645 | defer session.Close() 646 | //设置模式 647 | session.SetMode(mgo.Monotonic, true) 648 | //选择数据库与集合 649 | c := session.DB("adatabase").C("acollection") 650 | //插入文档 651 | err = c.Insert(&Person{Name:"Ale", Phone:"+55 53 8116 9639"}, 652 | &Person{Name:"Cla", Phone:"+55 53 8402 8510"}) 653 | //出错判断 654 | if err != nil { 655 | log.Fatal(err) 656 | } 657 | //查询文档 658 | result := Person{} 659 | //注意mongodb存储后的字段大小写问题 660 | err = c.Find(bson.M{"name": "Ale"}).One(&result) 661 | //出错判断 662 | if err != nil { 663 | log.Fatal(err) 664 | } 665 | fmt.Println("Phone:", result.Phone) 666 | }) 667 | ``` 668 | 669 | 670 | - Mysql 671 | 672 | 673 | 674 | - ORM 675 | 676 | 677 | 678 | ## 扩展包 679 | 680 | 681 | 682 | ### 常用方法 683 | - gin 684 | - Context 685 | --------------------------------------------------------------------------------