如何使用MimeKit.MimeParser读取mbox文件中指定消息的指定附件
问题结论
可以使用MimeKit.MimeParser实现从mbox文件中读取第M条消息的第N个附件,你之前的方案失效的核心原因和修正方案如下:
原有方案问题说明
- 直接记录底层流的位置无效:
MimeParser默认会做内部缓冲预读,底层流的位置和Parser实际解析的位置不同步 - 用LINQ遍历匹配MessageID的方案效率低,且如果mbox内存在重复MessageID会匹配错误,也容易因为Parser未正确重置导致读取失败
正确实现方案
第一步:预遍历mbox存储消息元数据
预加载消息元数据时,需要存储MimeParser暴露的Position属性(而非底层流的Position)作为每条消息的起始偏移,初始化Parser时必须将persistent参数设为true,保证Parser位置和底层流位置同步:
// 消息元数据结构示例 public class MessageMeta { public long StartOffset { get; set; } public string MessageId { get; set; } public int AttachmentCount { get; set; } // 其他你需要存储的业务字段 } // 预遍历mbox生成元数据列表示例 public List<MessageMeta> PreloadMessageMetas(Stream mboxStream) { var metaList = new List<MessageMeta>(); // 初始化Parser时persistent参数设为true var parser = new MimeParser(mboxStream, MimeFormat.Mbox, persistent: true); while (!parser.IsEndOfStream) { // 先记录当前消息的起始偏移 long currentMsgStart = parser.Position; var msg = parser.ParseMessage(); metaList.Add(new MessageMeta { StartOffset = currentMsgStart, MessageId = msg.MessageId, AttachmentCount = msg.Attachments.Count() }); } return metaList; }
第二步:修正回溯读取附件的逻辑
不需要遍历所有消息匹配,直接将Parser定位到预存的目标消息起始偏移,仅解析当前目标消息即可:
public void SaveAttachment(Attachment att, Stream outStream) { _inputStream.Seek(0, SeekOrigin.Begin); // 重置Parser流,persistent参数保持为true _parser.SetStream(_inputStream, MimeFormat.Mbox, true); // 直接定位到目标消息的起始偏移,不需要遍历 _parser.Position = (att.Parent as Message).StartOffset; var mimeMsg = _parser.ParseMessage(); var mimeAtt = mimeMsg.Attachments.ToList()[att.AttachmentIndex]; if (mimeAtt is MessagePart msgPart) { msgPart.Message.WriteTo(outStream); } else if (mimeAtt is MimePart mimePart) { mimePart.Content.DecodeTo(outStream); } }
注意事项
- 不要直接使用底层流的Position定位,必须使用
MimeParser.Position的记录值,该值是mbox格式下的准确消息偏移 persistent参数必须设为true,否则Parser内部缓冲预读会导致位置不同步- 基于偏移定位的性能为O(1),远高于遍历全量消息匹配MessageID的方案,也不会出现MessageID重复导致的匹配错误
内容的提问来源于stack exchange,提问作者Velja Radenkovic
相关产品推荐
相关产品推荐

