Golang代码开发经验分享

Golang代码开发经验分享 你好,

我急需一位专家提供帮助。下面这个问题让我备受困扰。我正在尝试构建一个自动邮件发送器。除了自动发送邮件外,它还必须能够发送定时邮件。

请帮帮我。

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"gopkg.in/gomail.v2"
)

type Config struct {
	SMTPServer   string `json:"smtp_server"`
	SMTPPort     int    `json:"smtp_port"`
	SMTPUser     string `json:"smtp_user"`
	SMTPPassword string `json:"smtp_password"`
	EmailFrom    string `json:"email_from"`
	EmailTo      string `json:"email_to"`
}

func main() {
	// 解析命令行参数
	scheduleDate := flag.String("date", "", "Date for sending the email (YYYY-MM-DD)")
	scheduleTime := flag.String("time", "", "Time for sending the email (HH:MM)")
	flag.Parse()

	// 验证命令行参数
	if *scheduleDate == "" || *scheduleTime == "" {
		fmt.Println("Please provide both the date and time for scheduling the email.")
		return
	}

	// 打开 JSON 文件
	file, err := os.Open("config.json")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	// 读取 JSON 文件
	bytes, err := ioutil.ReadAll(file)
	if err != nil {
		panic(err)
	}

	// 将 JSON 数据解码到 Config 结构体中
	var config Config
	err = json.Unmarshal(bytes, &config)
	if err != nil {
		panic(err)
	}

	// 创建新的邮件消息
	m := gomail.NewMessage()

	// 设置邮件详情
	m.SetHeader("From", config.EmailFrom)
	m.SetHeader("To", config.EmailTo)
	m.SetHeader("Subject", "Test Mail from Go")

	// 设置邮件正文
	m.SetBody("text/html", "This is the content of the email.")

	// 为邮件服务器创建新的拨号器
	d := gomail.NewDialer(config.SMTPServer, config.SMTPPort, config.SMTPUser, config.SMTPPassword)

	// 解析提供的日期和时间字符串
	layout := "2006-01-02 15:04" // 用于解析日期和时间的格式
	scheduleDateTime := fmt.Sprintf("%s %s", *scheduleDate, *scheduleTime)
	scheduledTime, err := time.Parse(layout, scheduleDateTime)
	if err != nil {
		fmt.Println("Invalid date or time format. Please provide the date in the format 'YYYY-MM-DD' and the time in the format 'HH:MM'.")
		return
	}

	// 计算到预定时间的持续时间
	duration := scheduledTime.Sub(time.Now())

	// 检查预定时间是否已过
	if duration < 0 {
		fmt.Println("The specified scheduled time has already passed.")
		return
	}

	// 打印预定时间用于调试
	fmt.Println("Scheduled time:", scheduledTime)

	// 安排在指定时间发送邮件
	timer := time.NewTimer(duration)

	// 等待计时器到期
	<-timer.C

	// 打印当前时间用于调试
	fmt.Println("Current time:", time.Now())

	// ...

	// 使用拨号器发送邮件
	if err := d.DialAndSend(m); err != nil {
		fmt.Println("Failed to send the email:", err)
		os.Exit(1)
	}

	// 打印邮件已发送的消息
	fmt.Println("The email has been sent!")
}

更多关于Golang代码开发经验分享的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好,@Huseyin_Genc,你具体遇到了什么问题?

更多关于Golang代码开发经验分享的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个很好的定时邮件发送器基础实现。我来分享一些关键的Go语言开发经验,特别是针对你的代码可以改进的地方:

1. 使用context处理超时和取消

// 在main函数中添加context支持
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// 修改发送邮件部分支持context
if err := d.DialAndSendContext(ctx, m); err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        fmt.Println("Email sending timed out")
    } else {
        fmt.Println("Failed to send the email:", err)
    }
    os.Exit(1)
}

2. 改进配置管理

// 使用结构体标签验证
type Config struct {
    SMTPServer   string `json:"smtp_server" validate:"required,hostname_port"`
    SMTPPort     int    `json:"smtp_port" validate:"required,min=1,max=65535"`
    SMTPUser     string `json:"smtp_user" validate:"required,email"`
    SMTPPassword string `json:"smtp_password" validate:"required"`
    EmailFrom    string `json:"email_from" validate:"required,email"`
    EmailTo      string `json:"email_to" validate:"required,email"`
}

// 使用go-playground/validator进行验证
func loadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read config file: %w", err)
    }
    
    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("unmarshal config: %w", err)
    }
    
    validate := validator.New()
    if err := validate.Struct(config); err != nil {
        return nil, fmt.Errorf("validate config: %w", err)
    }
    
    return &config, nil
}

3. 实现邮件模板支持

