feat: Enhance dependency injection and service integration

- Updated main.go to initialize email service and include it in the dependency injection container.
- Refactored handlers to utilize context in service method calls, improving consistency and error handling.
- Introduced new service options for upload, security, and captcha services, enhancing modularity and testability.
- Removed unused repository implementations to streamline the codebase.

This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
This commit is contained in:
lan
2025-12-02 22:52:33 +08:00
parent 792e96b238
commit 034e02e93a
54 changed files with 2305 additions and 2708 deletions

View File

@@ -10,13 +10,13 @@ import (
const (
// 登录失败限制配置
MaxLoginAttempts = 5 // 最大登录失败次数
LoginLockDuration = 15 * time.Minute // 账号锁定时间
LoginAttemptWindow = 10 * time.Minute // 失败次数统计窗口
MaxLoginAttempts = 5 // 最大登录失败次数
LoginLockDuration = 15 * time.Minute // 账号锁定时间
LoginAttemptWindow = 10 * time.Minute // 失败次数统计窗口
// 验证码错误限制配置
MaxVerifyAttempts = 5 // 最大验证码错误次数
VerifyLockDuration = 30 * time.Minute // 验证码锁定时间
MaxVerifyAttempts = 5 // 最大验证码错误次数
VerifyLockDuration = 30 * time.Minute // 验证码锁定时间
// Redis Key 前缀
LoginAttemptKeyPrefix = "security:login_attempt:"
@@ -25,10 +25,22 @@ const (
VerifyLockedKeyPrefix = "security:verify_locked:"
)
// securityService SecurityService的实现
type securityService struct {
redis *redis.Client
}
// NewSecurityService 创建SecurityService实例
func NewSecurityService(redisClient *redis.Client) SecurityService {
return &securityService{
redis: redisClient,
}
}
// CheckLoginLocked 检查账号是否被锁定
func CheckLoginLocked(ctx context.Context, redisClient *redis.Client, identifier string) (bool, time.Duration, error) {
func (s *securityService) CheckLoginLocked(ctx context.Context, identifier string) (bool, time.Duration, error) {
key := LoginLockedKeyPrefix + identifier
ttl, err := redisClient.TTL(ctx, key)
ttl, err := s.redis.TTL(ctx, key)
if err != nil {
return false, 0, err
}
@@ -39,50 +51,50 @@ func CheckLoginLocked(ctx context.Context, redisClient *redis.Client, identifier
}
// RecordLoginFailure 记录登录失败
func RecordLoginFailure(ctx context.Context, redisClient *redis.Client, identifier string) (int, error) {
func (s *securityService) RecordLoginFailure(ctx context.Context, identifier string) (int, error) {
attemptKey := LoginAttemptKeyPrefix + identifier
// 增加失败次数
count, err := redisClient.Incr(ctx, attemptKey)
count, err := s.redis.Incr(ctx, attemptKey)
if err != nil {
return 0, fmt.Errorf("记录登录失败次数失败: %w", err)
}
// 设置过期时间(仅在第一次设置)
if count == 1 {
if err := redisClient.Expire(ctx, attemptKey, LoginAttemptWindow); err != nil {
if err := s.redis.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 {
if err := s.redis.Set(ctx, lockedKey, "1", LoginLockDuration); err != nil {
return int(count), fmt.Errorf("锁定账号失败: %w", err)
}
// 清除失败计数
_ = redisClient.Del(ctx, attemptKey)
_ = s.redis.Del(ctx, attemptKey)
}
return int(count), nil
}
// ClearLoginAttempts 清除登录失败记录(登录成功后调用)
func ClearLoginAttempts(ctx context.Context, redisClient *redis.Client, identifier string) error {
func (s *securityService) ClearLoginAttempts(ctx context.Context, identifier string) error {
attemptKey := LoginAttemptKeyPrefix + identifier
return redisClient.Del(ctx, attemptKey)
return s.redis.Del(ctx, attemptKey)
}
// GetRemainingLoginAttempts 获取剩余登录尝试次数
func GetRemainingLoginAttempts(ctx context.Context, redisClient *redis.Client, identifier string) (int, error) {
func (s *securityService) GetRemainingLoginAttempts(ctx context.Context, identifier string) (int, error) {
attemptKey := LoginAttemptKeyPrefix + identifier
countStr, err := redisClient.Get(ctx, attemptKey)
countStr, err := s.redis.Get(ctx, attemptKey)
if err != nil {
// key 不存在,返回最大次数
return MaxLoginAttempts, nil
}
var count int
fmt.Sscanf(countStr, "%d", &count)
remaining := MaxLoginAttempts - count
@@ -93,9 +105,9 @@ func GetRemainingLoginAttempts(ctx context.Context, redisClient *redis.Client, i
}
// CheckVerifyLocked 检查验证码是否被锁定
func CheckVerifyLocked(ctx context.Context, redisClient *redis.Client, email, codeType string) (bool, time.Duration, error) {
func (s *securityService) CheckVerifyLocked(ctx context.Context, email, codeType string) (bool, time.Duration, error) {
key := VerifyLockedKeyPrefix + codeType + ":" + email
ttl, err := redisClient.TTL(ctx, key)
ttl, err := s.redis.TTL(ctx, key)
if err != nil {
return false, 0, err
}
@@ -106,37 +118,67 @@ func CheckVerifyLocked(ctx context.Context, redisClient *redis.Client, email, co
}
// RecordVerifyFailure 记录验证码验证失败
func RecordVerifyFailure(ctx context.Context, redisClient *redis.Client, email, codeType string) (int, error) {
func (s *securityService) RecordVerifyFailure(ctx context.Context, email, codeType string) (int, error) {
attemptKey := VerifyAttemptKeyPrefix + codeType + ":" + email
// 增加失败次数
count, err := redisClient.Incr(ctx, attemptKey)
count, err := s.redis.Incr(ctx, attemptKey)
if err != nil {
return 0, fmt.Errorf("记录验证码失败次数失败: %w", err)
}
// 设置过期时间
if count == 1 {
if err := redisClient.Expire(ctx, attemptKey, VerifyLockDuration); err != nil {
if err := s.redis.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 {
if err := s.redis.Set(ctx, lockedKey, "1", VerifyLockDuration); err != nil {
return int(count), err
}
_ = redisClient.Del(ctx, attemptKey)
_ = s.redis.Del(ctx, attemptKey)
}
return int(count), nil
}
// ClearVerifyAttempts 清除验证码失败记录(验证成功后调用)
func ClearVerifyAttempts(ctx context.Context, redisClient *redis.Client, email, codeType string) error {
func (s *securityService) ClearVerifyAttempts(ctx context.Context, email, codeType string) error {
attemptKey := VerifyAttemptKeyPrefix + codeType + ":" + email
return redisClient.Del(ctx, attemptKey)
return s.redis.Del(ctx, attemptKey)
}
// 全局函数,保持向后兼容,用于已存在的代码
func CheckLoginLocked(ctx context.Context, redisClient *redis.Client, identifier string) (bool, time.Duration, error) {
svc := NewSecurityService(redisClient)
return svc.CheckLoginLocked(ctx, identifier)
}
func RecordLoginFailure(ctx context.Context, redisClient *redis.Client, identifier string) (int, error) {
svc := NewSecurityService(redisClient)
return svc.RecordLoginFailure(ctx, identifier)
}
func ClearLoginAttempts(ctx context.Context, redisClient *redis.Client, identifier string) error {
svc := NewSecurityService(redisClient)
return svc.ClearLoginAttempts(ctx, identifier)
}
func CheckVerifyLocked(ctx context.Context, redisClient *redis.Client, email, codeType string) (bool, time.Duration, error) {
svc := NewSecurityService(redisClient)
return svc.CheckVerifyLocked(ctx, email, codeType)
}
func RecordVerifyFailure(ctx context.Context, redisClient *redis.Client, email, codeType string) (int, error) {
svc := NewSecurityService(redisClient)
return svc.RecordVerifyFailure(ctx, email, codeType)
}
func ClearVerifyAttempts(ctx context.Context, redisClient *redis.Client, email, codeType string) error {
svc := NewSecurityService(redisClient)
return svc.ClearVerifyAttempts(ctx, email, codeType)
}