Exchange Web Services (EWS) 介绍

什么是 EWS?

Exchange Web Services (EWS) 是 Microsoft Exchange Server 提供的一组 Web 服务接口,允许客户端应用程序通过 SOAP 协议与 Exchange 服务器进行通信。它是一个功能强大的 API,使开发人员能够访问存储在 Exchange 服务器上的电子邮件、日历、联系人和任务等数据。

EWS 主要功能

  1. 邮件操作

    • 发送和接收电子邮件
    • 创建、读取、更新和删除邮件
    • 管理邮件文件夹
    • 设置邮件规则
  2. 日历管理

    • 创建和管理约会
    • 安排会议
    • 处理会议响应
    • 查看忙/闲状态
  3. 联系人管理

    • 创建和管理联系人
    • 管理通讯组列表
    • 搜索联系人
  4. 任务管理

    • 创建和跟踪任务
    • 设置任务提醒
    • 管理任务分配

EWS 通信协议

EWS 使用 SOAP (Simple Object Access Protocol) 协议进行通信。SOAP 是一种基于 HTTP 协议的 XML 消息传递协议,它通过 HTTP POST 方法发送 XML 格式的数据。这种设计使得 SOAP 能够:

  • 穿透防火墙(因为使用标准的 HTTP 端口)
  • 支持现有的网络基础设施
  • 利用 HTTP 的安全机制(如 HTTPS)

在 EWS 中,SOAP 协议具有以下特点:

  1. 消息结构

    • 使用 XML 格式封装请求和响应
    • 包含 Envelope(信封)、Header(头部)和 Body(主体)三个主要部分
    • 支持强类型的数据交换
  2. 操作类型

    • CreateItem:创建邮件、日历项等
    • FindItem:搜索邮件和其他项目
    • UpdateItem:更新现有项目
    • DeleteItem:删除项目
    • GetItem:获取项目详细信息
  3. 命名空间

    • types:定义数据类型 (xmlns:t)
    • messages:定义操作消息 (xmlns:m)
    • soap:定义 SOAP 信封 (xmlns:soap)

在使用 EWS 客户端库时,开发者无需直接处理 SOAP 消息,库会自动将方法调用转换为相应的 SOAP 请求。

EWS 使用场景

  1. 企业应用集成

    • 将企业应用与 Exchange 邮件系统集成
    • 自动化邮件处理流程
    • 构建自定义邮件客户端
  2. 自动化工作流

    • 自动创建会议邀请
    • 处理邮件归档
    • 管理日历同步
  3. 报告和监控

    • 邮箱使用情况统计
    • 日历事件分析
    • 邮件流量监控

使用示例:使用 Go 实现邮件列表分页查询

以下是一个使用 Golang 通过 EWS 查询收件箱最近10条邮件的示例,展示了如何实现分页查询和按时间排序:

  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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package main

import (
    "encoding/xml"
    "github.com/mhewedy/ews"
    "log"
)

// 定义请求和响应结构体
type FindItemRequest struct {
    XMLName xml.Name `xml:"m:FindItem"`
    Traversal string `xml:"Traversal,attr"`
    ItemShape *ItemShape `xml:"m:ItemShape"`
    IndexedPageItemView *IndexedPageItemView `xml:"m:IndexedPageItemView"`
    ParentFolderIds *FolderIDs `xml:"m:ParentFolderIds"`
    SortOrder *SortOrder `xml:"m:SortOrder"`
}

type IndexedPageItemView struct {
    MaxEntriesReturned int `xml:"MaxEntriesReturned,attr"`
    Offset int `xml:"Offset,attr"`
    BasePoint string `xml:"BasePoint,attr"`
}

type SortOrder struct {
    FieldOrder *FieldOrder `xml:"t:FieldOrder"`
}

type FieldOrder struct {
    Order string `xml:"Order,attr"`
    FieldURI *FieldURI `xml:"t:FieldURI"`
}

type ItemShape struct {
    BaseShape string `xml:"t:BaseShape"`
    AdditionalProperties *AdditionalProperties `xml:"t:AdditionalProperties"`
}

type AdditionalProperties struct {
    FieldURIs []FieldURI `xml:"t:FieldURI"`
}

type FieldURI struct {
    FieldURI string `xml:"FieldURI,attr"`
}

type FolderIDs struct {
    DistinguishedFolderId *DistinguishedFolderId `xml:"t:DistinguishedFolderId"`
}

type DistinguishedFolderId struct {
    Id string `xml:"Id,attr"`
}

// 响应结构体定义
type FindItemResponse struct {
    XMLName xml.Name `xml:"m:FindItemResponse"`
    ResponseMessages *ResponseMessages `xml:"m:ResponseMessages"`
}

type ResponseMessages struct {
    FindItemResponseMessage *FindItemResponseMessage `xml:"m:FindItemResponseMessage"`
}

