web框架必备的组件
一。 路由
1. url 和视图函数的映射
2. url 参数的匹配(正则表达式或者平常匹配)
go中:
视图处理函数需要传入的参数:context *gin.Context
(上下文的指针)
-
正常匹配路径:
router.GET("/user/:name",handler.Save)
能匹配到/user/john/
或/user/john
这种,不能匹配到/user/
这种 或/user/john/haha/
这种 -
完全匹配路由:
router.GET("/user/*name",handler.Save)
凡是/user/
后面的都能匹配到,如/user/ ,/user/john/haha/, /user/john/ , /user/john
-
正则表达式:不支持
3. 路由用户组
-
先创建一个用户组:
router := gin.Default() // router Group是为了将一些前缀相同的URL请求放在一起管理 group1 := router.Group("/g1")
-
再将这个用户组绑定视图函数
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
(上下文的指针)
-
获取路由的参数:
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 获取(判断参数,判断查询的对象)
-
从前端获取参数,如果参数不存在,则设置为空 ''
-
判断参数是否为空,如果为空,则返回(参数不能为空)
-
通过参数获取这个对象或获取这个列表
-
判断对象或者列表存不存在,如果不存在,则返回404找不到数据;如果存在,则返回对象或者列表
4.2 post 添加(判断参数,字段唯一性判断,字段必须性判断,添加后判断)
-
从前端获取参数,如果参数不存在,则设置为空 ''
-
判断参数是否为空,如果为空,则返回(参数不能为空)
-
进行字段唯一性判断(如果某字段唯一,则返回字段已拥有)
-
进行字段必须性判断(如果字段必须要而没有,则返回字段必须)
-
添加
-
如果添加成功,则返回200
-
如果添加不成功,返回错误信息
4.3 put 修改(判断参数,字段唯一性判断,字段必须性判断,修改后判断)
-
从前端获取参数,如果参数不存在,则设置为空 ''
-
判断参数是否为空,如果为空,则返回(参数不能为空)
-
进行字段唯一性判断(如果某字段唯一,则返回字段已拥有)
-
进行字段必须性判断(如果字段必须要而没有,则返回字段必须)
-
修改
-
如果修改成功,则返回200
-
如果修改不成功,返回错误信息
4.4 delete 删除(判断参数,删除后判断)
-
从前端获取参数,如果参数不存在,则设置为空 ''
-
判断参数是否为空,如果为空,则返回(参数不能为空)
-
删除
-
如果删除成功,则返回200
-
如果删除不成功,则返回错误信息
三。对数据库的操作
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 // 解析