golang gin的必备组件

web框架必备的组件

一。 路由

1. url 和视图函数的映射

2. url 参数的匹配(正则表达式或者平常匹配)

go中:

视图处理函数需要传入的参数:context *gin.Context(上下文的指针)

  1. 正常匹配路径router.GET("/user/:name",handler.Save) 能匹配到 /user/john//user/john这种,不能匹配到 /user/ 这种 或 /user/john/haha/这种

  2. 完全匹配路由router.GET("/user/*name",handler.Save) 凡是/user/后面的都能匹配到,如/user/ ,/user/john/haha/, /user/john/ , /user/john

  3. 正则表达式:不支持

3. 路由用户组

  1. 先创建一个用户组:

    router := gin.Default()    // router Group是为了将一些前缀相同的URL请求放在一起管理   
    group1 := router.Group("/g1")

  2. 再将这个用户组绑定视图函数

    group1.GET("/read1", func10)	// http://0.0.0.0:8888/g1/read1
    group1.GET("/read2", func11)	// http://0.0.0.0:8888/g1/read2

4. 请求方式

// 1. GET 请求
router.GET("/parm/:name", param2)
​
// 2. POST 请求
router.POST("/PPP", pp)
​
// 3. PATCH 请求
router.PATCH("/patch", pach, RouterMiddle2)
​
// 4. DELETE 请求
router.DELETE("/delete", del)

二。 视图函数

1. 获取 url 匹配的参数,获取请求头的信息,包括(get参数,post参数,auth参数等)

go中:

视图处理函数需要传入的参数:context *gin.Context(上下文的指针)

  1. 获取路由的参数

c.Param("name"),c.Param("action")
2. **获取GET的参数**:①:可以设置默认值(只有获取不到参数时才用默认值,这里为Guest):`c.DefaultQuery("firstname", "Guest")`。②:默认值为空:`c.Query("lastname")`
3. **获取POST的参数**:①:设置默认值:`c.DefaultPostForm("nick", "anonymous")`。 ②:默认值为空:`c.PostForm("message")`
4. **获取请求体**(返回的是map类型,键的头字母是大写):`c.Request.Body`
5. **获取请求头**(返回的是map类型,键的头字母是大写):`c.Request.Header`
6. **批量获取get参数保存为map**(返回的是map类型):`c.Request.URL.Query()`

获取 raw 中的请求体

// 获取请求体,最大长度 1024
buf := make([]byte, 1024)
n, _ := c.Request.Body.Read(buf)
fmt.Println(string(buf[0:n]))

2. 重定向

// c 是视图函数中的上下文 Context
// 参数1:固定参数?
// 参数2:重定向的 url
router.GET("/redirect", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "http://shanshanpt.github.io/")
})

3.对数据库数据的分页处理

4. 增删改查的一般流程

4.1 get 获取(判断参数,判断查询的对象)

  1. 从前端获取参数,如果参数不存在,则设置为空 ''

  2. 判断参数是否为空,如果为空,则返回(参数不能为空)

  3. 通过参数获取这个对象或获取这个列表

  4. 判断对象或者列表存不存在,如果不存在,则返回404找不到数据;如果存在,则返回对象或者列表

4.2 post 添加(判断参数,字段唯一性判断,字段必须性判断,添加后判断)

  1. 从前端获取参数,如果参数不存在,则设置为空 ''

  2. 判断参数是否为空,如果为空,则返回(参数不能为空)

  3. 进行字段唯一性判断(如果某字段唯一,则返回字段已拥有)

  4. 进行字段必须性判断(如果字段必须要而没有,则返回字段必须)

  5. 添加

  6. 如果添加成功,则返回200

  7. 如果添加不成功,返回错误信息

4.3 put 修改(判断参数,字段唯一性判断,字段必须性判断,修改后判断)

  1. 从前端获取参数,如果参数不存在,则设置为空 ''

  2. 判断参数是否为空,如果为空,则返回(参数不能为空)

  3. 进行字段唯一性判断(如果某字段唯一,则返回字段已拥有)

  4. 进行字段必须性判断(如果字段必须要而没有,则返回字段必须)

  5. 修改

  6. 如果修改成功,则返回200

  7. 如果修改不成功,返回错误信息

4.4 delete 删除(判断参数,删除后判断)

  1. 从前端获取参数,如果参数不存在,则设置为空 ''

  2. 判断参数是否为空,如果为空,则返回(参数不能为空)

  3. 删除

  4. 如果删除成功,则返回200

  5. 如果删除不成功,则返回错误信息

三。对数据库的操作

1. 数据库和数据表的创建

