Gin 框架之jwt 介绍与基本使用

文章目录

    • 一.JWT 介绍
    • 二.JWT认证与session认证的区别
      • 2.1 基于session认证流程图
      • 2.2 基于jwt认证流程图
    • 三. JWT 的构成
      • 3.1 header : 头部
      • 3.2 payload : 负载
        • 3.2.1 标准中注册的声明 (建议但不强制使用)
        • 3.2.2 公共的声明
        • 3.2.3 私有的声明
        • 3.2.4 定义一个payload
      • 3.3 signatrue : 签名
      • 3.4 得到 token
    • 四.base64 编码和解码的使用
      • 4.1 base64 编码
      • 4.2 base64 解码
    • 五.JWT 的本质原理
      • 5.1 签发
      • 5.2 校验
      • 5.3 jwt认证开发流程(重点)
    • 六、Gin 框架中使用jwt
      • 6.1 安装JWT库
      • 6.2 导入库
      • 6.3 使用JWT 鉴权认证
        • 6.3.1 JWT中间件开发
        • 6.3.2 使用JWT中间件
        • 6.3.3 生成JWT token
        • 6.3.4 访问路由签发token
        • 6.3.5 通过 token 鉴权获取用户信息

一.JWT 介绍

  • Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)
  • 该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景
  • JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源
  • 也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密

二.JWT认证与session认证的区别

2.1 基于session认证流程图

img

服务器需要存储用户的token信息

2.2 基于jwt认证流程图

img

服务端不需要存储用户token, 都存在客户端

三. JWT 的构成

JWT就是一段字符串, 由三段信息构成, 三段信息文本使用.(点) 拼接就构成了JWT字符串 :

  • eyJhbGciOiJIUzI1sNiIsIn.eyJzdWIiOiIxMjRG9OnRydWV9.TJVArHDcEfxjoYZgeFONFh7HgQ
  • 第一部分我们称它为头部 : header
  • 第二部分我们称其为载荷 : payload (类似于飞机上承载的物品)
  • 第三部分是签证 : signature

3.1 header : 头部

头部,JWT 的元数据,也就是描述这个 token 本身的数据,一个 JSON 对象。由两部分组成 :

  1. 声明类型(当前令牌名称)
  2. 声明加密算法
// 定义头部信息
header := map[string]interface{}{"alg": "HS256", // 声明加密算法,可以根据需要修改"typ": "JWT",   // 声明类型
}

将头部使用base64编码构成第一部分 (base64编码方法, 该编码可以对称解码)

package mainimport ("encoding/base64""encoding/json""fmt"
)func main() {// 定义头部信息header := map[string]interface{}{"alg": "HS256", // 声明加密算法,可以根据需要修改"typ": "JWT",   // 声明类型}// 将头部信息序列化为JSON格式字符串headerBytes, err := json.Marshal(header)if err != nil {fmt.Println("JSON encoding error:", err)return}headerStr := base64.RawURLEncoding.EncodeToString(headerBytes)fmt.Println(headerStr)// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
}

3.2 payload : 负载

存放用户有效信息的地方,一个 JSON 对象, 这些有效信息包含三个部分:

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明
3.2.1 标准中注册的声明 (建议但不强制使用)
  • iss: JWT签发者
  • sub: JWT所面向的用户
  • aud: 接收JWT的一方
  • exp: JWT的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该JWT都是不可用的
  • iat: JWT的签发时间
  • jti: JWT的唯一身份标识,主要用来作为一次性token,从而回避时序攻击
3.2.2 公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密。

3.2.3 私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

3.2.4 定义一个payload

除了上面的字段, 你自己也可以添加自己想要的字段, 需要注意的是:这些信息是不加密的, 所以最好不要存敏感信息

package mainimport ("encoding/base64""encoding/json""fmt"
)func main() {// 定义Payload信息payload := map[string]interface{}{"sub":   "1234567890",              // 主题,表示该JWT的所有者"name":  "John Doe",                // 自定义声明,可以根据需要添加其他声明"iat":   1516239022,                // 签发时间,表示JWT的签发时间,一般为当前时间的时间戳"exp":   1516239022 + 3600,         // 过期时间,表示JWT的过期时间,一般为签发时间加上有效期,以秒为单位"roles": []string{"admin", "user"}, // 自定义声明,可以存储用户角色等信息}// 将Payload信息序列化为JSON格式字符串payloadBytes, err := json.Marshal(payload)if err != nil {fmt.Println("JSON encoding error:", err)return}payloadStr := base64.RawURLEncoding.EncodeToString(payloadBytes)fmt.Println(payloadStr) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
}

