书接上回,忘记添加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 不存在或者无效。