2025-11-28 23:30:49 +08:00
|
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"carrotskin/internal/service"
|
|
|
|
|
|
"carrotskin/internal/types"
|
2025-11-30 18:56:56 +08:00
|
|
|
|
"carrotskin/pkg/database"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"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) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
user, err := service.GetUserByID(userID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil || user == nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
logger.MustGetLogger().Error("获取用户信息失败",
|
|
|
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondNotFound(c, "用户不存在")
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondSuccess(c, UserToUserInfo(user))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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()
|
2025-12-02 10:33:19 +08:00
|
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var req types.UpdateUserRequest
|
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, "请求参数错误", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
user, err := service.GetUserByID(userID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil || user == nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondNotFound(c, "用户不存在")
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理密码修改
|
|
|
|
|
|
if req.NewPassword != "" {
|
|
|
|
|
|
if req.OldPassword == "" {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, "修改密码需要提供原密码", nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
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)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
loggerInstance.Info("用户修改密码成功", zap.Int64("user_id", userID))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新头像
|
|
|
|
|
|
if req.Avatar != "" {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 验证头像 URL 是否来自允许的域名
|
|
|
|
|
|
if err := service.ValidateAvatarURL(req.Avatar); err != nil {
|
|
|
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-28 23:30:49 +08:00
|
|
|
|
user.Avatar = req.Avatar
|
|
|
|
|
|
if err := service.UpdateUserInfo(user); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
loggerInstance.Error("更新用户信息失败", zap.Int64("user_id", user.ID), zap.Error(err))
|
|
|
|
|
|
RespondServerError(c, "更新失败", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新获取更新后的用户信息
|
2025-12-02 10:33:19 +08:00
|
|
|
|
updatedUser, err := service.GetUserByID(userID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil || updatedUser == nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondNotFound(c, "用户不存在")
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondSuccess(c, UserToUserInfo(updatedUser))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var req types.GenerateAvatarUploadURLRequest
|
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, "请求参数错误", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
storageClient := storage.MustGetClient()
|
2025-12-02 11:22:14 +08:00
|
|
|
|
result, err := service.GenerateAvatarUploadURL(c.Request.Context(), storageClient, userID, req.FileName)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
logger.MustGetLogger().Error("生成头像上传URL失败",
|
|
|
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
|
zap.String("file_name", req.FileName),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondSuccess(c, &types.GenerateAvatarUploadURLResponse{
|
2025-11-28 23:30:49 +08:00
|
|
|
|
PostURL: result.PostURL,
|
|
|
|
|
|
FormData: result.FormData,
|
|
|
|
|
|
AvatarURL: result.FileURL,
|
2025-12-02 10:33:19 +08:00
|
|
|
|
ExpiresIn: 900,
|
|
|
|
|
|
})
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
avatarURL := c.Query("avatar_url")
|
|
|
|
|
|
if avatarURL == "" {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, "头像URL不能为空", nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
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),
|
2025-11-28 23:30:49 +08:00
|
|
|
|
zap.String("avatar_url", avatarURL),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondServerError(c, "更新头像失败", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
user, err := service.GetUserByID(userID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil || user == nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondNotFound(c, "用户不存在")
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondSuccess(c, UserToUserInfo(user))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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()
|
2025-12-02 10:33:19 +08:00
|
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var req types.ChangeEmailRequest
|
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, "请求参数错误", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
redisClient := redis.MustGetClient()
|
|
|
|
|
|
if err := service.VerifyCode(c.Request.Context(), redisClient, req.NewEmail, req.VerificationCode, service.VerificationTypeChangeEmail); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
loggerInstance.Warn("验证码验证失败", zap.String("new_email", req.NewEmail), zap.Error(err))
|
|
|
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
if err := service.ChangeUserEmail(userID, req.NewEmail); err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
loggerInstance.Error("更换邮箱失败",
|
2025-12-02 10:33:19 +08:00
|
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
|
zap.String("new_email", req.NewEmail),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
user, err := service.GetUserByID(userID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil || user == nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondNotFound(c, "用户不存在")
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
RespondSuccess(c, UserToUserInfo(user))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
2025-11-30 18:56:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 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()
|
2025-12-02 10:33:19 +08:00
|
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
|
if !ok {
|
2025-11-30 18:56:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
db := database.MustGetDB()
|
|
|
|
|
|
newPassword, err := service.ResetYggdrasilPassword(db, userID)
|
2025-11-30 18:56:56 +08:00
|
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
loggerInstance.Error("重置Yggdrasil密码失败", zap.Error(err), zap.Int64("userId", userID))
|
|
|
|
|
|
RespondServerError(c, "重置Yggdrasil密码失败", nil)
|
2025-11-30 18:56:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
loggerInstance.Info("Yggdrasil密码重置成功", zap.Int64("userId", userID))
|
|
|
|
|
|
RespondSuccess(c, gin.H{"password": newPassword})
|
2025-11-30 18:56:56 +08:00
|
|
|
|
}
|