Gin

Gin是Golang的一个后端框架,封装比较优雅,API友好。

go get -u github.com/gin-gonic/gin

1、hello word

package main

import (
"github.com/gin-gonic/gin"
"net/http"
) func main() {
r := gin.Default() // 创建引擎 // 绑定路由规则,执行函数,gin.Context封装了request和response
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK,"hello world")
}) r.Run() // 监听端口,默认是:8080
}

2、路由

2.1、基本路由

gin 框架中采用的路由库是基于httprouter做的

地址为:https://github.com/julienschmidt/httprouter

r.GET("/",func(c *gin.Context){...})
r.POST("/",func(c *gin.Context){...})
r.PUT("/",func(c *gin.Context){...})
r.DELETE("/",func(c *gin.Context){...})

此外还有一个匹配所有的请求方法Any

r.Any("/",func(c *gin.Context){...})

2.2、路由组

我们可以将拥有共同前缀的url划为一个路由组,习惯性用{}包裹同组路由,只是为了看着清晰。

func main() {
r := gin.Default() user := r.Group("user")
{
user.GET("/index", func(c *gin.Context) {
c.String(http.StatusOK,"GET")
})
user.POST("/index", func(c *gin.Context) {
c.String(http.StatusOK,"POST")
})
}
r.Run()
}

如上,我们可以借助postman工具,使用GET和POST请求localhost:8080/user/index,会分别返回GET和POST。

同时呢,路由组也是支持嵌套的。

user := r.Group("/user")
{
user.GET("/index", func(c *gin.Context) {
c.String(http.StatusOK,"GET")
})
user.POST("/index", func(c *gin.Context) {
c.String(http.StatusOK,"POST")
}) // 嵌套路由组
boy := user.Group("/boy")
{
boy.GET("/index", func(c *gin.Context) {
c.String(http.StatusOK,"boy")
})
}
}

同样,我们在postman进行测试,使用get方式访问localhost:8080/user/boy/index,返回boy。

通常我们将路由组分在划分业务逻辑或划分API版本。

2.3、RESTful API

REST与技术无关,代表一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

简单来说,就是客户端与服务器之间进行交互时候,使用HTTP协议中4个请求方法代表不同的动作。

GET(获取资源),POST(新建资源),PUT(更新资源),DELETE(删除资源)

只要API程序尊徐了REST风格,那么就可以称其为RESTful API。

比如我们现在要编写一个学生管理系统,我们可以对一个学生进行查询,创建,更新,删除等操作。按照以往经验,我们会设计成如下模式:

请求方法 URL 含义
GET /get_student 查询学生信息
POST /create_student 创建学生信息
PUT /update_student 更新学生信息
DELETE /delete_student 删除学生信息

同样的需求我们使用RESTful API设计:

请求方式 URL 含义
GET /student 查询学生信息
POST /student 创建学生信息
PUT /student 更新学生信息
DELETE /student 删除学生信息

如果足够细心的话,会发现上面我们路由组里面其实已经用到了这种方式,只不过一般返回数据是JSON格式。

func main() {
r := gin.Default() // 创建路由 r.GET("/student", func(c *gin.Context) {
// 我们在返回数据的时候,可以使用map,也可以使用gin.H。
c.JSON(http.StatusOK, map[string]interface{}{
"message":"get",
})
}) r.POST("/student", func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{ //我们在返回数据的时候,可以使用map,也可以使用gin.H。
"message":"post",
})
})
r.Run()
}

3、参数解析

3.1、API参数

请求的参数通过url路径进行传递,例如:/user/Negan/救世军。获取请求参数如下:

注意:出现汉字使用Chrome浏览器测试,postman不能解析中文。

func main() {
r := gin.Default()
r.GET("/user/:name/:title", func(c *gin.Context) {
name := c.Param("name")
title := c.Param("title")
c.JSON(http.StatusOK,gin.H{
"name":name,
"title":title,
})
})
r.Run()
}

3.2 、获取querystring参数

querystring是指URL中?后面携带的参数,例如:/user?name=Negan&tag=救世军。获取请求参数如下:

