Golang网站微框架Gin

Gin is a web framework written in Golang.

安装 Gin

获取包:

1
> go get github.com/gin-gonic/gin

添加引用:

1
import "github.com/gin-gonic/gin"

Gin web 入门

一个简单的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "hello world!")
})
//http://localhost:8081 postdata: name=tom
router.POST("/", postHome)
router.Run(":8081")
}
func postHome(c *gin.Context) {
uName := c.PostForm("name")
c.JSON(200, gin.H{
"say": "Hello " + uName,
})
}
请求方法名必须全部是大写字母
1
2
3
4
5
6
7
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
获取路由参数

通过 ContextParam 方法来获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
// http://localhost:8081/user/article/tommy
router.GET("/user/:type/:name", getRouteStr)
...
func getRouteStr(c *gin.Context) {
ctype := c.Param("type")
cname := c.Param("name")
c.JSON(200, gin.H{
"typeName": ctype,
"username": cname,
})
}
//result:
{
"typeName": "article",
"username": "tommy"
}
获取url参数

通过 DefaultQueryQuery 方法获取:

  • Query('xxx') 如果没有相应值,默认为空字符串
  • DefaultQuery("xxx","defaultValue") 可设置默认值,string类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
router.GET("/user", getQueryStrs)
...
func getQueryStrs(c *gin.Context) {
name := c.Query("name") //如果没有相应值,默认为空字符串
age := c.DefaultQuery("age", "0") //可设置默认值,string类型
c.JSON(200, gin.H{
"name": name,
"age": age,
})
}
//result:
//http://localhost:8081/user?name=tom&age=23
{
"age": "23",
"name": "tom"
}
//result2:
//http://localhost:8081/user?name=tom
{
"age": "0",
"name": "tom"
}

c.Request.URL.Query() 可获取所有url请求参数 map[] 集合:

1
2
3
4
5
6
7
//获取所有url请求参数
reqData := c.Request.URL.Query()
fmt.Printf("[info] req url data is %s\n", reqData)
//result:
//http://localhost:8080?id=3&name=zhangsan&address=beijing
[info] req url data is map[name:[zhangsan] address:[beijing] id:[3]]
获取表单参数

表单参数通过 PostFormDefaultPostForm 方法获取:

  • PostForm()
  • DefaultPostForm("xxx","defaultValue")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
router.POST("/", getFormStr)
...
func getFormStr(c *gin.Context) {
title := c.PostForm("title")
cont := c.DefaultPostForm("cont", "没有内容")
c.JSON(200, gin.H{
"title": title,
"cont": cont,
})
}
//result:
//http://localhost:8081 postData: title:这是一个标题
{
"cont": "没有内容",
"title": "这是一个标题"
}

c.Request.Body 获取所有 post body 数据:

1
2
3
4
5
6
7
8
9
10
11
//获取post body
x, _ := ioutil.ReadAll(c.Request.Body)
fmt.Printf("[info] %s", string(x))
//result:
// post body type :x-www-form-urlencoded (user=tom pwd=123)
[info] user=tom&pwd=123
//result:
// post body type: raw application/json
[info] {"name":"zhangfei","id":32}

获取所有 post data 数据 (map[] 类型)

1
2
3
4
5
6
7
c.Request.ParseForm()
reqBodyData := c.Request.PostForm
fmt.Printf("[info] req body data is %s \n", reqBodyData)
//result:
// post body type :x-www-form-urlencoded (user=tom pwd=123)
[info] req body data is map[user:[tom] pwd:[123]]
路由群组

支持 一级多级 分组的路由规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
//路由分组
articleGroup := router.Group("/article")
{
articleGroup.GET("/one/:id", getArticleByid)
articleGroup.GET("/list", getArticleList)
}
//多级路由分组
apiGroup := router.Group("/api")
apiv1Group := apiGroup.Group("/v1")
{
apiv1Group.GET("/user", getApiV1User)
}
apiv2Group := apiGroup.Group("/v2")
{
apiv2Group.GET("/order", getApiV2Order)
}
...
func getArticleByid(c *gin.Context) {
a_id := c.Param("id")
c.String(200, a_id)
}
//result:
//http://localhost:8081/article/one/3
3
func getArticleList(c *gin.Context) {
c.JSON(200, gin.H{
"a": "1",
"b": "2",
})
}
//result:
//http://localhost:8081/article/list
{
"a": "1",
"b": "2"
}
func getApiV1User(c *gin.Context) {
c.String(200, "api/v1/user")
}
//result:
//http://localhost:8081/api/v1/user
api/v1/user
func getApiV2Order(c *gin.Context) {
c.String(200, "api/v2/order")
}
//result:
//http://localhost:8081/api/v2/order
api/v2/order
数据绑定
  • Bind()
  • BindJSON()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
