Files
backend/internal/handler/user_handler.go

288 lines
8.2 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/service"
"carrotskin/internal/types"
"carrotskin/pkg/database"
"carrotskin/pkg/logger"
"carrotskin/pkg/redis"
"carrotskin/pkg/storage"
"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) {
userID, ok := GetUserIDFromContext(c)
if !ok {
return
}
user, err := service.GetUserByID(userID)
if err != nil || user == nil {
logger.MustGetLogger().Error("获取用户信息失败",
zap.Int64("user_id", userID),
zap.Error(err),
)
RespondNotFound(c, "用户不存在")
return
}
RespondSuccess(c, UserToUserInfo(user))
}
// 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, ok := GetUserIDFromContext(c)
if !ok {
return
}
var req types.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondBadRequest(c, "请求参数错误", err)
return
}
user, err := service.GetUserByID(userID)
if err != nil || user == nil {
RespondNotFound(c, "用户不存在")
return
}
// 处理密码修改
if req.NewPassword != "" {
if req.OldPassword == "" {
RespondBadRequest(c, "修改密码需要提供原密码", nil)
return
}
if err := service.ChangeUserPassword(userID, req.OldPassword, req.NewPassword); err != nil {
loggerInstance.Error("修改密码失败", zap.Int64("user_id", userID), zap.Error(err))
RespondBadRequest(c, err.Error(), nil)
return
}
loggerInstance.Info("用户修改密码成功", zap.Int64("user_id", userID))
}
// 更新头像
if req.Avatar != "" {
// 验证头像 URL 是否来自允许的域名
if err := service.ValidateAvatarURL(req.Avatar); err != nil {
RespondBadRequest(c, err.Error(), nil)
return
}
user.Avatar = req.Avatar
if err := service.UpdateUserInfo(user); err != nil {
loggerInstance.Error("更新用户信息失败", zap.Int64("user_id", user.ID), zap.Error(err))
RespondServerError(c, "更新失败", err)
return
}
}
// 重新获取更新后的用户信息
updatedUser, err := service.GetUserByID(userID)
if err != nil || updatedUser == nil {
RespondNotFound(c, "用户不存在")
return
}
RespondSuccess(c, UserToUserInfo(updatedUser))
}
// 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) {
userID, ok := GetUserIDFromContext(c)
if !ok {
return
}
var req types.GenerateAvatarUploadURLRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondBadRequest(c, "请求参数错误", err)
return
}
storageClient := storage.MustGetClient()
result, err := service.GenerateAvatarUploadURL(c.Request.Context(), storageClient, userID, req.FileName)
if err != nil {
logger.MustGetLogger().Error("生成头像上传URL失败",
zap.Int64("user_id", userID),
zap.String("file_name", req.FileName),
zap.Error(err),
)
RespondBadRequest(c, err.Error(), nil)
return
}
RespondSuccess(c, &types.GenerateAvatarUploadURLResponse{
PostURL: result.PostURL,
FormData: result.FormData,
AvatarURL: result.FileURL,
ExpiresIn: 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) {
userID, ok := GetUserIDFromContext(c)
if !ok {
return
}
avatarURL := c.Query("avatar_url")
if avatarURL == "" {
RespondBadRequest(c, "头像URL不能为空", nil)
return
}
if err := service.ValidateAvatarURL(avatarURL); err != nil {
RespondBadRequest(c, err.Error(), nil)
return
}
if err := service.UpdateUserAvatar(userID, avatarURL); err != nil {
logger.MustGetLogger().Error("更新头像失败",
zap.Int64("user_id", userID),
zap.String("avatar_url", avatarURL),
zap.Error(err),
)
RespondServerError(c, "更新头像失败", err)
return
}
user, err := service.GetUserByID(userID)
if err != nil || user == nil {
RespondNotFound(c, "用户不存在")
return
}
RespondSuccess(c, UserToUserInfo(user))
}
// 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, ok := GetUserIDFromContext(c)
if !ok {
return
}
var req types.ChangeEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondBadRequest(c, "请求参数错误", 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))
RespondBadRequest(c, err.Error(), nil)
return
}
if err := service.ChangeUserEmail(userID, req.NewEmail); err != nil {
loggerInstance.Error("更换邮箱失败",
zap.Int64("user_id", userID),
zap.String("new_email", req.NewEmail),
zap.Error(err),
)
RespondBadRequest(c, err.Error(), nil)
return
}
user, err := service.GetUserByID(userID)
if err != nil || user == nil {
RespondNotFound(c, "用户不存在")
return
}
RespondSuccess(c, UserToUserInfo(user))
}
// ResetYggdrasilPassword 重置Yggdrasil密码
// @Summary 重置Yggdrasil密码
// @Description 重置当前用户的Yggdrasil密码并返回新密码
// @Tags user
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} model.Response "重置成功"
// @Failure 401 {object} model.ErrorResponse "未授权"
// @Failure 500 {object} model.ErrorResponse "服务器错误"
// @Router /api/v1/user/yggdrasil-password/reset [post]
func ResetYggdrasilPassword(c *gin.Context) {
loggerInstance := logger.MustGetLogger()
userID, ok := GetUserIDFromContext(c)
if !ok {
return
}
db := database.MustGetDB()
newPassword, err := service.ResetYggdrasilPassword(db, userID)
if err != nil {
loggerInstance.Error("重置Yggdrasil密码失败", zap.Error(err), zap.Int64("userId", userID))
RespondServerError(c, "重置Yggdrasil密码失败", nil)
return
}
loggerInstance.Info("Yggdrasil密码重置成功", zap.Int64("userId", userID))
RespondSuccess(c, gin.H{"password": newPassword})
}