package service import ( "crypto/tls" "net" "bytes" "fmt" "github.com/leanote/leanote/app/db" "github.com/leanote/leanote/app/info" . "github.com/leanote/leanote/app/lea" "gopkg.in/mgo.v2/bson" "html/template" "net/smtp" "strconv" "strings" "time" ) // 发送邮件 type EmailService struct { tpls map[string]*template.Template } func NewEmailService() *EmailService { return &EmailService{tpls: map[string]*template.Template{}} } // 发送邮件 var host = "" var emailPort = "" var username = "" var password = "" var ssl = false func InitEmailFromDb() { host = configService.GetGlobalStringConfig("emailHost") emailPort = configService.GetGlobalStringConfig("emailPort") username = configService.GetGlobalStringConfig("emailUsername") password = configService.GetGlobalStringConfig("emailPassword") if configService.GetGlobalStringConfig("emailSSL") == "1" { ssl = true } } //return a smtp client func dial(addr string) (*smtp.Client, error) { conn, err := tls.Dial("tcp", addr, nil) if err != nil { LogW("Dialing Error:", err) return nil, err } //分解主机端口字符串 host, _, _ := net.SplitHostPort(addr) return smtp.NewClient(conn, host) } func SendEmailWithSSL (auth smtp.Auth, to []string, msg []byte) (err error) { //create smtp client c, err := dial(host + ":" + emailPort) if err != nil { LogW("Create smpt client error:", err) return err } defer c.Close() if auth != nil { if ok, _ := c.Extension("AUTH"); ok { if err = c.Auth(auth); err != nil { LogW("Error during AUTH", err) return err } } } if err = c.Mail(username); 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() } func (this *EmailService) SendEmail(to, subject, body string) (ok bool, e string) { InitEmailFromDb() if host == "" || emailPort == "" || username == "" || password == "" { return } hp := strings.Split(host, ":") auth := smtp.PlainAuth("", username, password, hp[0]) var content_type string mailtype := "html" if mailtype == "html" { content_type = "Content-Type: text/" + mailtype + "; charset=UTF-8" } else { content_type = "Content-Type: text/plain" + "; charset=UTF-8" } msg := []byte("To: " + to + "\r\nFrom: " + username + "<" + username + ">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body) send_to := strings.Split(to, ";") var err error if ssl { err = SendEmailWithSSL(auth, send_to, msg) } else { Log("no ssl") err = smtp.SendMail(host + ":" + emailPort, auth, username, send_to, msg) } if err != nil { e = fmt.Sprint(err) return } ok = true return } // AddUser调用 // 可以使用一个goroutine func (this *EmailService) RegisterSendActiveEmail(userInfo info.User, email string) bool { token := tokenService.NewToken(userInfo.UserId.Hex(), email, info.TokenActiveEmail) if token == "" { return false } subject := configService.GetGlobalStringConfig("emailTemplateRegisterSubject") tpl := configService.GetGlobalStringConfig("emailTemplateRegister") if tpl == "" { return false } tokenUrl := configService.GetSiteUrl() + "/user/activeEmail?token=" + token // {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username} token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))), "user": map[string]interface{}{ "userId": userInfo.UserId.Hex(), "email": userInfo.Email, "username": userInfo.Username, }, } var ok bool ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value) if !ok { return false } // 发送邮件 ok, _ = this.SendEmail(email, subject, tpl) return ok } // 修改邮箱 func (this *EmailService) UpdateEmailSendActiveEmail(userInfo info.User, email string) (ok bool, msg string) { // 先验证该email是否被注册了 if userService.IsExistsUser(email) { ok = false msg = "该邮箱已注册" return } token := tokenService.NewToken(userInfo.UserId.Hex(), email, info.TokenUpdateEmail) if token == "" { return } subject := configService.GetGlobalStringConfig("emailTemplateUpdateEmailSubject") tpl := configService.GetGlobalStringConfig("emailTemplateUpdateEmail") // 发送邮件 tokenUrl := configService.GetSiteUrl() + "/user/updateEmail?token=" + token // {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.userId} {user.email} {user.username} token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))), "newEmail": email, "user": map[string]interface{}{ "userId": userInfo.UserId.Hex(), "email": userInfo.Email, "username": userInfo.Username, }, } ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value) if !ok { return } // 发送邮件 ok, msg = this.SendEmail(email, subject, tpl) return } func (this *EmailService) FindPwdSendEmail(token, email string) (ok bool, msg string) { subject := configService.GetGlobalStringConfig("emailTemplateFindPasswordSubject") tpl := configService.GetGlobalStringConfig("emailTemplateFindPassword") // 发送邮件 tokenUrl := configService.GetSiteUrl() + "/findPassword/" + token // {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username} token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail)))} ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value) if !ok { return } // 发送邮件 ok, msg = this.SendEmail(email, subject, tpl) return } // 发送邀请链接 func (this *EmailService) SendInviteEmail(userInfo info.User, email, content string) bool { subject := configService.GetGlobalStringConfig("emailTemplateInviteSubject") tpl := configService.GetGlobalStringConfig("emailTemplateInvite") token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "registerUrl": configService.GetSiteUrl() + "/register?from=" + userInfo.Username, "content": content, "user": map[string]interface{}{ "username": userInfo.Username, "email": userInfo.Email, }, } var ok bool ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value) if !ok { return false } // 发送邮件 ok, _ = this.SendEmail(email, subject, tpl) return ok } // 发送评论 func (this *EmailService) SendCommentEmail(note info.Note, comment info.BlogComment, userId, content string) bool { subject := configService.GetGlobalStringConfig("emailTemplateCommentSubject") tpl := configService.GetGlobalStringConfig("emailTemplateComment") // title := "评论提醒" /* toUserId := note.UserId.Hex() // title := "评论提醒" // 表示回复回复的内容, 那么发送给之前回复的 if comment.CommentId != "" { toUserId = comment.UserId.Hex() } toUserInfo := userService.GetUserInfo(toUserId) sendUserInfo := userService.GetUserInfo(userId) subject := note.Title + " 收到 " + sendUserInfo.Username + " 的评论"; if comment.CommentId != "" { subject = "您在 " + note.Title + " 发表的评论收到 " + sendUserInfo.Username; if userId == note.UserId.Hex() { subject += "(作者)"; } subject += " 的评论"; } */ toUserId := note.UserId.Hex() // 表示回复回复的内容, 那么发送给之前回复的 if comment.CommentId != "" { toUserId = comment.UserId.Hex() } toUserInfo := userService.GetUserInfo(toUserId) // 被评论者 sendUserInfo := userService.GetUserInfo(userId) // 评论者 // {siteUrl} {blogUrl} // {blog.id} {blog.title} {blog.url} // {commentUser.userId} {commentUser.username} {commentUser.email} // {commentedUser.userId} {commentedUser.username} {commentedUser.email} token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "blogUrl": configService.GetBlogUrl(), "blog": map[string]string{ "id": note.NoteId.Hex(), "title": note.Title, "url": configService.GetBlogUrl() + "/view/" + note.NoteId.Hex(), }, "commentContent": content, // 评论者信息 "commentUser": map[string]interface{}{"userId": sendUserInfo.UserId.Hex(), "username": sendUserInfo.Username, "email": sendUserInfo.Email, "isBlogAuthor": userId == note.UserId.Hex(), }, // 被评论者信息 "commentedUser": map[string]interface{}{"userId": toUserId, "username": toUserInfo.Username, "email": toUserInfo.Email, "isBlogAuthor": toUserId == note.UserId.Hex(), }, } ok := false ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value) if !ok { return false } // 发送邮件 ok, _ = this.SendEmail(toUserInfo.Email, subject, tpl) return ok } // 验证模板是否正确 func (this *EmailService) ValidTpl(str string) (ok bool, msg string) { defer func() { if err := recover(); err != nil { ok = false msg = fmt.Sprint(err) } }() header := configService.GetGlobalStringConfig("emailTemplateHeader") footer := configService.GetGlobalStringConfig("emailTemplateFooter") str = strings.Replace(str, "{{header}}", header, -1) str = strings.Replace(str, "{{footer}}", footer, -1) _, err := template.New("tpl name").Parse(str) if err != nil { msg = fmt.Sprint(err) return } ok = true return } // ok, msg, subject, tpl func (this *EmailService) getTpl(str string) (ok bool, msg string, tpl *template.Template) { defer func() { if err := recover(); err != nil { ok = false msg = fmt.Sprint(err) } }() var err error var has bool if tpl, has = this.tpls[str]; !has { tpl, err = template.New("tpl name").Parse(str) if err != nil { msg = fmt.Sprint(err) return } this.tpls[str] = tpl } ok = true return } // 通过subject, body和值得到内容 func (this *EmailService) renderEmail(subject, body string, values map[string]interface{}) (ok bool, msg string, o string, b string) { ok = false msg = "" defer func() { // 必须要先声明defer,否则不能捕获到panic异常 if err := recover(); err != nil { ok = false msg = fmt.Sprint(err) // 这里的err其实就是panic传入的内容, } }() var tpl *template.Template values["siteUrl"] = configService.GetSiteUrl() // subject if subject != "" { ok, msg, tpl = this.getTpl(subject) if !ok { return } var buffer bytes.Buffer err := tpl.Execute(&buffer, values) if err != nil { msg = fmt.Sprint(err) return } o = buffer.String() } else { o = "" } // content header := configService.GetGlobalStringConfig("emailTemplateHeader") footer := configService.GetGlobalStringConfig("emailTemplateFooter") body = strings.Replace(body, "{{header}}", header, -1) body = strings.Replace(body, "{{footer}}", footer, -1) values["subject"] = o ok, msg, tpl = this.getTpl(body) if !ok { return } var buffer2 bytes.Buffer err := tpl.Execute(&buffer2, values) if err != nil { msg = fmt.Sprint(err) return } b = buffer2.String() return } // 发送email给用户 // 需要记录 func (this *EmailService) SendEmailToUsers(users []info.User, subject, body string) (ok bool, msg string) { if users == nil || len(users) == 0 { msg = "no users" return } // 尝试renderHtml ok, msg, _, _ = this.renderEmail(subject, body, map[string]interface{}{}) if !ok { Log(msg) return } go func() { for _, user := range users { LogJ(user) m := map[string]interface{}{} m["userId"] = user.UserId.Hex() m["username"] = user.Username m["email"] = user.Email ok2, msg2, subject2, body2 := this.renderEmail(subject, body, m) ok = ok2 msg = msg2 if ok2 { sendOk, msg := this.SendEmail(user.Email, subject2, body2) this.AddEmailLog(user.Email, subject, body, sendOk, msg) // 把模板记录下 // 记录到Email Log if sendOk { // Log("ok " + user.Email) } else { // Log("no " + user.Email) } } else { // Log(msg); } } }() return } func (this *EmailService) SendEmailToEmails(emails []string, subject, body string) (ok bool, msg string) { if emails == nil || len(emails) == 0 { msg = "no emails" return } // 尝试renderHtml ok, msg, _, _ = this.renderEmail(subject, body, map[string]interface{}{}) if !ok { Log(msg) return } // go func() { for _, email := range emails { if email == "" { continue } m := map[string]interface{}{} m["email"] = email ok, msg, subject, body = this.renderEmail(subject, body, m) if ok { sendOk, msg := this.SendEmail(email, subject, body) this.AddEmailLog(email, subject, body, sendOk, msg) // 记录到Email Log if sendOk { Log("ok " + email) } else { Log("no " + email) } } else { Log(msg) } } // }() return } // 添加邮件日志 func (this *EmailService) AddEmailLog(email, subject, body string, ok bool, msg string) { log := info.EmailLog{LogId: bson.NewObjectId(), Email: email, Subject: subject, Body: body, Ok: ok, Msg: msg, CreatedTime: time.Now()} db.Insert(db.EmailLogs, log) } // 展示邮件日志 func (this *EmailService) DeleteEmails(ids []string) bool { idsO := make([]bson.ObjectId, len(ids)) for i, id := range ids { idsO[i] = bson.ObjectIdHex(id) } db.DeleteAll(db.EmailLogs, bson.M{"_id": bson.M{"$in": idsO}}) return true } func (this *EmailService) ListEmailLogs(pageNumber, pageSize int, sortField string, isAsc bool, email string) (page info.Page, emailLogs []info.EmailLog) { emailLogs = []info.EmailLog{} skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc) query := bson.M{} if email != "" { query["Email"] = bson.M{"$regex": bson.RegEx{".*?" + email + ".*", "i"}} } q := db.EmailLogs.Find(query) // 总记录数 count, _ := q.Count() // 列表 q.Sort(sortFieldR). Skip(skipNum). Limit(pageSize). All(&emailLogs) page = info.NewPage(pageNumber, pageSize, count, nil) return }