Explorar o código

发送邮件验证码

runningwater hai 8 meses
pai
achega
3ad17da89f

+ 17 - 0
app/http/controllers/api/v1/auth/verify_code_controller.go

@@ -8,6 +8,7 @@ import (
 	"github.com/runningwater/gohub/pkg/captcha"
 	"github.com/runningwater/gohub/pkg/logger"
 	"github.com/runningwater/gohub/pkg/response"
+	"github.com/runningwater/gohub/pkg/verifycode"
 )
 
 type VerifyCodeController struct {
@@ -45,3 +46,19 @@ func (vc *VerifyCodeController) SendUsingPhone(c *gin.Context) {
 
 	response.Success(c)
 }
+
+// SendUsingEmail 发送邮箱验证码
+func (vc *VerifyCodeController) SendUsingEmail(c *gin.Context) {
+	// 1. 验证表单
+	request := requests.VerifyCodeEmailRequest{}
+	if ok := requests.Validate(c, &request, requests.VerifyCodeEmail); !ok {
+		return
+	}
+
+	// 2. 发送邮箱验证码
+	if err := verifycode.NewVerifyCode().SendEmail(request.Email); err != nil {
+		response.Abort500(c, "发送邮件失败~~")
+	} else {
+		response.Success(c)
+	}
+}

+ 43 - 0
app/requests/verify_code_request.go

@@ -46,3 +46,46 @@ func VerifyCodePhone(data any, c *gin.Context) map[string][]string {
 	// 3. 返回错误信息
 	return errs
 }
+
+type VerifyCodeEmailRequest struct {
+	Email string `json:"email,omitempy" valid:"email"`
+
+	CaptchaID     string `json:"captcha_id,omitempy" valid:"captcha_id"`
+	CaptchaAnswer string `json:"captcha_answer,omitempy" valid:"captcha_answer"`
+}
+
+func VerifyCodeEmail(data any, c *gin.Context) map[string][]string {
+	// 1. 定制认证规则
+	rules := govalidator.MapData{
+		"email":          []string{"required", "min:4", "max:30", "email"},
+		"captcha_id":     []string{"required"},
+		"captcha_answer": []string{"required", "digits:6"},
+	}
+	// 2. 定制错误信息
+	messages := govalidator.MapData{
+		"email": []string{
+			"required:邮箱不能为空",
+			"min:邮箱长度需大于 4",
+			"max:邮箱长度需小于 30",
+			"email:邮箱格式不正确",
+		},
+		"captcha_id": []string{
+			"required:验证码ID不能为空",
+		},
+		"captcha_answer": []string{
+			"required:验证码不能为空",
+			"digits:验证码格式不正确,长度必须为6位",
+		},
+	}
+
+	errs := validate(data, rules, messages)
+
+	// Captcha 图片验证
+	_data := data.(*VerifyCodeEmailRequest)
+	if ok := captcha.NewCaptcha().VerifyCaptcha(_data.CaptchaID, _data.CaptchaAnswer); !ok {
+		errs["captcha_answer"] = append(errs["captcha_answer"], "验证码错误")
+	}
+
+	// 3. 返回错误信息
+	return errs
+}

+ 1 - 1
config/verifycode.go

@@ -16,7 +16,7 @@ func init() {
 
 			// 方便本地和 测试环境调试
 			"debug_phone_prefix": "000",
-			"debug_email_prefix": "test@",
+			"debug_email_suffix": "test@",
 		}
 	})
 }

+ 10 - 0
gohub.http

@@ -26,4 +26,14 @@ Content-Type: application/json
   "phone": "15968875425",
   "captcha_id": "captcha_skip_test",
   "captcha_answer": "123456"
+}
+
+### POST /verify_code/email 发送邮箱验证码
+POST {{base_url}}/v1/auth/verify_code/email HTTP/1.1
+Content-Type: application/json
+
+{
+  "email": "summer@example.com",
+  "captcha_id": "captcha_skip_test",
+  "captcha_answer": "123456"
 }

