文章

GO操作邮件服务器 IMAP协议

本文章是记录在本地连接邮件服务器 测试收发是否正常过程中遇到的问题

GO操作邮件服务器 IMAP协议

教案工程代码发送邮件与测试邮件 此处是演示正确流程

教案工程代码发送邮件时使用的账户需要跟游戏中的账户进行分离开

  • 固定使用的发送方进行以下修改
1
2
3
4
5
6
7
8
//   email/app.go
const (
	host   = "mail. - "
	port   = 587
	user   = "t1@ - "
	pwd    = "csT29587"
	sender = "tank@ - "
)
  • 每次需要发送邮件,接收邮箱地址修改为:
1
2
3
4
const (
	user   = "t2@ - "
	pwd    = "csT29587"
)

进行测试时,可以检测此时发送的邮件内容(From,To,Body等等)

  • 使用下面的包使用imap协议去访问老金邮件服务器上的邮件

  • 使用理由

    • github上有300Fork2.1kStar
    • 现在还在维护
    1
    2
    3
    
    "github.com/emersion/go-imap"
    "github.com/emersion/go-imap/client"
    "github.com/emersion/go-message/mail"
    

代码流程

  • 创建邮件的客户端并使用接收方的邮件账户进行登录。这里的登录必须是接收方的账号登录!

  • 账号为”t2@ - “

  • 密码为” - “

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    const (
        //这里由于使用imap协议,用到SSL的话,需要连接到服务器的993端口
    	mailserver = "mail. - :993"
    	user       = "t2@ - "
    	pwd        = " - "
    )
      
    tlsConfig := &tls.Config{
    		ServerName: "mail. - ",
    }
    c, err := client.DialTLS(mailserver, tlsConfig)
    if err != nil {
        t.Errorf("初始化邮箱客户端失败!:%v", err)
        return
    }
    defer c.LoggedOut()
    t.Log("成功连接邮箱服务器")
      
    if err := c.Login(user, pwd); err != nil {
        t.Errorf("登录邮箱失败!:%v", err)
        return
    }
    
  • 在成功登录之后,就使用这个客户端实体,获取到接收信箱中的最新邮件。由于此时发送与接受都是一个服务器上,所以延迟几乎可以忽略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    
    //这里的接收信箱,是服务器上作了缓存,会将邮件保存在服务器文件夹中。同理也会有其余的信箱文件夹,例如已发送信件的文件夹
      
    //这里的名称不能被修改
    mailbox, err := c.Select("Inbox", false)
    if err != nil {
        t.Errorf("获取发件箱失败!:%v", err)
        return
    }
    // 确保文件夹中有邮件
    if mailbox.Messages == 0 {
        t.Fatal("文件夹为空,没有邮件可供读取。")
    }
    //创建序号对象
    seqset := new(imap.SeqSet)
    seqset.AddNum(mailbox.Messages) // 最新邮件的序号
      
    // 获取邮件
    section := &imap.BodySectionName{}
    messages := make(chan *imap.Message, 1)
    done := make(chan error, 1)
    //开一个协程去获取最新邮件实体
    go func() {
        done <- c.Fetch(seqset, []imap.FetchItem{section.FetchItem()}, messages)
    }()
      
    msg := <-messages
    if msg == nil {
        t.Log("No message retrieved")
        return
    }
      
    // 解析邮件内容
    mailr := msg.GetBody(section)
    if mailr == nil {
        t.Log("Server didn't return message body")
        return
    }
      
    mr, err := mail.CreateReader(mailr)
    if err != nil {
        t.Log(err)
        return
    }
      
    // 输出邮件基本信息,这里就可以进行检测。
    subject, _ := mr.Header.Subject()
    fmt.Printf("Subject: %s\n", subject)
      
    from, _ := mr.Header.AddressList("From")
    for _, addr := range from {
        fmt.Printf("From: %s <%s>\n", addr.Name, addr.Address)
    }
      
    to, _ := mr.Header.AddressList("To")
    for _, addr := range to {
        fmt.Printf("To: %s <%s>\n", addr.Name, addr.Address)
    }
      
    // 解析邮件正文并提取验证码
    // var code string
    var bodyContent string
    for {
        p, err := mr.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            t.Errorf("读取正文出错:%v", err)
            continue
        }
      
        //此处能正确获取到这个Body,也可以进行内容的获取
        switch p.Header.(type) {
            case *mail.InlineHeader:
            bodyBytes, _ := io.ReadAll(p.Body)
            bodyContent = string(bodyBytes)
            fmt.Println("Body:", bodyContent) // 输出邮件正文
        }
    }
    

    源可用代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    
    package auth
      
    import (
    	"crypto/tls"
    	"fmt"
    	"io"
    	"regexp"
    	"tank/common/log"
    	"tank/common/pg_conn"
    	"tank/user_mgt/internal/db"
    	"tank/user_mgt/internal/maintain"
    	"tank/user_mgt/pkg/utils"
    	"testing"
      
    	"github.com/emersion/go-imap"
    	"github.com/emersion/go-imap/client"
    	"github.com/emersion/go-message/mail"
    )
    const (
        mailserver = "mail. - :993"
        user       = "t2@ - "
        pwd        = " - "
    )
      
    func TestGetEmail(){
          
    	tlsConfig := &tls.Config{
    		ServerName: "mail. - ",
    	}
    	c, err := client.DialTLS(mailserver, tlsConfig)
    	if err != nil {
    		t.Errorf("初始化邮箱客户端失败!:%v", err)
    		return
    	}
    	defer c.LoggedOut()
    	t.Log("成功连接邮箱服务器")
    	if err := c.Login(user, pwd); err != nil {
    		t.Errorf("登录邮箱失败!:%v", err)
    		return
    	}
      
    	mailbox, err := c.Select("Inbox", false)
    	if err != nil {
    		t.Errorf("获取发件箱失败!:%v", err)
    		return
    	}
    	// 确保文件夹中有邮件
    	if mailbox.Messages == 0 {
    		t.Fatal("文件夹为空,没有邮件可供读取。")
    	}
      
    	// 获取最新 10 封邮件的 UID 范围
    	seqset := new(imap.SeqSet)
    	// 确保 seqset 范围在有效范围内
    	seqset.AddNum(mailbox.Messages) // 最新邮件的序号
    	// 获取邮件
    	section := &imap.BodySectionName{}
    	messages := make(chan *imap.Message, 1)
    	done := make(chan error, 1)
    	go func() {
    		done <- c.Fetch(seqset, []imap.FetchItem{section.FetchItem()}, messages)
    	}()
      
    	msg := <-messages
    	if msg == nil {
    		t.Log("No message retrieved")
    		return
    	}
      
    	// 解析邮件内容
    	mailr := msg.GetBody(section)
    	if mailr == nil {
    		t.Log("Server didn't return message body")
    		return
    	}
      
    	mr, err := mail.CreateReader(mailr)
    	if err != nil {
    		t.Log(err)
    		return
    	}
      
    	// 输出邮件基本信息
    	subject, _ := mr.Header.Subject()
    	fmt.Printf("Subject: %s\n", subject)
      
    	from, _ := mr.Header.AddressList("From")
    	for _, addr := range from {
    		fmt.Printf("From: %s <%s>\n", addr.Name, addr.Address)
    	}
      
    	to, _ := mr.Header.AddressList("To")
    	for _, addr := range to {
    		fmt.Printf("To: %s <%s>\n", addr.Name, addr.Address)
    	}
      	
    	var bodyContent string
    	for {
    		p, err := mr.NextPart()
    		if err == io.EOF {
    			break
    		}
    		if err != nil {
    			t.Errorf("读取正文出错:%v", err)
    			continue
    		}
      
    		//此处能正确获取到这个
    		switch p.Header.(type) {
    		case *mail.InlineHeader:
    			bodyBytes, _ := io.ReadAll(p.Body)
    			bodyContent = string(bodyBytes)
    			fmt.Println("Body:", bodyContent) // 输出邮件正文
    		}
    	}
    }
    

    返回信息(我的测试返回)

    avatar3

设计思路 要测试邮件服务是否正常

  • 使用golang创建客户端1、2 调用函数使用客户端1进行发送 再使用客户端2获取邮件服务器中成功接收的邮件查看是否能接受到 由于同一个邮件服务器,因此延迟几乎为0 就能马上读到
  • 随后就是对emersion/go-imap包的使用方式 由于涉及到发与收 因此需要一直阻塞等待邮件的到来 使用for select去等待 并且有一个无缓冲区的channal去等待信息的填充,也能实现阻塞等待,让整个协程等待
本文由作者按照 CC BY 4.0 进行授权