课后作业:CommunityTopics(添加JWT和Redis)| 青训营

书接上回,忘记添加sql文件了课后作业:CommunityTopics| 青训营 - 掘金 (juejin.cn),现在在这里补上,新增的一些都是一些中间件,对于中间件怎么实现不重要,重要的开始要会用,会用后再学,毕竟像我这种小菜鸟还不会造轮子

在学习Redis和JWT的过程中遇到了很多坑,都是不知道怎么使用造成的,对于我这种小白来说,没有取过于深究一些细节问题,最重要的是完成我的目的

项目地址:字节青训营CommunityTopics:(gitee.com)

Sql代码(mysql)

sql 复制代码
CREATE TABLE IF NOT EXISTS post (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '评论id',
    user_id     BIGINT NOT NULL COMMENT '创建者id',
    topic_id    BIGINT NOT NULL COMMENT '所属文章id',
    root_id     TINYINT NOT NULL COMMENT '根评论id',
    content     VARCHAR(255) NULL COMMENT '内容',
    likes       BIGINT NOT NULL COMMENT '点赞数',
    create_time BIGINT NOT NULL COMMENT '创建时间'
);

CREATE INDEX post_create_time_index ON post (create_time);

CREATE TABLE IF NOT EXISTS topic (
    id          INT AUTO_INCREMENT PRIMARY KEY COMMENT '文章id',
    title       VARCHAR(255) NOT NULL COMMENT '文章标题',
    content     VARCHAR(255) NULL COMMENT '内容',
    create_time BIGINT NOT NULL COMMENT '创建时间',
    user_id     BIGINT NOT NULL COMMENT '用户id',
    view_count  BIGINT NOT NULL COMMENT '浏览数'
);

CREATE TABLE IF NOT EXISTS user (
    id          BIGINT NOT NULL PRIMARY KEY COMMENT '用户id',
    name        VARCHAR(20) NOT NULL COMMENT '用户名',
    avatar      VARCHAR(255) NOT NULL COMMENT '用户头像',
    level       INT NOT NULL COMMENT '用户等级',
    create_time BIGINT NOT NULL COMMENT '创建时间'
);
go 复制代码
//下载jwt和Redis命令行命令
go get github.com/dgrijalva/jwt-go
go get github.com/go-redis/redis/v8

结构:

  • 对server.go有一点修改,后面介绍
  • 新增了communityTopucs,sql文件
  • 新增了两个测试文件
  • 新增了连接redis数据和生成JWT的文件

修改文件说明

最重要的一块:

  • 新增了一个获取token的函数,修改了一个需要token认证的函数
less 复制代码
engine.GET("/community/getToken/:uid", func(c *gin.Context) {
    // ...
})

engine.POST("/community/post/publish", util.AuthMiddleware(), func(c *gin.Context) {
    // ...
})

第一个路由是 GET 请求,用于获取生成的 JWT Token;第二个路由是 POST 请求,用于发布评论。其中,第三个路由使用了 util.AuthMiddleware() 中间件来验证 JWT Token。

最重要的就是添加了util.AuthMiddleware()

最重要的就是添加了util.AuthMiddleware()

最重要的就是添加了util.AuthMiddleware()

  • 获取token:
go 复制代码
engine.GET("/community/getToken/:uid", func(c *gin.Context) {  
    uidStr := c.Param("uid")  
    uid, err := strconv.ParseInt(uidStr, 10, 64)  
    if err != nil {  
        c.JSON(http.StatusBadRequest, "")  
    }  
    token, _ := util.GenerateToken(uid, time.Hour*3)  
    c.JSON(200, token)  
})

这个函数是用于生成 JWT Token 的,它从 URL 路径中获取用户 ID,然后调用 util.GenerateToken() 函数生成 JWT Token,并将 Token 返回给客户端。

新增文件说明(除sql):

jwtUtil.go