注意:querystring参数可以通过DefaultQuery()Query()两个方法获取,前者如果不存在,则返回一个默认值,后者不存在则返回空字符串。

func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
name := c.DefaultQuery("name","Rick") // 如果不存在,就使用默认
tag := c.Query("tag") // 如果不存在,则返回空字符串
c.JSON(http.StatusOK,gin.H{
"name":name,
"tag":tag,
})
})
r.Run()
}

3.3、获取表单参数

表单传输为POST请求,http创建的传世格式为四种:

  • application/json
  • application/x-www/form-urlencoded
  • application/xml
  • multipart/form-data

同样,表单参数的获取,gin框架也为我们提供了两种方法,DefaultPostForm()PostForm()方法。前者如果获取不到会返回一个默认值,后者会返回一个空字符串。

func main() {
r := gin.Default()
r.POST("/", func(c *gin.Context) {
name := c.PostForm("name")
tag := c.DefaultPostForm("tag","Boss")
c.JSON(http.StatusOK,gin.H{
"name":name,
"tag":tag,
})
})
r.Run()
}

3.4 、数据解析与绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringFormJsonXML以及URI等参数到结构体中。

注意:解析的数据必须存在,若接收空值则报错。

type Login struct {
User string `form:"user" json:"user" uri:"user" binding:"required"`
Password string `form:"password" json:"password" uri:"password" binding:"required"`
} func main() {
r := gin.Default()
login := Login{} // 声明一个结构体
// 绑定json示例 {"user":"Negan","password":"123456"}
r.POST("/json", func(c *gin.Context) {
// c.ShouldBind() 通吃,会根据content-type自动推导
if err := c.ShouldBindJSON(&login); err != nil{
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"user": login.User,
"password": login.Password,
})
}) // 绑定form表单示例,我们直接在postman上进行测试
r.POST("/form", func(c *gin.Context) {
if err := c.ShouldBind(&login);err != nil{ //ShouldBind()会自动推导
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"user":login.User,
"password":login.Password,
})
}) // 绑定QueryString参数
r.GET("/query", func(c *gin.Context) {
if err := c.ShouldBindQuery(&login); err != nil{
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"user":login.User,
"password":login.Password,
})
}) // 绑定API参数
r.GET("/api/:user/:password", func(c *gin.Context) {
if err := c.ShouldBindUri(&login); err != nil{
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"user":login.User,
"password":login.Password,
})
}) r.Run()
}

4、文件上传

4.1、单文件上传

package main

import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
) func main() {
r := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32MB
r.MaxMultipartMemory = 8 << 20 // 修改为8MB
r.POST("/upload", func(c *gin.Context) {
// 单个文件
file, err := c.FormFile("file") // 表单的name
if err != nil{
c.JSON(http.StatusInternalServerError, gin.H{
"message":err.Error(),
})
return
}
log.Println(file.Filename)
dst := fmt.Sprintf("H:\\GinDemo\\lesson03\\%s", file.Filename) // 拼接文件保存路径
if err := c.SaveUploadedFile(file, dst); err != nil{
c.JSON(http.StatusInternalServerError,gin.H{
"message":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"message":fmt.Sprintf("%s upload", file.Filename),
})
})
r.Run()
}

4.2、多文件上传

func main() {
r := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32MB
r.MaxMultipartMemory = 8 << 20 // 修改为8MB
r.POST("/upload", func(c *gin.Context) {
form, _ := c.MultipartForm() // 多文件
files := form.File["files"] // 表单的name
for index, file := range files{
log.Println(file.Filename)
dst := fmt.Sprintf("H:\\GinDemo\\lesson03\\%s_%d",file.Filename,index)
// 上传文件到指定目录
c.SaveUploadedFile(file,dst)
}
c.JSON(http.StatusOK,gin.H{
"message":"文件上传能完成",
})
})
r.Run()
}

4.3、使用FastDFS实现文件上传

package tool

import (
"bufio"
"fmt"
"github.com/tedcy/fdfs_client"
"os"
"strings"
) // 上传文件到fastDFS系统
func UploadFile(fileName string)string{
client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
if err != nil{
fmt.Println("打开fast客户端失败",err.Error())
return ""
}
defer client.Destory()
fileId, err := client.UploadByFilename(fileName)
if err != nil{
fmt.Println("上传文件失败",err.Error())
return ""
}
return fileId
} // 从配置文件中读取服务器的ip和端口配置
func FileServerAddr() string{
file,err := os.Open("./config/fastdfs.conf")
if err != nil{
fmt.Println(err)
return ""
}
reader := bufio.NewReader(file)
for{
line, err := reader.ReadString('\n')
line = strings.TrimSpace(line)
if err != nil{
return ""
}
line = strings.TrimSuffix(line,"\n")
str := strings.SplitN(line,"=",2)
switch str[0] {
case "http_server_port":return str[1]
}
}
} // 下载文件
func DownLoadFile(fileId,tempFile string){
client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
if err != nil{
fmt.Println("打开fast客户端失败",err.Error())
return
}
defer client.Destory()
if err = client.DownloadToFile(fileId,tempFile,0,0);err != nil{
fmt.Println("下载文件失败", err.Error())
return
}
} // 删除
func DeleteFile(fileId string){
client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
if err != nil{
fmt.Println("打开fast客户端失败",err.Error())
return
}
defer client.Destory()
if err = client.DeleteFile(fileId);err != nil{
fmt.Println("删除文件失败", err.Error())
return
}
}

配置文件

tracker_server=123.56.243.64:22122
http_server_port=http://123.56.243.64:80
maxConns=100

5、重定向

5.1、http重定向

r.GET("/", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})

