594 lines
16 KiB
Go
594 lines
16 KiB
Go
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)
|
||
}
|