gin的基础使用
下载和安装gin模块
go get -u github.com/gin-gonic/gin
简单接口demo
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 创建一个默认的路由引擎
r.GET("/ping", func(c *gin.Context) { // 路由
c.JSON(200, gin.H{ // 返回json数据
"message": "hello golang",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
//r.Run(":8081") // 自定义端口
}
浏览器访问:http://127.0.0.1:8080/ping
返回JSON格式数据
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// gin.H 是map[string]interface{}的缩写
r.GET("/someJSON", func(c *gin.Context) {
// 方式一:自己拼接JSON
c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 方法二:使用结构体
var msg struct {
Name string `json:"user"`
Message string
Age int
}
msg.Name = "小王子"
msg.Message = "Hello world!"
msg.Age = 18
c.JSON(http.StatusOK, msg)
})
r.Run(":8080")
}
浏览器访问:http://localhost:8080/someJSON
,http://localhost:8080/moreJSON
获取参数
推荐一篇博主写的文章,很全,获取请求参数和绑定参数示例都是全的
获取querystring类型参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/user/search", func(c *gin.Context) {
username := c.DefaultQuery("username", "小王子") // 没有username对应参数时默认小王子
//username := c.Query("username")
address := c.Query("address") // 获取address参数
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
浏览器访问:http://localhost:8080/user/search?username=萧寂&address=河南省
获取form表单参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/user/search", func(c *gin.Context) {
// DefaultPostForm取不到值时会返回指定的默认值
//username := c.DefaultPostForm("username", "小王子")
username := c.PostForm("username")
address := c.PostForm("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
使用apiFox新建快捷请求,地址栏输入:http://localhost:8080/user/search
,请求方式选择post
,请求参数选择Body
里面的form-data
或者x-www-form-urlencoded
,新增username
字段,值为萧寂
,address
字段,值为河南省
,点击发送请求即可
获取JSON参数
package main
import (
"encoding/json"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/json", func(c *gin.Context) {
// 注意:下面为了举例子方便,暂时忽略了错误处理
b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
// 定义map或结构体
var m map[string]interface{}
// 反序列化
_ = json.Unmarshal(b, &m)
c.JSON(http.StatusOK, m)
})
r.Run(":8080")
}
使用apiFox新建快捷请求,地址栏输入:http://localhost:8080/json
,请求方式选择post
,请求参数选择Body
里面的json
或者xml
或者raw
,内容如下,然后点击发请求
{
"username":"萧寂",
"address":"河南省"
}
获取path参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/user/search/:username/:address", func(c *gin.Context) {
username := c.Param("username")
address := c.Param("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
浏览器访问:http://localhost:8080/user/search/萧寂/河南省
获取请求头参数和设置响应头参数
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/headers", func(c *gin.Context) {
//获取请求头参数 不区分大小写
str := c.GetHeader("hahaha")
fmt.Println("获取请求头参数:", str)
//设置响应头
c.Header("token", "6666666666abc")
c.JSON(200, gin.H{"msg": "成功"})
})
// 启动服务器
r.Run(":8080")
}
使用apiFox新建快捷请求,地址栏输入:http://localhost:8080/headers
,请求方式选择post
,请求参数选择headers
里面的新增参数hahaha
内容数字或者英文任意
,然后点击发请求
然后查看响应头参数和控制台打印的请求头参数
绑定参数
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。
推荐一篇博主写的文章,很全,获取请求参数和绑定参数示例都是全的
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
// Binding from JSON
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
router := gin.Default()
// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
// 使用apiFox新建快捷请求,地址栏输入:`http://localhost:8080/loginJSON`,请求方式选择`post`,请求参数选择`Body`里面的`json`或者`xml`或者`raw`,内容为:{ "password": "123456",user": "萧寂" },然后点击发请求
router.POST("/loginJSON", func(c *gin.Context) {
var login Login
if err := c.ShouldBind(&login); err == nil {
fmt.Printf("login info:%#v\n", login)
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// 绑定form表单示例 (user=q1mi&password=123456)
// **使用apiFox新建快捷请求,地址栏输入:`http://localhost:8080/loginForm`,请求方式选择`post`,请求参数选择`Body`里面的`form-data`或者`x-www-form-urlencoded`,新增`user`字段,值为`萧寂`,`password`字段,值为`123456`,点击发送请求即可**
router.POST("/loginForm", func(c *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := c.ShouldBind(&login); err == nil {
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
// 浏览器请求:http://localhost:8080/loginForm?user==萧寂&password=123456
router.GET("/loginForm", func(c *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := c.ShouldBind(&login); err == nil {
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
路由组的使用和抽取
在日常项目中,随着接口越来越多,需要对接口进行管理,例如对用户表进行操作的可以统一
user
前缀,对学生表进行操作可以统一student
前缀,或者以版本号为根路由进行管理,例如v1v2这种,这样就形成了一个个的路由组
路由组基本使用
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 按照版本号对路由进行分组
v1 := r.Group("/v1")
{
v1.GET("/user", func(c *gin.Context) {
c.String(http.StatusOK, "v1/user")
})
v1.GET("/student", func(c *gin.Context) {
c.String(http.StatusOK, "v1/student")
})
v1.GET("/teacher", func(c *gin.Context) {
c.String(http.StatusOK, "v1/teacher")
})
}
// 版本2进行分组
v2 := r.Group("/v2")
{
v2.GET("/user", func(c *gin.Context) {
c.String(http.StatusOK, "v2/user")
})
v2.GET("/student", func(c *gin.Context) {
c.String(http.StatusOK, "v2/student")
})
v2.GET("/teacher", func(c *gin.Context) {
c.String(http.StatusOK, "v2/teacher")
})
}
// 启动服务器
r.Run(":8080")
}
浏览器访问如下:
http://localhost:8080/v1/user
http://localhost:8080/v1/student
http://localhost:8080/v1/teacher
http://localhost:8080/v2/user
http://localhost:8080/v2/student
http://localhost:8080/v2/teacher
拆分模块化管理
上面代码虽然将路由进行了分组,但是如果接口过多,全部都写在一个文件内也不好维护,因此需要将对应功能的路由进行抽取
路由抽取的规则:
- 总路由中设置路由组(总路由)
- 模块中的路由负责映射具体的业务(模块路由)
1.创建总路由项目根目录下创建router/router.go
文件,内容如下:
package router
import "github.com/gin-gonic/gin"
func Router(r *gin.Engine) {
}
2.在main.go
引入总路由文件,如下:
package main
import (
"demo01/router"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 指定总路由
router.Router(r)
// 启动服务器
r.Run(":8080")
}
3.修改总路由的代码如下:router/router.go
package router
import (
v1 "demo01/v1"
v2 "demo01/v2"
"github.com/gin-gonic/gin"
)
func Router(r *gin.Engine) {
r1 := r.Group("/v1") // 版本1的分组
r2 := r.Group("/v2") // 版本2的分组
// 模块分组
v1.Router(r1)
v2.Router(r2)
}
4.新建分组的文件夹,分组是什么起什么名字便于管理,例如我这里是v1和v2,则在根目录下新建v1/v1.go
文件和v2/v2.go
文件,v1和v2文件夹下面同时在新建一个router.go
文件,用于存放每个分组对应的路由(后面会放一个总项目结构,可参考)
v1/router.go
代码如下
package v1
import (
"github.com/gin-gonic/gin"
)
func Router(r *gin.RouterGroup) {
r.GET("/user", User)
r.GET("/student", Student)
r.GET("/teacher", Teacher)
}
v1/v1.go
代码如下:
package v1
import (
"github.com/gin-gonic/gin"
"net/http"
)
func User(c *gin.Context) {
c.String(http.StatusOK, "v1/user")
}
func Student(c *gin.Context) {
c.String(http.StatusOK, "v1/student")
}
func Teacher(c *gin.Context) {
c.String(http.StatusOK, "v1/teacher")
}
v2/router.go
代码如下:
package v2
import (
"github.com/gin-gonic/gin"
)
func Router(r *gin.RouterGroup) {
r.GET("/user", User)
r.GET("/student", Student)
r.GET("/teacher", Teacher)
}
v2/v2.go
代码如下:
package v2
import (
"github.com/gin-gonic/gin"
"net/http"
)
func User(c *gin.Context) {
c.String(http.StatusOK, "v2/user")
}
func Student(c *gin.Context) {
c.String(http.StatusOK, "v2/student")
}
func Teacher(c *gin.Context) {
c.String(http.StatusOK, "v2/teacher")
}
原理:在main.go启动后会先进总路由的方法分配总路由,在总路由方法里面又分配了模块路由,访问到模块路由后找到在模块文件夹内的主文件的对应方法处理相关逻辑
大概结构如下
- demo01
- router
- - router.go
- v1
- - router.go
- - v1.go
- v2
- - router.go
- - v2.go
- go.mod
- - go.sum
- main.go
重定向
HTTP重定向
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})
// 启动服务器
r.Run(":8080")
}
路由重定向
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
// 指定重定向的URL
c.Request.URL.Path = "/test2" // 重定向的URL
r.HandleContext(c) // 重定向
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"hello": "world"})
})
// 启动服务器
r.Run(":8080")
}
中间件
什么是中间件:
- Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数(中间件函数),这个钩子函数就叫中间件。
- 中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等等。
自定义中间件
方式一
创建中间件文件
根目录下创建middleware/mw.go
文件,内容如下:
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
)
func Middleware(context *gin.Context) {
fmt.Println("这是自定义的中间件,里面可以写统一的业务逻辑")
}
在main.go
里面进行引入注册,切记要在路由之前引入
package main
import (
"demo01/middleware"
"demo01/router"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 使用中间件
// 切记,中间件一定要在路由之前引入
r.Use(middleware.Middleware)
// 指定总路由
router.Router(r)
// 启动服务器
r.Run(":8080")
}
访问任意接口即可查看控制台打印中间件的那句话是否触发
方式二
修改刚刚的middleware/mw.go
内容如下:
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
)
func Middleware() gin.HandlerFunc {
// gin.HandlerFunc等价于func(*Context)函数
// 所以必须有个返回值,并且返回值是一个函数
return func(context *gin.Context) {
fmt.Println("这是自定义的中间件,里面可以写统一的业务逻辑")
}
}
在main.go
进行引入注册(相对于方式一多加个括号调用)
package main
import (
"demo01/middleware"
"demo01/router"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 使用中间件
// 切记,中间件一定要在路由之前引入
r.Use(middleware.Middleware())
// 指定总路由
router.Router(r)
// 启动服务器
r.Run(":8080")
}
中间件链
如果定义众多中间件,会形成一条中间件链,而通过Next 函数来对后面的中间件进行执行。
修改刚刚的middleware/mw.go
内容如下:
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
)
func Middleware01() gin.HandlerFunc {
// gin.HandlerFunc等价于func(*Context)函数
// 所以必须有个返回值,并且返回值是一个函数
return func(context *gin.Context) {
fmt.Println("中间件01-开始")
// Next 函数作用:继续走中间件链中的下一个中间件。
// 执行Next就会等下一个中间件执行完毕再回到当前中间件继续执行
// Next的顺序是main.go里面注册的顺序走
context.Next()
fmt.Println("中间件01-结束")
}
}
func Middleware02() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("中间件02-开始")
fmt.Println("中间件02-结束")
}
}
func Middleware03() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("中间件03-开始")
context.Next()
fmt.Println("中间件03-结束")
}
}
在main.go
进行引入注册这三个中间件
package main
import (
"demo01/middleware"
"demo01/router"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 使用中间件
// 切记,中间件一定要在路由之前引入
r.Use(middleware.Middleware01())
r.Use(middleware.Middleware03())
r.Use(middleware.Middleware02())
// 指定总路由
router.Router(r)
// 启动服务器
r.Run(":8080")
}
终止链条调用
修改刚刚的middleware/mw.go
内容如下:
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
)
func Middleware01() gin.HandlerFunc {
// gin.HandlerFunc等价于func(*Context)函数
// 所以必须有个返回值,并且返回值是一个函数
return func(context *gin.Context) {
fmt.Println("中间件01-开始")
context.Next()
fmt.Println("中间件01-结束")
}
}
func Middleware02() gin.HandlerFunc {
// gin.HandlerFunc等价于func(*Context)函数
// 所以必须有个返回值,并且返回值是一个函数
return func(context *gin.Context) {
fmt.Println("中间件02-开始")
fmt.Println("中间件02-结束")
}
}
func Middleware03() gin.HandlerFunc {
// gin.HandlerFunc等价于func(*Context)函数
// 所以必须有个返回值,并且返回值是一个函数
return func(context *gin.Context) {
fmt.Println("中间件03-开始")
// 终止链条调用
// 这里如果直接使用return只会结束03后面的逻辑代码,并不会影响链条向下执行,因此不能使用return终止链条
context.Abort()
context.Next()
fmt.Println("中间件03-结束")
}
}
Abort()方法的作用:终止中间件链条的调用,但是逻辑代码不会被影影响
main.go注册跟刚刚一样不变,运行项目发现到03之后不会执行了,因为到03之后链条就断了,但是只断了后面的,然后直接从03再返回到01了
中间件的作用域
全局中间件
上面讲的在main.go
里面注册的就是全局中间件的
针对某个路由组生效
在上面的路由组案例中,将router/router.go
内容更改如下:
package router
import (
"demo01/middleware"
v1 "demo01/v1"
v2 "demo01/v2"
"github.com/gin-gonic/gin"
)
func Router(r *gin.Engine) {
r1 := r.Group("/v1") // 版本1的分组
r1.Use(middleware.Middleware01()) // 只针对r1路由组生效的中间件
r2 := r.Group("/v2") // 版本2的分组
// 模块分组
v1.Router(r1)
v2.Router(r2)
}
启动项目,则上面注册的路由就只针对r1路由组生效了
局部中间件
针对某个路由生效,以上面的v1/router.go
代码为例
在具体路由和函数之间的参数加上中间件
例如我想让v1/user
这个路由使用中间件,v1下的其他路由不使用中间件,则v1/router.go
修改如下:
package v1
import (
"demo01/middleware"
"github.com/gin-gonic/gin"
)
func Router(r *gin.RouterGroup) {
r.GET("/user", middleware.Middleware01(),User) // 注册局部中间件,则当前中间件只针对当前路由有效
r.GET("/student", Student)
r.GET("/teacher", Teacher)
}
配置文件yaml使用
安装模块
go get github.com/spf13/viper
项目结构
- demo
- - conf
- - - app.yaml
- - - conf.go
- - go.mod
- - - go.sum
- - main.go
app.yaml
内容如下:
app:
name: ginApi
port: :8088
mode: release
mysql:
conn: root:123456@tcp(127.0.0.1:3306)/tableName?charset=utf8mb4&parseTime=true&loc=Local
redis:
host: 127.0.0.1
port: 6379
password: 123456
db: 1
conf.go
内容如下(主要负责读取解析app.yaml
内容)
package conf
import (
"encoding/json"
"fmt"
"github.com/spf13/viper"
)
type Config struct {
AppConf *AppConfig
SqlConn string
RedisConf *RedisConfig
//....
}
type AppConfig struct {
Name string
Port string
Mode string
}
type RedisConfig struct {
Host string
Port string
Password string
Db int
}
func InitConf() *Config {
viper.SetConfigType("yaml") //设置配置文件格式
viper.AddConfigPath("conf") //设置配置文件的路径
viper.SetConfigName("app") //设置配置文件名
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
fmt.Println("找不到配置文件..")
} else {
fmt.Println("配置文件出错..")
}
}
//打印获取到的配置文件的key
fmt.Println("viper.AllKeys()", viper.AllKeys())
//[mysql.conn redis.port redis.password redis.host app.name app.port app.mode]
//返回配置文件的数据
return &Config{
AppConf: &AppConfig{
Name: viper.GetString("app.name"),
Port: viper.GetString("app.port"),
Mode: viper.GetString("app.mode"),
},
SqlConn: viper.GetString("mysql.conn"),
RedisConf: &RedisConfig{
Host: viper.GetString("redis.host"),
Port: viper.GetString("redis.port"),
Password: viper.GetString("redis.password"),
Db: viper.GetInt("redis.db"),
},
}
}
func (c *Config) String() string {
// 下面代码用于打印配置文件的数据
// 将配置文件的数据编码为 JSON 字符串
configJSON, err := json.MarshalIndent(c, "", " ")
if err != nil {
fmt.Println("配置文件转json失败:", err)
return ""
}
// 返回 JSON 字符串
return string(configJSON)
}
使用配置内容,这里以main.go
里面使用为例
package main
import (
"fmt"
"ginApi-initConf/conf"
"github.com/gin-gonic/gin"
)
func main() {
//初始化路由
route := gin.Default()
// 初始化配置文件的数据
config := conf.InitConf()
// 打印配置文件的mysql配置
fmt.Println("config.SqlConn", config.SqlConn)
// 打印配置文件的项目启动端口
fmt.Println("redis db num:", config.AppConf.Port)
// 打印config配置
fmt.Println("config:", config.String())
//项目启动
route.Run(config.AppConf.Port)
}
自定义返回的统一格式
根目录下创建result/result.go
文件,内容如下:
package result
import "github.com/gin-gonic/gin"
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
var CodeMsg = map[int]string{
0: "请求失败",
}
// 封装请求的格式
func response(c *gin.Context, code int, data any, msg string) {
c.JSON(200, Response{
Code: code,
Data: data,
Msg: msg,
})
}
/**
* 需要传入数据和提示信息
*/
func Ok(c *gin.Context, data any, msg string) {
response(c, 0, data, msg)
}
/**
* 需要传入数据
*/
func OkData(c *gin.Context, data any) {
Ok(c, data, "请求成功")
}
/**
* 不需要传入数据,自定义提示信息
*/
func OkMsg(c *gin.Context, msg string) {
Ok(c, nil, msg)
}
/**
* 请求错误消息全局封装
* 需要传入错误码,错误信息和错误数据
*/
func Fail(c *gin.Context, code int, data any, msg string) {
response(c, code, data, msg)
}
/**
* 不需要传入错误数据,自定义提示信息
*/
func FailMsg(c *gin.Context, msg string) {
response(c, 1, nil, msg)
}
/**
* 自定义错误码
*/
func FailCode(c *gin.Context, code int, msg string) {
msg, ok := CodeMsg[code]
if !ok {
msg = "未知错误"
}
response(c, code, nil, msg)
}
使用方式如下:
下方六个方法返回值依次为:
{"code":0,"msg":"登录成功","data":{"token":"123456789","user":"admin"}}
{"code":0,"msg":"请求成功","data":{"token":"123456789","user":"admin"}}
{"code":0,"msg":"登录成功","data":null}
{"code":1,"msg":"登录失败","data":{"text":"服务器错误"}}
{"code":1,"msg":"登录失败","data":null}
{"code":1,"msg":"未知错误","data":null}
package main
import (
"github.com/gin-gonic/gin"
"shikesuyun/result"
)
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
// 1.成功响应: 传递数据和提示信息
//result.Ok(c, map[string]any{
// "token": "123456789",
// "user": "admin",
//}, "登录成功")
// 2.成功响应: 传递数据,使用默认提示信息
//result.OkData(c, map[string]any{
// "token": "123456789",
// "user": "admin",
//})
// 3.成功响应: 传入自定义提示信息
//result.OkMsg(c, "登录成功")
// 4.失败响应: 传入错误信息和错误码
//result.Fail(c, 1, map[string]any{
// "text": "服务器错误",
//}, "登录失败")
// 5.失败响应: 传入错误信息
//result.FailMsg(c, "登录失败")
// 6.失败响应: 传入错误码
result.FailCode(c, 1, "登录失败")
})
// 启动服务器
r.Run()
}
浏览器访问:http://localhost:8080/hello
依次可检验上面六个方法
JWT的生成和校验
安装模块
go get -u github.com/golang-jwt/jwt/v4
整体结构如下:
- demo
- - jwt_plugin
- - - jwt.go // jwt加密和验证的模块代码
- - middlemare
- - - middlemare.go // 中间件,调用上述jwt.go的验证方法对jwt密钥进行验证
- - go.mod
- - - go.sum
- - main.go // 写两个测试路由
加密和验证实现方法如下
在jwt_plugin/jwt.go
文件内,内容如下:
package jwt_plugin
import "github.com/golang-jwt/jwt/v4"
var key = "abcdefg" // 密钥字符串
// 要加密的字符串内容
type Data struct {
Name string
Age int
Gender int
jwt.RegisteredClaims
}
// 加密
func Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, data)
sign, err := token.SignedString([]byte(key))
if err != nil {
return "", err
}
return sign, nil
}
// 验证
func Verify(sign string, data jwt.Claims) error {
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return []byte(key), nil
})
return err
}
中间件进行验证的代码如下
在middlemare/middlemare.go
文件内,内容如下:
package middlemare
import (
"demo/jwt_plugin"
"fmt"
"github.com/gin-gonic/gin"
)
// jwt权限校验
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取token
access_token := c.Request.Header.Get("access_token")
fmt.Println(access_token)
// 调用jwt_plugin里面的方法进行解析验证token
data := &jwt_plugin.Data{}
err := jwt_plugin.Verify(access_token, data)
if err != nil {
c.JSON(200, gin.H{
"code": 401,
"msg": "token校验失败",
})
// 终止链条调用
c.Abort()
}
c.Next()
}
}
在接口上面使用中间件进行校验
在main.go
文件内,内容如下:
package main
import (
"demo/jwt_plugin"
"demo/middlemare"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"time"
)
func main() {
// 写简单接口
r := gin.Default()
// 登录接口返回token
r.POST("/login", func(c *gin.Context) { // 路由
data := jwt_plugin.Data{
Name: "zhangsan",
Age: 18,
Gender: 1,
RegisteredClaims: jwt.RegisteredClaims{
// 设置过期时间
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
// 设置签发时间
IssuedAt: jwt.NewNumericDate(time.Now()),
// 设置生效时间
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
// 对上面的数据进行加密处理
sign, err := jwt_plugin.Sign(data)
if err != nil {
c.JSON(500, gin.H{
"message": "加密失败",
})
return
}
c.JSON(200, gin.H{ // 返回json数据
"access_token": sign,
})
})
// 用户接口校验token,校验通过才能访问
// 哪个接口需要进行校验就把middlemare.Auth()加到哪个接口上面
// 或者也可以像上面一样加入路由组里面,这样当前路由组里面的接口都会进行jwt校验
r.POST("/user", middlemare.Auth(), func(c *gin.Context) { // 路由
c.JSON(200, gin.H{ // 返回json数据
"message": "hello golang",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
测试代码
打开apifox,新建一个POST
接口,访问http://127.0.0.1:8080/login
,结果如下:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiemhhbmdzYW4iLCJBZ2UiOjE4LCJHZW5kZXIiOjEsImV4cCI6MTcyNjgxNTE2OSwibmJmIjoxNzI2NzI4NzY5LCJpYXQiOjE3MjY3Mjg3Njl9.rKmoqnoqBRYYSDNRLXtkJUshjFcPx1FC7g0itwVW5mQ"
}
再次访问http://127.0.0.1:8080/user
,得到结果如下:
{
"code": 401,
"msg": "token校验失败"
}
在http://127.0.0.1:8080/user
接口下面的headers
下面新建access_token
字段,参数值为/login
返回的token
,再次请求http://127.0.0.1:8080/user
得到结果如下:
{
"message": "hello golang"
}
至此完成了jwt的token的生成与校验