文章目录
用户认证中间件
增加常量
go
package consts
// CtxKey 上下文 key
type CtxKey string
const (
JWTAccessTokenSecret = "You're making my blood run beyond dimensions" // JWT 访问令牌密钥
JWTRefreshTokenSecret = "You're making my blood run out of emissions" // JWT 刷新令牌密钥
JWTTokenExpireDay = 24 // JWT 令牌过期时间(小时)
JWTRefreshExpireWeek = 7 * 24 // JWT 刷新令牌过期时间(小时)
CtxKeyUserID CtxKey = "userId" // 用户ID上下文 key
)

在logic下创建中间件
go
package middleware
import (
"backend/internal/consts"
"backend/internal/model"
"net/http"
"strings"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/golang-jwt/jwt/v5"
)
// Auth 认证中间件
func Auth(r *ghttp.Request) {
// 从请求中获取用户id(根据Access token获取用户id)
// 从请求头中获取 jwt access token
// r := g.RequestFromCtx(ctx) // 从 ctx 中获取请求对象
// 获取请求上下文
ctx := r.GetCtx()
// 从请求头中获取 Authorization 字段的值
authorizationValue := r.GetHeader("Authorization")
// HTTP 请求头里通常会带:Authorization: Bearer <access_token>
// 检查 Authorization 头值是否以 Bearer 开头(Bearer 是 OAuth2.0 中用于表示携带 JWT 的标准方式)。
if len(authorizationValue) == 0 || !strings.HasPrefix(authorizationValue, "Bearer ") {
r.Response.WriteStatusExit(http.StatusForbidden, "缺少token")
}
accessToken := strings.TrimPrefix(authorizationValue, "Bearer ")
// 解析token获取用户id
var claim model.JWTClaims
// 解析 JWT:将 JWT 字符串(即 accessToken)分解为 Header、Payload、Signature
// 验证签名:通过密钥 consts.JWTAccessTokenSecret 来验证 Signature 是否正确,确保 Token 没有被篡改。
// 填充 claim:将解析出来的 Payload 内容(例如用户的 UserId、Username)填充到 claim 变量中
// keyFunc:这个是一个回调函数,用来返回签名验证所需的密钥。jwt.ParseWithClaims 会使用它来验证 JWT 的 Signature 是否有效
token, err := jwt.ParseWithClaims(accessToken, &claim, func(token *jwt.Token) (any, error) {
return []byte(consts.JWTAccessTokenSecret), nil
})
if err != nil || !token.Valid {
r.Response.WriteStatusExit(http.StatusForbidden, "无效的token")
}
g.Log().Debugf(ctx, "claim: %v", claim)
// 向请求的上下文中写入用户 id
r.SetCtxVar(consts.CtxKeyUserID, claim.UserId) // 使用自定义 Key 类型,防止被其他中间件覆盖
// r.SetCtxVar("userId", "xxx")
r.Middleware.Next() // 继续执行后续中间件
}

修改controller层的me
go
package userinfo
import (
v1 "backend/api/userinfo/v1"
"backend/internal/consts"
"context"
"strconv"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/errors/gerror"
)
func (c *ControllerV1) Me(ctx context.Context, req *v1.MeReq) (res *v1.MeRes, err error) {
// 从请求上下文获取 userId
userId, ok := ctx.Value(consts.CtxKeyUserID).(uint64)
g.Log().Debugf(ctx, "从请求上下文中获取 userId: %d", userId)
if !ok || userId == 0 {
return nil, gerror.New("用户信息获取失败")
}
// 根据用户id获取用户信息
// FormatUint 用来将 uint64 类型的整数转换为字符串
userInfo, err := c.svc.GetInfo(ctx, strconv.FormatUint(userId, 10))
// userInfo, err := c.svc.GetInfo(ctx, "7848643939821819")
if err != nil {
return nil, gerror.New("获取用户信息失败")
}
// 返回用户信息
return &v1.MeRes{
Username: userInfo.Username,
Avatar: userInfo.Avatar,
Email: userInfo.Email,
}, err
}

