go-imap
go-imapgo-imap库是为了实现连接邮箱服务器获取邮件内容而诞生的工具包。地址https://github.com/emersion/go-imap示例在使用该包的时候还需要一定的imap协议知识,当然都是边看边学就好,英文不好真的是吃亏。连接服务器c, err := client.DialTLS(conf.Conf.MTRIMAPAddr, nil)defer c.Logout()
go-imap
go-imap库是为了实现连接邮箱服务器获取邮件内容而诞生的工具包。
地址
https://github.com/emersion/go-imap
示例
在使用该包的时候还需要一定的imap协议知识,当然都是边看边学就好,英文不好真的是吃亏。
连接服务器
c, err := client.DialTLS(conf.Conf.MTRIMAPAddr, nil)
defer c.Logout()
登录
//login
if err := c.Login(conf.Conf.MTRIMAPUsername, conf.Conf.MTRIMAPPassword); err != nil {
return "", errors.Errorf(err, "[getVoucher]: login fail!")
}
选择邮箱,这里有分收件箱,发件箱等,这里选择的是发件箱
mbox, err := c.Select("INBOX", false)
if err != nil {
return "", errors.Errorf(err, "[getVoucher]: Select INBOX fail!")
}
其中邮箱的状态
// A mailbox status.
type MailboxStatus struct {
// The mailbox name.
Name string
// True if the mailbox is open in read-only mode.
ReadOnly bool
// The mailbox items that are currently filled in. This map's values
// should not be used directly, they must only be used by libraries
// implementing extensions of the IMAP protocol.
Items map[StatusItem]interface{}
// The Items map may be accessed in different goroutines. Protect
// concurrent writes.
ItemsLocker sync.Mutex
// The mailbox flags.
Flags []string
// The mailbox permanent flags.
PermanentFlags []string
// The sequence number of the first unseen message in the mailbox.
UnseenSeqNum uint32
// The number of messages in this mailbox.
Messages uint32
// The number of messages not seen since the last time the mailbox was opened.
Recent uint32
// The number of unread messages.
Unseen uint32
// The next UID.
UidNext uint32
// Together with a UID, it is a unique identifier for a message.
// Must be greater than or equal to 1.
UidValidity uint32
}
然后我们选择所需要邮件的数量,总数就是mbox.Messages。最大的数量就是最新的一份邮件。seqset设置了start和stop标志了读取的邮件编号从start到stop。若start设置为mbox.Messages.那么就只读最新的一封邮件,以此类推。
//seqset 开始结束
from := getfromIndex(mbox.Messages)
to := mbox.Messages
seqset := new(imap.SeqSet)
seqset.AddRange(from, to)
messages := make(chan *imap.Message, 10)
section := &imap.BodySectionName{}
type SeqSet struct {
Set []Seq
}
// Seq represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
// represented by setting Start = Stop. Zero is used to represent "*", which is
// safe because seq-number uses nz-number rule. The order of values is always
// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
type Seq struct {
Start, Stop uint32
}
那么接下来需要设置的是需要获取到邮件的那些内容。通过item来表示。通过以下方式来设置,FetchFlags表示要获取对应的邮件的flag信息。
目前定义的系统标记有:
/Seen
邮件已读
/Answered
邮件已回复
/Flagged
邮件标记为紧急或者特别注意。
/Deleted
邮件为删除状态。
/Draft
邮件未写完(标记为草稿状态)。
/Recent
//items 是需要筛选的 刷选模式为flag和body
items := []imap.FetchItem{imap.FetchFlags}
uidItem := imap.FetchUid
items = append(items, section.FetchItem())
items = append(items, imap.FetchItem(uidItem))
使用channel来获取数据
done := make(chan error, 1)
go func() {
done <- c.Fetch(seqset, items, messages)
}()
使用for msg := range messages 方式来获取每一个channel返回的数据。
for msg := range messages {
//只是未读邮件
if len(msg.Flags) == 0 {
section, err := imap.ParseBodySectionName("BODY[]")
if err != nil {
return "", errors.Errorf(err, "ParseBodySectionName err!")
}
r := msg.GetBody(section)
if r == nil { //循环读取 不退出
continue
}
//设置已读
mr, err := mail.CreateReader(r)
if err != nil {
return "", errors.Errorf(err, "CreateReader fail")
}
header := mr.Header
//判断header.subject 是否包含MTR Confirmation Note
subject, err := header.Subject()
if strings.Contains(subject, target) {
//若包含则进入解析body 对body查找对应的orderID
for {
p, err := mr.NextPart()
if err == io.EOF {
break
} else if err != nil {
return "", errors.Errorf(err, " message Reader NextPart err")
}
switch h := p.Header.(type) {
case mail.TextHeader: //一封邮件会有两份是TextHeader 类型 一个不是html
content, err := ioutil.ReadAll(p.Body)
if err != nil {
return "", errors.Errorf(err, "Read message fail")
}
if !strings.Contains(string(content), refNo) { //应该是break 不需要再循环
break
}
logger.Info("find target refNo:", refNo, "msg.SeqNum", msg.SeqNum, "len of message", len(content))
findTargetFlag = true
content, err = convHtml(ctx, downloadPath, content)
if err != nil {
return "", errors.Errorf(err, "convHtml fail")
}
if content == nil || len(content) == 0 {
return "", fmt.Errorf("convHtml content is nil")
}
targetContent = string(content)
msgSeq = int(msg.SeqNum)
case mail.AttachmentHeader:
filename, _ := h.Filename()
}
}
}
} //只对未读邮件做处理
}
踩坑
在设置邮件状态的时候,一开始把以下函数放在了range中。导致了死锁。因为在range中相当于把box资源加锁。而在setflag中也需要去获得锁。相当于setflag去请求一个不未释放的锁。以后这种需要多加注意了。
//设置邮件已读状态
func setSeenFlag(c *client.Client, msgSeq int) (err error) {
if c == nil {
return fmt.Errorf("[setSeenFlag] para c is nil")
}
if msgSeq < 0 {
return fmt.Errorf("[setSeenFlag] para msgSeq is invalid")
}
seen_Set, err := imap.ParseSeqSet(strconv.Itoa(msgSeq))
if err != nil{
return errors.Errorf(err, "ParseSeqSet fail!")
}
done := make(chan error, 1)
go func() {
done <- c.Store(seen_Set, imap.AddFlags, []interface{}{imap.SeenFlag}, nil)
}()
if err != nil {
return errors.Errorf(err, "[setSeenFlag] resv down err")
}
//there can be bug set time out 2s
select {
case err := <-done:
if err != nil {
return errors.Errorf(err, "[setSeenFlag] resv down err")
}
case <-time.After(2 * time.Second):
return fmt.Errorf("set flag time out(2s)")
}
return nil
}
以上踩坑还向作者反馈,感谢emersion指出问题所在!
更多推荐
所有评论(0)