当前位置: 首页 > news >正文

go邮件发送——附件与图片显示

1.邮件方法

package mailSendimport ("bytes""encoding/base64""fmt""html/template""io""log""mime/multipart""net/smtp""net/url""os""path/filepath""strconv""strings""time""github.com/gabriel-vasile/mimetype""github.com/jordan-wright/email"
)// MailConfig 存储邮件相关的配置信息
type MailConfig struct {From     stringHost     stringPort     intEmail    stringPassword string
}// MailContent 存储邮件的内容信息
type MailContent struct {To             []stringCc             []stringBcc            []stringSubject        stringBody           stringAttachmentPath []string // 你的附件路径
}// SendMailBySmtp 发送邮件
func SendMailBySmtp(config *MailConfig, content *MailContent) error {// 连接到SMTP服务器auth := smtp.PlainAuth("", config.Email, config.Password, config.Host)var message bytes.Bufferwriter := multipart.NewWriter(&message)// 设置邮件头部headers := map[string]string{"From":         config.From,"To":           strings.Join(content.To, ","),"Cc":           strings.Join(content.Cc, ","),"Bcc":          strings.Join(content.Bcc, ","),"Subject":      content.Subject,"MIME-Version": "1.0","Content-Type": "multipart/mixed; boundary=" + writer.Boundary(),}// 设置邮件头部BuildHeaders(&message, headers)// 读取HTML模板文件,模板文件与当前文件在同一个目录下tpl, err := ParseTemplate("./email_template.html")if err != nil {log.Printf("Failed to parse template file: %+v", err)return err}imagePath := "/Users/yangge/Pictures/vlcsnap-2024-04-07-20h31m31s596.png"imageMine, base64Data, err := GetImgMineAndContent(imagePath)if err != nil {log.Printf("Failed to detect MIME type for imagePath %s: %+v", imagePath, err)return err}// 创建一个buffer来保存渲染后的HTML内容var bodyBuffer bytes.Bufferdata := &TemplateData{Body:        content.Body,Timestamp:   time.Now().Format("2006-01-02 15:04:05"),ImageMine:   imageMine,ImageData:   base64Data, // 获取图片base64编码ImageUrl:    "",UserList:    nil,ServiceList: nil,}// 渲染HTML模板if err = tpl.Execute(&bodyBuffer, data); err != nil {log.Printf("Failed to execute bodyBuffer: %+v", err)return err}// 添加HTML版本的邮件正文message.WriteString("--" + writer.Boundary() + "\r\n")message.WriteString("Content-Type:text/html;charset=utf-8\r\n")message.WriteString("\r\n")message.WriteString(bodyBuffer.String() + "\r\n")// 附件部分for _, attachmentPath := range content.AttachmentPath {err := AppendAttachToMessage(&message, writer, attachmentPath)if err != nil {log.Printf("Failed to attach file %s: %v", attachmentPath, err)continue}}message.WriteString("--" + writer.Boundary() + "--\r\n")if err := writer.Close(); err != nil {log.Printf("Failed to close multipart writer: %v", err)return err}startTime := time.Now()port := strconv.Itoa(config.Port)if err := smtp.SendMail(config.Host+":"+port, auth, config.Email, append(content.To, content.Cc...), message.Bytes()); err != nil {if strings.Contains(err.Error(), "short response") {log.Printf("⚠️ 服务器短响应但邮件可能已发送成功: %v", err)// 选择性返回 nil,表示你要“容忍”这类错误elapsedTime := time.Since(startTime).Seconds()log.Printf("smtp邮件发送成功!耗时: %s 秒", fmt.Sprintf("%.2f", elapsedTime))return nil}log.Printf("发送邮件失败: %+v", err)return err}elapsedTime := time.Since(startTime).Seconds()log.Printf("smtp邮件发送成功!耗时: %s 秒", fmt.Sprintf("%.2f", elapsedTime))return nil
}func BuildHeaders(w *bytes.Buffer, headers map[string]string) {for key, value := range headers {w.WriteString(key + ": " + value + "\r\n")}w.WriteString("\r\n")
}func ParseTemplate(tplPath string) (*template.Template, error) {tpl, err := template.ParseFiles(tplPath)if err != nil {log.Printf("解析模板失败: %v", err)return nil, err}return tpl, nil
}// AppendAttachToMessage 添加附件到邮箱信息体中
//
// 参数
//
//	message (*bytes.Buffer): 消息buff对象
//	writer (*multipart.Writer): 附件写入处理器
//	filePath (string): 附件路径
func AppendAttachToMessage(message *bytes.Buffer, writer *multipart.Writer, filePath string) (err error) {mimeType, err := mimetype.DetectFile(filePath)if err != nil {message.WriteString("Content-Type:application/octet-stream" + "\r\n")}openFile, err := os.Open(filePath)if err != nil {log.Printf("Failed to open attachment %s: %v", filePath, err)return}defer openFile.Close()// 读取附件方式二var fileContent []bytebuffer := make([]byte, 2048)for {n, err := io.ReadFull(openFile, buffer)if n > 0 {fileContent = append(fileContent, buffer[:n]...)}if err != nil {if err != io.EOF && err != io.ErrUnexpectedEOF {log.Printf("Failed to read attachment %s: %v", filePath, err)}break}}_, file := filepath.Split(filePath)encodedFilename := url.QueryEscape(file)message.WriteString("--" + writer.Boundary() + "\r\n")message.WriteString("Content-Disposition: attachment; filename*=utf-8''" + encodedFilename + "\r\n") // 使用utf-8编码message.WriteString("Content-Type: " + mimeType.String() + "\r\n")message.WriteString("Content-Transfer-Encoding: base64\r\n")message.WriteString("\r\n")message.WriteString(base64.StdEncoding.EncodeToString(fileContent))message.WriteString("\r\n")return nil
}