//绑定普通表单 (user=tom&&pwd=123)
router.POST("/loginform", func(c *gin.Context) {
var form Login
if c.Bind(&form) == nil {
if form.User == "tom" && form.Password == "123" {
c.JSON(200, gin.H{"status": "form logined in"})
} else {
c.JSON(201, gin.H{"status": "form no login"})
}
}
})
//绑定JSON ({"user":"tom","pwd":"123"}) Content-Type:application/json
router.POST("/loginjson", func(c *gin.Context) {
var json Login
if c.BindJSON(&json) == nil {
if json.User == "tom" && json.Password == "123" {
c.JSON(200, gin.H{"status": "json logined in"})
} else {
c.JSON(201, gin.H{"status": "json no login"})
}
}
})
...
type Login struct {
User string `form:"user" json:"user"`
Password string `form:"pwd" json:"pwd"`
}
POST上传文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
//表单上传文件 http://localhost:8081/upload
// key:upload value: file....
//result:
//{
//"filename": "1009e3ee4bc0919e11d32e00ccf55cdf.jpg"
//}
router.POST("/upload", func(c *gin.Context) {
_, header, _ := c.Request.FormFile("upload")
filename := header.Filename
c.JSON(200, gin.H{
"filename": filename,
})
})
...
根据客户端的请求类型,返回对应的响应格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
router.GET("/getdata", func(c *gin.Context) {
contentType := c.Request.Header.Get("Content-Type")
switch contentType {
case "application/json":
c.JSON(200, gin.H{"user": "张飞", "address": "长坂坡"})
case "application/xml":
c.XML(200, gin.H{"user": "张飞", "address": "长坂坡"})
case "application/x-www-form-urlencoded":
c.String(200, "张飞 长坂坡")
}
})
...
字符串响应
1
2
3
4
import "net/http"
c.String(200, "some string")
c.String(http.StatusOK, "some string")
JSON/XML/YAML等格式响应
1
2
3
4
5
...
c.JSON(http.StatusOK, msg)
c.XML(http.StatusOK, msg)
c.YAML(http.StatusOK, msg)
...
视图响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
//加载模板
router.LoadHTMLGlob("templates/*")
// router.LoadHTMLFiles("templates/index.html","templates/article.html")
router.GET("/", func(c *gin.Context) {
//根据完整文件名渲染模板,并传递参数
c.HTML(200, "index.html", gin.H{
"say": "Hello World!",
})
})
...
//index.html:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h3>{{ .say }}</h3>
</body>
</html>
//result:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h3>Hello World!</h3>
</body>
</html>
加载多层模板路径

经测试:如果是多层级的模板文件,要在模板文件中使用

1
{{define xxx}} {{end}}

将该模板作为嵌套模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//加载模板
router.LoadHTMLGlob("templates/**/*")
// router.LoadHTMLFiles("templates/index.html","templates/article.html")
router.GET("/articles/index", func(c *gin.Context) {
//根据完整文件名渲染模板,并传递参数
c.HTML(200, "articles/index.html", gin.H{
"say": "Article index!",
})
})
router.GET("/users/index", func(c *gin.Context) {
c.HTML(200, "/users/index.html", gin.H{
"say": "User index!",
})
})

templates/articles/index.html:

1
2
3
4
5
6
7
8
9
10
11
{{define "articles/index.html"}}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h3>{{ .say }}</h3>
</body>
</html>
{{end}}

templates/users/index.html:

1
2
3
4
5
6
7
8
9
10
11
{{define "users/index.html"}}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h3>{{ .say }}</h3>
</body>
</html>
{{end}}
提供静态文件
  • Static()
  • StaticFS()
  • StatucFile()

示例:

1
2
3
4
5
6
7
8
9
func main() {
router := gin.Default()
router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}

待详细研究。

重定向