然后将其进行base64加密,得到JWT的第二部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

3.3 signatrue : 签名

signature 是根据 headertoken 生成, 由三部分构成 :

  • base64 编码后的 header
  • base64 编码后的 payload
  • secret : 秘钥 (只有服务端知道)

这个部分需要将base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT的第三部分。

package mainimport ("crypto/hmac""crypto/sha256""encoding/base64""encoding/json""fmt"
)func main() {// 定义头部信息header := map[string]interface{}{"alg": "HS256","typ": "JWT",}// 定义Payload信息payload := map[string]interface{}{"sub":   "1234567890","name":  "John Doe","iat":   1516239022,"exp":   1516239022 + 3600,"roles": []string{"admin", "user"},}// 将头部信息序列化为JSON格式字符串headerBytes, err := json.Marshal(header)if err != nil {fmt.Println("JSON encoding error:", err)return}headerStr := base64.RawURLEncoding.EncodeToString(headerBytes)// 将Payload信息序列化为JSON格式字符串payloadBytes, err := json.Marshal(payload)if err != nil {fmt.Println("JSON encoding error:", err)return}payloadStr := base64.RawURLEncoding.EncodeToString(payloadBytes)// 定义秘钥secret := "your-secret-key" // 替换为实际的秘钥// 生成签名signature := generateSignature(headerStr, payloadStr, secret)fmt.Println(signature) // C-94Wc6olGK6CEbkA9Xj0ogDQIFdPsEefZKCZrz_fvA// 生成的签名字符串
}func generateSignature(headerStr, payloadStr, secret string) string {// 构造要签名的数据dataToSign := headerStr + "." + payloadStr// 使用HMAC-SHA256算法生成签名h := hmac.New(sha256.New, []byte(secret))h.Write([]byte(dataToSign))signatureBytes := h.Sum(nil)// 对签名进行base64编码signature := base64.RawURLEncoding.EncodeToString(signatureBytes)return signature
}

3.4 得到 token

算出签名之后, 把 header、payload、signatrue 三部分使用 .(点) 拼接成一个大字符串, 然后返回给客户端让其存储

package mainimport ("crypto/hmac""crypto/sha256""encoding/base64""encoding/json""fmt"
)func main() {// 定义头部信息header := map[string]interface{}{"alg": "HS256","typ": "JWT",}// 定义Payload信息payload := map[string]interface{}{"sub":   "1234567890","name":  "John Doe","iat":   1516239022,"exp":   1516239022 + 3600,"roles": []string{"admin", "user"},}// 将头部信息序列化为JSON格式字符串headerBytes, err := json.Marshal(header)if err != nil {fmt.Println("JSON encoding error:", err)return}headerStr := base64.RawURLEncoding.EncodeToString(headerBytes)// 将Payload信息序列化为JSON格式字符串payloadBytes, err := json.Marshal(payload)if err != nil {fmt.Println("JSON encoding error:", err)return}payloadStr := base64.RawURLEncoding.EncodeToString(payloadBytes)// 将base64加密后的header和payload拼接起来dataToSign := headerStr + "." + payloadStr// 定义秘钥secret := "your-secret-key" // 替换为实际的秘钥// 生成签名signature := generateSignature(dataToSign, secret)// 最终的JWT字符串jwtToken := dataToSign + "." + signaturefmt.Println(jwtToken)// 最终生成的JWT字符串// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTYyNDI2MjIsImlhdCI6MTUxNjIzOTAyMiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sInN1YiI6IjEyMzQ1Njc4OTAifQ.C-94Wc6olGK6CEbkA9Xj0ogDQIFdPsEefZKCZrz_fvA
}func generateSignature(dataToSign, secret string) string {// 使用HMAC-SHA256算法生成签名h := hmac.New(sha256.New, []byte(secret))h.Write([]byte(dataToSign))signatureBytes := h.Sum(nil)// 对签名进行base64编码signature := base64.RawURLEncoding.EncodeToString(signatureBytes)return signature
}

