Files
backend/internal/handler/user_handler.go

416 lines
11 KiB
Go
Raw Normal View History

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,
}))
}