支持 内部外部 地址的重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
//跳转到外部地址
router.GET("/abc", func(c *gin.Context) {
c.Redirect(302, "http://www.baidu.com")
})
//跳转到内部地址
router.GET("def", func(c *gin.Context) {
c.Redirect(302, "/home")
})
router.GET("/home", func(c *gin.Context) {
c.String(200, "home page")
})
...
自定义中间件及中间件的使用方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
func main() {
// router := gin.Default()
router := gin.New()
//全局中间件
router.Use(MyLogger())
router.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
c.String(200, example)
})
//单路由中间件
router.GET("/abc",MyMiddelware(),getAbc)
//群组路由中间件
aGroup:=router.Group("/",MyMiddelware())
//中间件可以同时添加多个
aGroup:=router.Group("/",MyMiddelware(),My2Middelware(),My3Middelware())
//或者:群组路由中间件
bGroup:=router.Group("/")
bGroup.Use(MyMiddelware())
{
bGroup.GET("/v1",getV1)
}
router.Run(":8081")
}
func MyLogger() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("example", "123465")
c.Next()
}
}
异步处理

goroutine 中只能使用只读的上下文 c.Copy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
//异步
router.GET("/async", func(c *gin.Context) {
// goroutine 中只能使用只读的上下文 c.Copy()
cCp := c.Copy()
go func() {
time.Sleep(5 * time.Second)
//需要使用只读上下文
log.Println("Done! in path " + cCp.Request.URL.Path)
}()
})
//同步
router.GET("/sync", func(c *gin.Context) {
time.Sleep(5 * time.Second)
//可以使用原始上下文
log.Println("Done! in path " + c.Request.URL.Path)
})
...
初始化不带中间件和带有默认中间件的路由
  • r := gin.New() 创建不带中间件的路由
  • r := gin.Default() 创建带有默认中间件的路由:日志与恢复中间件

其他示例

获取所有请求参数
  • 获取所有URL请求参数
1
2
reqData := c.Request.URL.Query()
fmt.Printf("[info] req url data is %s\n", reqData)
  • 获取所有Body请求参数
1
2
3
c.Request.ParseForm()
reqBodyData := c.Request.PostForm
fmt.Printf("[info] req body data is %s \n", reqBodyData)
获取请求头信息
1
2
3
4
5
6
7
8
9
// 获取请求头Header中的 key sign timestamp
token := c.Request.Header.Get("X-Auth-Token")
key := c.Request.Header.Get("X-Auth-Key")
timestamp := c.Request.Header.Get("X-Auth-TimeStamp")
fmt.Printf("[info] key is %s ,timestamp is %s\n", key, timestamp)
//获取Post put请求模式下的Content-length
conlength := c.Request.Header.Get("Content-Length")
fmt.Printf("[info] Content-Length is %s\n", conlength)
获取请求 Method Host URL ContentLength
1
2
3
4
5
6
7
//判断请求Method
// GET POST PUT DELETE OPTIONS
fmt.Println("[info]", c.Request.Method)
fmt.Println("[info]", c.Request.Host) // localhost:8080
fmt.Println("[info]", c.Request.URL) ///?id=3&name=zhangsan&address=beijing
//获取请求头中数据长度 ContentLength
fmt.Println("[info]", c.Request.ContentLength) // POST for 16 or GET for 0
CORS 跨域请求 (未测试)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// CORS middleware
g.Use(CORSMiddleware())
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
if c.Request.Method == "OPTIONS" {
c.Abort(200)
return
}
c.Next()
}
}
定义 struct 时,参数的首字母要大写才能被访问到
1
2
3
4
5
type ReturnMsg struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
输出一个struct对象
1
2
3
4
5
6
7
8
9
...
c.JSON(403, ReturnMsg{Code: 1, Msg: "req error"})
...
type ReturnMsg struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}

gin中间件阻止请求继续访问

c.Abort()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
token := c.Request.Header.Get("X-Auth-Token")
key := c.Request.Header.Get("X-Auth-Key")
timestamp := c.Request.Header.Get("X-Auth-TimeStamp")
//判断请求头中是否含有必须的三个参数
if token == "" || key == "" || timestamp == "" {
//经测试,c.Abort() 在前在后均可
// c.Abort()
// c.JSON(403, ReturnMsg{Code: 1, Msg: "req error"})
c.JSON(403, ReturnMsg{Code: 1, Msg: "req error"})
c.Abort()
return //一定要加return
}
c.Next()
gin安装报错

安装 gin 包时可能会报 x/net 相关错误,可以先下载该必须包:

1
2
3
4
5
6
7
$ mkdir -p $GOPATH/src/golang.org/x/
$ cd $GOPATH/src/golang.org/x/
$ git clone https://github.com/golang/net.git net
$ go install net