5.2、路由重定向

使用HandleContext

// 路由重定向
r.GET("/a", func(c *gin.Context) {
// 跳转到/b对应的路由处理函数
c.Request.URL.Path = "/b" // 把请求的uri修改
r.HandleContext(c) // 继续后续的处理
}) r.GET("/b", func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"msg": "BBBBBB",
})
})

6、Gin渲染

6.1、HTML渲染

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。LoadHTMLGlob()可以加载路径下所有的模板文件,LoadHTMLFiles()加载模板文件需要我们自己填入。

我们定义一个存放模板文件的templates文件夹,然后在内部分别定义一个usersposts文件夹。里面分别定义同名文件index.tmpl

users\index.tmpl文件内容:

{{define "users/index.tmpl"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/xxx/index.css">
<title>Document</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}

posts\index.tmpl文件内容:

{{define "posts/index.tmpl"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}

我们很容易发现,HTML文件开头和结尾我们定义了{{define}}{{end}},Gin框架在进行模板渲染时候会根据这个我们定义的名字进行查找文件。{{.title}}则是Gin后端给我们传过来的数据。

Gin后端代码如下:

package main

import (
"github.com/gin-gonic/gin"
"html/template"
) func main() {
r := gin.Default()
// 解析模板
// r.LoadHTMLFiles("templates/users/index.tmpl","templates/posts/index.tmpl")
r.LoadHTMLGlob("templates/**/*") // 加载所有
r.GET("/posts", func(c *gin.Context) {
c.HTML(200,"posts/index.tmpl",gin.H{ // 模板渲染
"title":"我是posts页面",
})
})
r.GET("/users", func(c *gin.Context) {
c.HTML(200,"users/index.tmpl",gin.H{ // 模板渲染
"title":"<a href='http://www.baidu.com'>百度一下</a>",
})
})
r.Run() // 启动server
}

我们分别访问localhost:8080/posts和localhost:8080/users,会得到不同的页面。

当我们访问localhost:8080/users时,我们会发现浏览器显示的是<a href='http://www.baidu.com'>百度一下</a>,和我们预期的不一样,我们预期的是,页面上应该显示一个超链接才对。但是我们看到的是直接当字符串解析了。

那么怎么解决这个问题呢?

6.2、自定义模板函数

接上面的问题,我们可以定义一个不转义相应内容的safe模板函数。

package main

import (
"github.com/gin-gonic/gin"
"html/template"
) func main() {
r := gin.Default()
// gin框架中添加自定义模板函数
r.SetFuncMap(template.FuncMap{
"safe": func(s string) template.HTML {
return template.HTML(s)
},
})
// 解析模板
r.LoadHTMLGlob("templates/**/*") // 加载所有
r.GET("/users", func(c *gin.Context) {
c.HTML(200,"users/index.tmpl",gin.H{ // 模板渲染
"title":"<a href='http://www.baidu.com'>百度一下</a>",
})
})
r.Run() // 启动server
}

index.tmpl中使用定义好的safe模板函数:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>修改模板引擎的标识符</title>
</head>
<body>
<div>{{ .title | safe }}</div>
</body>
</html>

6.3、静态文件处理

当我们渲染HTML文件中需要引入静态文件时,我们调用gin.Static方法即可。

func main() {
r := gin.Default()
r.Static("/static", "./static") // 第一个参数是url路径,第二个参数是实际文件所在路径
r.LoadHTMLGlob("templates/**/*")
// ...
r.Run(":8080")
}

7、中间件

7.1、定义全局中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。

// 定义中间件(统计请求耗时)
func MiddleWare() gin.HandlerFunc{
return func(c *gin.Context) {
start := time.Now()
c.Set("name","Negan") // 通过c.Set在请求上下文中设置值,后续处理函数能够取到该值
c.Next() // 调用该请求剩余的部分
// c.Abort() // 不调用该请求剩余的部分
// 计算耗时
cost := time.Since(start)
log.Println(cost)
}
}

7.2、注册中间件

在Gin框架中,我们可以为每个路由添加任意数量的中间件。

7.2.1、为全局路由注册
func main() {
r := gin.New() // 新建一个没有任何默认中间件的路由引擎
r.Use(MiddleWare()) // 注册一个全局中间件
r.GET("/", func(c *gin.Context) {
name := c.MustGet("name").(string) // 取值,并自动捕获处理异常
c.JSON(http.StatusOK,gin.H{
"name":name,
})
})
r.Run()
}
7.2.2、为某一个路由单独注册(可注册多个)
func main() {
r := gin.New() // 新建一个没有任何默认中间件的路由引擎
r.GET("/index1", MiddleWare(), func(c *gin.Context) {
name := c.MustGet("name").(string) // 取值,并自动处理异常
c.JSON(http.StatusOK,gin.H{
"name":name,
})
})
r.Run()
}
7.2.3、为某一个方法注册中间件
// 处理器函数
func M1(c *gin.Context){
c.JSON(http.StatusOK,gin.H{
"msg":"OK",
})
} func main(){
r := gin.New()
r.User(M1,MiddleWare()) // m1处理器函数注册中间件
r.GET("/m1",M1)
r.Run()
}
7.2.4、为路由组注册中间件

为路由组注册中间件有两种写法:

  • 写法一:
user := r.Group("/user")
user.Use(MiddleWare())
{
user.GET("/index1", func(c *gin.Context) {
name := c.MustGet("name").(string) // 取值,并自动处理异常
c.JSON(http.StatusOK,gin.H{
"name":name,
})
})
user.GET("/index2",func(c *gin.Context) {
name := c.MustGet("name").(string) // 取值,并自动处理异常
c.JSON(http.StatusOK,gin.H{
"name":name,
})
})
}
  • 写法二:
user := r.Group("/user", MiddleWare())
{
user.GET("/index1", func(c *gin.Context) {
name := c.MustGet("name").(string) // 取值,并自动处理异常
c.JSON(http.StatusOK,gin.H{
"name":name,
})
})
user.GET("/index2",func(c *gin.Context) {
name := c.MustGet("name").(string) // 取值,并自动处理异常
c.JSON(http.StatusOK,gin.H{
"name":name,
})
})
}

7.3、中间件注意事项

gin.Default默认使用了LoggerRecovery中间件,其中:Logger中间件将日志写入gin.DefaultWriter,即配置了GIN_MODE=releaseRecovery中间件会recover任何panic,如果有panic的话,会写入500响应码。

如果不想使用上面两个默认中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

8、会话控制

8.1、Cookie

HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出。cookie就是解决HTTP无状态的方案之一,cookie实际上就是服务器在浏览器上保存的一段信息,浏览器有了cookie之后,每次向服务器发送请求时都会将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求。

8.1.1、Go操作cookie

标准库net/http中定义了cookie,它代表一个出现在HTTP响应头中Set-Cookie的值,或者HTTP请求头中Cookie的值的HTTP cookie

type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
// MaxAge=0表示未设置Max-Age属性
// MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
// MaxAge>0表示存在Max-Age属性,单位是秒
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string // 未解析的“属性-值”对的原始文本
}

具体实现代码:

// 创建两个cookie
func setCookie(w http.ResponseWriter, r *http.Request){
// 创建cookie1
cookie1 := http.Cookie{
Name: "user",
Value: "admin",
HttpOnly: true,
MaxAge: 999,
}
// 创建cookie2
cookie2 := http.Cookie{
Name: "user1",
Value: "admin1",
HttpOnly:true,
MaxAge:999,
} // 将cookie发送给浏览器
//w.Header().Set("Set-Cookie", cookie.String())
//w.Header().Add("Set-Cookie",cookie.String())
http.SetCookie(w, &cookie)
} func getCookie(w http.ResponseWriter, r *http.Request){
// 会将两个cookie全部获取
//cookie := r.Header.Get("Cookie")
//fmt.Fprintln(w,"获取的cookie是:", cookie) // 如果要得到一个cookie,可以直接调用Cookie方法
cookie,_ := r.Cookie("user1")
fmt.Fprintln(w,"获得的cookie是:",cookie) // user1=admin1
} func main() {
http.HandleFunc("/setCookie", setCookie)
http.HandleFunc("/getCookie", getCookie)
http.ListenAndServe(":8888",nil)
}
8.1.2、Gin中操作Cookie
func main() {
r := gin.New() // 新建一个没有任何默认中间件的路由引擎
r.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("gin_cookie") // 获取cookie
if err != nil {
cookie = "NotSet"
// 设置cookie
c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
}
fmt.Println(cookie)
})
r.Run()
}