注:创建时,要写好数据库中表的关系图,以确定表和字段之间的关系

1.1 在数据库软件上创建

1.2 直接执行 sql 语句创建

2. 数据迁移

2.1 数据表的转移:

直接生成 .sql 文件,然后在另一台电脑直接运行这个文件(保留数据和结构)

2.2 数据表的修改:

直接在原本的表格上修改(navicat或者语句)

3. 数据表中数据的操作

3.1 直接使用 mysql 库进行 sql 操作,要注意注入问题

四。序列化和反序列化

1. 序列化

转化为 json,传到前端

①. 第一种

(直接序列化)

// c 是视图函数中的 Context
// gin.H is a shortcut for map[string]interface{}
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
// output:
{"message": "hey","status": 200}
​
// 或者   (最常用)
item := db.GetUploadList()          // 返回一个结构体
c.JSON(http.StatusOK, gin.H{"message": item, "status": 200})    // 直接赋值结构体给 message

②. 第二种

(定义一个结构体,通过结构体来传递 json,不过没有状态码)

// 1. 定义一个结构体
type jsonData struct{
    Id      int     `json:"id"`
    Name    string  `json:"name"`
    Code    uint    `json:"code"`       // 返回状态
}
​
var j jsonData
j.Id = 1
j.Name = "小红"
j.Code = 200
c.JSON(http.StatusOK, j)
// output:{"id": 1, "name": "小红", "code": 200}
​
// 2. 输出字典的数组(最常用),一维,二维都可以
type jsonlist struct{
    Id      int         `json:"json"`
    Name    string      `json:"name"`
    Code    int         `json:"code"`
    Other   [][]int     `json:"other"`
}
var jl jjlist
f :=[][]int {
    {1,2,3},
    {4,5,6},
    {7,8,9},
}
jl.Id = 1
jl.Name = "heihei"
jl.Code = 200
jl.Other = f
c.JSON(http.StatusOK, jl)
// output:{"id": 1, "name": "heihei", "code": 200, "other":[[],[]]}

③. 第三种:JSONP

(使用JSONP可以跨域传输,如果参数中存在回调参数,那么返回的参数将是回调函数的形式)

// 1. 平常形式
data := map[string]interface{}{
	"foo": "bar",
	"code": 200,
}
c.JSONP(http.StatusOK, data)
// output:{"foo":"bar", "code":200}

// 2. 输出二维数组(一维也可以)
f :=[][]int {
	{1,2,3},
	{4,5,6},
	{7,8,9},
}

data := map[string]interface{}{
	"foo": "bar",
	"data": f,
	"code": 200,
}
c.JSONP(http.StatusOK, data)
// output:{"foo":"bar", "data":[[],[]], "code":200}

④. 第四种:SecureJSON

(使用SecureJSON可以防止json劫持,如果返回的数据是数组,则会默认在返回值前加上"while(1)")

// 1.传递的是一个一维数组
names := []string{"lena", "austin", "foo"}
c.SecureJSON(http.StatusOK, names)

// output:while(1);["lena","austin","foo"]

// 2.传递的是一个二维数组
f :=[][]int {
    {1,2,3},
    {4,5,6},
    {7,8,9},
}
c.SecureJSON(http.StatusOK, f)
// output:while(1);[[1,2,3],[4,5,6],[7,8,9]]

2. 反序列化

传来的参数可能是 json 的字符串,需要把它反序列化为这个语言的某种类型

五。上传文件

(图片,视频,音频,文本)

0. 文件路径的获取

// 1. 获取当前路径
dir , _  := os.Getwd()           

// 2. 判断文件或文件夹是否存在(在上传时要判断)
/*
如果返回的错误为nil,说明文件或文件夹存在
如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在
如果返回的错误为其它类型,则不确定是否在存在
*/
_, err := os.Stat(path)
if err == nil {
    fmt.Println("文件存在")
}
if os.IsNotExist(err) {
    fmt.Println("文件不存在")
}

1. 上传单个文件

file, err := c.FormFile("file")		// 从前端根据键获取文件
fmt.Println(file.Filename)			// 打印文件名

dst := ""							// 根据文件来上传文件类型(.txt,.mp4,.mp3等都可以)
c.SaveUploadedFile(file, dst)		// 上传文件到指定的路径

2. 上传多个文件

form, err := c.MultipartForm()			// 实例化多文件表单
files := form.File["files"]				// 从前端接受files参数的值,files的类型是:												// []*multipart.FileHeader