golang中相关易错点

时间戳转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import (
"fmt"
"time"
"strconv"
)
func main() {
i, err := strconv.ParseInt("1405544146", 10, 64)
if err != nil {
panic(err)
}
tm := time.Unix(i, 0)
fmt.Println(tm)
}

什么类型可以声明为常量及在func外部声明变量时不能使用:=

数字类型,字符串或布尔类型可以声明为 const 常量;array ,slicemap 不能声明为常量。

map类型不能声明为常量:

1
2
3
4
5
6
7
8
const myString = "hello"
const pi = 3.14 // untyped constant
const life int = 42 // typed constant (can use only with ints)
const (
First = 1
Second = 2
Third = 4
)

在函数func外部声明变量,要用完整模式:

1
2
3
4
var romanNumeralDict = map[int]string{
1:"a",
2:"b",
}

在函数func内部声明变量,可以使用缩写模式:

1
2
3
romanNumeralDict := map[int]string{
...
}
判断map中是否存在某值,可以写在一行
1
2
3
4
5
6
// 查找键值是否存在
if v, ok := m1["a"]; ok {
fmt.Println(v)
} else {
fmt.Println("Key Not Found")
}
map排序

map 是无序的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import (
"fmt"
"sort"
)
func main() {
// fmt.Printf("Hello,是按揭")
// fmt.Printf("ni好呀")
m := map[string]string{
"sign": "1399dke",
"user": "zhangsan",
"timestamp": "36644747373",
"id": "3",
}
fmt.Println(m)
var keys []string
for k := range m {
// fmt.Println(k)
keys = append(keys, k)
}
sort.Strings(keys)
// fmt.Println(keys)
for _, k := range keys {
fmt.Println("key:", k, "Value :", m[k])
}
}

for range

for range 可以遍历 slicemap。并通过两个参数(index和value),分别获取到slice或者map中某个元素所在的index以及其值。

在Go的 for…range 循环中,Go始终使用值拷贝的方式代替被遍历的元素本身。

1
2
3
4
for index, value := range mySlice {
fmt.Println("index: " + index)
fmt.Println("value: " + value)
}
go语言string、int、int64互相转换
1
2
3
4
5
6
7
8
#string到int
int,err:=strconv.Atoi(string)
#string到int64
int64, err := strconv.ParseInt(string, 10, 64)
#int到string
string:=strconv.Itoa(int)
#int64到string
string:=strconv.FormatInt(int64,10)
http.Request
1
2
3
func getURL(w http.ResponseWriter, r *http.Request) {
url := r.URL.String()
}
中文 url 编码问题
1
2
3
4
5
6
7
8
9
10
import "net/url"
//url编码
str := "中文-_."
unstr := "%2f"
fmt.Printf("url.QueryEscape:%s", url.QueryEscape(str))
fmt.Println()
s, _ := url.QueryUnescape(unstr)
fmt.Printf("url.QueryUnescape:%s", s)
fmt.Println()

go 中url编码和字符转码(类似php中的urlencode 和htmlspecialchars):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"html"
"net/url"
"testing"
)
func Test_Escape(t *testing.T) {
//url编码
str := "中文-_."
unstr := "%2f"
fmt.Printf("url.QueryEscape:%s", url.QueryEscape(str))
fmt.Println()
s, _ := url.QueryUnescape(unstr)
fmt.Printf("url.QueryUnescape:%s", s)
fmt.Println()
//字符转码
hstr := "<"
hunstr := "&lt"
fmt.Printf("html.EscapeString:%s", html.EscapeString(hstr))
fmt.Println()
fmt.Printf("html.UnescapeString:%s", html.UnescapeString(hunstr))
fmt.Println()
}

golang中字符串拼接

一种说法:

如果是少量小文本拼接,用 “+” 就好
如果是大量小文本拼接,用 strings.Join
如果是大量大文本拼接,用 bytes.Buffer


strings 包
获取字符串的MD5值
1
2
3
4
5
6
7
8
9
10
import (
"crypto/md5"
"encoding/hex"
)
func GetMD5Hash(text string) string {
hasher := md5.New()
hasher.Write([]byte(text))
return hex.EncodeToString(hasher.Sum(nil))
}

相关链接


相关问题

坚持原创技术分享,您的支持将鼓励我继续创作!
如有疑问或需要技术讨论,请留言或发邮件到 service@itfanr.cc