topfans/backend/gateway/controller/user_controller.go
2026-04-07 22:29:48 +08:00

594 lines
16 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}