// // Copyright (C) 2024 veypi // 2025-03-04 16:08:06 // Distributed under terms of the MIT license. // package models import ( "time" "github.com/veypi/vigo" ) // VerificationCode 统一验证码表(邮箱 + 短信) type VerificationCode struct { vigo.Model Target string `gorm:"index;size:100;not null" json:"target"` // 邮箱或手机号 Type string `gorm:"index;size:20;not null" json:"type"` // "email" | "sms" Code string `gorm:"size:10;not null" json:"-"` // 验证码(不返回) Purpose string `gorm:"index;size:20;not null" json:"purpose"` // register/login/reset_password/bind Status CodeStatus `gorm:"default:0" json:"status"` ExpiresAt time.Time `gorm:"index;not null" json:"expires_at"` UsedAt *time.Time `json:"used_at"` Attempts int `gorm:"default:0" json:"attempts"` SendCount int `gorm:"default:1" json:"send_count"` RemoteIP string `gorm:"size:50" json:"remote_ip"` } // TableName 表名 func (VerificationCode) TableName() string { return "verification_codes" } // CodeStatus 验证码状态 type CodeStatus int const ( CodeStatusPending CodeStatus = iota // 待验证 CodeStatusUsed // 已使用 CodeStatusExpired // 已过期 CodeStatusFailed // 验证失败(超过最大尝试次数) ) // IsExpired 检查验证码是否过期 func (c *VerificationCode) IsExpired() bool { return time.Now().After(c.ExpiresAt) } // CanRetry 检查是否可以重试验证 func (c *VerificationCode) CanRetry(maxAttempts int) bool { return c.Status == CodeStatusPending && c.Attempts < maxAttempts && !c.IsExpired() } // IsUsed 检查是否已使用 func (c *VerificationCode) IsUsed() bool { return c.Status == CodeStatusUsed && c.UsedAt != nil } // 验证码用途常量 const ( CodePurposeRegister = "register" CodePurposeLogin = "login" CodePurposeResetPassword = "reset_password" CodePurposeBind = "bind" )