package controller import ( "context" "fmt" "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" pb "github.com/topfans/backend/pkg/proto/user" "go.uber.org/zap" ) // UserController 用户控制器 type UserController struct { userServiceClient pb.UserSocialService } // NewUserController 创建用户控制器 func NewUserController(dubboClient *client.Client) (*UserController, error) { svc, err := pb.NewUserSocialService(dubboClient) if err != nil { return nil, err } return &UserController{ userServiceClient: svc, }, nil } // GetCurrentUser 获取当前用户信息 // @Summary 获取当前用户信息 // @Description 获取当前登录用户的完整信息 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response{data=dto.GetMeResponseDTO} // @Router /api/v1/auth/me [get] func (ctrl *UserController) GetCurrentUser(c *gin.Context) { // 从认证中间件获取用户信息 userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") logger.Logger.Info("GetCurrentUser request", zap.Any("user_id", userID), zap.Any("star_id", starID), ) // 创建带 Attachments 的 context // 注意:Dubbo Attachments 的值必须是 string 或 []string ctx := context.Background() userIDStr := strconv.FormatInt(userID.(int64), 10) starIDStr := strconv.FormatInt(starID.(int64), 10) attachments := map[string]interface{}{ "user_id": userIDStr, "star_id": starIDStr, } logger.Logger.Info("Setting Dubbo attachments", zap.String("user_id", userIDStr), zap.String("star_id", starIDStr), zap.String("attachment_key", fmt.Sprintf("%v", constant.AttachmentKey)), ) ctx = context.WithValue(ctx, constant.AttachmentKey, attachments) // 调用 Dubbo 服务 resp, err := ctrl.userServiceClient.GetCurrentUser(ctx, &pb.GetCurrentUserRequest{}) if err != nil { logger.Logger.Error("GetCurrentUser failed", zap.Error(err)) response.HandleError(c, err) return } // 获取 Star 信息 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 } } // 转换为 DTO 并返回 data := dto.ToGetMeResponseDTO(resp.User, resp.FanProfile, star) response.Success(c, data) } // GetUser 获取指定用户信息(公开接口) // @Summary 获取指定用户信息 // @Description 根据用户ID获取用户公开信息 // @Tags users // @Accept json // @Produce json // @Param user_id path int true "用户ID" // @Success 200 {object} response.Response // @Router /api/v1/users/{user_id} [get] func (ctrl *UserController) GetUser(c *gin.Context) { userIDStr := c.Param("user_id") userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": "BAD_REQUEST", "message": "invalid user_id", }) return } // 调用 Dubbo 服务(无需 Attachments) ctx := context.Background() resp, err := ctrl.userServiceClient.GetUser(ctx, &pb.GetUserRequest{ UserId: userID, }) if err != nil { logger.Logger.Error("GetUser failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "code": "INTERNAL_ERROR", "message": err.Error(), }) return } c.JSON(http.StatusOK, resp) } // GetMyProfile 获取当前用户粉丝档案 // @Summary 获取当前用户粉丝档案 // @Description 获取当前登录用户的粉丝档案信息 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response{data=dto.ProfileResponseDTO} // @Router /api/v1/me/profile [get] func (ctrl *UserController) GetMyProfile(c *gin.Context) { userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") logger.Logger.Debug("GetMyProfile request", 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.GetMyProfile(ctx, &pb.GetMyProfileRequest{}) if err != nil { logger.Logger.Error("GetMyProfile failed", zap.Error(err)) response.HandleError(c, err) return } // 获取 Star 信息 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 } } // 转换为 DTO 并返回 data := dto.ToProfileResponseDTO(resp.User, resp.FanProfile, star) response.Success(c, data) } // GetFanProfile 获取指定粉丝档案(公开接口) // @Summary 获取粉丝档案 // @Description 根据用户ID和明星ID获取粉丝档案信息 // @Tags users // @Accept json // @Produce json // @Param user_id query int true "用户ID" // @Param star_id query int true "明星ID" // @Success 200 {object} response.Response // @Router /api/v1/fan-profiles [get] func (ctrl *UserController) GetFanProfile(c *gin.Context) { userIDStr := c.Query("user_id") starIDStr := c.Query("star_id") userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": "BAD_REQUEST", "message": "invalid user_id", }) return } starID, err := strconv.ParseInt(starIDStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": "BAD_REQUEST", "message": "invalid star_id", }) return } // 调用 Dubbo 服务(无需 Attachments) ctx := context.Background() resp, err := ctrl.userServiceClient.GetFanProfile(ctx, &pb.GetFanProfileRequest{ UserId: userID, StarId: starID, }) if err != nil { logger.Logger.Error("GetFanProfile failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "code": "INTERNAL_ERROR", "message": err.Error(), }) return } c.JSON(http.StatusOK, resp) } // UpdateNickname 更新昵称 // @Summary 更新用户昵称 // @Description 更新当前用户的粉丝昵称 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param request body pb.UpdateNicknameRequest true "更新昵称请求" // @Success 200 {object} response.Response{data=dto.UpdateNicknameResponseDTO} // @Router /api/v1/me/nickname [put] func (ctrl *UserController) UpdateNickname(c *gin.Context) { var req pb.UpdateNicknameRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "请求参数错误") return } userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") // 创建带 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.UpdateNickname(ctx, &req) if err != nil { logger.Logger.Error("UpdateNickname failed", zap.Error(err)) response.HandleError(c, err) return } // 转换为 DTO 并返回 data := dto.ToUpdateNicknameResponseDTO(resp.FanProfile.Nickname) response.Success(c, data) } // UpdatePassword 更新密码 // @Summary 更新用户密码 // @Description 更新当前用户的登录密码 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param request body pb.UpdatePasswordRequest true "更新密码请求" // @Success 200 {object} response.Response // @Router /api/v1/account/password [post] func (ctrl *UserController) UpdatePassword(c *gin.Context) { var req pb.UpdatePasswordRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "请求参数错误") return } userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") // 创建带 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 服务 _, err := ctrl.userServiceClient.UpdatePassword(ctx, &req) if err != nil { logger.Logger.Error("UpdatePassword failed", zap.Error(err)) response.HandleError(c, err) return } logger.Logger.Info("UpdatePassword successful", zap.Any("user_id", userID), ) // 返回空 data response.Success(c, gin.H{}) } // UpdateAvatar 更新用户头像 // @Summary 更新用户头像 // @Description 更新当前用户的头像URL // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param request body pb.UpdateAvatarRequest true "更新头像请求" // @Success 200 {object} response.Response // @Router /api/v1/me/avatar [put] func (ctrl *UserController) UpdateAvatar(c *gin.Context) { var req pb.UpdateAvatarRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "请求参数错误: "+err.Error()) return } // 验证URL格式 if req.AvatarUrl == "" { response.BadRequest(c, "avatar_url不能为空") return } // 使用 asset_controller 中的 isValidURL 函数验证URL格式 // 这里我们直接使用简单的验证 if len(req.AvatarUrl) > 500 { response.BadRequest(c, "头像URL过长,最大长度为500字符") return } userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") // 创建带 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.UpdateAvatar(ctx, &req) if err != nil { logger.Logger.Error("UpdateAvatar failed", zap.Error(err)) response.HandleError(c, err) return } logger.Logger.Info("UpdateAvatar successful", zap.Any("user_id", userID), zap.String("avatar_url", resp.AvatarUrl), ) // 返回响应 response.Success(c, gin.H{ "avatar_url": resp.AvatarUrl, }) } // AddIdentity 添加粉丝身份 // @Summary 添加粉丝身份 // @Description 为当前用户添加一个新的明星粉丝身份 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param request body pb.AddIdentityRequest true "添加身份请求" // @Success 200 {object} response.Response{data=dto.AddIdentityResponseDTO} // @Router /api/v1/my/fan-identities [post] func (ctrl *UserController) AddIdentity(c *gin.Context) { var req pb.AddIdentityRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "请求参数错误") return } userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") // 创建带 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 服务 _, err := ctrl.userServiceClient.AddIdentity(ctx, &req) if err != nil { logger.Logger.Error("AddIdentity failed", zap.Error(err)) response.HandleError(c, err) return } // 获取 Star 信息 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 } } // 转换为 DTO 并返回 var identityID string if star != nil { identityID = star.IdentityId } data := dto.ToAddIdentityResponseDTO(identityID) response.Success(c, data) } // SwitchIdentity 切换粉丝身份 // @Summary 切换粉丝身份 // @Description 切换到指定的粉丝身份,返回新的访问令牌 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param request body pb.SwitchIdentityRequest true "切换身份请求" // @Success 200 {object} response.Response{data=dto.SwitchIdentityResponseDTO} // @Router /api/v1/my/fan-identities/switch [post] func (ctrl *UserController) SwitchIdentity(c *gin.Context) { var req pb.SwitchIdentityRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": "BAD_REQUEST", "message": err.Error(), }) return } userID, _ := c.Get("user_id") starID, _ := c.Get("star_id") // 创建带 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.SwitchIdentity(ctx, &req) if err != nil { logger.Logger.Error("SwitchIdentity failed", zap.Error(err)) response.HandleError(c, err) return } // 获取 Star 信息 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 } } // 转换为 DTO 并返回 data := dto.ToSwitchIdentityResponseDTO( resp.AccessToken, resp.ExpiresIn, resp.FanProfile, star, ) response.Success(c, data) } // GetFanIdentities 获取可选粉丝身份列表(公开接口) // @Summary 获取可选粉丝身份列表 // @Description 获取所有可选的明星粉丝身份列表 // @Tags users // @Accept json // @Produce json // @Param keyword query string false "搜索关键词" // @Success 200 {object} response.Response{data=dto.FanIdentityListResponseDTO} // @Router /api/v1/fan-identities [get] func (ctrl *UserController) GetFanIdentities(c *gin.Context) { keyword := c.Query("keyword") // 调用 Dubbo 服务(无需 Attachments) ctx := context.Background() resp, err := ctrl.userServiceClient.GetFanIdentities(ctx, &pb.GetFanIdentitiesRequest{ Keyword: keyword, }) if err != nil { logger.Logger.Error("GetFanIdentities failed", zap.Error(err)) response.HandleError(c, err) return } // 转换为 DTO 并返回 data := dto.ToFanIdentityListResponseDTO(resp.Stars) response.Success(c, data) } // GetMyFanIdentities 获取我的粉丝身份列表 // @Summary 获取我的粉丝身份列表 // @Description 获取当前用户拥有的所有粉丝身份列表 // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response{data=dto.MyFanIdentitiesResponseDTO} // @Router /api/v1/my/fan-identities [get] func (ctrl *UserController) GetMyFanIdentities(c *gin.Context) { // 从上下文中获取用户ID和明星ID(中间件已验证) userID, exists := c.Get("user_id") if !exists { response.Error(c, http.StatusUnauthorized, "用户未认证") return } starID, exists := c.Get("star_id") if !exists { response.Error(c, http.StatusUnauthorized, "明星身份未设置") return } // 创建 Context 并设置 Dubbo Attachments ctx := context.WithValue(context.Background(), constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(starID.(int64), 10), }) // 调用 Dubbo 服务 resp, err := ctrl.userServiceClient.GetMyFanIdentities(ctx, &pb.GetMyFanIdentitiesRequest{}) if err != nil { logger.Logger.Error("GetMyFanIdentities failed", zap.Int64("user_id", userID.(int64)), zap.Int64("star_id", starID.(int64)), zap.Error(err), ) response.HandleError(c, err) return } // 转换为 DTO 并返回 data := dto.ToMyFanIdentitiesResponseDTO(resp.Items, resp.CurrentStarId) response.Success(c, data) }