package auth import ( "errors" "sync" "time" "ws_usernode/internal/pkg" ) // ErrCaptchaInvalid 表示图形验证码校验失败。 var ErrCaptchaInvalid = errors.New("auth: 图形验证码错误") // Captcha 图形验证码(防机器人,登录前置)。 type Captcha struct { ID string Text string } // CaptchaStore 为图形验证码存储。单实例内存实现为默认; // 多实例部署需改 DB/Redis(PLAN §6 注明)。 type CaptchaStore interface { // New 生成一个验证码并返回其 ID。 New() (*Captcha, error) // Verify 校验并一次性消费。失败或过期返回 false。 Verify(id, answer string) bool } // MemoryCaptchaStore 单实例内存实现。 type MemoryCaptchaStore struct { ttl time.Duration mu sync.Mutex entries map[string]*captchaEntry } type captchaEntry struct { text string expiresAt time.Time } // NewMemoryCaptchaStore 创建内存图形验证码存储。 func NewMemoryCaptchaStore(ttl time.Duration) *MemoryCaptchaStore { return &MemoryCaptchaStore{ttl: ttl, entries: make(map[string]*captchaEntry)} } func (s *MemoryCaptchaStore) New() (*Captcha, error) { text, err := pkg.RandomDigits(4) if err != nil { return nil, err } id, err := pkg.RandomHex(16) if err != nil { return nil, err } s.mu.Lock() defer s.mu.Unlock() s.entries[id] = &captchaEntry{text: text, expiresAt: time.Now().Add(s.ttl)} return &Captcha{ID: id, Text: text}, nil } func (s *MemoryCaptchaStore) Verify(id, answer string) bool { s.mu.Lock() defer s.mu.Unlock() e, ok := s.entries[id] if !ok { return false } delete(s.entries, id) // 一次性 return e.text == answer && time.Now().Before(e.expiresAt) }