Files
backend/internal/handler/user_handler.go
lan 4b4980820f
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Test / test (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / build (push) Has been cancelled
chore: 初始化仓库,排除二进制文件和覆盖率文件
2025-11-28 23:30:49 +08:00

416 lines
11 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 handler
import (
"carrotskin/internal/model"
"carrotskin/internal/service"
"carrotskin/internal/types"
"carrotskin/pkg/config"
"carrotskin/pkg/logger"
"carrotskin/pkg/redis"
"carrotskin/pkg/storage"
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// GetUserProfile 获取用户信息
// @Summary 获取用户信息
// @Description 获取当前登录用户的详细信息
// @Tags user
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} model.Response "获取成功"
// @Failure 401 {object} model.ErrorResponse "未授权"
// @Router /api/v1/user/profile [get]
func GetUserProfile(c *gin.Context) {
loggerInstance := logger.MustGetLogger()
// 从上下文获取用户ID (由JWT中间件设置)
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
model.CodeUnauthorized,
model.MsgUnauthorized,
nil,
))
return
}
// 获取用户信息
user, err := service.GetUserByID(userID.(int64))
if err != nil || user == nil {
loggerInstance.Error("获取用户信息失败",
zap.Int64("user_id", userID.(int64)),
zap.Error(err),
)
c.JSON(http.StatusNotFound, model.NewErrorResponse(
model.CodeNotFound,
"用户不存在",
err,
))
return
}
// 返回用户信息
c.JSON(http.StatusOK, model.NewSuccessResponse(&types.UserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Avatar: user.Avatar,
Points: user.Points,
Role: user.Role,
Status: user.Status,
LastLoginAt: user.LastLoginAt,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}))
}
// UpdateUserProfile 更新用户信息
// @Summary 更新用户信息
// @Description 更新当前登录用户的头像和密码(修改邮箱请使用 /change-email 接口)
// @Tags user
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body types.UpdateUserRequest true "更新信息修改密码时需同时提供old_password和new_password"
// @Success 200 {object} model.Response{data=types.UserInfo} "更新成功"
// @Failure 400 {object} model.ErrorResponse "请求参数错误"
// @Failure 401 {object} model.ErrorResponse "未授权"
// @Failure 404 {object} model.ErrorResponse "用户不存在"
// @Failure 500 {object} model.ErrorResponse "服务器错误"
// @Router /api/v1/user/profile [put]
func UpdateUserProfile(c *gin.Context) {
loggerInstance := logger.MustGetLogger()
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
model.CodeUnauthorized,
model.MsgUnauthorized,
nil,
))
return
}
var req types.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
"请求参数错误",
err,
))
return
}
// 获取用户
user, err := service.GetUserByID(userID.(int64))
if err != nil || user == nil {
c.JSON(http.StatusNotFound, model.NewErrorResponse(
model.CodeNotFound,
"用户不存在",
err,
))
return
}
// 处理密码修改
if req.NewPassword != "" {
// 如果提供了新密码,必须同时提供旧密码
if req.OldPassword == "" {
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
"修改密码需要提供原密码",
nil,
))
return
}
// 调用修改密码服务
if err := service.ChangeUserPassword(userID.(int64), req.OldPassword, req.NewPassword); err != nil {
loggerInstance.Error("修改密码失败",
zap.Int64("user_id", userID.(int64)),
zap.Error(err),
)
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
err.Error(),
nil,
))
return
}
loggerInstance.Info("用户修改密码成功",
zap.Int64("user_id", userID.(int64)),
)
}
// 更新头像
if req.Avatar != "" {
user.Avatar = req.Avatar
}
// 保存更新(仅当有头像修改时)
if req.Avatar != "" {
if err := service.UpdateUserInfo(user); err != nil {
loggerInstance.Error("更新用户信息失败",
zap.Int64("user_id", user.ID),
zap.Error(err),
)
c.JSON(http.StatusInternalServerError, model.NewErrorResponse(
model.CodeServerError,
"更新失败",
err,
))
return
}
}
// 重新获取更新后的用户信息
updatedUser, err := service.GetUserByID(userID.(int64))
if err != nil || updatedUser == nil {
c.JSON(http.StatusNotFound, model.NewErrorResponse(
model.CodeNotFound,
"用户不存在",
err,
))
return
}
// 返回更新后的用户信息
c.JSON(http.StatusOK, model.NewSuccessResponse(&types.UserInfo{
ID: updatedUser.ID,
Username: updatedUser.Username,
Email: updatedUser.Email,
Avatar: updatedUser.Avatar,
Points: updatedUser.Points,
Role: updatedUser.Role,
Status: updatedUser.Status,
LastLoginAt: updatedUser.LastLoginAt,
CreatedAt: updatedUser.CreatedAt,
UpdatedAt: updatedUser.UpdatedAt,
}))
}
// GenerateAvatarUploadURL 生成头像上传URL
// @Summary 生成头像上传URL
// @Description 生成预签名URL用于上传用户头像
// @Tags user
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body types.GenerateAvatarUploadURLRequest true "文件名"
// @Success 200 {object} model.Response "生成成功"
// @Failure 400 {object} model.ErrorResponse "请求参数错误"
// @Router /api/v1/user/avatar/upload-url [post]
func GenerateAvatarUploadURL(c *gin.Context) {
loggerInstance := logger.MustGetLogger()
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
model.CodeUnauthorized,
model.MsgUnauthorized,
nil,
))
return
}
var req types.GenerateAvatarUploadURLRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
"请求参数错误",
err,
))
return
}
// 调用UploadService生成预签名URL
storageClient := storage.MustGetClient()
cfg := *config.MustGetRustFSConfig()
result, err := service.GenerateAvatarUploadURL(c.Request.Context(), storageClient, cfg, userID.(int64), req.FileName)
if err != nil {
loggerInstance.Error("生成头像上传URL失败",
zap.Int64("user_id", userID.(int64)),
zap.String("file_name", req.FileName),
zap.Error(err),
)
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
err.Error(),
nil,
))
return
}
// 返回响应
c.JSON(http.StatusOK, model.NewSuccessResponse(&types.GenerateAvatarUploadURLResponse{
PostURL: result.PostURL,
FormData: result.FormData,
AvatarURL: result.FileURL,
ExpiresIn: 900, // 15分钟 = 900秒
}))
}
// UpdateAvatar 更新头像URL
// @Summary 更新头像URL
// @Description 上传完成后更新用户的头像URL到数据库
// @Tags user
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param avatar_url query string true "头像URL"
// @Success 200 {object} model.Response "更新成功"
// @Failure 400 {object} model.ErrorResponse "请求参数错误"
// @Router /api/v1/user/avatar [put]
func UpdateAvatar(c *gin.Context) {
loggerInstance := logger.MustGetLogger()
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
model.CodeUnauthorized,
model.MsgUnauthorized,
nil,
))
return
}
avatarURL := c.Query("avatar_url")
if avatarURL == "" {
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
"头像URL不能为空",
nil,
))
return
}
// 更新头像
if err := service.UpdateUserAvatar(userID.(int64), avatarURL); err != nil {
loggerInstance.Error("更新头像失败",
zap.Int64("user_id", userID.(int64)),
zap.String("avatar_url", avatarURL),
zap.Error(err),
)
c.JSON(http.StatusInternalServerError, model.NewErrorResponse(
model.CodeServerError,
"更新头像失败",
err,
))
return
}
// 获取更新后的用户信息
user, err := service.GetUserByID(userID.(int64))
if err != nil || user == nil {
c.JSON(http.StatusNotFound, model.NewErrorResponse(
model.CodeNotFound,
"用户不存在",
err,
))
return
}
// 返回更新后的用户信息
c.JSON(http.StatusOK, model.NewSuccessResponse(&types.UserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Avatar: user.Avatar,
Points: user.Points,
Role: user.Role,
Status: user.Status,
LastLoginAt: user.LastLoginAt,
CreatedAt: user.CreatedAt,
}))
}
// ChangeEmail 更换邮箱
// @Summary 更换邮箱
// @Description 通过验证码更换用户邮箱
// @Tags user
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body types.ChangeEmailRequest true "更换邮箱请求"
// @Success 200 {object} model.Response{data=types.UserInfo} "更换成功"
// @Failure 400 {object} model.ErrorResponse "请求参数错误"
// @Failure 401 {object} model.ErrorResponse "未授权"
// @Router /api/v1/user/change-email [post]
func ChangeEmail(c *gin.Context) {
loggerInstance := logger.MustGetLogger()
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
model.CodeUnauthorized,
model.MsgUnauthorized,
nil,
))
return
}
var req types.ChangeEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
"请求参数错误",
err,
))
return
}
// 验证验证码
redisClient := redis.MustGetClient()
if err := service.VerifyCode(c.Request.Context(), redisClient, req.NewEmail, req.VerificationCode, service.VerificationTypeChangeEmail); err != nil {
loggerInstance.Warn("验证码验证失败",
zap.String("new_email", req.NewEmail),
zap.Error(err),
)
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
err.Error(),
nil,
))
return
}
// 更换邮箱
if err := service.ChangeUserEmail(userID.(int64), req.NewEmail); err != nil {
loggerInstance.Error("更换邮箱失败",
zap.Int64("user_id", userID.(int64)),
zap.String("new_email", req.NewEmail),
zap.Error(err),
)
c.JSON(http.StatusBadRequest, model.NewErrorResponse(
model.CodeBadRequest,
err.Error(),
nil,
))
return
}
// 获取更新后的用户信息
user, err := service.GetUserByID(userID.(int64))
if err != nil || user == nil {
c.JSON(http.StatusNotFound, model.NewErrorResponse(
model.CodeNotFound,
"用户不存在",
err,
))
return
}
c.JSON(http.StatusOK, model.NewSuccessResponse(&types.UserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Avatar: user.Avatar,
Points: user.Points,
Role: user.Role,
Status: user.Status,
LastLoginAt: user.LastLoginAt,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}))
}