文件包含了生成 JWT Token 和验证 JWT Token 的函数。

  • GenerateToken 函数用于生成 JWT Token。它接收用户 ID 和过期时间作为参数,并返回生成的 Token 字符串。在生成 Token 时,使用了自定义的声明信息 CustomClaims,其中包含了用户 ID 和 Token 的过期时间。
  • VerifyToken 函数用于验证 JWT Token 的有效性。它接收 Token 字符串作为参数,并返回解析后的声明信息 CustomClaims 或错误。
  1. GenerateToken 函数 它接受 userId 和 expiration 作为输入参数,并返回生成的 token 字符串。该 token 使用 jwt.NewWithClaims 函数创建,允许我们添加自定义声明(在本例中是 UserId)和设置 token 的过期时间。然后,使用 JWT 密钥(jwtSecret)对 token 进行签名,签名后的 token 字符串存储在 Redis 中,并设置了给定的过期时间。
go 复制代码
func GenerateToken(userId int64, expiration time.Duration) (string, error) {
	// 创建一个新的 JWT token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, &CustomClaims{
		UserId: userId,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(expiration).Unix(), // Token 的过期时间(在此示例中为expiration)
		},
	})

	// 使用密钥对 token 进行签名
	tokenString, err := token.SignedString([]byte(jwtSecret))
	if err != nil {
		return "", err
	}

	// 将 Token 存储到 Redis 中,设置过期时间为expiration
	err = SetToken(tokenString, expiration)
	if err != nil {
		return "", err
	}

	return tokenString, nil
}
  1. CustomClaims 结构体

定义了要存储在 JWT token 中的自定义声明。在这里,它包括 UserId 字段,用于存储在 token 中的用户ID,并且还嵌入了 jwt.StandardClaims,其中包含标准 JWT 声明,如过期时间、发行时间等。设置这个的原因是为了能获取除userId

go 复制代码
type CustomClaims struct {
	UserId int64 `json:"user_id"` // 用户ID
	jwt.StandardClaims
}
  1. VerifyToken 函数

用于解析和验证 JWT token。它接受 tokenString 作为输入,并在 token 有效时返回自定义声明(*CustomClaims)。函数使用 jwt.ParseWithClaims 解析 token 并验证其签名。为验证使用了提供的 JWT 密钥(jwtSecret)。如果 token 有效并且成功提取了自定义声明,则函数返回自定义声明;否则,返回一个表示无效 token 的错误。

go 复制代码
func VerifyToken(tokenString string) (*CustomClaims, error) {
	// 解析并验证 token 的有效性
	parsedToken, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(jwtSecret), nil
	})
	if err != nil {
		return nil, err
	}

	// 验证 token 的声明信息
	claims, ok := parsedToken.Claims.(*CustomClaims)
	if !ok || !parsedToken.Valid {
		return nil, jwt.ErrSignatureInvalid
	}

	return claims, nil
}

redisUtil.go

文件包含了操作 Redis 的函数。

  • init 函数用于连接Redis

  • GetToken 函数用于从 Redis 中获取存储的 Token。它接收 Token 字符串作为参数,并返回从 Redis 中获取的结果。

  • SetToken 函数用于将 Token 存储到 Redis 中,并设置 Token 的过期时间。它接收 Token 字符串和过期时间作为参数,并返回可能发生的错误。

  1. init 函数初始化与 Redis 服务器的连接,其中包括正确的地址和密码(如果有的话)。并声明一个rdb的全局变量
csharp 复制代码
var rdb *redis.Client

func init() {
	// 初始化 Redis 客户端
	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379", // 请根据实际情况修改为正确的 Redis 地址
		Password: "",               // 如果有密码,请填写 Redis 密码,否则留空
		DB:       0,                //Redis 支持多个数据库,索引从 0 到 15,这些索引对应着不同的数据库。默认情况下,Redis 客户端连接的数据库索引为 0,即 `DB: 0`。**
	})
}
  1. GetToken 函数用于从 Redis 中获取存储的 token。它接受 token 字符串作为输入,并返回 Redis 中对应的 token 值。
go 复制代码
func GetToken(token string) (string, error) {
	return rdb.Get(context.Background(), token).Result()
}

SetToken 函数

用于将token存入Redis。它接受 token 字符串和expiration过期时间作为输入。

