417 lines
11 KiB
Go
417 lines
11 KiB
Go
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,
|
||
})
|
||
}
|