package controller import ( "context" "net/http" "strconv" "dubbo.apache.org/dubbo-go/v3/client" "dubbo.apache.org/dubbo-go/v3/common/constant" "github.com/gin-gonic/gin" "github.com/topfans/backend/gateway/dto" "github.com/topfans/backend/gateway/pkg/response" "github.com/topfans/backend/pkg/logger" pbCommon "github.com/topfans/backend/pkg/proto/common" pb "github.com/topfans/backend/pkg/proto/user" "go.uber.org/zap" ) // AuthController 认证控制器 type AuthController struct { userServiceClient pb.UserSocialService } // pbError 用于包装 proto 错误消息 type pbError struct { message string } func (e *pbError) Error() string { return e.message } // NewAuthController 创建认证控制器 func NewAuthController(dubboClient *client.Client) (*AuthController, error) { svc, err := pb.NewUserSocialService(dubboClient) if err != nil { return nil, err } return &AuthController{ userServiceClient: svc, }, nil } // Register 用户注册 // @Summary 用户注册 // @Description 用户注册接口,需要提供手机号、密码、选择明星身份 // @Tags auth // @Accept json // @Produce json // @Param request body pb.RegisterRequest true "注册请求" // @Success 200 {object} response.Response{data=dto.RegisterResponseDTO} // @Router /api/v1/auth/register [post] func (ctrl *AuthController) Register(c *gin.Context) { var req pb.RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { logger.Logger.Warn("Invalid register request", zap.Error(err)) response.BadRequest(c, "请求参数错误") return } logger.Logger.Info("Register request received", zap.String("mobile", req.Mobile), zap.Int64("star_id", req.StarId), ) // 调用 Dubbo 服务(无需 Attachments) ctx := context.Background() resp, err := ctrl.userServiceClient.Register(ctx, &req) if err != nil { logger.Logger.Error("Register failed", zap.Error(err)) response.HandleError(c, err) return } // 检查业务错误 if resp.Base != nil && resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.HandleError(c, &pbError{message: resp.Base.Message}) return } // 获取 Star 信息用于 DTO 转换 starResp, err := ctrl.userServiceClient.GetFanIdentities(ctx, &pb.GetFanIdentitiesRequest{}) if err != nil { logger.Logger.Error("Failed to get star info", zap.Error(err)) response.HandleError(c, err) return } // 找到对应的 Star var star *pb.Star for _, s := range starResp.Stars { if s.StarId == req.StarId { star = s break } } logger.Logger.Info("Register successful", zap.Int64("user_id", resp.User.Id), ) // 转换为 DTO 并返回 data := dto.ToRegisterResponseDTO( resp.AccessToken, resp.ExpiresIn, resp.User, resp.FanProfile, star, ) response.Success(c, data) } // Login 用户登录 // @Summary 用户登录 // @Description 用户登录接口,需要提供手机号和密码 // @Tags auth // @Accept json // @Produce json // @Param request body pb.LoginRequest true "登录请求" // @Success 200 {object} response.Response{data=dto.LoginResponseDTO} // @Router /api/v1/auth/login [post] func (ctrl *AuthController) Login(c *gin.Context) { var req pb.LoginRequest if err := c.ShouldBindJSON(&req); err != nil { logger.Logger.Warn("Invalid login request", zap.Error(err)) response.BadRequest(c, "请求参数错误") return } logger.Logger.Info("Login request received", zap.String("mobile", req.Mobile), ) // 调用 Dubbo 服务(无需 Attachments) ctx := context.Background() resp, err := ctrl.userServiceClient.Login(ctx, &req) if err != nil { logger.Logger.Error("Login failed", zap.Error(err)) response.HandleError(c, err) return } // 检查业务错误 if resp.Base != nil && resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.HandleError(c, &pbError{message: resp.Base.Message}) return } // 获取 Star 信息用于 DTO 转换 starResp, err := ctrl.userServiceClient.GetFanIdentities(ctx, &pb.GetFanIdentitiesRequest{}) if err != nil { logger.Logger.Error("Failed to get star info", zap.Error(err)) response.HandleError(c, err) return } // 找到对应的 Star var star *pb.Star for _, s := range starResp.Stars { if s.StarId == resp.FanProfile.StarId { star = s break } } logger.Logger.Info("Login successful", zap.Int64("user_id", resp.User.Id), ) // 转换为 DTO 并返回 data := dto.ToLoginResponseDTO( resp.AccessToken, resp.ExpiresIn, "", // refresh_token 暂时为空 resp.User, resp.FanProfile, star, ) response.Success(c, data) } // RefreshToken 刷新 Token // @Summary 刷新访问令牌 // @Description 使用当前访问令牌刷新获取新的访问令牌 // @Tags auth // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response // @Router /api/v1/auth/refresh [post] func (ctrl *AuthController) RefreshToken(c *gin.Context) { // 从认证中间件获取用户信息 userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{ "code": "UNAUTHORIZED", "message": "user not authenticated", }) return } starID, _ := c.Get("star_id") logger.Logger.Info("RefreshToken request received", zap.Any("user_id", userID), zap.Any("star_id", starID), ) // 创建带 Attachments 的 context // 注意:Dubbo Attachments 的值必须是 string 或 []string ctx := context.Background() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(starID.(int64), 10), }) // 调用 Dubbo 服务 resp, err := ctrl.userServiceClient.RefreshToken(ctx, &pb.RefreshTokenRequest{}) if err != nil { logger.Logger.Error("RefreshToken failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "code": "INTERNAL_ERROR", "message": err.Error(), }) return } logger.Logger.Info("RefreshToken successful", zap.Any("user_id", userID), ) c.JSON(http.StatusOK, resp) } // Logout 用户登出 // @Summary 用户登出 // @Description 使用户访问令牌失效 // @Tags auth // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response // @Router /api/v1/auth/logout [post] func (ctrl *AuthController) Logout(c *gin.Context) { // 从认证中间件获取用户信息 userID, exists := c.Get("user_id") if !exists { response.Unauthorized(c, "请先登录") return } logger.Logger.Info("Logout request received", zap.Any("user_id", userID), ) // 创建带 Attachments 的 context // 注意:Dubbo Attachments 的值必须是 string 或 []string ctx := context.Background() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), }) // 调用 Dubbo 服务 _, err := ctrl.userServiceClient.Logout(ctx, &pb.LogoutRequest{}) if err != nil { logger.Logger.Error("Logout failed", zap.Error(err)) response.HandleError(c, err) return } logger.Logger.Info("Logout successful", zap.Any("user_id", userID), ) // 返回空 data response.Success(c, gin.H{}) } // ValidateToken 验证 Token(用于客户端检查 Token 是否有效) // @Summary 验证访问令牌 // @Description 验证访问令牌的有效性 // @Tags auth // @Accept json // @Produce json // @Param request body pb.ValidateTokenRequest true "验证请求" // @Success 200 {object} response.Response // @Router /api/v1/auth/validate [post] func (ctrl *AuthController) ValidateToken(c *gin.Context) { var req pb.ValidateTokenRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": "BAD_REQUEST", "message": err.Error(), }) return } // 调用 Dubbo 服务 ctx := context.Background() resp, err := ctrl.userServiceClient.ValidateToken(ctx, &req) if err != nil { logger.Logger.Error("ValidateToken failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "code": "INTERNAL_ERROR", "message": err.Error(), }) return } c.JSON(http.StatusOK, resp) } // CheckNickname 检查昵称是否已被注册 // @Summary 检查昵称是否被注册 // @Description 检查指定昵称是否已被他人使用 // @Tags auth // @Accept json // @Produce json // @Param request body pb.CheckNicknameRequest true "检查昵称请求" // @Success 200 {object} response.Response{data=pb.CheckNicknameResponse} // @Router /api/v1/auth/check-nickname [post] func (ctrl *AuthController) CheckNickname(c *gin.Context) { var req pb.CheckNicknameRequest if err := c.ShouldBindJSON(&req); err != nil { logger.Logger.Warn("Invalid check nickname request", zap.Error(err)) response.BadRequest(c, "请求参数错误") return } // 校验 nickname 不能为空 if req.Nickname == "" { response.BadRequest(c, "昵称不能为空") return } logger.Logger.Info("CheckNickname request received", zap.String("nickname", req.Nickname), ) // 调用 Dubbo 服务 ctx := context.Background() resp, err := ctrl.userServiceClient.CheckNickname(ctx, &req) if err != nil { logger.Logger.Error("CheckNickname failed", zap.Error(err)) response.HandleError(c, err) return } // 检查业务错误 if resp.Base != nil && resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.HandleError(c, &pbError{message: resp.Base.Message}) return } logger.Logger.Info("CheckNickname successful", zap.String("nickname", req.Nickname), zap.Bool("exists", resp.Exists), ) response.Success(c, gin.H{ "exists": resp.Exists, }) } // CheckMobile 检查手机号是否已被注册 // @Summary 检查手机号是否被注册 // @Description 检查指定手机号是否已被他人使用 // @Tags auth // @Accept json // @Produce json // @Param request body pb.CheckMobileRequest true "检查手机号请求" // @Success 200 {object} response.Response{data=pb.CheckMobileResponse} // @Router /api/v1/auth/check-mobile [post] func (ctrl *AuthController) CheckMobile(c *gin.Context) { var req pb.CheckMobileRequest if err := c.ShouldBindJSON(&req); err != nil { logger.Logger.Warn("Invalid check mobile request", zap.Error(err)) response.BadRequest(c, "请求参数错误") return } // 校验 mobile 不能为空 if req.Mobile == "" { response.BadRequest(c, "手机号不能为空") return } logger.Logger.Info("CheckMobile request received", zap.String("mobile", req.Mobile), ) // 调用 Dubbo 服务 ctx := context.Background() resp, err := ctrl.userServiceClient.CheckMobile(ctx, &req) if err != nil { logger.Logger.Error("CheckMobile failed", zap.Error(err)) response.HandleError(c, err) return } // 检查业务错误 if resp.Base != nil && resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.HandleError(c, &pbError{message: resp.Base.Message}) return } logger.Logger.Info("CheckMobile successful", zap.String("mobile", req.Mobile), zap.Bool("exists", resp.Exists), ) response.Success(c, gin.H{ "exists": resp.Exists, }) }