8.2、Session

Cookie虽然在一定程度上解决了"保持状态"的需求,但是Cookie本身最大支持4096字节,以及Cookie保存在客户端,可能会被拦截窃取。这时就需要一种新的东西,能支持更多的字节,保存在客户端,有较高的安全性,这就是Session

我们将Session ID保存到Cookie中,服务器通过该Session ID就能找到与之对应的Session数据 。

Session我们可以使用第三方库实现(基于Redis)

package main

import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/redis"
"github.com/gin-gonic/gin"
) func main() {
r := gin.Default()
store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
r.Use(sessions.Sessions("mysession", store)) r.GET("/incr", func(c *gin.Context) {
session := sessions.Default(c)
var count int
v := session.Get("count")
if v == nil {
count = 0
} else {
count = v.(int)
count++
}
session.Set("count", count)
session.Save()
c.JSON(200, gin.H{"count": count})
})
r.Run(":8000")
}

8.3、JWT

JWT全称JSON Web Token,是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAth2.0业务场景下。

JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token再发回给用户,用户后续请求只需要带上这个Token,服务端解密后就能获取该用户的相关信息了。

8.3.1、生成和解析JWT

我们在这里直接使用jwt-go这个库来实现我们生成和解析JWT的功能。

  • 定义需求

我们根据自己的需求来来决定JWT中保存哪些数据,比如我们规定在JWT中存储username信息,那么我们就定义一个MyClaims结构体。