2.模板代码

<html lang="en">
<head><meta charset="UTF-8"><style>.data-table {width: 100%;border-collapse: collapse;margin-top: 10px;}.data-table th {background: #3498db;color: white;text-align: left;padding: 12px;font-weight: 500;}.data-table td {padding: 15px;border-bottom: 1px solid #e0e0e0;}.data-table tr:nth-child(even) {background: #f9f9f9;}.data-table tr:hover {background: #f1f9ff;}.badge {padding: 5px 10px;border-radius: 12px;font-size: 14px;font-weight: bold;}.badge-success {background: #e8f7f0;color: #2ecc71;}.badge-warning {background: #fef7ec;color: #f39c12;}.badge-error {background: #fdedee;color: #e74c3c;}.progress-bar {width: 100%;height: 10px;background: #ecf0f1;border-radius: 5px;margin-top: 5px;overflow: hidden;}.progress-value {height: 100%;background: #3498db;border-radius: 5px;}.footer {margin-top: 20px;text-align: center;padding: 20px;background: #2c3e50;color: #ecf0f1;font-size: 14px;}.footer a {color: #3498db;text-decoration: none;}.footer a:hover {text-decoration: underline;}</style>
</head>
<body>
{{.Body}}
<h3>{{.Timestamp}}</h3>
<div style="margin: 0 auto">{{if and .ImageMine .ImageData}}<div style="margin: 10px;"><h2>这是使用base64加载的图片</h2><img src="data:{{.ImageMine}};base64,{{.ImageData}}" alt="描述图片的文字"/></div>{{end}}{{if .ImageUrl}}<div style="margin: 10px;"><h2>这是从网络加载的图片</h2><img src="{{.ImageUrl}}" alt="描述图片的文字"/></div>{{end}}{{if .UserList}}<div style="margin: 10px;"><div class="section-title"><i>👥</i><h3>团队成员</h3></div><table class="data-table"><thead><tr><th>姓名</th><th>职位</th><th>邮箱</th><th>状态</th></tr></thead><tbody>{{range .UserList}}<tr><td>{{.Name}}</td><td>{{.Position}}</td><td><a href="mailto:{{.Email}}">{{.Email}}</a></td><td>{{if eq .Status "online"}}<span class="badge user-online">在线</span>{{else if eq .Status "busy"}}<span class="badge user-busy">忙碌</span>{{else}}<span class="badge user-offline">离线</span>{{end}}</td></tr>{{end}}</tbody></table></div>{{end}}{{if .ServiceList}}<div class="section"><div class="section-title"><i>📊</i><h3>服务数据统计</h3></div><table class="data-table"><thead><tr><th>服务名称</th><th>状态</th><th>使用率</th><th>最后更新</th></tr></thead><tbody>{{range .ServiceList}}<tr><td>{{.Name}}</td><td>{{if eq .Status "active"}}<span class="badge service-active">运行中</span>{{else if eq .Status "warning"}}<span class="badge service-warning">警告</span>{{else}}<span class="badge service-error">停止</span>{{end}}</td><td><div class="progress-bar"><div class="progress-value" style="width: {{.Usage}}%"></div></div><div>{{.Usage}}%</div></td><td>{{.LastUpdate}}</td></tr>{{end}}</tbody></table></div>{{end}}<div class="footer"><p>&copy; 2025 Jordan 邮件服务. 保留所有权利</p><p><a href="#">隐私政策</a> | <a href="#">使用条款</a> | <a href="#">联系我们</a></p><p>此邮件为系统自动发送,请勿直接回复</p></div>
</div>
</body>
</html>

3.邮件发送

package mainimport ("apiProject/email/mailSend""log"
)func main() {config := &mailSend.MailConfig{From:     "你的QQ邮箱",Host:     "smtp.qq.com",Port:     587,Email:    "你的QQ邮箱",Password: "123456789", // 这是QQ邮箱开通SMTP服务授权码}content := &mailSend.MailContent{To:      []string{"你的QQ邮箱"},Cc:      []string{"你的QQ邮箱"},Bcc:     []string{},Subject: "使用smtp发送的邮件",Body:    "这是来自go smtp发送的邮件",// 这里是macOS的绝对路径,Windows请改成对应的绝对路径AttachmentPath: []string{"/Users/xxx/xx/xxxx.jpg","/Users/xxx/xx/dsa92c.mp4",},}if err := mailSend.SendMailBySmtp(config, content); err != nil {log.Fatalf("smtp发送邮件失败===%+v", err)}
}

http://www.dtcms.com/a/499062.html

相关文章:

  • AI+若依框架(基础篇)
  • 机器学习(2) 线性回归和代价函数
  • 基于Ollama和sentence-transformers,通过RAG实现问答式定制化回复
  • Sentinel:阿里云高并发流量控制
  • 从技术到商业:电商返利平台的核心指标设计(GMV、佣金率、留存率)与技术支撑体系
  • ShardingSphere 源码解析之分片引擎(下)
  • winxp下做网站网店设计美工培训
  • 【论文学习】大语言模型(LLM)论文
  • 做网站ssl证书必须要吗如何黑掉jsp做的网站
  • CLICKHOUSE分布式表初体验
  • 学习周报十八
  • 《Kubernetes 集群搭建全指南:从核心概念到环境部署!》
  • 找工作哪个网站好58同城做网站最简单的
  • 携程网站用js怎么做淄博哪家网络公司做网站好
  • Linux Shell 正则表达式中的 POSIX 字符集:用法与实战
  • MQTT协议,EMQX自建服务器
  • 力扣1287. 有序数组中出现次数超过25%的元素
  • Linux网络与路由配置完全指南
  • 【高并发服务器】六、日志宏的实现
  • 什么是网络割接
  • 中山网站建设文化报价个人网站设计结构图
  • 专业简历制作网站推荐渭南网站建设网站排名优化
  • Electron学习(一):创建第一个应用并打包成功
  • EF Core FromExpression 方法
  • 工厂方法模式
  • 做装机u盘那个网站好市桥做网站
  • SAP MM采购对账功能分享
  • 网页设计与网站建设考试名词解释2019网站建设工作的作用
  • 【有源码】基于Python与Spark的火锅店数据可视化分析系统-基于机器学习的火锅店综合竞争力评估与可视化分析-基于用户画像聚类的火锅店市场细分与可视化研究
  • Linux: perf: sched latency,周期性抓取看趋势,做对比