type FindItemResponseMessage struct {
    ResponseCode string `xml:"m:ResponseCode"`
    RootFolder  *RootFolder `xml:"m:RootFolder"`
}

type RootFolder struct {
    TotalItemsInView int     `xml:"TotalItemsInView,attr"`
    IncludesLastItemInRange bool    `xml:"IncludesLastItemInRange,attr"`
    Items            *Items  `xml:"t:Items"`
}

type Items struct {
    Message []Message `xml:"t:Message"`
}

type Message struct {
    ItemId *ItemId `xml:"t:ItemId"`
    Subject string `xml:"t:Subject"`
    DateTimeReceived string `xml:"t:DateTimeReceived"`
}

type ItemId struct {
    Id        string `xml:"Id,attr"`
    ChangeKey string `xml:"ChangeKey,attr"`
}

func main() {
    // 创建 EWS 客户端
    client, err := ews.NewClient(
        "https://outlook.office365.com/EWS/Exchange.asmx",
        "username", // 不能加 @xxx
        "password",
        &ews.Config{
            // NTLM 认证配置
            NTLM: true,            // 启用 NTLM 认证
            SkipTLS: false,       // 是否跳过 SSL 验证
        },
    )
    if err != nil {
        log.Fatal("创建客户端失败:", err)
    }

    // 构建查找最近10条邮件的请求
    req := &FindItemRequest{
        Traversal: "Shallow",
        ItemShape: &ItemShape{
            BaseShape: "IdOnly",
            AdditionalProperties: &AdditionalProperties{
                FieldURIs: []FieldURI{
                    {FieldURI: "item:Subject"},
                    {FieldURI: "item:DateTimeReceived"},
                },
            },
        },
        IndexedPageItemView: &IndexedPageItemView{
            MaxEntriesReturned: 10,  // 限制返回10条
            Offset: 0,               // 从第一条开始
            BasePoint: "Beginning",  // 从开始位置计算偏移
        },
        ParentFolderIds: &FolderIDs{
            DistinguishedFolderId: &DistinguishedFolderId{
                Id: "inbox",
            },
        },
        SortOrder: &SortOrder{
            FieldOrder: &FieldOrder{
                Order: "Descending",                    // 降序排序
                FieldURI: &FieldURI{
                    FieldURI: "item:DateTimeReceived", // 按接收时间排序
                },
            },
        },
    }

    // 将请求结构体转换为 XML
    xmlData, err := xml.MarshalIndent(req, "", "  ")
    if err != nil {
        log.Fatal("生成 XML 失败:", err)
    }

    // 打印生成的 XML 请求体
    log.Printf("生成的 XML 请求体:\n%s\n", xmlData)

    // 发送请求到 Exchange 服务器并解析响应
    resp := &FindItemResponse{}
    err = client.DoAndParse(req, resp)
    if err != nil {
        log.Fatal("请求失败:", err)
    }

    // 处理响应数据
    if resp.ResponseMessages != nil &&
        resp.ResponseMessages.FindItemResponseMessage != nil &&
        resp.ResponseMessages.FindItemResponseMessage.RootFolder != nil &&
        resp.ResponseMessages.FindItemResponseMessage.RootFolder.Items != nil {
        
        // 遍历邮件列表
        for _, msg := range resp.ResponseMessages.FindItemResponseMessage.RootFolder.Items.Message {
            log.Printf("邮件主题: %s, 接收时间: %s\n", msg.Subject, msg.DateTimeReceived)
        }

        // 打印统计信息
        log.Printf("总计找到 %d 封邮件\n", 
            resp.ResponseMessages.FindItemResponseMessage.RootFolder.TotalItemsInView)
    }
}

在 Golang 示例中,我们使用了第三方库 github.com/mhewedy/ews 来实现 EWS 客户端。这个库提供了基本的接口来与 Exchange 服务器进行交互,并支持 NTLM 认证。但需要注意以下限制:

  1. 功能限制

    • 目前不支持邮件列表操作
    • 不支持附件处理
    • 其他高级功能可能需要自己实现
  2. 协议扩展

    • 对于不支持的功能,需要自己定义相应的请求和响应结构体
    • 需要了解 EWS SOAP 协议的具体细节
    • 可能需要参考 Microsoft 的 EWS 文档来实现额外功能

使用前需要通过以下命令安装:

1
go get github.com/mhewedy/ews

总结

EWS 是一个强大的工具,为开发人员提供了丰富的 API 来与 Exchange 服务器进行交互。通过 EWS,我们可以构建各种企业级应用程序,实现邮件系统的自动化和集成。虽然 Microsoft 现在推荐使用 Microsoft Graph API 作为新项目的首选,但在许多企业环境中,EWS 仍然是一个重要且广泛使用的接口。

0%