修改cmd文件
go
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"backend/internal/controller/hello"
"backend/internal/controller/userinfo"
"backend/internal/logic/middleware"
"backend/utility/injection"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// 服务注入
injection.SetupDefaultInjector(ctx)
defer injection.ShutdownDefaultInjector()
// 定义了一个路由组,所有的路由都会以 /api/v1 为前缀。
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
// 不需要登录也能访问的接口
group.POST("/auth/login", userinfo.NewV1(), "Login") // 登录
group.POST("/users", userinfo.NewV1(), "Create") // 创建用户
// 需要登录才能访问的接口
group.Middleware(middleware.Auth)
group.GET("/users/me", userinfo.NewV1(), "Me") // 我的信息
group.Bind(
hello.NewV1(),
// userinfo.NewV1(), // 用户模块相关接口
)
})
s.Run()
return nil
},
}
)

启动服务进行测试

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzYwMDk0fQ.tJwBpbK1ZJlO7k32bxQZ6sdk1rjg36cuuy0PbKUbuLU

官方给的Context共享变量

设置前后端跨域中间件
go
package middleware
import (
"github.com/gogf/gf/v2/net/ghttp"
)
func CORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}

修改cmd文件注册跨域中间件
go
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"backend/internal/controller/hello"
"backend/internal/controller/userinfo"
"backend/internal/logic/middleware"
"backend/utility/injection"
)
var (
// 运行 main 命令时,程序会进入 gcmd.Command 的 Func 函数,这是你定义的 main 命令的核心部分。
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
// 在 Func 函数内部,首先初始化了一个 GoFrame 服务器
s := g.Server()
// 服务注入
injection.SetupDefaultInjector(ctx)
defer injection.ShutdownDefaultInjector()
// 定义了一个路由组,所有的路由都会以 /api/v1 为前缀。
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
// 注册通用响应中间件和CORS跨域中间件
// ghttp.MiddlewareHandlerResponse 是 GoFrame 的默认响应中间件,负责处理 HTTP 响应的通用逻辑。
group.Middleware(ghttp.MiddlewareHandlerResponse, middleware.CORS)
// 不需要登录也能访问的接口
group.POST("/auth/login", userinfo.NewV1(), "Login") // 登录
group.POST("/users", userinfo.NewV1(), "Create") // 创建用户
// 需要登录才能访问的接口
group.Middleware(middleware.Auth)
group.GET("/users/me", userinfo.NewV1(), "Me") // 我的信息
group.Bind(
hello.NewV1(),
// userinfo.NewV1(), // 用户模块相关接口
)
})
s.Run()
return nil
},
}
)

实现Refresh-token接口
增加api相关接口定义
go
package v1
import (
"github.com/gogf/gf/v2/frame/g"
)
// CreateReq 创建用户请求结构体
type CreateReq struct {
g.Meta `path:"/users" method:"post" tags:"用户模块" sm:"创建用户"`
Username string `p:"username" v:"required|length:3,20" dc:"用户名"`
Email string `p:"email" v:"required|email" dc:"邮箱"`
Password string `p:"password" v:"required|length:6,20" dc:"密码"`
ConfirmPassword string `p:"confirmPassword" v:"required|same:Password#确认密码必须传|两次密码需保持一致" dc:"确认密码"`
}
// CreateRes 创建用户返回结构体
type CreateRes struct {
// mime:"application/json" 表示:这个接口的响应类型是 JSON
g.Meta `mime:"application/json"`
UserId uint64 `json:"userID" dc:"用户ID"`
Username string `json:"username" dc:"用户名"`
}
type LoginReq struct {
g.Meta `path:"/auth/login" method:"post" tags:"用户模块" sm:"登录"`
Username string `p:"username" v:"required|length:3,20" dc:"用户名"`
Password string `p:"password" v:"required|length:6,20" dc:"密码"`
}
type LoginRes struct {
AccessToken string `json:"accessToken" dc:"访问令牌"`
RefreshToken string `json:"refreshToken" dc:"刷新令牌"`
}
type MeReq struct {
// sm即 summary 接口/参数概要描述
g.Meta `path:"/users/me" method:"get" tags:"用户模块" sm:"获取当前登录的用户信息"`
}
type MeRes struct {
Username string `json:"username"`
Email string `json:"email"`
Avatar string `json:"avatar"`
}
type RefreshTokenReq struct {
g.Meta `path:"/auth/refresh" method:"post" tags:"用户模块" sm:"刷新令牌"`
RefreshToken string `p:"refreshToken" v:"required" dc:"刷新令牌"`
}
type RefreshTokenRes struct {
AccessToken string `json:"accessToken" dc:"访问令牌"`
RefreshToken string `json:"refreshToken" dc:"刷新令牌"`
}

