Files
EchoChat/backend/go-service/app/auth/controller/auth_controller.go
2026-05-26 22:41:53 +08:00

299 lines
8.8 KiB
Go
Raw 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 提供 auth 模块的 HTTP 接口处理
package controller
import (
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
"strings"
"github.com/echochat/backend/app/auth/service"
"github.com/echochat/backend/app/constants"
"github.com/echochat/backend/app/dto"
"github.com/echochat/backend/config"
"github.com/echochat/backend/pkg/logs"
"github.com/echochat/backend/pkg/middleware"
"github.com/echochat/backend/pkg/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// AuthController 前台认证控制器
// 处理用户注册、登录、Token 刷新、个人信息管理等接口
type AuthController struct {
authService *service.AuthService
oauthService *service.OAuthService
oauthCfg *config.OAuthConfig
}
// NewAuthController 创建前台认证控制器实例
func NewAuthController(authService *service.AuthService, oauthService *service.OAuthService, oauthCfg *config.OAuthConfig) *AuthController {
return &AuthController{authService: authService, oauthService: oauthService, oauthCfg: oauthCfg}
}
// Register 用户注册
// POST /api/v1/auth/register
func (ctrl *AuthController) Register(c *gin.Context) {
funcName := "controller.auth_controller.Register"
ctx := c.Request.Context()
var req dto.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数校验失败: "+err.Error())
return
}
logs.Info(ctx, funcName, "注册请求",
zap.String("username", req.Username),
zap.String("email", logs.MaskEmail(req.Email)),
)
resp, err := ctrl.authService.Register(ctx, &req)
if err != nil {
handleAuthError(c, err, "注册失败")
return
}
utils.ResponseOK(c, resp)
}
// Login 用户登录
// POST /api/v1/auth/login
func (ctrl *AuthController) Login(c *gin.Context) {
funcName := "controller.auth_controller.Login"
ctx := c.Request.Context()
var req dto.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数校验失败: "+err.Error())
return
}
logs.Info(ctx, funcName, "登录请求",
zap.String("account", req.Account),
zap.String("ip", c.ClientIP()),
)
resp, err := ctrl.authService.Login(ctx, &req, c.ClientIP(), constants.ClientTypeFrontend)
if err != nil {
handleAuthError(c, err, "登录失败")
return
}
utils.ResponseOK(c, resp)
}
func (ctrl *AuthController) OAuthAuthorize(c *gin.Context) {
provider := strings.ToLower(strings.TrimSpace(c.Param("provider")))
authURL, err := ctrl.oauthService.BuildAuthorizeURL(c.Request.Context(), provider)
if err != nil {
handleAuthError(c, err, "第三方登录初始化失败")
return
}
utils.ResponseOK(c, gin.H{"url": authURL})
}
func (ctrl *AuthController) OAuthCallback(c *gin.Context) {
provider := strings.ToLower(strings.TrimSpace(c.Param("provider")))
code := strings.TrimSpace(c.Query("code"))
state := strings.TrimSpace(c.Query("state"))
if code == "" {
ctrl.redirectOAuthFailure(c, "第三方登录授权失败")
return
}
resp, err := ctrl.oauthService.Callback(c.Request.Context(), provider, code, state, c.ClientIP())
if err != nil {
ctrl.redirectOAuthFailure(c, err.Error())
return
}
payload, err := json.Marshal(resp)
if err != nil {
ctrl.redirectOAuthFailure(c, "登录结果生成失败")
return
}
ctrl.redirectOAuthResult(c, "success", base64.RawURLEncoding.EncodeToString(payload))
}
func (ctrl *AuthController) redirectOAuthFailure(c *gin.Context, message string) {
ctrl.redirectOAuthResult(c, "error", base64.RawURLEncoding.EncodeToString([]byte(message)))
}
func (ctrl *AuthController) redirectOAuthResult(c *gin.Context, status, payload string) {
callbackURL := "/echoChat-frontend/#/pages/auth/oauth-callback"
if ctrl.oauthCfg != nil && strings.TrimSpace(ctrl.oauthCfg.FrontendCallbackURL) != "" {
callbackURL = strings.TrimSpace(ctrl.oauthCfg.FrontendCallbackURL)
}
sep := "?"
if strings.Contains(callbackURL, "?") {
sep = "&"
}
c.Redirect(http.StatusFound, callbackURL+sep+"status="+url.QueryEscape(status)+"&payload="+url.QueryEscape(payload))
}
// Logout 用户登出
// POST /api/v1/auth/logout需认证
func (ctrl *AuthController) Logout(c *gin.Context) {
funcName := "controller.auth_controller.Logout"
ctx := c.Request.Context()
userID, ok := middleware.GetCurrentUserID(c)
if !ok {
utils.ResponseUnauthorized(c, "无法获取用户信息")
return
}
clientType := middleware.GetCurrentClientType(c)
logs.Info(ctx, funcName, "用户登出",
zap.Int64("user_id", userID),
zap.String("client_type", clientType),
)
if err := ctrl.authService.Logout(ctx, userID, clientType); err != nil {
utils.ResponseError(c, "登出失败")
return
}
utils.ResponseOK(c, nil)
}
// RefreshToken 刷新 Access Token
// POST /api/v1/auth/refresh-token
func (ctrl *AuthController) RefreshToken(c *gin.Context) {
funcName := "controller.auth_controller.RefreshToken"
ctx := c.Request.Context()
var req dto.RefreshTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数校验失败: "+err.Error())
return
}
resp, err := ctrl.authService.RefreshToken(ctx, req.RefreshToken)
if err != nil {
handleAuthError(c, err, "刷新 Token 失败")
return
}
utils.ResponseOK(c, resp)
}
// GetProfile 获取当前用户信息
// GET /api/v1/auth/profile需认证
func (ctrl *AuthController) GetProfile(c *gin.Context) {
funcName := "controller.auth_controller.GetProfile"
ctx := c.Request.Context()
userID, ok := middleware.GetCurrentUserID(c)
if !ok {
utils.ResponseUnauthorized(c, "无法获取用户信息")
return
}
logs.Debug(ctx, funcName, "获取个人信息", zap.Int64("user_id", userID))
userInfo, err := ctrl.authService.GetProfile(ctx, userID)
if err != nil {
handleAuthError(c, err, "获取个人信息失败")
return
}
utils.ResponseOK(c, userInfo)
}
// UpdateProfile 更新个人资料
// PUT /api/v1/auth/profile需认证
func (ctrl *AuthController) UpdateProfile(c *gin.Context) {
funcName := "controller.auth_controller.UpdateProfile"
ctx := c.Request.Context()
userID, ok := middleware.GetCurrentUserID(c)
if !ok {
utils.ResponseUnauthorized(c, "无法获取用户信息")
return
}
var req dto.UpdateProfileRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数校验失败: "+err.Error())
return
}
logs.Info(ctx, funcName, "更新个人资料", zap.Int64("user_id", userID))
userInfo, err := ctrl.authService.UpdateProfile(ctx, userID, &req)
if err != nil {
handleAuthError(c, err, "更新个人资料失败")
return
}
utils.ResponseOK(c, userInfo)
}
// ChangePassword 修改密码
// PUT /api/v1/auth/password需认证
func (ctrl *AuthController) ChangePassword(c *gin.Context) {
funcName := "controller.auth_controller.ChangePassword"
ctx := c.Request.Context()
userID, ok := middleware.GetCurrentUserID(c)
if !ok {
utils.ResponseUnauthorized(c, "无法获取用户信息")
return
}
var req dto.ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数校验失败: "+err.Error())
return
}
logs.Info(ctx, funcName, "修改密码", zap.Int64("user_id", userID))
if err := ctrl.authService.ChangePassword(ctx, userID, &req); err != nil {
handleAuthError(c, err, "修改密码失败")
return
}
utils.ResponseOK(c, nil)
}
// handleAuthError 统一认证业务错误映射
// 已知业务错误 → 返回 Service 层定义的具体提示(如"账号已被禁用"
// 未知错误 → 返回 fallbackMsg未传则默认"服务器内部错误"
func handleAuthError(c *gin.Context, err error, fallbackMsg ...string) {
switch err {
case service.ErrUserAlreadyExists:
utils.ResponseBadRequest(c, err.Error())
case service.ErrUserNotFound:
utils.ResponseNotFound(c, err.Error())
case service.ErrPasswordWrong:
utils.ResponseUnauthorized(c, "账号或密码错误")
case service.ErrUserDisabled:
utils.ResponseForbidden(c, err.Error())
case service.ErrUserDeleted:
utils.ResponseForbidden(c, err.Error())
case service.ErrNotAdmin:
utils.ResponseForbidden(c, err.Error())
case service.ErrRefreshTokenType:
utils.ResponseBadRequest(c, err.Error())
case service.ErrOAuthProviderUnsupported:
utils.ResponseBadRequest(c, err.Error())
case service.ErrOAuthProviderDisabled:
utils.ResponseBadRequest(c, err.Error())
case service.ErrOAuthStateInvalid:
utils.ResponseBadRequest(c, err.Error())
default:
msg := "服务器内部错误"
if len(fallbackMsg) > 0 && fallbackMsg[0] != "" {
msg = fallbackMsg[0]
}
utils.ResponseError(c, msg)
}
}