dst := "C:\\Go\\project\\src\\gin_demo\\%s"		// 默认路径,可通过静态路径设置
for _, file := range files {
    fmt.Println(file.Filename)			// 打印文件名,这个是有带后缀的,所以可以用这个原本的名字
    
    savePath := fmt.Sprintf(dst, file.Filename)		// 拼接路径
    c.SaveUploadedFile(file, savePath)	// 上传文件到指定的路径
}

六。认证

1.认证表和用户表的生成和关联

2.token 的生成和生效时间

生成token(加密方法:MD5,可以用其他加密方法)

这个用作登陆时加密的,加密后放到数据库中

// 这个用作登陆加密的
import (
	"fmt"
	"crypto/md5"
	"encoding/hex"
)

func md5V(str string) string  {
    h := md5.New()
    h.Write([]byte(str))
    return hex.EncodeToString(h.Sum(nil))
}
toke := md5V(params)			// 加密,加密之后放到用户对应的数据库中

② 自定义验证中间件

这个用作验证的,通过数据库进行验证

type MyUser struct{
	ID		int
	Name	string
}

func auth(c *gin.Context){
    // 这是中间件执行的地方
	params := c.DefaultQuery("token", "kong")		// 获取参数 token,也可以获取post的
	/*
   	1. 通过token,验证获取数据库的用户和密码
   	2. 若可查到,则返回true,不能则返回false
   	3. 查看生效时间,若过期,则返回false,若没过期,则返回true
   	标识:				
   	authIndent := false
   	authIndent := true
    */
	fmt.Println("验证中")
    u := MyUser{1, "小明"}			// 根据token从数据库中找到的用户,需要根据有无用户来传递
    
    // 传到主视图去,
    // authIndent := c.MustGet("authIndent").(bool) 获取,最后一个括号是类型
	c.Set("authIndent", authIndent)		// False 可以时不通过		
    c.Set("user", u)			// 传递用户到下一个中间件
    
}

③主视图中使用

// 可以定义一个路由组,在这个组内,用这个中间件
authorized := router.Group("/admin", auth)
authorized.GET("/secrets", func(c *gin.Context) {
    authIndent := c.MustGet("authIndent").(bool)
    // get user, it was set by the BasicAuth middleware
    if res{
        fmt.Println("通过")
         c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    }else{
        fmt.Println("不通过")
        c.JSON(http.StatusOK, gin.H{"message": "hey", "status": 500})
    }
})

七。权限

1. 权限的类别

分权限类别,用的时候直接放到中间件去

① 游客

② 普通用户

③ VIP 用户

④ VVIP 用户

2. 分用户级别进行资源权限的控制

同验证一样,通过中间件来控制权限,不过中间件位置放在认证之后,主视图之前

① 编写权限中间件

func permission(c *gin.Context){
    user := c.MustGet("user").(MyUser)		// 从验证那里传过来的用户对象
    /* 
    1. 通过 user 对象,获得这个对象的角色
    2. 根据这个角色id来返回 true 或者false
    permiIndent := false
   	permiIndent := true
    */
    c.Set("permiIndent", permiIndent)
}

②加入到主视图函数中

// 可以定义一个路由组,在这个组内,用这个中间件
authorized := router.Group("/admin", auth, permission)		// 先是验证,后权限
authorized.GET("/secrets", func(c *gin.Context) {
    authIndent := c.MustGet("authIndent").(bool)
    permiIndent := c.MustGet("permiIndent").(bool)
    
    // 只有验证和权限都通过时,才返回数据,否则不返回数据
    if (authIndent && permiIndent){
        fmt.Println("通过")
         c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    }else{
        fmt.Println("不通过")
        c.JSON(http.StatusOK, gin.H{"message": "hey", "status": 500})
    }
})

八。模板

(不是前后分离的项目需要用到)

九。中间件

1. 全局中间件

在视图执行之前或之后,执行的中间件函数(还可以给视图函数传递参数)

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        // 设置example变量到Context的Key中,通过Get等函数可以取得
        // 下面通过 example := c.MustGet("example").(string) 获取,最后一个括号是返回类型
        c.Set("example", "12345")
        // 发送request之前
        c.Next()
        // 发送request之后
        latency := time.Since(t)
        log.Print(latency)

        // 这个c.Write是ResponseWriter,我们可以获得状态等信息
        status := c.Writer.Status()
        log.Println(status)
    }
}

