feat: 添加种子数据初始化功能,重构多个处理程序以简化错误响应和用户验证
This commit is contained in:
142
internal/service/security_service.go
Normal file
142
internal/service/security_service.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"carrotskin/pkg/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
// 登录失败限制配置
|
||||
MaxLoginAttempts = 5 // 最大登录失败次数
|
||||
LoginLockDuration = 15 * time.Minute // 账号锁定时间
|
||||
LoginAttemptWindow = 10 * time.Minute // 失败次数统计窗口
|
||||
|
||||
// 验证码错误限制配置
|
||||
MaxVerifyAttempts = 5 // 最大验证码错误次数
|
||||
VerifyLockDuration = 30 * time.Minute // 验证码锁定时间
|
||||
|
||||
// Redis Key 前缀
|
||||
LoginAttemptKeyPrefix = "security:login_attempt:"
|
||||
LoginLockedKeyPrefix = "security:login_locked:"
|
||||
VerifyAttemptKeyPrefix = "security:verify_attempt:"
|
||||
VerifyLockedKeyPrefix = "security:verify_locked:"
|
||||
)
|
||||
|
||||
// CheckLoginLocked 检查账号是否被锁定
|
||||
func CheckLoginLocked(ctx context.Context, redisClient *redis.Client, identifier string) (bool, time.Duration, error) {
|
||||
key := LoginLockedKeyPrefix + identifier
|
||||
ttl, err := redisClient.TTL(ctx, key)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
if ttl > 0 {
|
||||
return true, ttl, nil
|
||||
}
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// RecordLoginFailure 记录登录失败
|
||||
func RecordLoginFailure(ctx context.Context, redisClient *redis.Client, identifier string) (int, error) {
|
||||
attemptKey := LoginAttemptKeyPrefix + identifier
|
||||
|
||||
// 增加失败次数
|
||||
count, err := redisClient.Incr(ctx, attemptKey)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("记录登录失败次数失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置过期时间(仅在第一次设置)
|
||||
if count == 1 {
|
||||
if err := redisClient.Expire(ctx, attemptKey, LoginAttemptWindow); err != nil {
|
||||
return int(count), fmt.Errorf("设置过期时间失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果超过最大次数,锁定账号
|
||||
if count >= MaxLoginAttempts {
|
||||
lockedKey := LoginLockedKeyPrefix + identifier
|
||||
if err := redisClient.Set(ctx, lockedKey, "1", LoginLockDuration); err != nil {
|
||||
return int(count), fmt.Errorf("锁定账号失败: %w", err)
|
||||
}
|
||||
// 清除失败计数
|
||||
_ = redisClient.Del(ctx, attemptKey)
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// ClearLoginAttempts 清除登录失败记录(登录成功后调用)
|
||||
func ClearLoginAttempts(ctx context.Context, redisClient *redis.Client, identifier string) error {
|
||||
attemptKey := LoginAttemptKeyPrefix + identifier
|
||||
return redisClient.Del(ctx, attemptKey)
|
||||
}
|
||||
|
||||
// GetRemainingLoginAttempts 获取剩余登录尝试次数
|
||||
func GetRemainingLoginAttempts(ctx context.Context, redisClient *redis.Client, identifier string) (int, error) {
|
||||
attemptKey := LoginAttemptKeyPrefix + identifier
|
||||
countStr, err := redisClient.Get(ctx, attemptKey)
|
||||
if err != nil {
|
||||
// key 不存在,返回最大次数
|
||||
return MaxLoginAttempts, nil
|
||||
}
|
||||
|
||||
var count int
|
||||
fmt.Sscanf(countStr, "%d", &count)
|
||||
remaining := MaxLoginAttempts - count
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
return remaining, nil
|
||||
}
|
||||
|
||||
// CheckVerifyLocked 检查验证码是否被锁定
|
||||
func CheckVerifyLocked(ctx context.Context, redisClient *redis.Client, email, codeType string) (bool, time.Duration, error) {
|
||||
key := VerifyLockedKeyPrefix + codeType + ":" + email
|
||||
ttl, err := redisClient.TTL(ctx, key)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
if ttl > 0 {
|
||||
return true, ttl, nil
|
||||
}
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// RecordVerifyFailure 记录验证码验证失败
|
||||
func RecordVerifyFailure(ctx context.Context, redisClient *redis.Client, email, codeType string) (int, error) {
|
||||
attemptKey := VerifyAttemptKeyPrefix + codeType + ":" + email
|
||||
|
||||
// 增加失败次数
|
||||
count, err := redisClient.Incr(ctx, attemptKey)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("记录验证码失败次数失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置过期时间
|
||||
if count == 1 {
|
||||
if err := redisClient.Expire(ctx, attemptKey, VerifyLockDuration); err != nil {
|
||||
return int(count), err
|
||||
}
|
||||
}
|
||||
|
||||
// 如果超过最大次数,锁定验证
|
||||
if count >= MaxVerifyAttempts {
|
||||
lockedKey := VerifyLockedKeyPrefix + codeType + ":" + email
|
||||
if err := redisClient.Set(ctx, lockedKey, "1", VerifyLockDuration); err != nil {
|
||||
return int(count), err
|
||||
}
|
||||
_ = redisClient.Del(ctx, attemptKey)
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// ClearVerifyAttempts 清除验证码失败记录(验证成功后调用)
|
||||
func ClearVerifyAttempts(ctx context.Context, redisClient *redis.Client, email, codeType string) error {
|
||||
attemptKey := VerifyAttemptKeyPrefix + codeType + ":" + email
|
||||
return redisClient.Del(ctx, attemptKey)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user