diff --git a/internal/service/user_service.go b/internal/service/user_service.go index a4182b6..a22ee7e 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -21,12 +21,14 @@ import ( ) type UserService struct { - userDao *dao.UserDao + userDao *dao.UserDao + emailSendLogDao *dao.EmailSendLogDao } func NewUserService() *UserService { return &UserService{ - userDao: dao.NewUserDao(), + userDao: dao.NewUserDao(), + emailSendLogDao: dao.NewEmailSendLogDao(), } } @@ -141,6 +143,12 @@ func (svc *UserService) SendEmailVerifyCode(ctx context.Context, emailAddr strin if cacheErr := cache.SetUserSendEmailLimit(ctx, emailAddr); cacheErr != nil { log.Error(ctx, "func", "SendEmailVerifyCode", "msg", "set send email limit error", "error", cacheErr) } + + record := &dao.EmailSendLog{Email: emailAddr, Status: dao.EmailSendStatusSent} + if logErr := svc.emailSendLogDao.Create(dao.DB.WithContext(ctx), record); logErr != nil { + log.Error(ctx, "func", "SendEmailVerifyCode", "msg", "create email send log error", "error", logErr) + } + return nil } @@ -156,7 +164,16 @@ func (svc *UserService) RegisterByEmail(ctx context.Context, emailAddr, password _ = cache.DeleteUserEmailCode(ctx, emailAddr) - return svc.registerByEmailInternal(ctx, emailAddr, password) + uid, err = svc.registerByEmailInternal(ctx, emailAddr, password) + if err != nil { + return 0, err + } + + if logErr := svc.emailSendLogDao.MarkRegistered(dao.DB.WithContext(ctx), emailAddr); logErr != nil { + log.Error(ctx, "func", "RegisterByEmail", "msg", "mark email send log registered error", "error", logErr) + } + + return uid, nil } func (svc *UserService) registerByEmailInternal(ctx context.Context, emailAddr, password string) (uid int64, err error) { diff --git a/internal/storage/dao/email_send_log.go b/internal/storage/dao/email_send_log.go new file mode 100644 index 0000000..18947b5 --- /dev/null +++ b/internal/storage/dao/email_send_log.go @@ -0,0 +1,50 @@ +package dao + +import ( + "gorm.io/gorm" +) + +type EmailSendStatus int8 + +const ( + EmailSendStatusSent EmailSendStatus = 0 // 已发送,用户未注册 + EmailSendStatusRegistered EmailSendStatus = 1 // 用户已完成注册 +) + +type EmailSendLog struct { + BaseModel + Email string `gorm:"column:email;type:varchar(255);not null;comment:邮箱地址" json:"email"` + Status EmailSendStatus `gorm:"column:status;type:tinyint;not null;default:0;comment:状态: 0=已发送未注册 1=已注册" json:"status"` +} + +func (e *EmailSendLog) TableName() string { + return "email_send_log" +} + +type EmailSendLogDao struct{} + +func NewEmailSendLogDao() *EmailSendLogDao { + return &EmailSendLogDao{} +} + +func (d *EmailSendLogDao) Create(tx *gorm.DB, log *EmailSendLog) error { + return tx.Create(log).Error +} + +func (d *EmailSendLogDao) GetLatestByEmail(tx *gorm.DB, email string) (*EmailSendLog, error) { + var record EmailSendLog + err := tx.Where("email = ?", email).Order("id DESC").First(&record).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + return &record, nil +} + +func (d *EmailSendLogDao) MarkRegistered(tx *gorm.DB, email string) error { + return tx.Model(&EmailSendLog{}). + Where("email = ? AND status = ?", email, EmailSendStatusSent). + Update("status", EmailSendStatusRegistered).Error +} diff --git a/migrations/email_send_log.sql b/migrations/email_send_log.sql new file mode 100644 index 0000000..e4cc67f --- /dev/null +++ b/migrations/email_send_log.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS `email_send_log` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `email` VARCHAR(255) NOT NULL COMMENT '邮箱地址', + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态: 0=已发送未注册, 1=已注册', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_email` (`email`), + INDEX `idx_status` (`status`), + INDEX `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邮件发送记录表';