feat: 添加Yggdrasil密码重置功能,更新依赖和配置
This commit is contained in:
@@ -37,6 +37,9 @@ func RegisterRoutes(router *gin.Engine) {
|
||||
|
||||
// 更换邮箱
|
||||
userGroup.POST("/change-email", ChangeEmail)
|
||||
|
||||
// Yggdrasil密码相关
|
||||
userGroup.POST("/yggdrasil-password/reset", ResetYggdrasilPassword) // 重置Yggdrasil密码并返回新密码
|
||||
}
|
||||
|
||||
// 材质路由
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"carrotskin/internal/service"
|
||||
"carrotskin/internal/types"
|
||||
"carrotskin/pkg/config"
|
||||
"carrotskin/pkg/database"
|
||||
"carrotskin/pkg/logger"
|
||||
"carrotskin/pkg/redis"
|
||||
"carrotskin/pkg/storage"
|
||||
@@ -413,3 +414,49 @@ func ChangeEmail(c *gin.Context) {
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}))
|
||||
}
|
||||
|
||||
// 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()
|
||||
db := database.MustGetDB()
|
||||
|
||||
// 从上下文获取用户ID
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
|
||||
model.CodeUnauthorized,
|
||||
"未授权",
|
||||
nil,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
userId := userID.(int64)
|
||||
|
||||
// 重置Yggdrasil密码
|
||||
newPassword, err := service.ResetYggdrasilPassword(db, userId)
|
||||
if err != nil {
|
||||
loggerInstance.Error("[ERROR] 重置Yggdrasil密码失败", zap.Error(err), zap.Int64("userId", userId))
|
||||
c.JSON(http.StatusInternalServerError, model.NewErrorResponse(
|
||||
model.CodeServerError,
|
||||
"重置Yggdrasil密码失败",
|
||||
nil,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
loggerInstance.Info("[INFO] Yggdrasil密码重置成功", zap.Int64("userId", userId))
|
||||
c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{
|
||||
"password": newPassword,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2,22 +2,24 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// User 用户模型
|
||||
type User struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
Username string `gorm:"column:username;type:varchar(255);not null;uniqueIndex" json:"username"`
|
||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"-"` // 密码不返回给前端
|
||||
Email string `gorm:"column:email;type:varchar(255);not null;uniqueIndex" json:"email"`
|
||||
Avatar string `gorm:"column:avatar;type:varchar(255);not null;default:''" json:"avatar"`
|
||||
Points int `gorm:"column:points;type:integer;not null;default:0" json:"points"`
|
||||
Role string `gorm:"column:role;type:varchar(50);not null;default:'user'" json:"role"`
|
||||
Status int16 `gorm:"column:status;type:smallint;not null;default:1" json:"status"` // 1:正常, 0:禁用, -1:删除
|
||||
Properties string `gorm:"column:properties;type:jsonb" json:"properties"` // JSON字符串,存储为PostgreSQL的JSONB类型
|
||||
LastLoginAt *time.Time `gorm:"column:last_login_at;type:timestamp" json:"last_login_at,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
Username string `gorm:"column:username;type:varchar(255);not null;uniqueIndex" json:"username"`
|
||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"-"` // 密码不返回给前端
|
||||
Email string `gorm:"column:email;type:varchar(255);not null;uniqueIndex" json:"email"`
|
||||
Avatar string `gorm:"column:avatar;type:varchar(255);not null;default:''" json:"avatar"`
|
||||
Points int `gorm:"column:points;type:integer;not null;default:0" json:"points"`
|
||||
Role string `gorm:"column:role;type:varchar(50);not null;default:'user'" json:"role"`
|
||||
Status int16 `gorm:"column:status;type:smallint;not null;default:1" json:"status"` // 1:正常, 0:禁用, -1:删除
|
||||
Properties *datatypes.JSON `gorm:"column:properties;type:jsonb" json:"properties,omitempty"` // JSON数据,存储为PostgreSQL的JSONB类型
|
||||
LastLoginAt *time.Time `gorm:"column:last_login_at;type:timestamp" json:"last_login_at,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@@ -14,3 +14,9 @@ func GetYggdrasilPasswordById(Id int64) (string, error) {
|
||||
}
|
||||
return yggdrasil.Password, nil
|
||||
}
|
||||
|
||||
// ResetYggdrasilPassword 重置Yggdrasil密码
|
||||
func ResetYggdrasilPassword(userId int64, newPassword string) error {
|
||||
db := database.MustGetDB()
|
||||
return db.Model(&model.Yggdrasil{}).Where("id = ?", userId).Update("password", newPassword).Error
|
||||
}
|
||||
@@ -129,13 +129,19 @@ func GenerateCaptchaData(ctx context.Context, redisClient *redis.Client) (string
|
||||
redisDataJSON,
|
||||
expireTime,
|
||||
); err != nil {
|
||||
return "", "", "", 0, fmt.Errorf("存储验证码到Redis失败: %w", err)
|
||||
return "", "", "", 0, fmt.Errorf("存储验证码到redis失败: %w", err)
|
||||
}
|
||||
return mBase64, tBase64, captchaID, y - 10, nil
|
||||
}
|
||||
|
||||
// VerifyCaptchaData 验证用户验证码
|
||||
func VerifyCaptchaData(ctx context.Context, redisClient *redis.Client, dx int, id string) (bool, error) {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
redisKey := redisKeyPrefix + id
|
||||
|
||||
// 从Redis获取验证信息,使用注入的客户端
|
||||
@@ -144,11 +150,11 @@ func VerifyCaptchaData(ctx context.Context, redisClient *redis.Client, dx int, i
|
||||
if redisClient.Nil(err) { // 使用封装客户端的Nil错误
|
||||
return false, errors.New("验证码已过期或无效")
|
||||
}
|
||||
return false, fmt.Errorf("Redis查询失败: %w", err)
|
||||
return false, fmt.Errorf("redis查询失败: %w", err)
|
||||
}
|
||||
var redisData RedisData
|
||||
if err := json.Unmarshal([]byte(dataJSON), &redisData); err != nil {
|
||||
return false, fmt.Errorf("解析Redis数据失败: %w", err)
|
||||
return false, fmt.Errorf("解析redis数据失败: %w", err)
|
||||
}
|
||||
tx := redisData.Tx
|
||||
ty := redisData.Ty
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"carrotskin/internal/model"
|
||||
"carrotskin/pkg/redis"
|
||||
"encoding/base64"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ func TestSerializeUser_NilUser(t *testing.T) {
|
||||
func TestSerializeUser_ActualCall(t *testing.T) {
|
||||
logger := zaptest.NewLogger(t)
|
||||
user := &model.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
Properties: "{}",
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
// Properties 使用 datatypes.JSON,测试中可以为空
|
||||
}
|
||||
|
||||
result := SerializeUser(logger, user, "test-uuid-123")
|
||||
|
||||
@@ -50,6 +50,7 @@ func RegisterUser(jwtService *auth.JWTService, username, password, email, avatar
|
||||
Role: "user",
|
||||
Status: 1,
|
||||
Points: 0, // 初始积分可以从配置读取
|
||||
// Properties 字段使用 datatypes.JSON,默认为 nil,数据库会存储 NULL
|
||||
}
|
||||
|
||||
if err := repository.CreateUser(user); err != nil {
|
||||
|
||||
@@ -7,18 +7,19 @@ import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"carrotskin/pkg/config"
|
||||
"carrotskin/pkg/email"
|
||||
"carrotskin/pkg/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
// 验证码类型
|
||||
VerificationTypeRegister = "register"
|
||||
VerificationTypeRegister = "register"
|
||||
VerificationTypeResetPassword = "reset_password"
|
||||
VerificationTypeChangeEmail = "change_email"
|
||||
|
||||
|
||||
// 验证码配置
|
||||
CodeLength = 6 // 验证码长度
|
||||
CodeLength = 6 // 验证码长度
|
||||
CodeExpiration = 10 * time.Minute // 验证码有效期
|
||||
CodeRateLimit = 1 * time.Minute // 发送频率限制
|
||||
)
|
||||
@@ -39,6 +40,12 @@ func GenerateVerificationCode() (string, error) {
|
||||
|
||||
// SendVerificationCode 发送验证码
|
||||
func SendVerificationCode(ctx context.Context, redisClient *redis.Client, emailService *email.Service, email, codeType string) error {
|
||||
// 测试环境下直接跳过,不存储也不发送
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查发送频率限制
|
||||
rateLimitKey := fmt.Sprintf("verification:rate_limit:%s:%s", codeType, email)
|
||||
exists, err := redisClient.Exists(ctx, rateLimitKey)
|
||||
@@ -78,8 +85,14 @@ func SendVerificationCode(ctx context.Context, redisClient *redis.Client, emailS
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func VerifyCode(ctx context.Context, redisClient *redis.Client, email, code, codeType string) error {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
codeKey := fmt.Sprintf("verification:code:%s:%s", codeType, email)
|
||||
|
||||
|
||||
// 从Redis获取验证码
|
||||
storedCode, err := redisClient.Get(ctx, codeKey)
|
||||
if err != nil {
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -78,6 +79,33 @@ func GetPasswordByUserId(db *gorm.DB, userId int64) (string, error) {
|
||||
return passwordStore, nil
|
||||
}
|
||||
|
||||
// ResetYggdrasilPassword 重置并返回新的Yggdrasil密码
|
||||
func ResetYggdrasilPassword(db *gorm.DB, userId int64) (string, error) {
|
||||
// 生成新的16位随机密码
|
||||
newPassword := model.GenerateRandomPassword(16)
|
||||
|
||||
// 检查Yggdrasil记录是否存在
|
||||
_, err := repository.GetYggdrasilPasswordById(userId)
|
||||
if err != nil {
|
||||
// 如果不存在,创建新记录
|
||||
yggdrasil := model.Yggdrasil{
|
||||
ID: userId,
|
||||
Password: newPassword,
|
||||
}
|
||||
if err := db.Create(&yggdrasil).Error; err != nil {
|
||||
return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err)
|
||||
}
|
||||
return newPassword, nil
|
||||
}
|
||||
|
||||
// 如果存在,更新密码
|
||||
if err := repository.ResetYggdrasilPassword(userId, newPassword); err != nil {
|
||||
return "", fmt.Errorf("重置Yggdrasil密码失败: %w", err)
|
||||
}
|
||||
|
||||
return newPassword, nil
|
||||
}
|
||||
|
||||
// JoinServer 记录玩家加入服务器的会话信息
|
||||
func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serverId, accessToken, selectedProfile, ip string) error {
|
||||
// 输入验证
|
||||
|
||||
Reference in New Issue
Block a user