Gin框架快速入门

Gin 是一个 Go (Golang) 编写的轻量级 http web 框架

Gin 的官网:https://gin-gonic.com/zh-cn/

Gin Github 地址:https://github.com/gin-gonic/gin

环境搭建

  1. 下载&安装

在项目目录下面执行

go get -u github.com/gin-gonic/gin
  1. 将 gin 引入到代码中
import "github.com/gin-gonic/gin"
  1. 新建Main.go
package main

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

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(200, "value %v", "hello gin")
	})
	r.Run()
  r.Run(":9000")//若改变端口
}
  1. Gin,启动!
go run main.go

golang 程序的热加载

所谓热加载就是当我们对代码进行修改时,程序能够自动重新加载并执行,这在我们开发中是非常便利的,可以快速进行代码测试,省去了每次手动重新编译

工具 1(推荐):https://github.com/gravityblast/fresh

go get github.com/pilu/fresh
go run github.com/pilu/fresh

工具 2:https://github.com/codegangsta/gin

go get -u github.com/codegangsta/gin

Gin 框架中的路由

GET(SELECT)从服务器取出资源(一项或多项)
POST(CREATE)在服务器新建一个资源
PUT(UPDATE)在服务器更新资源(客户端提供改变后的完整资源)
DELETE(DELETE)从服务器删除资源

c.String() c.JSON() c.JSONP() c.HTML()

GET POST PUT

例子

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type Article struct {
	Title string `json:"title"`
}

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "value %v", "hello gin!!")
	})
	r.POST("/add", func(c *gin.Context) {
		c.String(200, "post request")
	})
	r.PUT("/edit", func(c *gin.Context) {
		c.String(200, "put 1request")
	})
	r.GET("/json", func(c *gin.Context) {
		c.JSONP(http.StatusOK, gin.H{
			"success": true,
			"msg":     "hello gin",
		})
	})
	r.GET("/jsonp", func(c *gin.Context) {
		a := Article{"jsonp"}
		c.JSONP(http.StatusOK, a)
	})
	err := r.Run("127.0.0.1:8080")
	if err != nil {
		return
	}

}

Gin HTML 模板渲染

模板放在不同目录里面的配置方法

Gin 框架中如果不同目录下面有同名模板的话我们需要给模版起别名

{{ define "admin/index.html" }}
#html内容
{{ end }}

在main.go中这样写

r.LoadHTMLGlob("templates/**/*")

gin 模板基本语法

{{.}} 输出数据

例子

{{ define "default/index.html" }} <!DOCTYPE html> <html lang="en"> <head>

<meta charset="UTF-8">

<meta http-equiv="X-UA-Compatible" content="IE=edge">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Document</title> </head> <body>

<h1>前台模板</h1>

<h3>{{.title}}</h3>

<h4>{{.user.Name}}</h4> <h4>{{.user.Age}}</h4> </body> </html> {{end}}

其中user结构体定义如下

type user struct{
	Age int
	Name string
}
注释

可以多行,不能嵌套

{{/* a comment */}}
变量
<h4>{{$obj := .title}}</h4>
<h4>{{$obj}}</h4>
移除空格
{{- .Name -}}

”-“要紧挨着“{{“和“}}”

比较函数
符号含义
eq如果 arg1 == arg2 则返回真
ne如果 arg1 != arg2 则返回真
lt如果 arg1 < arg2 则返回真
le如果 arg1 <= arg2 则返回真
gt如果 arg1 > arg2 则返回真
ge如果 arg1 >= arg2 则返回真
条件判断

例子

