Files
smartmate/backend/service/user.go
LoveLosita 5038ec2fc5 Version:0.0.2.dev.260203
feat: implement redis-based logout and jwt middleware 🚀 feat: 实现基于 Redis 的登出机制与 JWT 中间件 🚀

Middleware Construction: Implemented JWTTokenAuth middleware for Gin, featuring structured claims parsing and active session validation. 🛡️
中间件构建:为 Gin 框架实现了 JWTTokenAuth 中间件,支持结构化 Claims 解析与活跃会话验证。🛡️

Redis Integration: Introduced Redis for high-performance state management. Integrated CacheDAO into the Dependency Injection (DI) chain. 
Redis 引入:引入 Redis 进行高性能状态管理。将 CacheDAO 成功集成至依赖注入 (DI) 调用链中。

Secure Logout Module: Developed the logout functional module using a Redis Blacklist mechanism. 🔐
安全登出模块:开发了基于 Redis 黑名单 机制的登出功能模块。🔐

Marked invalidated tokens by storing jti (JWT ID) in Redis with automatic TTL expiration.
通过在 Redis 中存储 jti(JWT 唯一标识)并设置自动 TTL 过期,实现 Token 的主动失效。

Added blacklist checkpoints in both AuthMiddleware and RefreshToken logic to prevent session resurrection.
在认证中间件与 Token 刷新逻辑中同步增设黑名单检查点,杜绝登出后的“死灰复燃”。

Architecture Refinement: Upgraded ValidateRefreshToken and Service-layer handlers to use type-safe struct assertions instead of raw MapClaims. 🏗️
架构精进:升级了 ValidateRefreshToken 与 Service 层处理器,改用类型安全的结构体断言取代原始的 MapClaims,提升了代码健壮性。🏗️
2026-02-03 16:53:16 +08:00

144 lines
4.3 KiB
Go

// Package service 业务逻辑层
// 包含所有核心业务逻辑
package service
import (
"errors"
"time"
"github.com/LoveLosita/smartflow/backend/auth"
"github.com/LoveLosita/smartflow/backend/dao"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/LoveLosita/smartflow/backend/utils"
"gorm.io/gorm"
)
type UserService struct {
userRepo *dao.UserDAO
cacheRepo *dao.CacheDAO
}
func NewUserService(userRepo *dao.UserDAO, cacheRepo *dao.CacheDAO) *UserService {
return &UserService{
userRepo: userRepo, // 把传进来的 DAO 揣进口袋里
cacheRepo: cacheRepo,
}
}
func (sv *UserService) UserRegister(user model.UserRegisterRequest) (*model.UserRegisterResponse, error) {
//检查是否有空字段
if user.Username == "" || user.Password == "" ||
user.PhoneNumber == "" {
return nil, respond.MissingParam
}
// 检查字段长度是否超过90%
if len(user.Username) > 45 || len(user.Password) > 229 || len(user.PhoneNumber) > 18 {
return nil, respond.ParamTooLong
}
//检查用户名是否已存在
result, err := sv.userRepo.IfUsernameExists(user.Username)
if err != nil {
return nil, err
}
if result {
return nil, respond.InvalidName
}
hashedPwd, err := utils.HashPassword(user.Password) //调用utils层的方法
if err != nil {
return nil, err
}
user.Password = hashedPwd //将user的密码字段改为加密后的密码
newUser, err := sv.userRepo.Create(user.Username, user.PhoneNumber, user.Password)
if err != nil {
return nil, err
}
//返回注册成功的用户ID
return &model.UserRegisterResponse{ID: newUser.ID}, nil
}
func (sv *UserService) UserLogin(req *model.UserLoginRequest) (*model.Tokens, error) {
var tokens model.Tokens
hashedPwd, err := sv.userRepo.GetUserHashedPasswordByName(req.Username) //调用dao层的方法
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, respond.WrongName
}
return nil, err
}
result, err := utils.CompareHashPwdAndPwd(hashedPwd, req.Password) //比较密码是否匹配
if err != nil { //其他错误
return &tokens, err
} else if !result { //密码不匹配
return nil, respond.WrongPwd
}
id, err := sv.userRepo.GetUserIDByName(req.Username)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, respond.WrongName
}
return nil, err
}
tokens.AccessToken, tokens.RefreshToken, err = auth.GenerateTokens(id) //生成jwt key
if err != nil { //其他错误
return nil, err
}
return &tokens, nil
}
/*func (sv *UserService) RefreshTokenHandler(refreshToken string) (*model.Tokens, error) {
// 验证刷新令牌
token, err := auth.ValidateRefreshToken(refreshToken, sv.cacheRepo)
if err != nil || !token.Valid { // 刷新令牌无效
return nil, respond.InvalidRefreshToken
}
// 生成新的访问令牌和刷新令牌
if claims, ok := token.Claims.(jwt.MapClaims); ok {
userID := int(claims["user_id"].(float64))
newAccessToken, newRefreshToken, err := auth.GenerateTokens(userID)
if err != nil {
return nil, err
}
// 返回新的访问令牌和刷新令牌
return &model.Tokens{AccessToken: newAccessToken, RefreshToken: newRefreshToken}, nil
} else {
return nil, respond.InvalidClaims
}
}*/
func (sv *UserService) RefreshTokenHandler(refreshToken string) (*model.Tokens, error) {
// 1. 验证刷新令牌 (这里已经包含了 Redis 黑名单检查)
token, err := auth.ValidateRefreshToken(refreshToken, sv.cacheRepo)
if err != nil {
return nil, err
}
// 2. 改动点:直接断言为你定义的结构体 model.MyCustomClaims
if claims, ok := token.Claims.(*model.MyCustomClaims); ok {
// 3. 这里的 userID 已经是 int 了,不再需要 (float64) 转换
newAccessToken, newRefreshToken, err := auth.GenerateTokens(claims.UserID)
if err != nil {
return nil, err
}
// 返回新的双 Token
return &model.Tokens{
AccessToken: newAccessToken,
RefreshToken: newRefreshToken,
}, nil
}
return nil, respond.InvalidClaims
}
func (sv *UserService) UserLogout(jti string, expireTime time.Time) error {
//1.直接把 jti 扔进黑名单
expiration := time.Until(expireTime)
err := sv.cacheRepo.SetBlacklist(jti, expiration)
if err != nil {
return err
}
return nil
}