Files
doc_ai_backed/internal/storage/cache/user.go
yoge a29936f31c feat: add email verification code for registration and optimize email service
- Add POST /user/email/code endpoint to send 6-digit verification code
- Require email code verification before completing registration
- Add email code cache with 10min TTL and 5/day send rate limit
- Fix nil client guard, TLS conn leak, domain parsing, and Resend error body in email pkg
- Deploy via ssh inline command using current branch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 01:47:06 +08:00

117 lines
2.9 KiB
Go

package cache
import (
"context"
"errors"
"fmt"
"strconv"
"time"
"github.com/redis/go-redis/v9"
)
const (
UserSmsCodeTTL = 10 * time.Minute
UserSendSmsLimitTTL = 24 * time.Hour
UserSendSmsLimitCount = 5
)
const (
UserSmsCodePrefix = "user:sms_code:%s"
UserSendSmsLimit = "user:send_sms_limit:%s"
)
func GetUserSmsCode(ctx context.Context, phone string) (string, error) {
code, err := RedisClient.Get(ctx, fmt.Sprintf(UserSmsCodePrefix, phone)).Result()
if err != nil {
if err == redis.Nil {
return "", nil
}
return "", err
}
return code, nil
}
func SetUserSmsCode(ctx context.Context, phone, code string) error {
return RedisClient.Set(ctx, fmt.Sprintf(UserSmsCodePrefix, phone), code, UserSmsCodeTTL).Err()
}
func GetUserSendSmsLimit(ctx context.Context, phone string) (int, error) {
limit, err := RedisClient.Get(ctx, fmt.Sprintf(UserSendSmsLimit, phone)).Result()
if err != nil {
if err == redis.Nil {
return 0, nil
}
return 0, err
}
return strconv.Atoi(limit)
}
func SetUserSendSmsLimit(ctx context.Context, phone string) error {
count, err := RedisClient.Incr(ctx, fmt.Sprintf(UserSendSmsLimit, phone)).Result()
if err != nil {
return err
}
if count > UserSendSmsLimitCount {
return errors.New("send sms limit")
}
return RedisClient.Expire(ctx, fmt.Sprintf(UserSendSmsLimit, phone), UserSendSmsLimitTTL).Err()
}
func DeleteUserSmsCode(ctx context.Context, phone string) error {
return RedisClient.Del(ctx, fmt.Sprintf(UserSmsCodePrefix, phone)).Err()
}
const (
UserEmailCodeTTL = 10 * time.Minute
UserSendEmailLimitTTL = 24 * time.Hour
UserSendEmailLimitCount = 5
)
const (
UserEmailCodePrefix = "user:email_code:%s"
UserSendEmailLimit = "user:send_email_limit:%s"
)
func GetUserEmailCode(ctx context.Context, email string) (string, error) {
code, err := RedisClient.Get(ctx, fmt.Sprintf(UserEmailCodePrefix, email)).Result()
if err != nil {
if err == redis.Nil {
return "", nil
}
return "", err
}
return code, nil
}
func SetUserEmailCode(ctx context.Context, email, code string) error {
return RedisClient.Set(ctx, fmt.Sprintf(UserEmailCodePrefix, email), code, UserEmailCodeTTL).Err()
}
func DeleteUserEmailCode(ctx context.Context, email string) error {
return RedisClient.Del(ctx, fmt.Sprintf(UserEmailCodePrefix, email)).Err()
}
func GetUserSendEmailLimit(ctx context.Context, email string) (int, error) {
limit, err := RedisClient.Get(ctx, fmt.Sprintf(UserSendEmailLimit, email)).Result()
if err != nil {
if err == redis.Nil {
return 0, nil
}
return 0, err
}
return strconv.Atoi(limit)
}
func SetUserSendEmailLimit(ctx context.Context, email string) error {
key := fmt.Sprintf(UserSendEmailLimit, email)
count, err := RedisClient.Incr(ctx, key).Result()
if err != nil {
return err
}
if count > UserSendEmailLimitCount {
return errors.New("send email limit")
}
return RedisClient.Expire(ctx, key, UserSendEmailLimitTTL).Err()
}