// MyClaims 自定义声明结构体并内嵌jwt.StandardClaims
// jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个username字段,所以自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type MyClaims struct {
Username string `json:"username"`
jwt.StandardClaims
} const TokenExpireDuration = time.Hour * 2 // 设置过期时间为两小时
var MySecret = []byte("永远不要高估自己") // 定义一个密钥
  • 生成JWT和解析JTW
// 生成JWT
func GenToken(username string) (string, error) {
t := MyClaims{
username,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
Issuer: "Negan", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, t)
// 使用指定的secret签名并获得完整的编码后的字符串token
fmt.Println(token.SignedString(MySecret))
return token.SignedString(MySecret)
} // 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
// 解析token
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{},
func(token *jwt.Token) (interface{}, error) {
return MySecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
8.3.2、Gin中使用JWT
// 定义用户结构体
type UserInfo struct {
Username string `json:"username" form:"username" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
} func authHandler(c *gin.Context) {
// 获取用户发送的用户名以及密码
user := UserInfo{}
if err := c.ShouldBind(&user); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 404,
"msg": "无效的参数",
})
return
}
// 检验用户名以及密码是否正确
if user.Username == "root" && user.Password == "123456" {
// 生成token
tokenString, err := GenToken(user.Username)
if err != nil{
fmt.Println(err)
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"data": gin.H{"token": tokenString},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 400,
"msg": "鉴权失败",
})
return
} func JWTAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
c.JSON(http.StatusOK, gin.H{
"code": 400,
"msg": "请求头auth为空",
})
fmt.Println("请求头为空")
c.Abort() // 终止下面代码
return
}
// 按照空格分割
parts := strings.SplitN(authHeader, " ", 2)
fmt.Println(parts[0])
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusOK, gin.H{
"code": 404,
"msg": "请求头中auth格式有误",
})
c.Abort()
return
}
// parts[1]是获取到的tokenString,使用我们之前定义解析函数解析
msg, err := ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 400,
"msg": "无效的token",
})
c.Abort()
return
}
// 将当前获取的username 保存到上下文中
c.Set("username", msg.Username)
fmt.Println(msg.Username)
c.Next() // 后续处理函数会通过c.Get("username")来获取
}
} func main() {
r := gin.Default()
r.POST("/auth", authHandler) // 设置Token r.GET("/home",JWTAuthMiddleware(), func(c *gin.Context) {
username := c.MustGet("username").(string)
fmt.Println(username)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"data": username,
})
})
r.Run()
}

9、Gin中使用goroutine

当我们在中间件或者handler中启动新的gouroutine时,不能使用原始上下文(c *gin.Context),必须使用其只读副本c.Copy()

如果不使用只读副本,则c的后续的操作不可控,造成并发不安全。

10、运行多个服务

我们可以在多个端口启动服务,例如:

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 {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
}) return e
} func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
}) return e
} 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,
}
// 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
g.Go(func() error {
return server01.ListenAndServe()
}) g.Go(func() error {
return server02.ListenAndServe()
}) if err := g.Wait(); err != nil {
log.Fatal(err)
}
}

11、图片验证码

import (
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"image/color"
) type CaptchaResult struct{
Id string `json:"id"`
Base64lob string `json:"base_64_lob"`
VertifyValue string `json:"code"`
} // 生成图形验证码
func GenerateCaptcha(c *gin.Context){
var parameters = base64Captcha.ConfigCharacter{
Height: 30,
Width: 60,
Mode: 3,
ComplexOfNoiseDot: 0,
ComplexOfNoiseText: 0,
IsShowHollowLine: false,
IsShowNoiseDot: false,
IsShowNoiseText: false,
IsShowSineLine: false,
IsShowSlimeLine: false,
IsUseSimpleFont: true,
CaptchaLen: 4,
BgColor: &color.RGBA{
R: 3,
G: 102,
B: 214,
A: 254,
},
} captchaId, captchaInterfaceInstance := base64Captcha.GenerateCaptcha("",parameters)
base64blob := base64Captcha.CaptchaWriteToBase64Encoding(captchaInterfaceInstance) captchaResult := CaptchaResult{Id: captchaId,Base64lob: base64blob}
Success(c, map[string]interface{}{
"captcha_result": captchaResult,
})
} // 验证验证码
func VertifyCaptcha(id string, value string)bool{
return base64Captcha.VerifyCaptcha(id, value)
}

Gin?有这一篇就够了!的更多相关文章

  1. 关于 Docker 镜像的操作,看完这篇就够啦 !(下)

    紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...

  2. ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...

  3. Ajax原理一篇就够了

    Ajax原理一篇就够了 一.什么是Ajax Ajax(Asynchronous JavaScript and XML的缩写)是一种异步请求数据的web开发技术,对于改善用户的体验和页面性能很有帮助.简 ...

  4. .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新 ...

  5. 想了解SAW,BAW,FBAR滤波器的原理?看这篇就够了!

    想了解SAW,BAW,FBAR滤波器的原理?看这篇就够了!   很多通信系统发展到某种程度都会有小型化的趋势.一方面小型化可以让系统更加轻便和有效,另一方面,日益发展的IC**技术可以用更低的成本生产 ...

  6. 如果这样来理解HTTPS,一篇就够了!

    1.前言 可能有初学者会问,即时通讯应用的通信安全,不就是对Socket长连接进行SSL/TLS加密这些知识吗,干吗要理解HTTPS协议呢. 这其实是个误解:当今主流的移动端IM数据通信,总结下来无外 ...

  7. [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了

    [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 本文首发自:博客园 文章地址: https://www.cnblogs.com/yilezhu/p/ ...

  8. JVM内存模型你只要看这一篇就够了

    JVM内存模型你只要看这一篇就够了 我是一只孤傲的鱼鹰 让我们不厌其烦的从内存模型开始说起:作为一般人需要了解到的,JVM的内存区域可以被分为:线程栈,堆,静态方法区(实际上还有更多功能的区域,并且这 ...

  9. 【java编程】ServiceLoader使用看这一篇就够了

    转载:https://www.jianshu.com/p/7601ba434ff4 想必大家多多少少听过spi,具体的解释我就不多说了.但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问 ...

  10. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

随机推荐

  1. 【vscode】vscode配置python

    [vscode]vscode配置python 前言 ‍ 每次配环境的经历,其实都值得写一篇博客记录一下,以便于自己以后查阅. ‍ 笔者环境: win10 ‍ 过程 ‍ step1:python解释器下 ...

  2. Redis 持久化机制简介【Redis 系列之三】

    〇.前言 Redis 持久化主要有两种:RDB(数据快照模式).AOF(追加模式),另外就是这两种模式的混合模式用. 本文将对这三种情况进行详细介绍. 博主 Redis 相关文章都在这里了:https ...

  3. 基于pandas的数据清洗 -- 缺失值(空值)的清洗

    博客地址:https://www.cnblogs.com/zylyehuo/ 开发环境 anaconda 集成环境:集成好了数据分析和机器学习中所需要的全部环境 安装目录不可以有中文和特殊符号 jup ...

  4. oracle服务 linux启动命令

    一.Linux下启动Oracle Linux下启动Oracle分为两步: 1)启动监听: 2)启动数据库实例: 1.登录服务器,切换到oracle用户,或者以oracle用户登录 [admin@dat ...

  5. [每日算法 - 华为机试] 剑指 Offer 10- II. 青蛙跳台阶问题

    入口 力扣https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/ 题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶.求该 ...

  6. Spring AOP 应用

    Spring AOP 应用 1. 介绍 AOP:面向切面编程,对面向对象编程的一种补充. AOP可以将一些公用的代码,自然的嵌入到指定方法的指定位置. 比如: 如上图,我们现在有四个方法,我们想在每个 ...

  7. FireDAC开发DataSnap应用系统【2】-使用TFDJSONDatasets功能

    类别 说明 TFDJSONDatasets FireDAC使用JSONDataSet,其中可包括多个DataSet对象 TFDJSONDataSetsWriter 把TDataSet写入TFDJSON ...

  8. Greenplum优化总结

    Greenplum优化总结 GP优化需要了解清理缓存.性能监控.执行计划分析等知识.优化主要包含以下四方面: 表.字段,SQL,GP配置.服务器配置,硬件及节点资源. 一. 清理缓存: #!/usr/ ...

  9. TensorFlow重新导入restore报错: OP_REQUIRES failed at save_restore_v2_ops.cc:184 : Not found: Key Variable not found in checkpoint

    最近在解决TensorFlow模型重新导入时遇到下面这个问题,发现网上很多解决办法都在误导,其实报错已经很明显说明问题的根源,只是我们不一定找到问题的根源.报错显示 不能在快照中找到 对应的键值. 报 ...

  10. Echarts服务端渲染以及客户端懒加载实现方案

    为了提升首屏的加载速度,考虑先用服务端渲染快速输出首屏图表,然后等待 echarts.js 加载完后,通过注水操作(Hydration),重新在客户端渲染同样的图表 tips:在客户端渲染的时候,应开 ...