生成controller层实现代码
bash
root@GoLang:~/proj/proj2/goframProj/backend# gf gen ctrl
generated: /root/proj/proj2/goframProj/backend/api/hello/hello.go
generated: /root/proj/proj2/goframProj/backend/api/userinfo/userinfo.go
generated: /root/proj/proj2/goframProj/backend/internal/controller/userinfo/userinfo_v1_refresh_token.go
done!

定义service层下的业务接口抽象
go
package userinfo
import (
// "backend/internal/dao"
"backend/internal/model"
"backend/internal/model/entity"
// "backend/internal/model/entity"
"context"
// "time"
// "github.com/gogf/gf/crypto/gmd5"
// "github.com/gogf/gf/v2/errors/gerror"
// "github.com/gogf/gf/v2/frame/g"
// "github.com/sony/sonyflake/v2"
)
// 把用户服务抽象成一个接口, 列出来所需要实现的方法
type UserInfoService interface {
Create(ctx context.Context, input *model.CreateUserInput) (*model.CreateUserOutput, error)
Login(ctx context.Context, input *model.LoginInput) (*model.LoginOutput, error)
GetInfo(ctx context.Context, userId string) (*entity.Userinfo, error)
RefreshToken(ctx context.Context, refreshToken string) (res *model.TokenOutput, err error)
}

