package auth import ( "errors" "net/http" "time" "skeleton/database" "skeleton/middlewares" "skeleton/models" "skeleton/utils" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) type Controller struct{} func NewController() *Controller { return &Controller{} } // Register godoc // @Summary 注册示例用户 // @Tags auth // @Accept json // @Produce json // @Param body body Credentials true "注册信息" // @Success 200 {object} utils.APIResponse{data=UserResponse} // @Router /auth/register [post] func (c *Controller) Register(ctx *gin.Context) { var input Credentials if err := ctx.ShouldBindJSON(&input); err != nil { ctx.JSON(http.StatusOK, utils.Failure(400, err.Error())) return } hash, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost) if err != nil { ctx.JSON(http.StatusOK, utils.Failure(500, "密码处理失败")) return } user := models.User{Username: input.Username, Password: string(hash)} if err := database.GetDB().Create(&user).Error; err != nil { ctx.JSON(http.StatusOK, utils.Failure(409, "用户名已存在或数据无效")) return } ctx.JSON(http.StatusOK, utils.Success(UserResponse{ID: user.ID, Username: user.Username})) } // Login godoc // @Summary 用户登录 // @Tags auth // @Accept json // @Produce json // @Param body body Credentials true "登录信息" // @Success 200 {object} utils.APIResponse{data=middlewares.TokenPair} // @Router /auth/login [post] func (c *Controller) Login(ctx *gin.Context) { var input Credentials if err := ctx.ShouldBindJSON(&input); err != nil { ctx.JSON(http.StatusOK, utils.Failure(400, err.Error())) return } var user models.User err := database.GetDB().Where("username = ?", input.Username).First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { ctx.JSON(http.StatusOK, utils.Failure(401, "用户名或密码错误")) return } if err != nil { ctx.JSON(http.StatusOK, utils.Failure(500, "查询用户失败")) return } if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(input.Password)) != nil { ctx.JSON(http.StatusOK, utils.Failure(401, "用户名或密码错误")) return } pair, err := middlewares.GenerateTokenPair(int(user.ID), user.Username) if err != nil { ctx.JSON(http.StatusOK, utils.Failure(500, "生成 token 失败")) return } ctx.JSON(http.StatusOK, utils.Success(pair)) } // Refresh godoc // @Summary 刷新并轮换 Token // @Tags auth // @Accept json // @Produce json // @Param body body RefreshRequest true "Refresh Token" // @Success 200 {object} utils.APIResponse{data=middlewares.TokenPair} // @Router /auth/refresh [post] func (c *Controller) Refresh(ctx *gin.Context) { var input RefreshRequest if err := ctx.ShouldBindJSON(&input); err != nil { ctx.JSON(http.StatusOK, utils.Failure(400, err.Error())) return } claims, err := middlewares.ParseToken(input.RefreshToken) if err != nil || claims.TokenType != middlewares.TokenTypeRefresh { ctx.JSON(http.StatusOK, utils.Failure(401, "refresh token 无效或已过期")) return } revoked, err := database.IsTokenRevoked(ctx.Request.Context(), claims.ID) if err != nil || revoked { ctx.JSON(http.StatusOK, utils.Failure(401, "refresh token 已撤销")) return } if err := revokeClaims(ctx, claims); err != nil { ctx.JSON(http.StatusOK, utils.Failure(500, "撤销旧 token 失败")) return } pair, err := middlewares.GenerateTokenPair(claims.UserID, claims.Username) if err != nil { ctx.JSON(http.StatusOK, utils.Failure(500, "生成 token 失败")) return } ctx.JSON(http.StatusOK, utils.Success(pair)) } // Logout godoc // @Summary 注销并撤销 Token // @Tags auth // @Security BearerAuth // @Accept json // @Produce json // @Param body body LogoutRequest false "可选 Refresh Token" // @Success 200 {object} utils.APIResponse // @Router /private/auth/logout [post] func (c *Controller) Logout(ctx *gin.Context) { claims, _ := middlewares.GetCurrentClaims(ctx) if err := revokeClaims(ctx, claims); err != nil { ctx.JSON(http.StatusOK, utils.Failure(500, "撤销 access token 失败")) return } var input LogoutRequest if ctx.ShouldBindJSON(&input) == nil && input.RefreshToken != "" { if refreshClaims, err := middlewares.ParseToken(input.RefreshToken); err == nil && refreshClaims.TokenType == middlewares.TokenTypeRefresh { _ = revokeClaims(ctx, refreshClaims) } } ctx.JSON(http.StatusOK, utils.Success(nil)) } func revokeClaims(ctx *gin.Context, claims *middlewares.JWTClaims) error { if claims == nil || claims.ExpiresAt == nil { return nil } return database.RevokeToken(ctx.Request.Context(), claims.ID, time.Until(claims.ExpiresAt.Time)) }