go 复制代码
func SetToken(token string, expiration time.Duration) error {  
    return rdb.Set(context.Background(), token, token, expiration).Err()  
}

AuthMiddleware.go

  1. AuthMiddleware 函数是一个 gin.HandlerFunc,用于验证 JWT Token 的中间件。中间件是 Gin 框架中常用的一种机制,用于在请求到达处理函数之前,执行一系列的操作。
go 复制代码
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 从请求头中获取 Authorization 字段
		authorizationHeader := c.GetHeader("Authorization")
		if authorizationHeader == "" {
			// 如果 Authorization 字段为空,返回状态码 401 和错误信息 "Missing token"
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing token"})
			c.Abort()
			return
		}

		// 解析并验证 Token
		claims, err := VerifyToken(authorizationHeader)
		if err != nil {
			// 如果 Token 无效或解析失败,返回状态码 401 和错误信息 "Invalid token"
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// 将解析的用户 ID 存入 Gin 的 Context 中,以便后续处理函数使用
		c.Set("user_id", claims.UserId)
		log.Printf("AuthMiddleware中间件设置了user_id:%v", claims.UserId)
		c.Next()
	}
}
  • 首先,通过 c.GetHeader("Authorization") 获取请求头中的 "Authorization" 字段的值,该字段通常用于传递 JWT Token。
  • 如果 "Authorization" 字段为空,表示请求未携带 Token,将返回状态码 401 和错误信息 "Missing token",并终止后续的处理。
  • 调用 VerifyToken 函数,对 Token 进行解析和验证。
  • 如果 Token 无效或解析失败,将返回状态码 401 和错误信息 "Invalid token",并终止后续的处理。
  • 如果 Token 有效,将从解析后的 Token 中提取用户 ID,并通过 c.Set("user_id", claims.UserId) 将用户 ID 存入 Gin 的 Context 中,以便后续处理函数可以获取用户 ID。
  • 使用 log.Printf 记录用户 ID 到日志中,方便调试。
  • 最后,通过 c.Next() 继续执行后续的中间件和处理函数。
  1. GetUserIDFromContext 函数用于从 Gin 的 Context 中获取用户 ID,并返回用户 ID 的字符串表示。
go 复制代码
func GetUserIDFromContext(c *gin.Context) string {
	userId, _ := c.Get("user_id")
	if userIdInt, ok := userId.(int64); ok {
		return strconv.FormatInt(userIdInt, 10)
	}
	return "0"
}
  • 首先,使用 c.Get 方法从 Gin 的 Context 中获取存储在其中的用户 ID。
  • 由于 Context 存储的值是 interface{} 类型,需要进行类型断言。
  • 如果成功从 Context 中获取了用户 ID,并且用户 ID 是 int64 类型,将使用 strconv.FormatInt 方法将其转换为字符串,并返回该字符串。
  • 如果无法获取用户 ID 或者用户 ID 不是 int64 类型,将返回字符串 "0" 表示用户 ID 不存在或者无效。

测试文件在代码中有很详细的解释,地址:字节青训营CommunityTopics:(gitee.com)

相关推荐
淇逢春1 年前
可被K整除的子数组问题 | 豆包MarsCode AI 刷题
青训营笔记
静水流深3971 年前
03 模型IO| 豆包MarsCode AI刷题
青训营笔记
用户247841860021 年前
第七次算法笔记 | 豆包MarsCode AI刷题
青训营笔记
幻61 年前
小S的倒排索引 | 豆包MarsCode AI刷题
青训营笔记
用户826014428301 年前
469. 环形数组最大子数组和问题
青训营笔记
用户605721920981 年前
奇妙货币交易问题 | 豆包MarsCode AI刷题
青训营笔记
我明天再来学Web渗透1 年前
“抖音互联网架构分析及高可用系统构建思考”(方向三)| 豆包MarsCode AI刷题
青训营笔记
用户302133066201 年前
第三次刷题 | 豆包MarsCode AI刷题
青训营笔记
用户9105973027701 年前
CSS详解| 豆包MarsCode AI刷题
青训营笔记
huyck1 年前
伴学笔记1|豆包MarsCode AI 刷题
青训营笔记