定义impl下的RefreshToken方法实现
go
package impl
import (
"backend/internal/consts"
"backend/internal/dao"
"backend/internal/model"
"backend/internal/model/entity"
"backend/internal/service/userinfo"
"backend/utility/injection"
"context"
"strconv"
"time"
"github.com/gogf/gf/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/golang-jwt/jwt/v5"
"github.com/sony/sonyflake/v2"
)
// 用户相关业务逻辑
// 定义一个结构体, 实现UserInfoService接口
type UserInfo struct {
snowflack *sonyflake.Sonyflake
}
// encryptPassword 加密密码
func (u *UserInfo) encryptPassword(password string) string {
return gmd5.MustEncryptString(password)
}
// func New() *UserInfo {
// return &UserInfo{}
// }
func New() userinfo.UserInfoService {
return &UserInfo{
snowflack: injection.MustInvoke[*sonyflake.Sonyflake](),
}
}
// 建议:在包级别初始化一次(比如 init() 或 main 启动时)
// var sf *sonyflake.Sonyflake
const (
defaultAvatar = "https://avatars.githubusercontent.com/u/51045272?v=4"
)
// func init() {
// st, err := time.Parse(time.DateOnly, "2025-11-01") // 用过去时间
// if err != nil {
// panic(err) // 这里只在启动阶段 panic 可以接受
// }
// sf, err = sonyflake.New(sonyflake.Settings{StartTime: st})
// if err != nil {
// panic(err)
// }
// }
// Create 创建用户
// func (u *UserInfo) Create(ctx context.Context, username, password, email string) error {
func (u *UserInfo) Create(ctx context.Context, input *model.CreateUserInput) (*model.CreateUserOutput, error) {
// 1. 判断用户名是否已经存在(根据用户名查重)
exist, err := dao.Userinfo.Ctx(ctx).
Where(dao.Userinfo.Columns().Username, input.Username).
Exist()
if err != nil {
g.Log().Errorf(ctx, "查询用户是否存在失败: %v", err)
return nil, err
}
if exist {
return nil, gerror.New("用户已存在")
}
// 2. 生成唯一 id
userId, err := u.snowflack.NextID()
if err != nil {
g.Log().Errorf(ctx, "生成用户ID失败: %v", err)
return nil, gerror.Wrap(err, "生成用户ID失败")
}
// 3. 创建用户
// 创建用户,入库
newUserInfo := entity.Userinfo{
UserId: uint64(userId), // 使用雪花算法生成唯一ID
Username: input.Username,
// Password: req.Password, // 是不是需要对用户输入的密码进行加密
Password: u.encryptPassword(input.Password), // 对字符串 s 做 MD5, 返回 MD5 的十六进制字符串
// 练习可用;生产换 bcrypt
Email: input.Email,
Avatar: defaultAvatar, // 简化注册流程,一般使用默认头像,后续支持用户在个人中心上传头像
}
// id, err := dao.Userinfo.Ctx(ctx).InsertAndGetId(newUserInfo)
_, err = dao.Userinfo.Ctx(ctx).Insert(newUserInfo)
if err != nil {
g.Log().Errorf(ctx, "创建用户失败:%v", err)
return nil, gerror.Wrap(err, "创建用户失败")
}
// 4. 返回结果
return &model.CreateUserOutput{
UserId: uint64(userId),
Username: input.Username,
}, nil
}
// Login 登录
func (u *UserInfo) Login(ctx context.Context, input *model.LoginInput) (*model.LoginOutput, error) {
// 拿用户输入的用户名和密码,去数据库查询
var user entity.Userinfo
err := dao.Userinfo.Ctx(ctx).
Where(dao.Userinfo.Columns().Username, input.Username).
Where(dao.Userinfo.Columns().Password, u.encryptPassword(input.Password)).
Scan(&user)
if err != nil {
g.Log().Errorf(ctx, "查询用户失败: %v", err)
return nil, gerror.Wrapf(err, "查询用户失败")
}
// 生成 JWT Token
tokenObj, err := genJwtByUserInfo(ctx, user.UserId, user.Username)
if err != nil {
g.Log().Errorf(ctx, "生成 JWT Token 失败: %v", err)
return nil, gerror.Wrap(err, "生成 JWT Token 失败")
}
// 返回结果
return &model.LoginOutput{
AccessToken: tokenObj.AccessToken,
RefreshToken: tokenObj.RefreshToken,
}, nil
}
// type JWTClaims struct {
// UserId uint64 `json:"userId"`
// Username string `json:"username"`
// jwt.RegisteredClaims
// }
// genJwtByUserInfo 根据用户信息生成 JWT
func genJwtByUserInfo(ctx context.Context, userID uint64, username string) (*model.TokenOutput, error) {
// 生成 Access Token
claims := &model.JWTClaims{
UserId: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "Simon",
Subject: "check-in-system",
ExpiresAt: jwt.NewNumericDate(time.Now().Add(consts.JWTTokenExpireDay * time.Hour)), // 设置过期时间为1天
},
}
// 创建一个新的 JWT 对象
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// SignedString:把 Header + Payload 按 JWT 规则 base64url 编码后,使用 secret 生成签名,最后拼成标准 JWT 字符串
// []byte(...):HMAC 需要字节数组形式的 key
signedAccessToken, err := accessToken.SignedString([]byte(consts.JWTAccessTokenSecret))
if err != nil {
g.Log().Errorf(ctx, "生成 JWT Token 失败: %v", err)
return nil, err
}
// 生成 refresh Token
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, &model.JWTClaims{
UserId: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "Simon",
Subject: "check-in-system",
ExpiresAt: jwt.NewNumericDate(time.Now().Add(consts.JWTRefreshExpireWeek * time.Hour)), // 设置过期时间为1周
},
})
signedRefreshToken, err := refreshToken.SignedString([]byte(consts.JWTRefreshTokenSecret))
if err != nil {
g.Log().Errorf(ctx, "生成 JWT Token 失败: %v", err)
return nil, err
}
return &model.TokenOutput{
AccessToken: signedAccessToken,
RefreshToken: signedRefreshToken,
}, nil
}
func (u *UserInfo) GetInfo(ctx context.Context, userId string) (*entity.Userinfo, error) {
var user entity.Userinfo
err := dao.Userinfo.Ctx(ctx).
Where(dao.Userinfo.Columns().UserId, userId).
Scan(&user)
if err != nil {
g.Log().Errorf(ctx, "查询用户失败: %v", err)
return nil, gerror.Wrapf(err, "查询用户失败")
}
return &user, nil
}
// RefreshToken 刷新 Token
func (u *UserInfo) RefreshToken(ctx context.Context, refreshToken string) (res *model.TokenOutput, err error) {
// 1. 解析 refreshToken,拿到 userid
// 解析token获取用户id
var claim model.JWTClaims
// 解析 JWT:将 JWT 字符串(即 accessToken)分解为 Header、Payload、Signature
// 验证签名:通过密钥 consts.JWTAccessTokenSecret 来验证 Signature 是否正确,确保 Token 没有被篡改。
// 填充 claim:将解析出来的 Payload 内容(例如用户的 UserId、Username)填充到 claim 变量中
// keyFunc:这个是一个回调函数,用来返回签名验证所需的密钥。jwt.ParseWithClaims 会使用它来验证 JWT 的 Signature 是否有效
token, err := jwt.ParseWithClaims(refreshToken, &claim, func(token *jwt.Token) (any, error) {
return []byte(consts.JWTRefreshTokenSecret), nil
})
if err != nil || !token.Valid {
g.Log().Errorf(ctx, "refresh token: %v, err: %+v", token, err)
return nil, gerror.New("refresh token 无效")
}
// 2. 根据 userid 获取用户信息
userInfo, err := u.GetInfo(ctx, strconv.FormatUint(claim.UserId, 10))
if err != nil {
g.Log().Errorf(ctx, "根据 userid 获取用户信息失败: %v", err)
return nil, gerror.Wrap(err, "根据 userid 获取用户信息失败")
}
// 3. 生成新的 accessToken 和 refreshToken
// 4. 返回新的 token
return genJwtByUserInfo(ctx, userInfo.UserId, userInfo.Username)
}

