项目中有需要对outlook 日历进行管理的需求,熟悉outlook的同学提醒用EWS的方式对日历进行创建,更新跟取消操作,度娘了一圈,发现还可以通过ics的方式操作outlook日历,可能不想走寻常路吧,所以采用了第二种方式,支持日历操作,已经测试通过,记录下以免忘记也是方便他人。
第一步
引入lombok和javaMail相关的jar
<!-- lombok 简化代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.5.0-b01</version> </dependency>
第二步
核心代码,日历功能由方式sendCalendarMail实现
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CalendarMail {
public static final String SCHEDULED = "SCHEDULED";
public static final String RECURRING = "RECURRING";
/**
* 发起者
*/
private String organizerAddress;
/**
* 发起者显示名
*/
private String organizerName;
/**
* 接收人
*/
private String[] toAddresses;
/**
* 主题
*/
private String subject;
/**
* 地点
*/
private String location;
/**
* 开始时间
*/
private String startTime;
/**
* 兼容旧版本
*/
private String endTime;
/**
* 时长
*/
private Integer duration;
/**
* 类型
* SCHEDULED、RECURRING
*/
private String type;
/**
* 如果 type = RECURRING,此字段生效,用于填充周期信息
*/
private CalendarRecurrence recurrence;
/**
* 邮件正文
*/
private String content;
/**
* 会议取消/更新
*/
private String cancelId;
/**
* METHOD:CANCEL 取消会议 METHOD:REQUEST 创建和更新会议
*/
private String method = "REQUEST";
}
import javax.activation.CommandMap;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.util.ByteArrayDataSource;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
/**
* 简单邮件(以HTML带附件的邮件)发送器
*/
@Service("SimpleMailSender")
public class SimpleMailSender implements IMailSender {
@QConfig("config.properties")
private Properties config;
/**
* 以文本格式发送邮件
*
* @param toAddress 收件人列表
* @param subject 标题
* @param content 内容
*/
public void sendTextMail(List<String> toAddress, List<String> ccAddress, String subject, String content) {
this.sendMail(toAddress, ccAddress, subject, content, true, null, null);
}
/**
* 以HTML格式发送邮件
*
* @param toAddress 收件人列表
* @param subject 标题
* @param content 内容
*/
public void sendHtmlMail(List<String> toAddress, List<String> ccAddress, String subject, String content) {
this.sendMail(toAddress, ccAddress, subject, content, false, null, null);
}
/**
* 发邮件
*
* @param toAddress 收件人列表
* @param subject 标题
* @param content 内容
* @param textType:true是text类型发送,false是html类型发送
*/
public void sendMail(List<String> toAddress, List<String> ccAddress, String subject, String content,
boolean textType, String[] attachFileNames, List<Image> imageList) {
MailSenderInfo mailInfo = new MailSenderInfo();
mailInfo.setMailServerHost(config.getProperty("mailServerHost"));
mailInfo.setMailServerPort(config.getProperty("mailServerPort"));
mailInfo.setFromAddress(config.getProperty("mailFromAddress"));
mailInfo.setUserName(config.getProperty("mailUserName"));
mailInfo.setPassword(
new AES256().decrypt(config.getProperty("mailPassword"))
);
mailInfo.setValidate(true);
mailInfo.setToAddress(toAddress);
mailInfo.setCcAddress(ccAddress);
mailInfo.setSubject(subject);
mailInfo.setContent(content);
mailInfo.setImagelist(imageList);
if (textType) {
this.sendTextMail(mailInfo);
} else {
mailInfo.setAttachFileNames(attachFileNames);
this.sendHtmlMail(mailInfo);
}
}
/**
* 以文本格式发送邮件
*
* @param mailInfo 待发送的邮件的信息
*/
public void sendTextMail(MailSenderInfo mailInfo) {
// 判断是否需要身份认证
MyAuthenticator authenticator = null;
Properties pro = mailInfo.getProperties();
if (mailInfo.isValidate()) {
// 如果需要身份认证,则创建一个密码验证器
authenticator = new MyAuthenticator(mailInfo.getUserName(),
mailInfo.getPassword());
}
// 根据邮件会话属性和密码验证器构造一个发送邮件的session
Session sendMailSession = Session
.getDefaultInstance(pro, authenticator);
try {
// 根据session创建一个邮件消息
Message mailMessage = new MimeMessage(sendMailSession);
// 创建邮件发送者地址
Address from = new InternetAddress(mailInfo.getFromAddress());
// 设置邮件消息的发送者
mailMessage.setFrom(from);
// 创建邮件的接收者、抄送人地址,并设置到邮件消息中
List<String> toList = mailInfo.getToAddress();
if (null != toList && !toList.isEmpty()) {
Address[] tos = new Address[toList.size()];
Address to = null;
int countTo = 0;
for (String str : toList) {
to = new InternetAddress(str);
tos[countTo] = to;
countTo++;
}
List<String> ccList = mailInfo.getCcAddress();
if (null != ccList && !ccList.isEmpty()) {
Address[] ccs = new Address[ccList.size()];
Address cc = null;
int countCC = 0;
for (String str : ccList) {
cc = new InternetAddress(str);
ccs[countCC] = cc;
countCC++;
}
mailMessage.setRecipients(Message.RecipientType.CC, ccs);
}
mailMessage.setRecipients(Message.RecipientType.TO, tos);
// 设置邮件消息的主题
mailMessage.setSubject(mailInfo.getSubject());
// 设置邮件消息发送的时间
mailMessage.setSentDate(new Date());
// 设置邮件消息的主要内容
String mailContent = mailInfo.getContent();
mailMessage.setText(mailContent);
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);
// 发送邮件
Transport.send(mailMessage);
} else {
throw new RuntimeException("邮件接收人不能为空!");
}
} catch (MessagingException ex) {
Cat.logError(ex);
throw new RuntimeException(ex.getMessage(), ex);
}
}
/**
* 以HTML格式发送邮件
*
* @param mailInfo 待发送的邮件信息
*/
public void sendHtmlMail(MailSenderInfo mailInfo) {
// 判断是否需要身份认证
MyAuthenticator authenticator = null;
Properties pro = mailInfo.getProperties();
// 如果需要身份认证,则创建一个密码验证器
if (mailInfo.isValidate()) {
authenticator = new MyAuthenticator(mailInfo.getUserName(),
mailInfo.getPassword());
}
// 根据邮件会话属性和密码验证器构造一个发送邮件的session
Session sendMailSession = Session
.getDefaultInstance(pro, authenticator);
try {
// 根据session创建一个邮件消息
Message mailMessage = new MimeMessage(sendMailSession);
// 创建邮件发送者地址
Address from = new InternetAddress(mailInfo.getFromAddress());
// 设置邮件消息的发送者
mailMessage.setFrom(from);
// 创建邮件的接收者和抄送者地址,并设置到邮件消息中
List<String> toList = mailInfo.getToAddress();
if (null != toList && !toList.isEmpty()) {
Address[] tos = new Address[toList.size()];
Address to = null;
int countTo = 0;
for (String str : toList) {
to = new InternetAddress(str);
tos[countTo] = to;
countTo++;
}
List<String> ccList = mailInfo.getCcAddress();
if (null != ccList && !ccList.isEmpty()) {
Address[] ccs = new Address[ccList.size()];
Address cc = null;
int countCC = 0;
for (String str : ccList) {
cc = new InternetAddress(str);
ccs[countCC] = cc;
countCC++;
}
// 添加抄送人
mailMessage.setRecipients(Message.RecipientType.CC, ccs);
}
// Message.RecipientType.TO属性表示接收者的类型为TO
mailMessage.setRecipients(Message.RecipientType.TO, tos);
// 设置邮件消息的主题
mailMessage.setSubject(mailInfo.getSubject());
// 设置邮件消息发送的时间
mailMessage.setSentDate(new Date());
// MiniMultipart类是一个容器类,包含MimeBodyPart类型的对象
MimeMultipart mainPart = new MimeMultipart();
// 创建一个包含HTML内容的MimeBodyPart
BodyPart html = new MimeBodyPart();
// 设置HTML内容
html.setContent(mailInfo.getContent(), "text/html; charset=utf-8");
mainPart.addBodyPart(html);
// 向Multipart添加附件
String[] attachFileNames = mailInfo.getAttachFileNames();
if (null != attachFileNames && attachFileNames.length > 0) {
for (String fileName : attachFileNames) {
if (StringUtils.isBlank(fileName)) {
continue;
}
MimeBodyPart mbpFile = new MimeBodyPart();
FileDataSource fds = new FileDataSource(fileName);
mbpFile.setDataHandler(new DataHandler(fds));
try {
//乱码
mbpFile.setFileName(MimeUtility.encodeWord(fds.getName()));
/**
* 乱码
* String filename= new String(fds.getName().getBytes(),"ISO-8859-1");
* mbpFile.setFileName(filename);
*/
// 向MimeMessage添加(Multipart代表附件)
mainPart.addBodyPart(mbpFile);
} catch (UnsupportedEncodingException e) {
Cat.logError(e);
}
}
}
mailInfo.setAttachFileNames(null);
if (null != mailInfo.getImagelist() && !mailInfo.getImagelist().isEmpty()) {
for (Image im : mailInfo.getImagelist()) {
MimeBodyPart img = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource(im.getImagePath()));
img.setDataHandler(dh);
img.setContentID(im.getImageName());
mainPart.addBodyPart(img);
}
mainPart.setSubType("related");
}
// 将MiniMultipart对象设置为邮件内容
mailMessage.setContent(mainPart);
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);
// 发送邮件
Transport.send(mailMessage);
} else {
throw new RuntimeException("邮件接收人不能为空!");
}
} catch (MessagingException ex) {
Cat.logError(ex);
throw new RuntimeException(ex.getMessage(), ex);
}
}
@Override
public String sendCalendarMail(CalendarMail calendarMail) {
if (null == calendarMail.getDuration()) {
calendarMail.setDuration((int) (DateUtils.getDateSub(calendarMail.getStartTime(), calendarMail.getEndTime()) / NumConstants.NUM_1000) / NumConstants.NUM_60);
}
Properties props = new Properties();
props.put("mail.smtp.port", config.getProperty("mailServerPort"));
props.put("mail.smtp.host", config.getProperty("mailServerHost"));
props.put("mail.smtp.auth", "true");
Authenticator authenticator = new MyAuthenticator(config.getProperty("mailUserName"), new AES256().decrypt(config.getProperty("mailPassword")));
Session session = Session.getInstance(props, authenticator);
MimeMessage message = new MimeMessage(session);
try {
message.setFrom(new InternetAddress(calendarMail.getOrganizerAddress(), calendarMail.getOrganizerName()));
StringBuilder messageBody = new StringBuilder();
StringBuilder attendees = new StringBuilder();
Address[] tos = new Address[calendarMail.getToAddresses().length];
int count = 0;
for (String to : calendarMail.getToAddresses()) {
attendees.append(String.format("ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:%s\n", to));
tos[count] = new InternetAddress(to);
count++;
}
message.setRecipients(Message.RecipientType.TO, tos);
message.setSubject(calendarMail.getSubject());
Multipart multipart = new MimeMultipart();
BodyPart htmlContent = new MimeBodyPart();
htmlContent.setContent(calendarMail.getContent(), "text/html; charset=utf-8");
multipart.addBodyPart(htmlContent);
String startTime = calendarMail.getStartTime().replace("-", "").replace(" ", "T").replace(":", "");
if (StringUtils.isBlank(startTime)) {
return null;
}
String mailId = StringUtils.isBlank(calendarMail.getCancelId()) ? UUID.randomUUID().toString() : calendarMail.getCancelId();
messageBody.append(String.format("BEGIN:VCALENDAR\n"
+ "PRODID:-//Microsoft Corporation//Outlook 9.0 MIMEDIR//EN\n"
+ "VERSION:2.0\n"
+ "METHOD:%s\n" //METHOD:CANCEL 取消会议 METHOD:REQUEST 创建和更新会议
+ "BEGIN:VEVENT\n"
+ "%s"
+ "DTSTART;TZID=Asia/Shanghai:%s\n"
+ "DURATION:%s\n"
+ "%s"
+ "LOCATION:%s\n"
+ "UID:%s\n"
+ "SEQUENCE:1\n"
+ "CLASS:PUBLIC\n" + "BEGIN:VALARM\n"
+ "TRIGGER:-PT15M\n" + "ACTION:DISPLAY\n"
+ "DESCRIPTION:Reminder\n" + "END:VALARM\n"
+ "END:VEVENT\n" + "END:VCALENDAR"
//, StringUtils.isBlank(calendarMail.getCancelId()) ? "REQUEST" : "CANCEL"
, StringUtils.isBlank(calendarMail.getMethod()) ? "REQUEST" : calendarMail.getMethod()
, attendees
, startTime
, createCalendarDuration(calendarMail.getDuration())
, createRecurringInfo(calendarMail)
, calendarMail.getLocation()
, mailId));
Cat.logEvent("CalendarMail", StringUtils.isBlank(calendarMail.getMethod()) ? "REQUEST" : "CANCEL" + "-" + mailId);
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(new DataHandler(new ByteArrayDataSource(messageBody.toString(),
"text/calendar;method=REQUEST;charset=\"UTF-8\"")));
// 添加会议邀请内容
multipart.addBodyPart(messageBodyPart);
message.setContent(multipart, "text/plain;charset=UTF-8");
Transport.send(message);
return mailId;
} catch (Exception e) {
Cat.logError(e);
}
return null;
}
/**
* 配置时长信息
*
* @param duration duration
* @return str
*/
private String createCalendarDuration(Integer duration) {
if (null == duration) {
duration = NumConstants.NUM_60;
}
int hour = duration / NumConstants.NUM_60;
int min = duration - hour * NumConstants.NUM_60;
return String.format("PT%sH%sM", hour, min);
}
/**
* 配置周期会议信息
*
* @param calendarMail calendar info
* @return str
*/
private String createRecurringInfo(CalendarMail calendarMail) {
if (!CalendarMail.RECURRING.equals(calendarMail.getType()) || null == calendarMail.getRecurrence()) {
return "";
}
CalendarRecurrence recurrence = calendarMail.getRecurrence();
int type = recurrence.getType();
// 结束时间
String until = recurrence.getEndDateTime()
.replace("-", "")
.replace(" ", "T")
.replace(":", "") + "Z";
// freq
String freq = CalendarRecurrence.typeMap.get(type);
String base = String.format("RRULE:FREQ=%s;INTERVAL=%s;UNTIL=%s", freq, recurrence.getRepeatInterval(), until);
switch (type) {
case NumConstants.NUM_1:
return base + "\n";
case NumConstants.NUM_11:
// 这里在日历上的提示是周一到周五,而不是每个工作日
return String.format("%s;BYDAY=MO,TU,WE,TH,FR;WKST=SU\n", base);
case NumConstants.NUM_2:
return String.format("%s;WKST=SU;BYDAY=%s\n", base, CalendarRecurrence.weekdayMap.get(recurrence.getWeeklyDay()));
case NumConstants.NUM_3:
return String.format("%s;BYMONTHDAY=%s\n", base, recurrence.getMonthlyDay());
default:
return "";
}
}
@Override
public void sendHtmlMail(List<String> toAddress, List<String> ccAddress, String subject,
String content, String[] attachFileNames, List<Image> imageList) {
this.sendMail(toAddress, ccAddress, subject, content, false, attachFileNames, imageList);
}
}