Files
EchoChat/backend/go-service/app/auth/controller/auth_controller.go
bujinyuan d975adaf2a feat(auth): 完整认证系统 — 注册/登录/JWT/路由(Task 4 + Task 5)
Task 4 — 认证服务层:
- pkg/utils/password.go: bcrypt 密码加密与校验
- pkg/utils/jwt.go: Access/Refresh Token 生成与解析(jwt/v5@v5.2.1,兼容 Go 1.23)
- app/dto/auth_dto.go: 认证相关请求/响应 DTO(含参数校验 binding tag)
- app/auth/service/auth_service.go: 核心业务逻辑(注册、登录、管理员登录、Token 刷新、个人信息、改密码)
- pkg/middleware/auth.go: JWT 认证中间件 + 角色权限检查中间件

Task 5 — Controller 与路由注册:
- app/auth/controller/auth_controller.go: 前台认证接口(7 个 API)
- app/auth/controller/admin_auth_controller.go: 后台管理认证接口
- app/auth/router.go: auth 模块路由定义(模块自包含)
- router/router.go: 主路由汇总入口(仅做调度,不含具体路由定义)
- cmd/server/main.go: 路由注册迁移到 router.Setup()

已验证全部接口:注册 ✓ 登录 ✓ 获取 profile ✓ 错误密码 ✓ 未认证拒绝 ✓ 非管理员拒绝 ✓

Made-with: Cursor
2026-02-28 16:42:28 +08:00

218 lines
5.9 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 (
"github.com/echochat/backend/app/auth/service"
"github.com/echochat/backend/app/dto"
"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
}
// NewAuthController 创建前台认证控制器实例
func NewAuthController(authService *service.AuthService) *AuthController {
return &AuthController{authService: authService}
}
// 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.ResponseCreated(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())
if err != nil {
handleAuthError(c, err)
return
}
utils.ResponseOK(c, resp)
}
// Logout 用户登出
// POST /api/v1/auth/logout需认证
func (ctrl *AuthController) Logout(c *gin.Context) {
funcName := "controller.auth_controller.Logout"
ctx := c.Request.Context()
userID, _ := middleware.GetCurrentUserID(c)
logs.Info(ctx, funcName, "用户登出", zap.Int64("user_id", userID))
// 当前为无状态 JWT登出由客户端删除 Token 实现
// 后续可扩展:将 Token 加入 Redis 黑名单
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)
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 统一处理认证相关业务错误,映射到对应的 HTTP 状态码
func handleAuthError(c *gin.Context, err error) {
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())
default:
utils.ResponseError(c, "服务器内部错误")
}
}