修改control层实现代码
go
package userinfo
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
v1 "backend/api/userinfo/v1"
)
func (c *ControllerV1) RefreshToken(ctx context.Context, req *v1.RefreshTokenReq) (res *v1.RefreshTokenRes, err error) {
output, err := c.svc.RefreshToken(ctx, req.RefreshToken)
if err != nil {
g.Log().Errorf(ctx, "刷新 token 失败: %v", err)
return nil, gerror.New("刷新 token 失败")
}
return &v1.RefreshTokenRes{
AccessToken: output.AccessToken,
RefreshToken: output.RefreshToken,
}, nil
}

cmd.go注册刷新token路由
go
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"backend/internal/controller/hello"
"backend/internal/controller/userinfo"
"backend/internal/logic/middleware"
"backend/utility/injection"
)
var (
// 运行 main 命令时,程序会进入 gcmd.Command 的 Func 函数,这是你定义的 main 命令的核心部分。
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
// 在 Func 函数内部,首先初始化了一个 GoFrame 服务器
s := g.Server()
// 服务注入
injection.SetupDefaultInjector(ctx)
defer injection.ShutdownDefaultInjector()
// 定义了一个路由组,所有的路由都会以 /api/v1 为前缀。
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
// 注册通用响应中间件和CORS跨域中间件
// ghttp.MiddlewareHandlerResponse 是 GoFrame 的默认响应中间件,负责处理 HTTP 响应的通用逻辑。
group.Middleware(ghttp.MiddlewareHandlerResponse, middleware.CORS)
// 不需要登录也能访问的接口
group.POST("/auth/login", userinfo.NewV1(), "Login") // 登录
group.POST("/users", userinfo.NewV1(), "Create") // 创建用户
group.POST("/auth/refresh", userinfo.NewV1(), "RefreshToken") // 刷新token
// 需要登录才能访问的接口
group.Middleware(middleware.Auth)
group.GET("/users/me", userinfo.NewV1(), "Me") // 我的信息
group.Bind(
hello.NewV1(),
// userinfo.NewV1(), // 用户模块相关接口
)
})
s.Run()
return nil
},
}
)

注意 group.POST("/auth/refresh", userinfo.NewV1(), "RefreshToken") 的第三个参数是对应的方法名

启动测试
bash
{
"username": "Jackson",
"password": "1234567"
}

{"code":0,"message":"OK","data":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM1OTc5fQ.VVaHmZTh5iigI3tCDeNTn0TYhKqretDTTHtrOfrmzVo","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY3MzQwNzU5fQ.72g2gbrCFMwR5K4y_TkqI5UwTP4YmU_0QiiifOEM9mI"}}

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM1OTc5fQ.VVaHmZTh5iigI3tCDeNTn0TYhKqretDTTHtrOfrmzVo
20秒后access token会失效

前端会自动拿到refreshtoken来刷新accesstoken
bash
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY3MzQwNzU5fQ.72g2gbrCFMwR5K4y_TkqI5UwTP4YmU_0QiiifOEM9mI"
}

bash
{"code":0,"message":"OK","data":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM2NDg1fQ.H1XdzyelFdutF_1HXvy0CKCccTQG5Vo8Ku33avx9ndc","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY3MzQxMjY1fQ.NMjYgNKW-4m0Ww04TB7lEB7WxWbx8Aq6z74SRjxhtto"}}
就可以正常登着账户
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM2NDg1fQ.H1XdzyelFdutF_1HXvy0CKCccTQG5Vo8Ku33avx9ndc

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!