Files
platform/web/services/verifier.go

125 lines
2.5 KiB
Go
Raw Normal View History

2025-03-18 17:57:07 +08:00
package services
import (
"context"
"errors"
"fmt"
"log/slog"
"math/rand"
"platform/init/rds"
"strconv"
"time"
"github.com/redis/go-redis/v9"
)
var Verifier = &verifierService{}
type verifierService struct {
}
type VerifierServiceError string
func (e VerifierServiceError) Error() string {
return string(e)
}
var (
ErrVerifierServiceInvalid = VerifierServiceError("验证码错误")
)
type VerifierServiceSendLimitErr int
func (e VerifierServiceSendLimitErr) Error() string {
return "发送频率过快"
}
type VerifierSmsPurpose int
const (
Login VerifierSmsPurpose = iota
)
func smsKey(phone string, purpose VerifierSmsPurpose) string {
return fmt.Sprintf("verify:sms:%d:%s", purpose, phone)
}
func (s *verifierService) SendSms(ctx context.Context, phone string, purpose VerifierSmsPurpose) error {
key := smsKey(phone, purpose)
keyLock := key + ":lock"
// 生成验证码
code := rand.Intn(900000) + 100000 // 6-digit code between 100000-999999
// 检查发送频率1 分钟内只能发送一次
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
result, err := tx.TTL(ctx, keyLock).Result()
if err != nil {
return err
}
if result > 0 {
return VerifierServiceSendLimitErr(result.Seconds())
}
if result != -2 {
return VerifierServiceError("验证码检查异常")
}
pipe := rds.Client.Pipeline()
pipe.Set(ctx, key, code, 10*time.Minute)
pipe.Set(ctx, keyLock, "", 1*time.Minute)
_, err = pipe.Exec(ctx)
if err != nil {
return err
}
return nil
}, keyLock)
if err != nil {
return err
}
// TODO: 发送短信验证码
slog.Debug("发送验证码", slog.String("phone", phone), slog.String("code", strconv.Itoa(code)))
return nil
}
func (s *verifierService) VerifySms(ctx context.Context, phone, code string) (bool, error) {
key := smsKey(phone, Login)
keyLock := key + ":lock"
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
// 检查验证码
val, err := rds.Client.Get(ctx, key).Result()
if err != nil && !errors.Is(err, redis.Nil) {
slog.Error("验证码获取失败", slog.Any("err", err))
return err
}
if val != code {
return ErrVerifierServiceInvalid
}
// 删除验证码
_, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Del(ctx, key)
pipe.Del(ctx, keyLock)
return nil
})
if err != nil {
slog.Error("验证码删除失败", slog.Any("err", err))
return err
}
return nil
}, key)
if err != nil {
if errors.Is(err, ErrVerifierServiceInvalid) {
return false, nil
}
return false, err
}
return true, nil
}