开始前的准备
1 2 $ go install github.com/mailhog/MailHog@latest $ go get github.com/jordan-wright/email
我们用mailhog来作为邮箱服务器
我们再powershell使用MailHog
命令开启服务
编码
封装mail包
我们依然先去封装mail包
pkg/mail/driver_interface.go
1 2 3 4 5 package mailtype Driver interface { Send(mail Email, config map [string ]string ) bool }
pkg/mail/driver_smtp.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mailimport ( "fmt" emailPKG "github.com/jordan-wright/email" "go-api-practice/pkg/logger" "net/smtp" ) type SMTP struct {} func (s *SMTP) Send(email Email, config map [string ]string ) bool { e := emailPKG.NewEmail() e.From = fmt.Sprintf("%v <%v>" , email.From.Name, email.From.Address) e.To = email.To e.Bcc = email.Bcc e.Cc = email.Cc e.Subject = email.Subject e.Text = email.Text e.HTML = email.HTML logger.DebugJSON("发送邮件" , "发件详情" , e) err := e.Send( fmt.Sprintf("%v:%v" , config["host" ], config["port" ]), smtp.PlainAuth( "" , config["username" ], config["password" ], config["host" ], ), ) if err != nil { logger.ErrorString("发送邮件" , "发件出错" , err.Error()) return false } logger.DebugString("发送邮件" , "发件成功" , "" ) return true }
实现了接口
pkg/mail/mail.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mailimport ( "go-api-practice/pkg/config" "sync" ) type From struct { Address string Name string } type Email struct { From From To []string Bcc []string Cc []string Subject string Text []byte HTML []byte } type Mailer struct { Driver Driver } var once sync.Oncevar internalMailer *Mailerfunc NewMailer () *Mailer { once.Do(func () { internalMailer = &Mailer{ Driver: &SMTP{}, } }) return internalMailer } func (mailer *Mailer) Send(email Email) bool { return mailer.Driver.Send(email, config.GetStringMapString("mail.smtp" )) }
最终封装了发送邮件的功能到一个Send
函数中
mail配置
config/mail.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package configimport "go-api-practice/pkg/config" func init () { config.Add("mail" , func () map [string ]interface {} { return map [string ]interface {}{ "smtp" : map [string ]interface {}{ "host" : config.Env("MAIL_HOST" , "localhost" ), "port" : config.Env("MAIL_PORT" , 1025 ), "username" : config.Env("MAIL_USERNAME" , "" ), "password" : config.Env("MAIL_PASSWORD" , "" ), }, "from" : map [string ]interface {}{ "address" : config.Env("MAIL_FROM_ADDRESS" , "go-api-practice@example.com" ), "name" : config.Env("MAIL_FROM_NAME" , "Go-API-Practice" ), }, } }) }
发送邮件接口
和前面发送短信的接口类似,我们去开发这个接口
pkg/verifycode/verifycode.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func (vc *VerifyCode) SendEmail(email string ) error { code := vc.generateVerifyCode(email) if !app.IsProduction() && strings.HasSuffix(email, config.GetString("verifycode.debug_email_suffix" )) { return nil } content := fmt.Sprintf("<h1>您的 Email 验证码是 %v </h1>" , 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 }
和手机发送验证码类似的,对于特殊邮箱可以跳过验证
app/requests/verify_code_request.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 type VerifyCodeEmailRequest struct { CaptchaID string `json:"captcha_id,omitempty" valid:"captcha_id"` CaptchaAnswer string `json:"captcha_answer,omitempty" valid:"captcha_answer"` Email string `json:"email,omitempty" valid:"email"` } func VerifyCodeEmail (data interface {}, c *gin.Context) map [string ][]string { rules := govalidator.MapData{ "email" : []string {"required" , "min:4" , "max:30" , "email" }, "captcha_id" : []string {"required" }, "captcha_answer" : []string {"required" , "digits:6" }, } messages := govalidator.MapData{ "email" : []string { "required:Email 为必填项" , "min:Email 长度需大于 4" , "max:Email 长度需小于 30" , "email:Email 格式不正确,请提供有效的邮箱地址" , }, "captcha_id" : []string { "required:图片验证码的 ID 为必填" , }, "captcha_answer" : []string { "required:图片验证码答案必填" , "digits:图片验证码长度必须为 6 位的数字" , }, } errs := validate(data, rules, messages) _data := data.(*VerifyCodeEmailRequest) errs = validators.ValidateCaptcha(_data.CaptchaID, _data.CaptchaAnswer, errs) return errs }
从前端中读取邮箱和验证码,特别的,由于我们验证captcha
验证码的代码部分是公共的,所以我们对它封装一层
app/requests/validators/custom_validators.go
1 2 3 4 5 6 7 8 9 10 package validatorsimport "go-api-practice/pkg/captcha" func ValidateCaptcha (captchaID, captchaAnswer string , errs map [string ][]string ) map [string ][]string { if ok := captcha.NewCaptcha().VerifyCaptcha(captchaID, captchaAnswer); !ok { errs["captcha_answer" ] = append (errs["captcha_answer" ], "图片验证码错误" ) } return errs }
我们顺便把手机号验证的部分也一并修改
最后我们再完成控制器部分
app/http/controllers/api/v1/auth/verify_code_controller.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (vc *VerifyCodeController) SendUsingEmail(c *gin.Context) { request := requests.VerifyCodeEmailRequest{} if ok := requests.Validate(c, &request, requests.VerifyCodeEmail); !ok { return } err := verifycode.NewVerifyCode().SendEmail(request.Email) if err != nil { response.Abort500(c, "发送失败" ) } else { response.Success(c) } }
注册接口
routes/api.go
1 2 3 4 vcc := new (auth.VerifyCodeController) authGroup.POST("/verify-codes/captcha" , vcc.ShowCaptcha) authGroup.POST("/verify-codes/phone" , vcc.SendUsingPhone) authGroup.POST("/verify-codes/email" , vcc.SendUsingEmail)
测试
创建接口
如果成功,结果如下