当前位置: 首页 > news >正文

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 即可。

相关文章:

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 : …...

从[redis:LinkedList]中学习链表

文章目录 adlistlistNodelistmacros[宏定义]listCreatelistInitNodelistEmptylistReleaselistAddNodeHeadlistLinkNodeHeadlistAddNodeTaillistLinkNodeTaillistInsertNodelistDelNodelistUlinkNodelistIndexredis3.2.100quicklistredis7.2.2quicklist redis的基本数据类型之一…...

Prometheus+grafana配置监控系统

使用docker compose安装 方便拓展, 配置信息都放在在 /docker/prometheus 目录下 1.目录结构如下 . ├── conf │ └── prometheus.yml ├── grafana_data ├── prometheus_data └── prometheus_grafana.yaml2.创建目录文件 mkdir /docker/prometheus &&am…...

Linux之安装配置CentOS 7

一、CentOS简介 CentOS(Community Enterprise Operating System,中文意思是社区企业操作系统)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码&#xff0c…...

神经网络与深度学习Pytorch版 Softmax回归 笔记

Softmax回归 目录 Softmax回归 1. 独热编码 2. Softmax回归的网络架构是一个单层的全连接神经网络。 3. Softmax回归模型概述及其在多分类问题中的应用 4. Softmax运算在多分类问题中的应用及其数学原理 5. 小批量样本分类的矢量计算表达式 6. 交叉熵损失函数 7. 模型预…...

git学习及简单maven打包

前提: 已经有远程仓库地址 和账号密码了 已经安装git了 1.本地新建文件夹A用作本地仓库 2.在A文件夹下右键打开GIT BASH HERE 3.创建用户和密码,方便追踪提交记录 git config --global user.email “caoqingqing0108” //创建邮箱 git config --global …...

如何用MapTalks IDE来发布网站?

简介 MapTalks IDE 全称 MapTalks集成设计环境(Integrated Design Environment),是由MapTalks技术团队开发的新一代web地图设计软件。 通过MapTalks IDE,您可以自由的创建二维和三维地图,在其中载入或创建地理数据&a…...

我用selenium开发了一个自动创建任务,解放重复性工作

我用selenium开发了一个自动创建任务,大大解放了我做重复性工作带来的疲惫感,收获了更多的乐趣。 我司有100多个服务,运维忙不过来的时候,就会让我们自己创建云负载,你首先需要在云服务上创建负载,再创建容…...

安卓11修改HDMI自适应分辨率

客户需要hdmi自适应屏幕分辨率,没发现有相关的指令,我发现设置中有个hdmi的Auto选项,于是就试试选中这个选项,试下了可以自适应,于是就找到相关代码,在开机完成后执行这个代码,基本满足需求&…...

Linux实验记录:使用Apache的虚拟主机功能

前言: 本文是一篇关于Linux系统初学者的实验记录。 参考书籍:《Linux就该这么学》 实验环境: VmwareWorkStation 17——虚拟机软件 RedHatEnterpriseLinux[RHEL]8——红帽操作系统 正文: 目录 前言: 正文&…...

分布式空间索引了解与扩展

目录 一、空间索引快速理解 (一)区域编码 (二)区域编码检索 (三)Geohash 编码 (四)RTree及其变体 二、业内方案选取 三、分布式空间索引架构 (一)PG数…...

Set和Map的应用场景

Set: 1.成员不能重复 2.只有键值,没有键名,有点类似数组 3.可以遍历,方法 add,delete,has Map: 1.本质上是键值对的集合,类似集合; 2.可以遍历,方法很多,可以干跟各种数据格式转换 Set和…...

小白级教程,10秒开服《幻兽帕鲁》

在帕鲁的世界,你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活,也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活,也可以为你在工厂工作。你也可以将它们进行售卖,或肢解后食用。 前言 马上过年…...

IDEA 构建开发环境

本博客主要讲解了如何创建一个Maven构建Java项目。(本文是创建一个用Maven构建项目的方式,所以需要对Maven有一定的了解) IDEA 构建开发环境 一、创建一个空工程二、构建一个普通的Maven模块 一、创建一个空工程 创建一个空的工程 * 设置整…...

归并排序----C语言数据结构