注意:secret 是保存在服务器端的,JWT的签发生成也是在服务器端的,secret 就是用来进行JWT的签发和JWT的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret,那就意味着客户端是可以自我签发JWT了。

四.base64 编码和解码的使用

首先 base64 是一种编码方式, 并非加密方式; 它跟语言无关, 任何语言都能使用 base64 编码&解码

4.1 base64 编码

	// 定义一个信息字段dic := map[string]interface{}{"id": 1, "name": "jarvis", "age": "male"}// 将其序列化成json格式字符串jsonBytes, err := json.Marshal(dic)if err != nil {fmt.Println("JSON encoding error:", err)return}jsonStr := string(jsonBytes)// 将json格式字符串encode再使用base64编码成一串Bytes格式编码base64Str := base64.StdEncoding.EncodeToString([]byte(jsonStr))fmt.Println([]byte(base64Str))// [101 121 74 112 90 67 73 54 73 68 69 115 73 67 50 70 109 90 121 66 67 74 112 73 106 111 103 73 109 70 48 105 71 108 112 77 97 86 120 73 106 111 103 73 109 116 65 87 120 108 73 106 111 103 73 109 116 65 87 120 108 73 106 111 103 73 109 116 65 87 120 108 73 106 111 103 73 61]fmt.Println(base64Str)// eyJhZ2UiOiJtYWxlIiwiaWQiOjEsIm5hbWUiOiJqYXJ2aXMifQ==

4.2 base64 解码

// 替换为你的 base64 编码字符串base64Str := "eyJhZ2UiOiJtYWxlIiwiaWQiOjEsIm5hbWUiOiJqYXJ2aXMifQ=="// base64 解码decodedBytes, err := base64.StdEncoding.DecodeString(base64Str)if err != nil {fmt.Println("Base64 decoding error:", err)return}// JSON 反序列化var dic map[string]interface{}err = json.Unmarshal(decodedBytes, &dic)if err != nil {fmt.Println("JSON decoding error:", err)return}fmt.Println(dic)// map[age:male id:1 name:jarvis]

五.JWT 的本质原理

/*
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{"company": "公司信息",...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{"user_id": 1,...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{"head": "头的加密字符串","payload": "体的加密字符串","secret_key": "安全码"
}
*/

5.1 签发

根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

  • 用基本信息存储 json 字典, 采用 base64 编码得到头字符串
  • 用关键信息存储 json 字典,采用 base64 编码得到体字符串
  • 用头、体编码的字符串再加安全码信息(secret)存储 json 字典, 采用 header 中指定的算法加密得到签名字符串
  • 最后形成的三段字符串用 . 拼接成token字符串返回给前台

5.2 校验

根据客户端带 token 的请求 反解出 user 对象

  • 将 token 按 .(点) 拆分为三段字符串, 第一段编码后的头字符串一般不需要做任何处理
  • 第二段编码后的体字符串, 要解码出用户主键, 通过主键从 User 表中就能得到登录用户, 过期时间和设备信息都是安全信息, 确保 token 没过期, 且是同一设备来的
  • 再将第一段 + 第二段 + 服务器安全码使用header中指定的不可逆算法加密, 与第三段 签名字符串进行对比校验, 通过后才能代表第二段校验得到的 user 对象就是合法的登录用户

5.3 jwt认证开发流程(重点)

  1. 用账号密码访问登录接口,登录接口逻辑中调用签发token算法,得到token,返回给客户端,客户端自己存到cookies中。

  2. 校验token的算法应该写在中间件中,所有请求都会进行认证校验,所以请求带了token,就会反解出用户信息。

六、Gin 框架中使用jwt

6.1 安装JWT库

使用Gin框架时,你可以选择一个适用于Go语言的JWT库。一个流行的选择是github.com/dgrijalva/jwt-go库。

go get -u github.com/golang-jwt/jwt/v5

6.2 导入库

在你的Go代码中导入github.com/golang-jwt/jwt/v5github.com/gin-gonic/gin

import ("github.com/golang-jwt/jwt/v5""github.com/gin-gonic/gin"
)

