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

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