书接上回,忘记添加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或错误。
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
}
CustomClaims结构体
定义了要存储在 JWT token 中的自定义声明。在这里,它包括 UserId 字段,用于存储在 token 中的用户ID,并且还嵌入了 jwt.StandardClaims,其中包含标准 JWT 声明,如过期时间、发行时间等。设置这个的原因是为了能获取除userId
go
type CustomClaims struct {
UserId int64 `json:"user_id"` // 用户ID
jwt.StandardClaims
}
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 字符串和过期时间作为参数,并返回可能发生的错误。
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`。**
})
}
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
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()继续执行后续的中间件和处理函数。
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 不存在或者无效。