6.3 使用JWT 鉴权认证

6.3.1 JWT中间件开发

JWT中间件: 创建一个JWT中间件,它将用于保护需要身份验证的路由。

package middlewareimport ("github.com/gin-gonic/gin""github.com/golang-jwt/jwt/v5""net/http""strings""webook/internal/web"
)// LoginJWTMiddlewareBuilder JWT 登录校验
type LoginJWTMiddlewareBuilder struct {paths []string
}func NewLoginJWTMiddlewareBuilder() *LoginJWTMiddlewareBuilder {return &LoginJWTMiddlewareBuilder{}
}// IgnorePaths 忽略的路径
func (l *LoginJWTMiddlewareBuilder) IgnorePaths(path string) *LoginJWTMiddlewareBuilder {l.paths = append(l.paths, path)return l
}func (l *LoginJWTMiddlewareBuilder) Build() gin.HandlerFunc {// 用 Go 的方式编码解码return func(ctx *gin.Context) {// 不需要登录校验的for _, path := range l.paths {if ctx.Request.URL.Path == path {return}}// 用 JWT 来校验tokenHeader := ctx.GetHeader("Authorization")if tokenHeader == "" {// 没登录ctx.AbortWithStatus(http.StatusUnauthorized)return}segs := strings.Split(tokenHeader, " ")if len(segs) != 2 {// 没登录,有人瞎搞ctx.AbortWithStatus(http.StatusUnauthorized)return}tokenStr := segs[1]claims := &web.UserClaims{}// ParseWithClaims 里面,一定要传入指针// 这里的95osj3fUD7fo0mlYdDbncXz4VD2igvf0 代表的是签发的时候的key,并且key 要和签发的时候一样token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {return []byte("95osj3fUD7fo0mlYdDbncXz4VD2igvf0"), nil})if err != nil {// 没登录ctx.AbortWithStatus(http.StatusUnauthorized)return}// token 验证不通过if token == nil || !token.Valid {// 没登录ctx.AbortWithStatus(http.StatusUnauthorized)return}// 将用户信息存储到上下文中ctx.Set("claims", claims)}
}
6.3.2 使用JWT中间件

使用JWT中间件: 在需要身份验证的路由上使用JWT中间件。