// 邮件模板结构
type EmailTemplate struct {
    Subject string `json:"subject"`
    Body    string `json:"body"`
}

// 模板渲染函数
func renderTemplate(templatePath string, data interface{}) (string, string, error) {
    tmpl, err := template.ParseFiles(templatePath)
    if err != nil {
        return "", "", err
    }
    
    var subjectBuf, bodyBuf bytes.Buffer
    if err := tmpl.ExecuteTemplate(&subjectBuf, "subject", data); err != nil {
        return "", "", err
    }
    if err := tmpl.ExecuteTemplate(&bodyBuf, "body", data); err != nil {
        return "", "", err
    }
    
    return subjectBuf.String(), bodyBuf.String(), nil
}

// 在main中使用
subject, body, err := renderTemplate("email.tmpl", map[string]interface{}{
    "UserName": "John Doe",
    "SendTime": time.Now().Format(time.RFC1123),
})
if err != nil {
    log.Fatal("Failed to render template:", err)
}
m.SetHeader("Subject", subject)
m.SetBody("text/html", body)

4. 添加重试机制

// 带指数退避的重试发送
func sendWithRetry(d *gomail.Dialer, m *gomail.Message, maxRetries int) error {
    var lastErr error
    
    for i := 0; i < maxRetries; i++ {
        if i > 0 {
            // 指数退避
            backoff := time.Duration(math.Pow(2, float64(i))) * time.Second
            time.Sleep(backoff)
        }
        
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        
        if err := d.DialAndSendContext(ctx, m); err != nil {
            lastErr = err
            log.Printf("Attempt %d failed: %v", i+1, err)
            continue
        }
        
        return nil
    }
    
    return fmt.Errorf("failed after %d attempts: %w", maxRetries, lastErr)
}

// 使用重试发送
if err := sendWithRetry(d, m, 3); err != nil {
    fmt.Println("Failed to send email after retries:", err)
    os.Exit(1)
}

5. 改进定时调度

// 使用cron表达式支持复杂调度
import "github.com/robfig/cron/v3"

func scheduleEmail(cronExpr string, config Config) error {
    c := cron.New()
    
    _, err := c.AddFunc(cronExpr, func() {
        // 发送邮件逻辑
        m := gomail.NewMessage()
        m.SetHeader("From", config.EmailFrom)
        m.SetHeader("To", config.EmailTo)
        m.SetHeader("Subject", "Scheduled Email")
        m.SetBody("text/html", "This is a scheduled email.")
        
        d := gomail.NewDialer(config.SMTPServer, config.SMTPPort, 
            config.SMTPUser, config.SMTPPassword)
        
        if err := d.DialAndSend(m); err != nil {
            log.Printf("Failed to send scheduled email: %v", err)
        } else {
            log.Println("Scheduled email sent successfully")
        }
    })
    
    if err != nil {
        return fmt.Errorf("invalid cron expression: %w", err)
    }
    
    c.Start()
    return nil
}

6. 添加并发安全性和优雅关闭

// 使用sync.WaitGroup管理goroutine
var wg sync.WaitGroup
stopChan := make(chan struct{})

// 信号处理
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)

// 启动邮件发送goroutine
wg.Add(1)
go func() {
    defer wg.Done()
    
    select {
    case <-timer.C:
        // 发送邮件逻辑
        if err := d.DialAndSend(m); err != nil {
            log.Printf("Failed to send email: %v", err)
        }
    case <-stopChan:
        log.Println("Email sending cancelled")
        return
    }
}()

// 等待信号
<-signalChan
close(stopChan)

// 等待所有goroutine完成
wg.Wait()
fmt.Println("Shutdown complete")

7. 添加日志记录

// 结构化日志
import "go.uber.org/zap"

func initLogger() *zap.Logger {
    logger, err := zap.NewProduction()
    if err != nil {
        log.Fatal("Failed to create logger:", err)
    }
    return logger
}

// 在main中使用
logger := initLogger()
defer logger.Sync()

logger.Info("Starting email scheduler",
    zap.String("scheduled_time", scheduleDateTime),
    zap.String("from", config.EmailFrom),
    zap.String("to", config.EmailTo),
)

// 记录发送结果
if err := d.DialAndSend(m); err != nil {
    logger.Error("Failed to send email",
        zap.Error(err),
        zap.String("to", config.EmailTo),
    )
    os.Exit(1)
}

logger.Info("Email sent successfully",
    zap.String("to", config.EmailTo),
    zap.Time("sent_at", time.Now()),
)

这些改进可以让你的邮件发送器更加健壮、可维护和可扩展。关键点包括:错误处理、配置验证、模板支持、重试机制、更好的调度支持和结构化日志。

回到顶部