func main(){
    router := gin.Default()
    // 1
    router.Use(Logger())
    router.GET("/logger", func(c *gin.Context) {
        example := c.MustGet("example").(string)	// 后面的括号是返回类型
        log.Println(example)
    })

    // 2
    // 下面测试BasicAuth()中间件登录认证
    //
    var secrets = gin.H{
        "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
        "austin": gin.H{"email": "austin@example.com", "phone": "666"},
        "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
    }
    // 下面 gin.BasicAuth() 中间件
    // gin.Accounts is a shortcut for map[string]string
    authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
        "foo":    "bar",
        "austin": "1234",
        "lena":   "hello2",
        "manu":   "4321",
    }))
    // 请求URL: 0.0.0.0:8888/admin/secrets
    authorized.GET("/secrets", func(c *gin.Context) {
        // get user, it was set by the BasicAuth middleware
        user := c.MustGet(gin.AuthUserKey).(string)
        if secret, ok := secrets[user]; ok {
            c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
        } else {
            c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
        }
    })

    router.Run(":8888")
}
// 用户测试代码
func main() {
    // 下面测试使用中间件
    resp,_ = http.Get("http://0.0.0.0:8888/logger")
    helpRead(resp)

    // 测试验证权限中间件BasicAuth
    resp,_ = http.Get("http://0.0.0.0:8888/admin/secrets")
    helpRead(resp)
}

/*
服务端使用Use方法导入middleware, 当请求/logger来到的时候, 会执行Logger(), 并且我们知道在GET注册的时候, 同时注册了匿名函数, 所有请看Logger函数中存在一个c.Next()的用法, 它是取出所有的注册的函数都执行一遍, 然后再回到本函数中, 所以, 本例中相当于是先执行了 c.Next()即注册的匿名函数, 然后回到本函数继续执行. 所以本例的Print的输出顺序是:
log.Println(example)
log.Print(latency)
log.Println(status)
如果将c.Next()放在log.Print(latency)后面, 那么log.Println(example)和log.Print(latency)执行的顺序就调换了. 所以一切都取决于c.Next()执行的位置. c.Next()的核心代码如下:
// Next should be used only in the middlewares.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func (c *Context) Next() {
	c.index++
	s := int8(len(c.handlers))
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

它其实是执行了后面所有的handlers.
关于使用gin.BasicAuth() middleware, 可以直接使用一个router group进行处理, 本质和logger一样.
*/

2. 单个路由中间件(视图函数之前)

进入路由之后,在视图函数执行之前,执行的中间件函数

3. 单个路由中间件(之后)

在进入路由之后,在视图函数执行之后,执行的中间件函数

十。静态文件

设置静态文件的路径

// 设置静态资源文件目录,并且绑定一个Url前缀
router.Static("/assets", "./assets")

// 第一个参数是api 第二个静态文件的文件夹相对目录
router.StaticFS("/data", http.Dir("./data")) 

// 第一个参数是api 第二个参数是具体的文件名字
// 为单个静态资源文件,绑定url
// 这里的意思就是将/favicon.ico这个url,绑定到./resources/favicon.ico这个文件
router.StaticFile("/favicon.ico", "./resources/favicon.ico")

十一。缓存

1. redis 缓存数据

十二。分页

MySQL 的分页查询 SQL 语句 - 一线大码 - 博客园

如果谁再问你“如何优化mysql分页查询”,请把这篇文章甩给他! - 知乎

https://zhuanlan.zhihu.com/p/68099481

https://zhuanlan.zhihu.com/p/84749696

十三。响应

1. 获取响应头

// 获取响应头中的 Range 字段
rangeHeader, ok := c.Request.Header["Range"]

2. 设置响应头

// 在视图函数中设置响应头, 内容类型,内容长度
func resHeader(c *gin.Context){
    c.Header("Content-Type", "image/jpg")
    c.Header("Content-Length", "54354354")
}

十四。跨域

gin框架写的http接口支持跨域请求的方法很简单,实现一个支持跨域的中间件接口就行,关键代码如下:

package main

import (
	"net/http"

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

// 跨域中间件
func cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		origin := c.Request.Header.Get("origin")
		if len(origin) == 0 {
			origin = c.Request.Header.Get("Origin")
		}
		c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST")
		c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}

func main() {
	router := gin.New()
	router.Use(cors())
	router.Use(gin.Recovery())
	router.GET("/ping", statusOKHandler)
	router.GET("/version", versionHandler)
	router.Run(":8080")
}

十五。JWT 和 Cookie

十六。日志记录

十七。api 文档展示

1. swag

gin-swagger生成API文档 - 静静别跑 - 博客园

简述 Gin 框架如何集成swagger_jinjiangcc-CSDN博客_gin整合swagger

十八。防止XSS攻击

import (
	"html/template"
)

html.escapeString(content)		// 解析
html.UnescapeString(content)   // 反解

template.HTMLEscapeString		// 解析

JSEscapeString					// 解析

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值