课后作业: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 函数 它接受 userIdexpiration 作为输入参数,并返回生成的 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)

相关推荐
Find25 天前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子25 天前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子25 天前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子25 天前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵1 个月前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六1 个月前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz1 个月前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity5651 个月前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记