目录 引言 1.归并排序的实现----c2.归并排序的复杂度分析时间复杂度空间复杂度 引言 归并排序(Merge Sort) 是一种基于分治法的排序算法,它的基本思想是将原始数组划分成较小的数组,然后递归地对这些小数组进行排序,最后将排好序…...

【网站项目】065健康综合咨询问诊平台

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…...

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

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

OpenHarmony—开发及引用静态共享包(API 9)

HAR(Harmony Archive)是静态共享包,可以包含代码、C库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。HAR不同于HAP,不能独立安装运行在设备上,只能作为应用模块的依赖项被引用。 接下来&a…...

测试面试题常见题

文章目录 功能测试一个完整的测试计划应该包含哪些内容一个完整的测试用例包含哪些内容?什么时候需要发测试报告?一份测试报告应该包含哪些内容?一个完整的缺陷报告应该包含哪些内容?简述等价类划分法并举例针对具体场景的测试用例…...

代码随想录算法训练营第六天 - 哈希表part02

454.四数之和II 核心思想:利用字典的key,value 4个数组两两分组,nums1nums2 的两两元素之和 及 计数 先存入字典中,然后对nums3和nums4的进行元素相加 然后对比字典中是否有对应的key,有就countvalue class Solution…...

【Javaweb程序设计】【C00165】基于SSM的高考志愿辅助填报系统(论文+PPT)

基于SSM的高考志愿辅助填报系统(论文PPT) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的高考志愿辅助填报系统 本系统分为前台系统模块、后台管理员模块以及后台学生模块 前台系统模块:当游客打开系统的网址后&…...

海外云手机为什么吸引用户?

近年来,随着全球化的飞速发展,海外云手机逐渐成为各行各业关注的焦点。那么,究竟是什么让海外云手机如此吸引用户呢?本文将深入探讨海外云手机的三大吸引力,揭示海外云手机的优势所在。 1. 高效的社交媒体运营 海外云…...

将`List<String>`转换为`List<Long>`

将List<String>转换为List<Long> 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在Java中&#xff0c;将List<String>转换为List<Long>可以…...

【Unity3D小功能】Unity3D中Text使用超链接并绑定点击事件

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中遇到了要给Text加超链接的需求&#xff0c;研究了实现…...

MyBatis-Plus CRUD 接口

Service CRUD 接口 public String services() {Boolean re false;/**Service CRUD 接口**//**Save 返回boolean **///1、插入一条数据Person person1 new Person();person1.setEmail("123qq.com");person1.setSex("男");//person1.setUser_id(0);//影响…...

在JVM中,Java对象是如何创建、存储和访问的?

在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;Java对象的创建、存储和访问是Java程序运行的核心部分。这个过程涉及到内存管理、对象模型以及运行时数据区域的概念。 1. Java对象的创建&#xff1a; a. 类加载&#xff1a; 在Java程序运行时&#xff0c;类加载器负…...

C++类和对象之进击篇

目录 1.类的6个默认成员函数2.构造函数2.1概念2.2特性 3.析构函数3.1概念3.2特性 4.拷贝构造函数4.1 概念4.2特征 5.赋值运算符重载5.1运算符重载5.2赋值运算符重载5.3前置和后置重载 6.日期类的实现7.const成员8.取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一…...

ElementUI 组件:Container 布局容器

ElementUI安装与使用指南 Container 布局容器 点击下载learnelementuispringboot项目源码 效果图 el-container.vue&#xff08;Container 布局容器&#xff09;页面效果图 项目里el-container.vue代码 <script> import PagePath from "/components/PagePat…...

小米商城服务治理之客户端熔断器(Google SRE客户端熔断器)

目录 前言 一、什么是Google SRE熔断器 二、Google SRE 熔断器的工作流程&#xff1a; 三、客户端熔断器 (google SRE 熔断器) golang GRPC 实现 四、客户端熔断器 (google SRE 熔断器) golang GRPC单元测试 大家可以关注个人博客&#xff1a;xingxing – Web Developer …...

Springboot 校验工具类

校验工具类 这个实现逻辑很简单,就是调用string的正则表达式 我这里的代码要导入糊涂工具包 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version> </dependency>import…...