添加邮件发送工具
This commit is contained in:
parent
da3c72bf96
commit
f51192eebe
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,30 @@
|
|||
package mailer
|
||||
|
||||
import (
|
||||
"gitee.com/zhucheer/orange/cfg"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func NewMailer(optionName string) (mailer *sendMail, err error){
|
||||
options:=cfg.Config.Exists("mailer."+optionName)
|
||||
|
||||
if options == false{
|
||||
return nil, errors.New("not found mailer config by "+ optionName+"")
|
||||
}
|
||||
host:=cfg.Config.GetString("mailer."+optionName+".host")
|
||||
port:=cfg.Config.GetInt("mailer."+optionName+".port")
|
||||
username:=cfg.Config.GetString("mailer."+optionName+".username")
|
||||
password:=cfg.Config.GetString("mailer."+optionName+".password")
|
||||
openTsl:=cfg.Config.GetBool("mailer."+optionName+".tsl")
|
||||
|
||||
mailerOption:=Mailer{
|
||||
Host:host,
|
||||
Port:port,
|
||||
UserName:username,
|
||||
Password:password,
|
||||
OpenTsl:openTsl,
|
||||
}
|
||||
mailer,err= GetSendMailer(mailerOption)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
package mailer
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"bytes"
|
||||
"mime/quotedprintable"
|
||||
"path"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"net/smtp"
|
||||
"errors"
|
||||
"time"
|
||||
"fmt"
|
||||
"strings"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Mailer struct {
|
||||
Host string
|
||||
Port int
|
||||
UserName string
|
||||
Password string
|
||||
OpenTsl bool
|
||||
}
|
||||
|
||||
// MiniType类型枚举
|
||||
var miniTypeMap = map[string]string{
|
||||
".jpg":"image/jpeg",
|
||||
".jpeg":"image/jpeg",
|
||||
".png":"image/png",
|
||||
".gif":"image/gif",
|
||||
".bmp":"application/x-bmp",
|
||||
}
|
||||
|
||||
type attachment struct {
|
||||
Name string
|
||||
MiniType string
|
||||
ContentId string
|
||||
Content string
|
||||
}
|
||||
|
||||
type sendMail struct{
|
||||
option Mailer // 邮箱配置信息
|
||||
from string // 发送人名称 默认是发送邮箱
|
||||
to []string // 收件人
|
||||
cc []string // 抄送人
|
||||
bcc []string // 密送人
|
||||
subject string // 邮件标题
|
||||
body string // 邮件内容
|
||||
bodyType string // 邮件正文类下 html/plain
|
||||
attachments []attachment
|
||||
}
|
||||
|
||||
// SetType 设置邮件类型 html/plain
|
||||
func (sm *sendMail) SetType(bodyType string) (*sendMail){
|
||||
sm.bodyType = bodyType
|
||||
return sm
|
||||
}
|
||||
|
||||
// SetHtml 设置html格式邮件类型
|
||||
func (sm *sendMail) SetHtml() (*sendMail){
|
||||
return sm.SetType("html")
|
||||
}
|
||||
|
||||
// SetText 设置文本邮件类型
|
||||
func (sm *sendMail) SetText() (*sendMail){
|
||||
return sm.SetType("plain")
|
||||
}
|
||||
|
||||
// From 添加发送人名称
|
||||
func (sm *sendMail) From(fromuser string) (*sendMail){
|
||||
sm.from = fromuser
|
||||
return sm
|
||||
}
|
||||
|
||||
// To 添加收件人
|
||||
func (sm *sendMail) To(touser []string) (*sendMail){
|
||||
sm.to = touser
|
||||
return sm
|
||||
}
|
||||
|
||||
// Cc 添加抄送人
|
||||
func (sm *sendMail) Cc(ccuser []string) (*sendMail){
|
||||
sm.cc = ccuser
|
||||
return sm
|
||||
}
|
||||
|
||||
// Bcc 添加密送人
|
||||
func (sm *sendMail) Bcc(bccuser []string) (*sendMail){
|
||||
sm.bcc = bccuser
|
||||
return sm
|
||||
}
|
||||
|
||||
// Subject添加邮件主题
|
||||
func (sm *sendMail) Subject(subject string) (*sendMail){
|
||||
sm.subject = subject
|
||||
return sm
|
||||
}
|
||||
|
||||
// byte2Base64将byte数据转base64
|
||||
// rfc2045中要求base64一行不要超过76个字符,超过需要添加换行符 https://tools.ietf.org/html/rfc2045
|
||||
func (sm *sendMail)byte2Base64(strByte []byte) string{
|
||||
strByteLen := len(strByte)
|
||||
|
||||
payload := make([]byte, base64.StdEncoding.EncodedLen(strByteLen))
|
||||
base64.StdEncoding.Encode(payload, strByte)
|
||||
|
||||
stream := ""
|
||||
for index, size := 0, len(payload); index < size; index++ {
|
||||
stream += string(payload[index])
|
||||
if (index+1)%76 == 0 {
|
||||
stream += "\r\n"
|
||||
}
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
// quotedEncode 将字符串进行quoted-printable编码
|
||||
func (sm *sendMail)quotedEncode(str string) string{
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
w := quotedprintable.NewWriter(buffer)
|
||||
w.Write([]byte(str))
|
||||
w.Close()
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
|
||||
// 添加附件
|
||||
func (sm *sendMail) AddAttachment(filePath string) (contentId string, err error){
|
||||
fileName := path.Base(filePath)
|
||||
fileExt := path.Ext(filePath)
|
||||
fileMiniType := "application/octet-stream"
|
||||
if _,ok := miniTypeMap[fileExt]; ok {
|
||||
fileMiniType = miniTypeMap[fileExt]
|
||||
}
|
||||
|
||||
fileBytes, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return contentId, err
|
||||
}
|
||||
|
||||
contentId = "qqivy"+strconv.Itoa(len(sm.attachments) + 1)+"@orange.generate"
|
||||
contentString:= sm.byte2Base64(fileBytes)
|
||||
|
||||
attachmentInfo := attachment{
|
||||
Name:fileName,
|
||||
MiniType:fileMiniType,
|
||||
ContentId:contentId ,
|
||||
Content:contentString,
|
||||
}
|
||||
|
||||
sm.attachments = append(sm.attachments, attachmentInfo)
|
||||
return contentId, nil
|
||||
}
|
||||
|
||||
|
||||
// 发送邮件
|
||||
func (sm *sendMail) Send(message string) (err error) {
|
||||
err = sm.validata()
|
||||
if err != nil{
|
||||
return err
|
||||
}
|
||||
|
||||
// Set up authentication information.
|
||||
auth := smtp.PlainAuth("", sm.option.UserName, sm.option.Password, sm.option.Host)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
boudary := "THIS_IS_BOUNDARY_ORANGE"
|
||||
sm.body = message
|
||||
|
||||
sm.writeHeader(buffer, boudary)
|
||||
sm.writeBody(buffer, boudary)
|
||||
sm.writeAttachment(buffer, boudary)
|
||||
buffer.WriteString("\r\n--" + boudary + "--\r\n")
|
||||
|
||||
toUsers := make([]string, len(sm.to) + len(sm.cc) + len(sm.bcc))
|
||||
toUsers = append(sm.to, sm.cc...)
|
||||
toUsers = append(toUsers, sm.bcc...)
|
||||
|
||||
addr:=fmt.Sprintf("%s:%d",sm.option.Host, sm.option.Port)
|
||||
|
||||
if sm.option.OpenTsl == true {
|
||||
err = sm.option.sendMailByTslDo(addr, auth, sm.option.UserName, toUsers, buffer.Bytes())
|
||||
}else{
|
||||
err = smtp.SendMail(addr, auth, sm.option.UserName, toUsers, buffer.Bytes())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// sendMailByTslDo
|
||||
func (mail *Mailer)sendMailByTslDo(addr string, auth smtp.Auth, from string,
|
||||
to []string, msg []byte) (err error) {
|
||||
|
||||
//create smtp client
|
||||
c, err := mail.TlsDial(addr)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if auth != nil {
|
||||
if ok, _ := c.Extension("AUTH"); ok {
|
||||
if err = c.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.Mail(from); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, addr := range to {
|
||||
if err = c.Rcpt(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Quit()
|
||||
}
|
||||
|
||||
//return a smtp client by TLS connect
|
||||
func (mail *Mailer)TlsDial(addr string) (*smtp.Client, error) {
|
||||
conn, err := tls.Dial("tcp", addr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//分解主机端口字符串
|
||||
host, _, _ := net.SplitHostPort(addr)
|
||||
return smtp.NewClient(conn, host)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// writeAttachment 构建smtp邮件附件
|
||||
func (sm *sendMail) writeAttachment(buffer *bytes.Buffer, boudary string) (attachment string) {
|
||||
for _, item:=range sm.attachments{
|
||||
attachment = "\r\n--" + boudary + "\r\n"
|
||||
attachment += "Content-Type: " + item.MiniType + "; name=" + item.Name + "\r\n"
|
||||
attachment += "Content-Transfer-Encoding: base64\r\n"
|
||||
attachment += "Content-ID: <" + item.ContentId +">\r\n"
|
||||
if item.MiniType != "application/octet-stream"{
|
||||
attachment += "Content-Disposition: inline; filename="+ item.Name+ "\r\n"
|
||||
}
|
||||
|
||||
attachment += "\r\n"
|
||||
attachment += item.Content
|
||||
attachment += "\r\n"
|
||||
|
||||
buffer.WriteString(attachment)
|
||||
}
|
||||
return attachment
|
||||
}
|
||||
|
||||
// writeBody 构建smtp邮件正文
|
||||
func (sm *sendMail) writeBody(buffer *bytes.Buffer, boudary string) string {
|
||||
body := "\r\n--" + boudary + "\r\n"
|
||||
body += "Content-Type: text/"+sm.bodyType+"; charset=utf-8\r\n"
|
||||
body += "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
body += "\r\n" + sm.quotedEncode(sm.body) +"\r\n\r\n"
|
||||
buffer.WriteString(body)
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
// writeHeader 构建smtp邮件头部
|
||||
func (sm *sendMail) writeHeader(buffer *bytes.Buffer, boudary string) string {
|
||||
header := "Date: "+ time.Now().Format(time.RFC1123Z) +"\r\n"
|
||||
header += "Subject: "+sm.subject+"\r\n"
|
||||
header += fmt.Sprintf("From: %s <%s>\r\n", "=?utf-8?Q?"+sm.quotedEncode(sm.from)+"?=", sm.option.UserName)
|
||||
header += "To: "+ strings.Join(sm.to, ", ") +"\r\n"
|
||||
if len(sm.cc) > 0{
|
||||
header += "Cc: "+ strings.Join(sm.cc, ", ") +"\r\n"
|
||||
}
|
||||
if len(sm.bcc) > 0{
|
||||
header += "Bcc: "+ strings.Join(sm.bcc, ", ") +"\r\n"
|
||||
}
|
||||
header += "MIME-Version: 1.0\r\n"
|
||||
header += "Content-Type: multipart/related;\r\n boundary=\""+ boudary +"\"\r\n"
|
||||
|
||||
headerString := header
|
||||
headerString += "\r\n"
|
||||
buffer.WriteString(headerString)
|
||||
return headerString
|
||||
}
|
||||
|
||||
|
||||
// validata 检测发送信息有效性
|
||||
func (sm *sendMail)validata() (err error){
|
||||
if sm.subject == ""{
|
||||
return errors.New("mail subject is empty")
|
||||
}
|
||||
if len(sm.to) == 0{
|
||||
return errors.New("to user addr is empty")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 获取发送邮件对象
|
||||
func GetSendMailer(mailOption Mailer) (ret *sendMail, err error){
|
||||
if mailOption.Host == "" || mailOption.Port == 0 ||
|
||||
mailOption.UserName == "" || mailOption.Password == ""{
|
||||
return ret, errors.New("mail option have an error")
|
||||
}
|
||||
ret = &sendMail{
|
||||
option: mailOption,
|
||||
from: mailOption.UserName, // 配置默认发送人
|
||||
bodyType: "html",
|
||||
}
|
||||
|
||||
// 465端口自动开启tsl
|
||||
if mailOption.Port == 465{
|
||||
ret.option.OpenTsl = true
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package mailer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gitee.com/zhucheer/orange/cfg"
|
||||
)
|
||||
|
||||
func TestNewMailer(t *testing.T){
|
||||
// go test smtp_test.go smtp.go init.go -v -args --config=../project/config/config.toml
|
||||
cfg.ParseParams()
|
||||
cfg.StartConfig()
|
||||
|
||||
defaultMailer, err := NewMailer("default")
|
||||
if err != nil{
|
||||
t.Errorf("NewMailer have error #1 %v", err)
|
||||
}
|
||||
if defaultMailer.option.Host == ""{
|
||||
t.Error("defaultMailer no Host")
|
||||
}
|
||||
|
||||
_,err=NewMailer("mailer01")
|
||||
if err.Error() != "not found mailer config by mailer01"{
|
||||
t.Error("mailer read have an error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetSendMailerDefaultPort(t *testing.T) {
|
||||
mailer,err:= GetSendMailer(Mailer{
|
||||
Host:"smtp.qq.com",
|
||||
Port:25,
|
||||
UserName:"000@qq.com",
|
||||
Password:"000",
|
||||
OpenTsl:false,
|
||||
})
|
||||
|
||||
err = mailer.To([]string{"zhu2943@qq.com"}).Cc([]string{"zhu.cheer@qq.com"}).SetText().Subject("test abc").Send("hello")
|
||||
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSendMailer(t *testing.T) {
|
||||
mailer,err:= GetSendMailer(Mailer{
|
||||
Host:"smtp.qq.com",
|
||||
Port:465,
|
||||
UserName:"000@qq.com",
|
||||
Password:"000",
|
||||
OpenTsl:true,
|
||||
})
|
||||
|
||||
|
||||
cid, err:=mailer.AddAttachment("./attachment.jpg")
|
||||
|
||||
mailer.To([]string{"zhu.cheer@qq.com"}).From("Orange Test").Cc([]string{"zhu.cheer@qq.com"}).Bcc([]string{"zhu.cheer@qq.com"}).
|
||||
SetHtml().Subject("test cc").Send("<b>bcc mailer</b> with an attachment")
|
||||
|
||||
if cid == "" || err != nil{
|
||||
t.Error("add attachment is error")
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,13 @@
|
|||
storage = "./storage/allimg"
|
||||
maxSize = 2096157
|
||||
ext = ["jpg"]
|
||||
[mailer]
|
||||
[mailer.default]
|
||||
host="smtp.qq.com"
|
||||
port=22
|
||||
username="xxx@qq.com"
|
||||
password="xxxx"
|
||||
tsl=true
|
||||
[database]
|
||||
initCap = 2
|
||||
maxCap = 5
|
||||
|
|
Loading…
Reference in New Issue