+ 10 - 4
pkg/mail/mail.go

@@ -1,6 +1,10 @@
 package mail
 
-import "sync"
+import (
+	"sync"
+
+	"github.com/runningwater/gohub/pkg/config"
+)
 
 // To 收件人邮箱列表
 type To []string
@@ -34,13 +38,15 @@ var mailer *Mailer
 // NewMailer 创建邮件发送器单例
 func NewMailer() *Mailer {
 	once.Do(func() {
-		mailer = &Mailer{}
+		mailer = &Mailer{
+			Driver: &SMTP{}, // 默认使用 SMTP 驱动
+		}
 	})
 	return mailer
 }
 
 // Send 使用配置的驱动发送邮件
 // 返回值 bool 表示是否发送成功
-func (m *Mailer) Send(email Email, config map[string]string) bool {
-	return m.Driver.Send(email, config)
+func (m *Mailer) Send(email Email) bool {
+	return m.Driver.Send(email, config.GetStringMapString("mail.smtp"))
 }

+ 37 - 4
pkg/verifycode/verifycode.go

@@ -1,6 +1,7 @@
 package verifycode
 
 import (
+	"fmt"
 	"strings"
 	"sync"
 
@@ -8,7 +9,8 @@ import (
 	"github.com/runningwater/gohub/pkg/config"
 	"github.com/runningwater/gohub/pkg/helpers"
 	"github.com/runningwater/gohub/pkg/logger"
-	"github.com/runningwater/gohub/pkg/sms"
+	"github.com/runningwater/gohub/pkg/mail"
+	// "github.com/runningwater/gohub/pkg/sms"
 )
 
 type VerifyCode struct {
@@ -41,11 +43,42 @@ func (v *VerifyCode) SendSMS(phone string) bool {
 		// 测试环境,直接返回成功
 		return true
 	}
+	logger.DebugJSON("验证码", "发送验证码", map[string]string{phone: code})
+	return true
 	// 发送验证码
-	return sms.NewSMS().Send(phone, sms.Message{
-		Template: config.GetString("sms.aliyun.template_code"),
-		Data:     map[string]string{"code": code},
+	// return sms.NewSMS().Send(phone, sms.Message{
+	// 	Template: config.GetString("sms.aliyun.template_code"),
+	// 	Data:     map[string]string{"code": code},
+	// })
+}
+
+// SendEmail 发送验证码
+// 发送验证码到邮箱, 调用示例
+//
+//	verifycode.NewVerifyCode().SendEmail(request.Email)
+func (v *VerifyCode) SendEmail(email string) error {
+	// 生成验证码
+	code := v.generateVerifyCode(email)
+
+	// 方便本地和 测试环境调试
+	if !app.IsProduction() &&
+		strings.HasSuffix(email, config.GetString("verifycode.debug_email_suffix")) {
+		return nil
+	}
+
+	content := fmt.Sprintf("<p>您的验证码是:%v</p>", code)
+	// 发送邮件
+	mail.NewMailer().Send(mail.Email{
+		From: mail.From{
+			Address: config.GetString("mail.from.address"),
+			Name:    config.GetString("mail.from.name"),
+		},
+		To:      []string{email},
+		Subject: "Email 验证码",
+		HTML:    []byte(content),
 	})
+
+	return nil
 }
 
 // CheckAnswer 检查验证码是否正确

+ 2 - 0
routes/api.go

@@ -23,6 +23,8 @@ func RegisterAPIRoutes(router *gin.Engine) {
 			authGroup.POST("/verify_code/captcha", vcc.ShowCaptcha)
 			// 发送手机验证码
 			authGroup.POST("/verify_code/phone", vcc.SendUsingPhone)
+			// 发送邮箱验证码
+			authGroup.POST("/verify_code/email", vcc.SendUsingEmail)
 		}
 	}
 }