func initWebServer() *gin.Engine {ser := gin.Default()ser.Use(cors.New(cors.Config{//AllowOrigins: []string{"*"},//AllowMethods: []string{"POST", "GET"},AllowHeaders: []string{"Content-Type", "Authorization"},// 允许跨域访问的响应头,不加这个前端拿不到token响应头ExposeHeaders: []string{"x-jwt-token"},// 是否允许你带 cookie 之类的东西AllowCredentials: true,AllowOriginFunc: func(origin string) bool {if strings.HasPrefix(origin, "http://localhost") {// 你的开发环境return true}return strings.Contains(origin, "http://你的公司域名.com")},MaxAge: 12 * time.Hour,}))// 注册登录校验中间件以及不要登录校验的路径ser.Use(middleware.NewLoginJWTMiddlewareBuilder().IgnorePaths("/users/signup").IgnorePaths("/users/login").Build())return ser
}
6.3.3 生成JWT token

生成JWT token: 在用户登录成功后,你可以生成JWT并将其返回给客户端。

// UserClaims 自定义的声明结构体并内嵌 jwt.StandardClaims
type UserClaims struct {jwt.RegisteredClaims// 声明你自己的要放进去 token 里面的数据Uid int64// 后续需要什么字段,就在这里添加
}func (u *UserHandler) LoginJWT(ctx *gin.Context) {type LoginReq struct {Email    string `json:"email"`Password string `json:"password"`}var req LoginReqif err := ctx.Bind(&req); err != nil {return}user, err := u.svc.Login(ctx, req.Email, req.Password)if err == service.ErrInvalidUserOrPassword {ctx.String(http.StatusOK, "用户名或密码不对")return}if err != nil {ctx.String(http.StatusOK, "系统错误")return}// 步骤2// 在这里用 JWT 设置登录态// 生成一个 JWT token// 将用户信息存储到token中claims := UserClaims{Uid: user.Id,}token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)tokenStr, err := token.SignedString([]byte("95osj3fUD7fo0mlYdDbncXz4VD2igvf0"))if err != nil {ctx.String(http.StatusInternalServerError, "系统错误")return}ctx.Header("x-jwt-token", tokenStr)fmt.Println(user)ctx.String(http.StatusOK, "登录成功")return
}
6.3.4 访问路由签发token

我们通过接口调试工具访问路由127.0.0.1:8080/users/login 签发用户tokenheader 中就会有X-Jwt-Token这个字段以及生成的token 对应值。

6.3.5 通过 token 鉴权获取用户信息

在平时开发中,我们一般不会直接传user_id 过来,一般是通过token来获取用户信息,比如我们需要查询用户信息,之前我们已经将用户ID放入到token中了,直接通过c, _ := ctx.Get("claims")来获取我们存放的用户信息,以下是具体代码;

func (u *UserHandler) ProfileJWT(ctx *gin.Context) {c, _ := ctx.Get("claims")// 你可以断定,必然有 claims//if !ok {//	// 你可以考虑监控住这里//	ctx.String(http.StatusOK, "系统错误")//	return//}// ok 代表是不是 *UserClaimsclaims, ok := c.(*UserClaims)if !ok {// 你可以考虑监控住这里ctx.String(http.StatusOK, "系统错误")return}fmt.Println("当前用户ID为:", claims.Uid)ctx.String(http.StatusOK, "查询成功")
}

最后我们只需要访问路由:127.0.0.1:8080/users/profile,在header中加入token 即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/248794.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux平台 LED情报板网关项目总结

LED情报板目前已经基本稳定 主要存在两个版本LED1.5 和LED2 两个版本的主要差别是 : 1.1.5的配置文件存在本地 2.0的存在平台 需要通过接口从平台获取 2.1.5的下发消息的时候需要同步返回 2.0的是异步返回,所以1.5的要尽可能完成 不然会导致发 布平台…

确认项目范围基准 常见的5大问题

确认项目范围基准的过程中,经常会遇到一些问题,如经常出现项目范围不明确、范围变更频繁等问题,往往会导致项目延期、超预算、质量下降等问题,严重的话可能会导致项目失败。 因此,我们在进行项目范围基准确认时&#x…

MySQL数据库基础合集

MySQL数据库基础合集 目录 MySQL数据库基础合集SQL关键字DDL关键字DML关键字DQL关键字DCL关键字约束关键字 SQL基础数据类型整数类型字符类型浮点类型时间类型 数据定义语言DDL1.查看数据库2.创建库3.删除库4.切换库5.创建表6.删除表7.查看表8.查看表属性9.插入列10.修改列11.设…

2023年数学大事件盘点

目录 ChatGPT驱动数学变革提速2023年阿贝尔奖花落得克萨斯大学奥斯汀分校教授路易斯卡法雷利一个全新的更紧的拉姆齐数的上界埃尔德什-图兰猜想新突破几何学家卡拉比逝世中国科学院院士、数学家杨乐逝世参考文献悄悄地,春节马上来临,在这辞旧迎新的日子里也是我们三行数学总结…

Adobe Camera Raw forMac/win:掌控原始之美的秘密武器

Adobe Camera Raw,这款由Adobe开发的插件,已经成为摄影师和设计师们的必备工具。对于那些追求完美、渴望探索更多创意可能性的专业人士来说,它不仅仅是一个插件,更是一个能够释放无尽创造力的平台。 在数字摄影时代,R…

github官网连接超时解决方案(图解版,亲测成功)

目录 一、521github镜像站1.1、521github镜像站访问地址 二、Github520镜像站2.1、Github520镜像站访问地址地址 三、UsbEAm Hosts Editor加速器3.1、UsbEAm加速器下载、安装及使用 四、SwitchHosts加速器4.1、SwitchHosts加速器下载、安装及使用 五、原名(Steam)目…

blender 画笔的衰成曲线Falloff Curve

Blender画笔是用来在雕刻模式或绘画模式下对物体进行修改的工具。画笔有不同的类型和设置,可以影响画笔的效果和外观。你提到的选项是画笔的衰减曲线(Falloff Curve)的预设,它们决定了画笔的强度如何随着距离中心的距离而变化。 …

计算机设计大赛 深度学习 opencv python 实现中国交通标志识别

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 🔥 优质…

【pdf密码】怎么打印加密的PDF文件?

PDF文件是可以打开查看的,但是现在不能编辑、不能打印,功能栏中的功能都是灰色的,这种设置了加密的PDF文件该如何加密? 如果PDF中的大多数功能按钮以及打印按钮都是灰色的状态,那就证明是文件的问题导致不能打印的。 …

Sentinel之力:解锁@SentinelResource注解的神奇威力

欢迎来到我的博客,代码的世界里,每一行都是一个故事 Sentinel之力:解锁SentinelResource注解的神奇威力 前言SentinelResource注解基础:数字法术的咒语SentinelResource 注解的基本概念和作用:在方法上添加 SentinelRe…

Linux 入门基础知识(一)—— Linux的基本使用

Linux 入门基础知识 一、Linux的基本使用和配置1.1、终端1.2、消耗内存1.3、运行级别1.6、登录前欢迎语1.5、登录后欢迎语1.6、shell1.7、ps aux1.8、设置主机名1.9、whoami和who am i1.10、命令提示符 二、Linux执行命令的过程详解和命令类型2.1、命令执行2.2、hash缓存表2.3、…

InputNumber数字输入框(antd-design组件库)简单使用

1.InputNumber数字输入框 通过鼠标或键盘,输入范围内的数值。 2.何时使用 当需要获取标准数值时。 组件代码来自: 数字输入框 InputNumber - Ant Design 3.本地验证前的准备 参考文章【react项目antd组件-demo:hello-world react项目antd组件-demo:hello…

浏览器内存泄漏排查指南

1、setTimeout执行原理 使用setInterval/setTimeOut遇到的坑 - 掘金 2、Chrome自带的Performance工具 当我们怀疑页面发生了内存泄漏的时候,可以先用Performance录制一段时间内页面的内存变化。 点击开始录制执行可能引起内存泄漏的操作点击停止录制 如果录制结束…

springboot整合mqtt实现消息订阅和推送

前言 mica-mqtt-client-spring-boot-starter是一个基于Spring Boot的MQTT客户端启动器,它集成了mica-mqtt客户端,提供了在Spring Boot应用程序中使用MQTT协议进行消息通信的能力。以下是关于mica-mqtt-client-spring-boot-starter的简介: 特…

dvwa靶场xss储存型

xss储存型 xxs储存型lowmessage框插入恶意代码name栏插入恶意代码 medium绕过方法 high xxs储存型 攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。产生层面:后端漏洞特征:持久性的、前端执行、储存在后端数据…

03 Redis之命令(基本命令+Key命令+String型Value命令与应用场景)

Redis 根据命令所操作对象的不同,可以分为三大类:对 Redis 进行基础性操作的命令,对 Key 的操作命令,对 Value 的操作命令。 3.1 Redis 基本命令 一些可选项对大小写敏感, 所以应尽量将redis的所有命令大写输入 首先通过 redis-…

【C项目】顺序表

简介:本系列博客为C项目系列内容,通过代码来具体实现某个经典简单项目 适宜人群:已大体了解C语法同学 作者留言:本博客相关内容如需转载请注明出处,本人学疏才浅,难免存在些许错误,望留言指正 作…

初识webpack(一)概念、入口配置、输出配置、loader等

目录 (一)概念 webpack的依赖图 (二)webpack的基本使用 (三)webpack的配置文件 1.入口(entry)配置 2.输出(output)配置 (三)loader 1.css文件处理 (1)安装css-loader和style-loader (2)在webpack.config.js中配置loader 2.less文件处理 3.postcss的使用 (1)安装…

python爬虫之豆瓣首页图片爬取

网址:https://movie.douban.com/ import requests from lxml import etree import re url https://movie.douban.com headers {User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.289 Safari/5…

代理IP在游戏中的作用有哪些?

游戏代理IP的作用是什么?IP代理软件相当于连接客户端和虚拟服务器的软件“中转站”,在我们向远程服务器提出需求后,代理服务器首先获得用户的请求,然后将服务请求转移到远程服务器,然后将远程服务器反馈的结果转移到客…