temp
This commit is contained in:
@@ -6,50 +6,58 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SMSCode 短信验证码记录
|
||||
// SMSCode 短信验证码记录实体
|
||||
// 记录用户发送的所有短信验证码,支持多种使用场景
|
||||
// 包含验证码的有效期管理、使用状态跟踪、安全审计等功能
|
||||
type SMSCode struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
|
||||
Phone string `gorm:"type:varchar(20);not null;index" json:"phone"`
|
||||
Code string `gorm:"type:varchar(10);not null" json:"-"` // 不返回给前端
|
||||
Scene SMSScene `gorm:"type:varchar(20);not null" json:"scene"`
|
||||
Used bool `gorm:"default:false" json:"used"`
|
||||
ExpiresAt time.Time `gorm:"not null" json:"expires_at"`
|
||||
UsedAt *time.Time `json:"used_at,omitempty"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"短信验证码记录唯一标识"`
|
||||
Phone string `gorm:"type:varchar(20);not null;index" json:"phone" comment:"接收手机号"`
|
||||
Code string `gorm:"type:varchar(10);not null" json:"-" comment:"验证码内容(不返回给前端)"`
|
||||
Scene SMSScene `gorm:"type:varchar(20);not null" json:"scene" comment:"使用场景"`
|
||||
Used bool `gorm:"default:false" json:"used" comment:"是否已使用"`
|
||||
ExpiresAt time.Time `gorm:"not null" json:"expires_at" comment:"过期时间"`
|
||||
UsedAt *time.Time `json:"used_at,omitempty" comment:"使用时间"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 额外信息
|
||||
IP string `gorm:"type:varchar(45)" json:"ip"`
|
||||
UserAgent string `gorm:"type:varchar(500)" json:"user_agent"`
|
||||
// 额外信息 - 安全审计相关数据
|
||||
IP string `gorm:"type:varchar(45)" json:"ip" comment:"发送IP地址"`
|
||||
UserAgent string `gorm:"type:varchar(500)" json:"user_agent" comment:"客户端信息"`
|
||||
}
|
||||
|
||||
// SMSScene 短信验证码使用场景
|
||||
// SMSScene 短信验证码使用场景枚举
|
||||
// 定义系统中所有需要使用短信验证码的业务场景
|
||||
type SMSScene string
|
||||
|
||||
const (
|
||||
SMSSceneRegister SMSScene = "register" // 注册
|
||||
SMSSceneLogin SMSScene = "login" // 登录
|
||||
SMSSceneChangePassword SMSScene = "change_password" // 修改密码
|
||||
SMSSceneResetPassword SMSScene = "reset_password" // 重置密码
|
||||
SMSSceneBind SMSScene = "bind" // 绑定手机号
|
||||
SMSSceneUnbind SMSScene = "unbind" // 解绑手机号
|
||||
SMSSceneRegister SMSScene = "register" // 注册 - 新用户注册验证
|
||||
SMSSceneLogin SMSScene = "login" // 登录 - 手机号登录验证
|
||||
SMSSceneChangePassword SMSScene = "change_password" // 修改密码 - 修改密码验证
|
||||
SMSSceneResetPassword SMSScene = "reset_password" // 重置密码 - 忘记密码重置
|
||||
SMSSceneBind SMSScene = "bind" // 绑定手机号 - 绑定新手机号
|
||||
SMSSceneUnbind SMSScene = "unbind" // 解绑手机号 - 解绑当前手机号
|
||||
)
|
||||
|
||||
// 实现 Entity 接口
|
||||
// 实现 Entity 接口 - 提供统一的实体管理接口
|
||||
// GetID 获取实体唯一标识
|
||||
func (s *SMSCode) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetCreatedAt 获取创建时间
|
||||
func (s *SMSCode) GetCreatedAt() time.Time {
|
||||
return s.CreatedAt
|
||||
}
|
||||
|
||||
// GetUpdatedAt 获取更新时间
|
||||
func (s *SMSCode) GetUpdatedAt() time.Time {
|
||||
return s.UpdatedAt
|
||||
}
|
||||
|
||||
// Validate 验证短信验证码
|
||||
// 检查短信验证码记录的必填字段是否完整,确保数据的有效性
|
||||
func (s *SMSCode) Validate() error {
|
||||
if s.Phone == "" {
|
||||
return &ValidationError{Message: "手机号不能为空"}
|
||||
@@ -64,24 +72,253 @@ func (s *SMSCode) Validate() error {
|
||||
return &ValidationError{Message: "过期时间不能为空"}
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
if !IsValidPhoneFormat(s.Phone) {
|
||||
return &ValidationError{Message: "手机号格式无效"}
|
||||
}
|
||||
|
||||
// 验证验证码格式
|
||||
if err := s.validateCodeFormat(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 业务方法
|
||||
func (s *SMSCode) IsExpired() bool {
|
||||
return time.Now().After(s.ExpiresAt)
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
// 检查输入的验证码是否匹配且有效
|
||||
func (s *SMSCode) VerifyCode(inputCode string) error {
|
||||
// 1. 检查验证码是否已使用
|
||||
if s.Used {
|
||||
return &ValidationError{Message: "验证码已被使用"}
|
||||
}
|
||||
|
||||
// 2. 检查验证码是否已过期
|
||||
if s.IsExpired() {
|
||||
return &ValidationError{Message: "验证码已过期"}
|
||||
}
|
||||
|
||||
// 3. 检查验证码是否匹配
|
||||
if s.Code != inputCode {
|
||||
return &ValidationError{Message: "验证码错误"}
|
||||
}
|
||||
|
||||
// 4. 标记为已使用
|
||||
s.MarkAsUsed()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExpired 检查验证码是否已过期
|
||||
// 判断当前时间是否超过验证码的有效期
|
||||
func (s *SMSCode) IsExpired() bool {
|
||||
return time.Now().After(s.ExpiresAt) || time.Now().Equal(s.ExpiresAt)
|
||||
}
|
||||
|
||||
// IsValid 检查验证码是否有效
|
||||
// 综合判断验证码是否可用,包括未使用和未过期两个条件
|
||||
func (s *SMSCode) IsValid() bool {
|
||||
return !s.Used && !s.IsExpired()
|
||||
}
|
||||
|
||||
// MarkAsUsed 标记验证码为已使用
|
||||
// 在验证码被成功使用后调用,记录使用时间并标记状态
|
||||
func (s *SMSCode) MarkAsUsed() {
|
||||
s.Used = true
|
||||
now := time.Now()
|
||||
s.UsedAt = &now
|
||||
}
|
||||
|
||||
// CanResend 检查是否可以重新发送验证码
|
||||
// 基于时间间隔和场景判断是否允许重新发送
|
||||
func (s *SMSCode) CanResend(minInterval time.Duration) bool {
|
||||
// 如果验证码已使用或已过期,可以重新发送
|
||||
if s.Used || s.IsExpired() {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查距离上次发送的时间间隔
|
||||
timeSinceCreated := time.Since(s.CreatedAt)
|
||||
return timeSinceCreated >= minInterval
|
||||
}
|
||||
|
||||
// GetRemainingTime 获取验证码剩余有效时间
|
||||
func (s *SMSCode) GetRemainingTime() time.Duration {
|
||||
if s.IsExpired() {
|
||||
return 0
|
||||
}
|
||||
return s.ExpiresAt.Sub(time.Now())
|
||||
}
|
||||
|
||||
// IsRecentlySent 检查是否最近发送过验证码
|
||||
func (s *SMSCode) IsRecentlySent(within time.Duration) bool {
|
||||
return time.Since(s.CreatedAt) < within
|
||||
}
|
||||
|
||||
// GetMaskedCode 获取脱敏的验证码(用于日志记录)
|
||||
func (s *SMSCode) GetMaskedCode() string {
|
||||
if len(s.Code) < 3 {
|
||||
return "***"
|
||||
}
|
||||
return s.Code[:1] + "***" + s.Code[len(s.Code)-1:]
|
||||
}
|
||||
|
||||
// GetMaskedPhone 获取脱敏的手机号
|
||||
func (s *SMSCode) GetMaskedPhone() string {
|
||||
if len(s.Phone) < 7 {
|
||||
return s.Phone
|
||||
}
|
||||
return s.Phone[:3] + "****" + s.Phone[len(s.Phone)-4:]
|
||||
}
|
||||
|
||||
// ================ 场景相关方法 ================
|
||||
|
||||
// IsSceneValid 检查场景是否有效
|
||||
func (s *SMSCode) IsSceneValid() bool {
|
||||
validScenes := []SMSScene{
|
||||
SMSSceneRegister,
|
||||
SMSSceneLogin,
|
||||
SMSSceneChangePassword,
|
||||
SMSSceneResetPassword,
|
||||
SMSSceneBind,
|
||||
SMSSceneUnbind,
|
||||
}
|
||||
|
||||
for _, scene := range validScenes {
|
||||
if s.Scene == scene {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSceneName 获取场景的中文名称
|
||||
func (s *SMSCode) GetSceneName() string {
|
||||
sceneNames := map[SMSScene]string{
|
||||
SMSSceneRegister: "用户注册",
|
||||
SMSSceneLogin: "用户登录",
|
||||
SMSSceneChangePassword: "修改密码",
|
||||
SMSSceneResetPassword: "重置密码",
|
||||
SMSSceneBind: "绑定手机号",
|
||||
SMSSceneUnbind: "解绑手机号",
|
||||
}
|
||||
|
||||
if name, exists := sceneNames[s.Scene]; exists {
|
||||
return name
|
||||
}
|
||||
return string(s.Scene)
|
||||
}
|
||||
|
||||
// ================ 安全相关方法 ================
|
||||
|
||||
// IsSuspicious 检查是否存在可疑行为
|
||||
func (s *SMSCode) IsSuspicious() bool {
|
||||
// 检查IP地址是否为空(可能表示异常)
|
||||
if s.IP == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查UserAgent是否为空(可能表示异常)
|
||||
if s.UserAgent == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 可以添加更多安全检查逻辑
|
||||
// 例如:检查IP是否来自异常地区、UserAgent是否异常等
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSecurityInfo 获取安全信息摘要
|
||||
func (s *SMSCode) GetSecurityInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"ip": s.IP,
|
||||
"user_agent": s.UserAgent,
|
||||
"suspicious": s.IsSuspicious(),
|
||||
"scene": s.GetSceneName(),
|
||||
"created_at": s.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 私有辅助方法 ================
|
||||
|
||||
// validateCodeFormat 验证验证码格式
|
||||
func (s *SMSCode) validateCodeFormat() error {
|
||||
// 检查验证码长度
|
||||
if len(s.Code) < 4 || len(s.Code) > 10 {
|
||||
return &ValidationError{Message: "验证码长度必须在4-10位之间"}
|
||||
}
|
||||
|
||||
// 检查验证码是否只包含数字
|
||||
for _, char := range s.Code {
|
||||
if char < '0' || char > '9' {
|
||||
return &ValidationError{Message: "验证码只能包含数字"}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 静态工具方法 ================
|
||||
|
||||
// IsValidScene 检查场景是否有效(静态方法)
|
||||
func IsValidScene(scene SMSScene) bool {
|
||||
validScenes := []SMSScene{
|
||||
SMSSceneRegister,
|
||||
SMSSceneLogin,
|
||||
SMSSceneChangePassword,
|
||||
SMSSceneResetPassword,
|
||||
SMSSceneBind,
|
||||
SMSSceneUnbind,
|
||||
}
|
||||
|
||||
for _, validScene := range validScenes {
|
||||
if scene == validScene {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSceneName 获取场景的中文名称(静态方法)
|
||||
func GetSceneName(scene SMSScene) string {
|
||||
sceneNames := map[SMSScene]string{
|
||||
SMSSceneRegister: "用户注册",
|
||||
SMSSceneLogin: "用户登录",
|
||||
SMSSceneChangePassword: "修改密码",
|
||||
SMSSceneResetPassword: "重置密码",
|
||||
SMSSceneBind: "绑定手机号",
|
||||
SMSSceneUnbind: "解绑手机号",
|
||||
}
|
||||
|
||||
if name, exists := sceneNames[scene]; exists {
|
||||
return name
|
||||
}
|
||||
return string(scene)
|
||||
}
|
||||
|
||||
// NewSMSCode 创建新的短信验证码(工厂方法)
|
||||
func NewSMSCode(phone, code string, scene SMSScene, expireTime time.Duration, clientIP, userAgent string) (*SMSCode, error) {
|
||||
smsCode := &SMSCode{
|
||||
Phone: phone,
|
||||
Code: code,
|
||||
Scene: scene,
|
||||
Used: false,
|
||||
ExpiresAt: time.Now().Add(expireTime),
|
||||
IP: clientIP,
|
||||
UserAgent: userAgent,
|
||||
}
|
||||
|
||||
// 验证实体
|
||||
if err := smsCode.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return smsCode, nil
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (SMSCode) TableName() string {
|
||||
return "sms_codes"
|
||||
|
||||
Reference in New Issue
Block a user