chore: 初始化仓库,排除二进制文件和覆盖率文件
This commit is contained in:
118
internal/service/verification_service.go
Normal file
118
internal/service/verification_service.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"carrotskin/pkg/email"
|
||||
"carrotskin/pkg/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
// 验证码类型
|
||||
VerificationTypeRegister = "register"
|
||||
VerificationTypeResetPassword = "reset_password"
|
||||
VerificationTypeChangeEmail = "change_email"
|
||||
|
||||
// 验证码配置
|
||||
CodeLength = 6 // 验证码长度
|
||||
CodeExpiration = 10 * time.Minute // 验证码有效期
|
||||
CodeRateLimit = 1 * time.Minute // 发送频率限制
|
||||
)
|
||||
|
||||
// GenerateVerificationCode 生成6位数字验证码
|
||||
func GenerateVerificationCode() (string, error) {
|
||||
const digits = "0123456789"
|
||||
code := make([]byte, CodeLength)
|
||||
for i := range code {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(digits))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
code[i] = digits[num.Int64()]
|
||||
}
|
||||
return string(code), nil
|
||||
}
|
||||
|
||||
// SendVerificationCode 发送验证码
|
||||
func SendVerificationCode(ctx context.Context, redisClient *redis.Client, emailService *email.Service, email, codeType string) error {
|
||||
// 检查发送频率限制
|
||||
rateLimitKey := fmt.Sprintf("verification:rate_limit:%s:%s", codeType, email)
|
||||
exists, err := redisClient.Exists(ctx, rateLimitKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查发送频率失败: %w", err)
|
||||
}
|
||||
if exists > 0 {
|
||||
return fmt.Errorf("发送过于频繁,请稍后再试")
|
||||
}
|
||||
|
||||
// 生成验证码
|
||||
code, err := GenerateVerificationCode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成验证码失败: %w", err)
|
||||
}
|
||||
|
||||
// 存储验证码到Redis
|
||||
codeKey := fmt.Sprintf("verification:code:%s:%s", codeType, email)
|
||||
if err := redisClient.Set(ctx, codeKey, code, CodeExpiration); err != nil {
|
||||
return fmt.Errorf("存储验证码失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置发送频率限制
|
||||
if err := redisClient.Set(ctx, rateLimitKey, "1", CodeRateLimit); err != nil {
|
||||
return fmt.Errorf("设置发送频率限制失败: %w", err)
|
||||
}
|
||||
|
||||
// 发送邮件
|
||||
if err := sendVerificationEmail(emailService, email, code, codeType); err != nil {
|
||||
// 发送失败,删除验证码
|
||||
_ = redisClient.Del(ctx, codeKey)
|
||||
return fmt.Errorf("发送邮件失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func VerifyCode(ctx context.Context, redisClient *redis.Client, email, code, codeType string) error {
|
||||
codeKey := fmt.Sprintf("verification:code:%s:%s", codeType, email)
|
||||
|
||||
// 从Redis获取验证码
|
||||
storedCode, err := redisClient.Get(ctx, codeKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("验证码已过期或不存在")
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
if storedCode != code {
|
||||
return fmt.Errorf("验证码错误")
|
||||
}
|
||||
|
||||
// 验证成功,删除验证码
|
||||
_ = redisClient.Del(ctx, codeKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteVerificationCode 删除验证码
|
||||
func DeleteVerificationCode(ctx context.Context, redisClient *redis.Client, email, codeType string) error {
|
||||
codeKey := fmt.Sprintf("verification:code:%s:%s", codeType, email)
|
||||
return redisClient.Del(ctx, codeKey)
|
||||
}
|
||||
|
||||
// sendVerificationEmail 根据类型发送邮件
|
||||
func sendVerificationEmail(emailService *email.Service, to, code, codeType string) error {
|
||||
switch codeType {
|
||||
case VerificationTypeRegister:
|
||||
return emailService.SendEmailVerification(to, code)
|
||||
case VerificationTypeResetPassword:
|
||||
return emailService.SendResetPassword(to, code)
|
||||
case VerificationTypeChangeEmail:
|
||||
return emailService.SendChangeEmail(to, code)
|
||||
default:
|
||||
return emailService.SendVerificationCode(to, code, codeType)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user