Gin是Golang的一个后端框架,封装比较优雅,API友好。
go get -u github.com/gin-gonic/gin
1、hello word
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {r := gin.Default() // 创建引擎// 绑定路由规则,执行函数,gin.Context封装了request和responser.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识别请求数据类型并利用反射机制自动提取请求中QueryString、Form、Json、XML以及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 mainimport ("fmt""github.com/gin-gonic/gin""log""net/http")func main() {r := gin.Default()// 处理multipart forms提交文件时默认的内存限制是32MBr.MaxMultipartMemory = 8 << 20 // 修改为8MBr.POST("/upload", func(c *gin.Context) {// 单个文件file, err := c.FormFile("file") // 表单的nameif 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提交文件时默认的内存限制是32MBr.MaxMultipartMemory = 8 << 20 // 修改为8MBr.POST("/upload", func(c *gin.Context) {form, _ := c.MultipartForm() // 多文件files := form.File["files"] // 表单的namefor 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 toolimport ("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:22122http_server_port=http://123.56.243.64:80maxConns=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文件夹,然后在内部分别定义一个users和posts文件夹。里面分别定义同名文件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 mainimport ("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时,我们会发现浏览器显示的是百度一下,和我们预期的不一样,我们预期的是,页面上应该显示一个超链接才对。但是我们看到的是直接当字符串解析了。
那么怎么解决这个问题呢?
6.2、自定义模板函数
接上面的问题,我们可以定义一个不转义相应内容的safe模板函数。
package mainimport ("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、注册中间件
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、为路由组注册中间件
为路由组注册中间件有两种写法:
- 写法一:
```java
user := r.Group(“/user”)
user.Use(MiddleWare())
{
user.GET(“/index1”, func(c *gin.Context) {
}) user.GET(“/index2”,func(c *gin.Context) {name := c.MustGet("name").(string) // 取值,并自动处理异常c.JSON(http.StatusOK,gin.H{"name":name,})
}) }name := c.MustGet("name").(string) // 取值,并自动处理异常c.JSON(http.StatusOK,gin.H{"name":name,})
- 写法二:```javauser := 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默认使用了Logger和Recovery中间件,其中:Logger中间件将日志写入gin.DefaultWriter,即配置了GIN_MODE=release。Recovery中间件会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 stringValue stringPath stringDomain stringExpires time.TimeRawExpires string// MaxAge=0表示未设置Max-Age属性// MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"// MaxAge>0表示存在Max-Age属性,单位是秒MaxAge intSecure boolHttpOnly boolRaw stringUnparsed []string // 未解析的“属性-值”对的原始文本}
具体实现代码:
// 创建两个cookiefunc setCookie(w http.ResponseWriter, r *http.Request){// 创建cookie1cookie1 := http.Cookie{Name: "user",Value: "admin",HttpOnly: true,MaxAge: 999,}// 创建cookie2cookie2 := 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") // 获取cookieif err != nil {cookie = "NotSet"// 设置cookiec.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 mainimport ("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 intv := 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
```java
// 生成JWT
func GenToken(username string) (string, error) {
t := MyClaims{
} // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, t) // 使用指定的secret签名并获得完整的编码后的字符串token fmt.Println(token.SignedString(MySecret)) return token.SignedString(MySecret) }username,jwt.StandardClaims{ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间Issuer: "Negan", // 签发人},
// 解析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”) }
<a name="mhWSM"></a>##### 8.3.2、Gin中使用JWT```java// 定义用户结构体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" {// 生成tokentokenString, 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) // 设置Tokenr.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 mainimport ("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)}
参考:https://www.cnblogs.com/huiyichanmian/p/14072124.html
GoWeb服务器搭建
参考:https://www.cnblogs.com/huiyichanmian/p/13798940.html
FastDFS分布式文件服务器搭建以及Golang和Python调用
参考:https://www.cnblogs.com/huiyichanmian/p/14096406.html
Golang操作Json
参考:https://www.cnblogs.com/huiyichanmian/p/12955230.html
