2018/12/20 更新:
这篇文章的同步方式存在缺陷,在仔细研究Exchange后有不少新的收获,找到了更好的解决方案,请移步这里观看~
需求功能
最近公司接到一个需求,将客户的 Exchange 服务器中的会议信息,同步到公司现有的会议管理系统中(MMS),为了实现这个需求,需要做以下几件事:
- 从 MMS 预定会议时,需要将会议信息推送到Exchange服务器中。
- 从Exchange预定会议时,在MMS系统中需要有一个监听机制(EWS 中称“通知”/Notifications),实时监测会议信息的变化,并将变化后的数据同步到MMS系统。
准备工作
获取 EWS Java API ,请移步:https://github.com/OfficeDev/ews-java-api
简单实现
1.获得 EWS 服务
传入参数为 Exchange 服务器的版本号,根据实际情况选择。
//在 Notifications 中,ExchangeVersion.Exchange2010 版本不支持流式通知,需要不断的执行 GetEvents 方法达到实时监听。
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);
//Exchange服务器的域名,使用ip可能会连接失败,如服务器在本地,没有域名可以在host文件中添加一条域名解析规则
String serverHost = "exchange.server.host"
//登录的账号和密码,账号一定是可以登录进去的账号,不一定是邮箱地址
ExchangeCredentials credentials = new WebCredentials("emailAddress", "password");
service.setCredentials(credentials);
//固定写法
service.setUrl(new URI("https://" + serverHost + "/ews/exchange.asmx"));
service.setCredentials(credentials);
service.setTraceEnabled(true);
2. 查询 Exchange 的会议信息
查找距现在24小时以内开始的所有会议邮件(Appoinement)
Date start = new Date();
Date end = new Date(start.getTime() + 1000*3600*24);
CalendarView cView = new CalendarView(start, end);
//指定要查看的邮箱
FolderId folderId = new FolderId(WellKnownFolderName.Calendar, new Mailbox(emailAddress));
FindItemsResults<Appointment> findResults = null;
try {
findResults = service.findAppointments(folderId, cView);
} catch (Exception e) {
e.printStackTrace();
}
ArrayList<Appointment> appointmentItems = findResults==null?null:findResults.getItems();
遍历结果,获取会议信息
for(Appointment ap:allAppointment){
ap.load();
String subject = ap.getSubject();
//如邮箱主题以“已取消”开头,说明该会议已经被取消
if(subject.startsWith("已取消")){
continue;
}
//得到HTML格式的内容,通过工具类提取body标签的内容
String html_body = ap.getBody().toString();
String body = DataUtils.getContentFromHtml(huml_body);
//会议的开始和结束时间
Date start = ap.getStart();
Date end = ap.getEnd();
//会议使用的资源
List<Attendee> resources = ap.getResources().getItems();
//参加会议的员工
List<Attendee> RequiredAttendees = ap.getRequiredAttendees().getItems();
List<Attendee> OptionalAttendees = ap.getOptionalAttendees().getItems();
}
从HTML提取body信息的工具类
public class DataUtils {
public static String getContentFromHtml(String content){
content = content.replaceAll("</?[^>]+>", ""); //剔出<html>的标签
content = content.replaceAll("<a>\\s*|\t|\r|\n</a>", "");
content = content.replaceAll(" ", "");
content = content.replaceAll("\n", "");
return content;
}
}
3. 向 Exchange 发送一条会议邮件
Appointment appointment = null;
try {
appointment = new Appointment(service);
appointment.setSubject("会议主题");
appointment.setBody(MessageBody.getMessageBodyFromText("会议消息体"));
appointment.setStart(new Date());
appointment.setEnd(new Date().getTime()+1000*3600*24);
appointment.setLocation("会议位置");
appointment.getResources().add("会议资源账号,如:meetingroom@company.com");
appointment.getRequiredAttendees().add("必须参加的员工的账号");
appointment.getOptionalAttendees().add("可选参加的员工的账号");
appointment.save();
appointment.update(ConflictResolutionMode.AutoResolve);
} catch (Exception e) {
e.printStackTrace();
}
4. 建立监听,接收会议变化事件的通知
try {
//将需要监听的事件作为参数传入构造方法
IAsyncResult asyncresult = service.beginSubscribeToPullNotificationsOnAllFolders(null, null, 5, null, EventType.NewMail, EventType.Created, EventType.Deleted);
PullSubscription subscription = service.endSubscribeToPullNotifications(asyncresult);
//由于博主Exchange服务器版本限制,只能通过定时调用GetEvent方法达到实时监听
TimerTask task = new TimerTask() {
@Override
public void run() {
try {
GetEventsResults events = subscription.getEvents();
Iterable<ItemEvent> itemEvents = events.getItemEvents();
for(ItemEvent itemEvent:itemEvents){
if(itemEvent.getEventType() == EventType.Created){
Item item = null;
try{
item = Item.bind(service,itemEvent.getItemId());
}catch (ServiceResponseException e){
//找不到该邮件
continue;
}
if(item instanceof Appointment){
Appointment appointment = Appointment.bind(service, item.getId());
//得到发生变化的会议,执行业务逻辑
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Timer().scheduleAtFixedRate(task, delay, 1 * 10 * 1000);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
遗留问题
- 监听效果不够优雅,使用这种异步订阅方式,在Exchange服务器中创建一个会议,会被触发两次。并且,修改会议或者取消会议,都会被 EventType.Created 响应一次。
- 如果使用同步订阅方式,得到的 itemID 不能和 Appoinement 绑定,即无法获取改动的会议信息。
参考资料: