认证授权主要流程实现
This commit is contained in:
124
web/services/verifier.go
Normal file
124
web/services/verifier.go
Normal file
@@ -0,0 +1,124 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user