后台提供邮件发送验证码服务

需求

  1. 我的博客需要增加登录验证码,想通过邮件发送验证码
  2. 希望提供一个http服务,其他服务和编程语言不必再写一个发邮件的方法

实现

package main

import (
    "encoding/base64"
    "errors"
    "net"
    "net/http"
    "strconv"
    "time"
)

func main() {
    http.HandleFunc("/Captcha", Captcha)
    http.ListenAndServe("localhost:8080", nil)
}

func Captcha(w http.ResponseWriter, r *http.Request) {
    code := r.FormValue("code")
    if code == "" {
        w.Write([]byte("false"))
        return
    }
    err := sendCaptcha(code, "smtp.qq.com:25", "user@qq.com", "邮箱授权码,不是登录密码", "user@qq.com", "to@qq.com")
    if err == nil {
        w.Write([]byte("true"))
        return
    }
    w.Write([]byte(err.Error()))
}

func sendCaptcha(code, addr, user, pass, form, to string) error {
    c, err := net.Dial("tcp", addr)
    if err != nil {
        return err
    }
    defer c.Close()

    buf := make([]byte, 256)
    n, err := c.Read(buf)
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("EHLO " + strconv.FormatInt(time.Now().Unix(), 10) + "\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("AUTH LOGIN\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    tmp := base64.StdEncoding.EncodeToString([]byte(user))
    _, err = c.Write([]byte(tmp + "\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    tmp = base64.StdEncoding.EncodeToString([]byte(pass))
    _, err = c.Write([]byte(tmp + "\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("MAIL FROM:<" + form + ">\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("RCPT TO:<" + to + ">\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("DATA\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("from:" + form + "\nto:" + to + "\nsubject:Captcha\nMIMI-Version:1.0\r\n" +
        "Content-Type: multipart/mixed; boundary=\"#BOUNDARY#\"\r\n\r\n" +
        "Content-Transfer-Encoding:7bit\r\n\r\n" +
        "--#BOUNDARY#\r\n" +
        "Content-Type: text/plain; charset=utf-8\r\n" +
        "Content-Transfer-Encoding: printable\r\n\r\n"))
    if err != nil {
        return err
    }

    _, err = c.Write([]byte(code))
    if err != nil {
        return err
    }

    _, err = c.Write([]byte("\r\n--#BOUNDARY#\r\n\r\n--#BOUNDARY#--\r\n.\r\nQUIT\r\n"))
    if err != nil {
        return err
    }
    n, err = c.Read(buf)
    if err != nil {
        return err
    }
    if buf[0] == '2' && buf[1] == '5' && buf[2] == '0' {
        return nil
    }
    return errors.New(string(buf[:n]))
}

用法

  1. 编译运行上述go代码
  2. 测试一下curl http://127.0.0.1:8080/Captcha?code=321

总结

  1. 之所以没有使用go自带的smtp是因为新版本go需要用tls连接,当然网上一大堆方案,但是我这里只是实现简单的发送邮件验证码而已,没必要搞那么复杂
  2. 我的这个服务是不会对外提供的,只是提供localhost的服务,这样比较安全,当然有需要的可以自己改
  3. 这样我的后台服务就能通过简单http get发送一个验证码邮件
  4. 特别注意的是现在邮箱的smtp密码都是授权码,不是登录的密码了

captcha