122 lines
2.5 KiB
Go
122 lines
2.5 KiB
Go
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 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 (
|
||
VerifierSmsPurposeLogin VerifierSmsPurpose = iota
|
||
)
|
||
|
||
type verifierService struct {
|
||
}
|
||
|
||
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) error {
|
||
key := smsKey(phone, VerifierSmsPurposeLogin)
|
||
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 {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func smsKey(phone string, purpose VerifierSmsPurpose) string {
|
||
return fmt.Sprintf("verify:sms:%d:%s", purpose, phone)
|
||
}
|