{{define "default/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>{{.title}}</h2>
    

    {{if gt .score 90}}
    <p>excellent</p>
    {{else if gt .score 80}}
    <p>great</p>
    {{else if gt .score 60}}
    <p>pass</p>
    {{else}}
    <p>fail</p>
    {{end}}

</body>
</html>
{{end}}
range

例子

r.GET("/", func(ctx *gin.Context) {
		ctx.HTML(http.StatusOK, "default/index.html", gin.H{
			"title": "首页",
			"score": 88,
			"hobby": []string{"eat", "sleep", "code"},
      "kong": []string{},
		})
	})
<ul>
{{range $key,$value:=.hobby}}
<li>{{$key}}:{{$value}}</li>
{{else}}
<li></li>
{{end}}
</ul>
<ul>
{{range $key,$value:=.kong}}
<li>{{$key}}:{{$value}}</li>
{{else}}
<li></li>
{{end}}
</ul>
with
{{with .news}}
<!-- 相当于.news.Title -->
<p>{{.Title}}</p>
<!-- 相当于.news.Content -->
<p>{{.Content}}</p>
{{end}}
预定义函数

执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一 般不在模板内定义函数,而是使用 Funcs 方法添加函数到模板里。

预定义的全局函数如下:

名称功能
and函数返回它的第一个 empty 参数或者最后一个参数; 就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or返回第一个非 empty 参数或者最后一个参数; 亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not返回它的单个参数的布尔值的否定
len返回它的参数的整数类型长度
index执行结果为第一个参数以剩下的参数为索引/键指向的值; 如"index x 1 2 3"返回 x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print即 fmt.Sprint
printf即 fmt.Sprintf
println即 fmt.Sprintln
html返回与其参数的文本表示形式等效的转义 HTML。 这个函数在 html/template 中不可用。
urlquery以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。 这个函数在 html/template 中不可用。
js返回与其参数的文本表示形式等效的转义 JavaScript。
call执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函 数的参数; 如"call .X.Y 1 2"等价于 go 语言里的 dot.X.Y(1, 2); 其中 Y 是函数类型的字段或者字典的值,或者其他类似情况; call 的第一个参数的执行结果必须是函数类型的值(和预定义函数如 print 明显不同); 该函数类型值必须有 1 到 2 个返回值,如果有 2 个则后一个必须是 error 接口类型; 如果有 2 个返回值的方法返回的 error 非 nil,模板执行会中断并返回给调用模板执行者 该错误;
自定义模板函数

main.go中:

LoadHTMLGlob之前SetFuncMap,添加自定义的模版函数

SetFuncMap就是设置了一个Map

r := gin.Default()
r.SetFuncMap(template.FuncMap{
  "UnixToTime": UnixToTime,
})
r.LoadHTMLGlob("templates/*/**")

实现函数

func UnixToTime(timestamp int64) string {
	t := time.Unix(timestamp, 0)
	fmt.Println(t)
	return t.Format("2006-01-02 15:04:05")
}

模版中:

调用函数

函数名 参数1 参数2……

<p>{{UnixToTime .date}}</p>

嵌套 template

新建public/header.html

{{define "public/header"}}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>public header</title>
    <style>
        h1 {
            background: #000;
            color: #fff;
            text-align: center;
        }
    </style>
</head>

<body>
    <h1>public header {{.title}}</h1>
</body>

</html>
{{end}}

在其他模版内引入

{{template "public/header" .}}

注意加上最后的“.”

静态文件服务

当我们渲染的 HTML 文件中引用了静态文件时,我们需要配置静态 web 服务

r.Static(“/static”, “./static”) 前面的/static 表示路由 后面的./static 表示路径

若css放在/static/css下,则在html中这样引入

<link rel="stylesheet" href="/static/css/base.css">

路由详解

GET POST 以及获取 Get Post 传值

Get 请求传值
r.GET("/", func(c *gin.Context) {
  username := c.Query("username")
  age := c.Query("age")
  page := c.DefaultQuery("page", "1")

  c.JSON(http.StatusOK, gin.H{
    "username": username,
    "age":      age,
    "page":     page,
  })
})

此时的GET请求应该如下:

GET http://127.0.0.1:8080?username=kamisatoayaka&age=18
Post 请求传值 获取 form 表单数据

前台:

<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "default/user.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="static/css/base.css">
</head>
<body>
    <form action="/doAddUser1" method="post">
        
        用户名:<input type="text" name="username" /> <br><br>
        密码:<input type="password" name="password" /> <br><br>      

        <input type="submit" value="提交">
    </form>
</body>
</html>

{{end}}

后台:

//post演示
r.GET("/user", func(c *gin.Context) {
  c.HTML(http.StatusOK, "default/user.html", gin.H{})
})
//获取表单post过来的数据
r.POST("/doAddUser1", func(c *gin.Context) {
  username := c.PostForm("username")
  password := c.PostForm("password")
  age := c.DefaultPostForm("age", "20")

  c.JSON(http.StatusOK, gin.H{
    "username": username,
    "password": password,
    "age":      age,
  })
})
获取 GET POST 传递的数据绑定到结构体

定义结构体,添加form标签

json标签的作用是解析的时候把json字段名改成小写

type UserInfo struct {
	Username string `json:"username" form:"username"`
	Password string `json:"password" form:"password"`
}

GET

声明为结构体

传的时候用它的指针!!!

别他妈写出什么user:=&UserInfo{}然后传&user了,你传牛魔呢

或者干脆用user:=new(UserInfo)

直接传user

浪费老子一个早上看你那💩代码

r.GET("/getUser", func(c *gin.Context) {
  user := UserInfo{}
  if err := c.ShouldBind(&user); err == nil {
    fmt.Printf("%#v", user)
    c.JSON(http.StatusOK, user)
  } else {
    c.JSON(http.StatusOK, gin.H{
      "err": err.Error(),
    })
  }
})

POST

r.POST("/doAddUser2", func(c *gin.Context) {
  user := UserInfo{}
  if err := c.ShouldBind(&user); err == nil {
    c.JSON(http.StatusOK, user)
  } else {
    c.JSON(http.StatusBadRequest, gin.H{
      "err": err.Error(),
    })
  }
})
获取 Post Xml 数据
r.POST("/xml", func(c *gin.Context) {
  article := Article{}

  xmlSliceData, _ := c.GetRawData() //获取 c.Request.Body 读取请求数据

  fmt.Println(xmlSliceData)

  if err := xml.Unmarshal(xmlSliceData, &article); err == nil {
    c.JSON(http.StatusOK, article)
  } else {
    c.JSON(http.StatusBadRequest, gin.H{
      "err": err.Error(),
    })
  }

})
动态路由
r.GET("/list/:cid", func(c *gin.Context) {

  cid := c.Param("cid")
  c.String(200, "%v", cid)

})

路由分组

在同文件下分组

func main() { router := gin.Default()

// 简单的路由组: v1 
  v1 := router.Group("/v1") {

  v1.POST("/login", loginEndpoint)

  v1.POST("/submit", submitEndpoint)

  v1.POST("/read", readEndpoint) }

// 简单的路由组: v2 
  v2 := router.Group("/v2") {

  v2.POST("/login", loginEndpoint)

  v2.POST("/submit", submitEndpoint)

  v2.POST("/read", readEndpoint) }

  router.Run(":8080")

}

拆分到不同文件

新建 routes 文件夹,routes 文件下面新建 adminRoutes.go、apiRoutes.go、defaultRoutes.go

在main.go中,导入routers包

routers.AdminRoutersInit(r)
routers.ApiRoutersInit(r)
routers.DefaultRoutersInit(r)

然后在各自的xxx.go下配置路由即可

Gin 中自定义控制器

新建controllers文件夹,在其下建立对应的控制器,以admin为例

新建admin文件夹,在其中建立userController.go

package admin

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

type UserController struct{}

func (con UserController) Index(c *gin.Context) {
	c.String(200, "用户列表")
}

func (con UserController) Add(c *gin.Context) {
	c.String(200, "add 用户列表")
}

之所以建立结构体是为了方便继承

然后在对应的router中使用即可

package routers

import (
	"demo4/controllers/admin"

	"github.com/gin-gonic/gin"
)

func AdminRoutersInit(r *gin.Engine) {
	adminRouters := r.Group("/admin")
	{
		adminRouters.GET("/", func(c *gin.Context) {
			c.String(200, "后台首页")
		})
		adminRouters.GET("/user", admin.UserController{}.Index)
		adminRouters.GET("/user/add", admin.UserController{}.Add)
		adminRouters.GET("/article", func(c *gin.Context) {
			c.String(200, "新闻列表")
		})
	}
}

继承

新建 controller/admin/BaseController.go

package admin import (

  "net/http"

  "github.com/gin-gonic/gin"
) 

type BaseController struct { } 
func (c BaseController) Success(ctx *gin.Context) {
  ctx.String(http.StatusOK, "成功") 
} 
func (c BaseController) Error(ctx *gin.Context) {
  ctx.String(http.StatusOK, "失败") 
}

可以让 UserController继承BaseController

type UserController struct{
	BaseController
}

然后直接调用BaseController的方法

Gin 中间件

next

先跳转到后面的剩余处理程序,再回来继续执行

func initMiddleware(c *gin.Context) {
	start := time.Now().UnixNano()
	fmt.Println("1-我是一个中间件")
	//调用该请求的剩余处理程序
	c.Next()

	fmt.Println("2-我是一个中间件")
	end := time.Now().UnixNano()

	fmt.Println(end - start)
}

Abort

终止调用请求的剩余处理程序

func initMiddleware(c *gin.Context) {
	start := time.Now().UnixNano()
	fmt.Println("1-我是一个中间件")
	//调用该请求的剩余处理程序
	c.Abort()

	fmt.Println("2-我是一个中间件")
	end := time.Now().UnixNano()

	fmt.Println(end - start)
}

一个路由可以添加多个中间件

r.GET("/login", initMiddleware, initMiddleware2, func(c *gin.Context) {
	c.String(200, "login")
})

全局中间件

在main函数中添加:

r.Use(func1,func2,...)

分组路由中间件

法1:

adminRouters:=r.Group("/admin",func)

法2:

adminRouters.Use(func)

中间件传值

ctx.Set("keyName","value")
key,_=ctx.Get("keyName") //key是空接口类型

Model

就是把一个常用的功能,放到一个模块里,中间件/控制器/main都可以使用该功能。

例如把时间转换放到一个model里。

文件上传

html模版:

<body>
    <h2>演示文件上传</h2>

    <form action="/admin/user/doUpload" method="post" enctype="multipart/form-data">
        
        用户名:<input type="text" name="username" placeholder="用户名" />
        <br>
        <br>
        头 像<input type="file" name="face" />
        <br>    <br>
				头 像2<input type="file" name="face2" />
        <br>    <br>
      	头 像2<input type="file" name="face[]" />
        <br>    <br>
        <input type="submit" value="提交">
    </form>
</body>

Controller:

func (con UserController) DoUpload(c *gin.Context) {
	username := c.PostForm("username")

	file, err := c.FormFile("face")
	file2, err := c.FormFile("face2")
  file,err:= c.FormFile("face[]")
  
  for _,file:=range file{
    dst:=path.Join("./static/upload",file.Filename)
    c.SaveUploadedFile(file,dst)
  }
  
	// file.Filename 获取文件名称  aaa.jpg   ./static/upload/aaa.jpg
	dst := path.Join("./static/upload", file.Filename)
	if err == nil {
		c.SaveUploadedFile(file, dst)
	}
	// c.String(200, "执行上传")
  dst2 := path.Join("./static/upload", file2.Filename)
	if err == nil {
		c.SaveUploadedFile(file2, dst2)
	}
  
	c.JSON(http.StatusOK, gin.H{
		"success":  true,
		"username": username,
		"dst":      dst,
    "dst2": dst2
	})
}

按日期存储文件

  1. 获取上传文件
  2. 判断后缀是否合法
  3. 创建保存目录
  4. 生成文件名和保存的目录
  5. 执行上传
func (c UserController) DoAdd(ctx *gin.Context) {
	username := ctx.PostForm("username")
	// 1、获取上传的文件
	file, err1 := ctx.FormFile("face")
	if err1 == nil {
		// 2、获取后缀名 判断类型是否正确 .jpg .png .gif .jpeg
		extName := path.Ext(file.Filename)
		allowExtMap := map[string]bool{".jpg": true, ".png": true, ".gif": true, ".jpeg": true}
		if _, ok := allowExtMap[extName]; !ok {
			ctx.String(200, "文件类型不合法")
			return
		}
		// 3、创建图片保存目录 static/upload/20200623
		day := models.GetDay()
		dir := "./static/upload/" + day
		if err := os.MkdirAll(dir, 0666); err != nil {
			log.Error(err)
		}
		// 4、生成文件名称 144325235235.png
		fileUnixName := strconv.FormatInt(models.GetUnix(), 10)
		// static/upload/20200623/144325235235.png
		saveDir := path.Join(dir, fileUnixName+extName)
		ctx.SaveUploadedFile(file, saveDir)
	}
	ctx.JSON(http.StatusOK, gin.H{"message": "文件上传成功", "username": username})
	// ctx.String(200, username)
} 

Cookie

SetCookie

c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)

第一个参数 key

第二个参数 value

第三个参数 过期时间.如果只想设置 Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递 nil

第四个参数 cookie 的路径

第五个参数 cookie 的路径 Domain 作用域 本地调试配置成 localhost , 正式上线配置成域名

第六个参数是 secure ,当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效

第七个参数 httpOnly,是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,

则通过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生

Cookie(获取)

cookie, err := c.Cookie("name")

Session

session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 session保存在服务器上。当客户端浏览器第一次访问服务器并发送请求时,服务器端会创建一个 session 对象,生成一个类似于 key,value 的键值对,然后将 value 保存到服务器 将 key(cookie)返回到浏览器(客户)端。浏览器下次访问时会携带 key(cookie),找到对应的 session(value)。

package main
import ( "github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	// 创建基于 cookie 的存储引擎,secret11111 参数是用于加密的密钥,可以改成redis等
	store := cookie.NewStore([]byte("secret11111"))
	// 设置 session 中间件,参数 mysession,指的是 session 的名字,也是 cookie 的名字
	// store 是前面创建的存储引擎,我们可以替换成其他存储引擎
	r.Use(sessions.Sessions("mysession", store))
	r.GET("/", func(c *gin.Context) {
		//初始化 session 对象
		session := sessions.Default(c)
		//设置过期时间
		session.Options(sessions.Options{
			MaxAge: 3600 * 6, // 6hrs
		})
		//设置 Session
		session.Set("username", "张三")
		session.Save()
		c.JSON(200, gin.H{"msg": session.Get("username")})
	})
	r.GET("/user", func(c *gin.Context) {
		// 初始化 session 对象
		session := sessions.Default(c)
		// 通过 session.Get 读取 session 值
		username := session.Get("username")
		c.JSON(200, gin.H{"username": username})
	})
	r.Run(":8000")
}

Gorm

gorm.model

// gorm.Model 定义
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

可以用继承加入到自己的结构体中,也可以不加入

默认用ID作为主键

type User struct {
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // 设置字段大小为255
  MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
  Num          int     `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
  Address      string  `gorm:"index:addr"` // 给address字段创建名为addr的索引
  IgnoreMe     int     `gorm:"-"` // 忽略本字段
}

tag

结构体标记(Tag)描述
Column指定列名
Type指定列数据类型
Size指定列大小, 默认值255
PRIMARY_KEY将列指定为主键
UNIQUE将列指定为唯一
DEFAULT指定列默认值
PRECISION指定列精度
NOT NULL将列指定为非 NULL
AUTO_INCREMENT指定列是否为自增类型
INDEX创建具有或不带名称的索引, 如果多个索引同名则创建复合索引
UNIQUE_INDEXINDEX 类似,只不过创建的是唯一索引
EMBEDDED将结构设置为嵌入
EMBEDDED_PREFIX设置嵌入结构的前缀
-忽略此字段
结构体标记(Tag)描述
MANY2MANY指定连接表
FOREIGNKEY设置外键
ASSOCIATION_FOREIGNKEY设置关联外键
POLYMORPHIC指定多态类型
POLYMORPHIC_VALUE指定多态值
JOINTABLE_FOREIGNKEY指定连接表的外键
ASSOCIATION_JOINTABLE_FOREIGNKEY指定连接表的关联外键
SAVE_ASSOCIATIONS是否自动完成 save 的相关操作
ASSOCIATION_AUTOUPDATE是否自动完成 update 的相关操作
ASSOCIATION_AUTOCREATE是否自动完成 create 的相关操作
ASSOCIATION_SAVE_REFERENCE是否自动完成引用的 save 的相关操作
PRELOAD是否自动完成预加载的相关操作

表名

type User struct {} // 默认表名是 `users`

// 将 User 的表名设置为 `profiles`
func (User) TableName() string {
  return "profiles"
}

func (u User) TableName() string {
  if u.Role == "admin" {
    return "admin_users"
  } else {
    return "users"
  }
}

// 禁用默认表名的复数形式,如果置为 true,则 `User` 的默认表名是 `user`
db.SingularTable(true)

列名

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // set column name to `beast_id`
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
  Age         int64     `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}
  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值