java邮箱插件,初步实现 Mail 插件 —— 收取邮件

在上篇中描述了发送邮件的主要过程,今天我想和大家分享一下 Smart Mail 插件的另外一个功能 —— 收取邮件,可能没有发送邮件那么常用。

在具体描述如何实现收取邮件之前,有必要对发送邮件与收取邮件各定义一个接口,为了功能更加清晰。

比如,对于发送邮件,我们可以这样定义:

public interface MailSender {

void addCc(String[] cc);

void addBcc(String[] bcc);

void addAttachment(String path);

void send();

}

对该接口提供一个抽象实现类,也就是上篇说到的使用模板方法的那个类了。

public abstract class AbstractMailSender implements MailSender {

...

}

该抽象类对开发人员是透明的,开发人员只需要知道 MailSender 接口,以及它的两个具体实现类 TextMailSender、HtmlMailSender 即可。

同理,也需要对收取邮件定义一个接口,收取邮件的实现过程正是开始。

第一步:定义一个邮件收取接口

public interface MailFetcher {

List fetch(int count);

MailInfo fetchLatest();

}

以上定义了两个接口方法:收取指定数量的邮件;收取最新一封邮件。通过 MailInfo 类将邮件信息做一个封装,它的数据结构是怎样的呢?

第二步:定义一个 JavaBean 以封装邮件信息

public class MailInfo extends BaseBean {

private String subject;

private String content;

private String from;

private String[] to;

private String[] cc;

private String[] bcc;

private String date;

// getter/setter...

}

想必以上这些字段大家都能理解,或许这里还缺少了 attachment(附件),目前暂未实现,如果将来有业务需求,可考虑以后进行扩展。

第三步:实现邮件收取接口

目前主要有两种收取邮件的协议,分别是:POP3 与 IMAP,前者使用广泛,后者功能强大。不管使用哪种协议,对于 JavaMail 而言,都有相应的支持。可惜 Apache Commons Email 组件并没有对收取邮件提供一个优雅的实现方案,我们只能有限地使用它,更多地还是扩展 JavaMail 了。其实也只能使用 Apache Commons Email 的 MimeMessageParser 了,用于解析邮件内容。

下面的代码稍微有些多,我将分块进行描述。

public class DefaultMailFetcher implements MailFetcher {

private static final Logger logger = Logger.getLogger(DefaultMailFetcher.class);

// 获取协议名(pop3 或 imap)

private static final String PROTOCOL = MailConstant.Fetcher.PROTOCOL;

private final String username;

private final String password;

public DefaultMailFetcher(String username, String password) {

this.username = username;

this.password = password;

}

...

由于是收取邮件,那么就需要提供某个账号的登录方式,比如 username 与 password,这样才能收取该账号的邮件,所以这里提供了两个必填字段,并且在构造器中进行初始化。此外,还从常量类中获取了指定的协议名,其实都是在配置文件里进行管理的,本文最后将统一给出。

随后需要的是实现接口里的那两个方法:

...

@Override

public List fetch(int count) {

// 创建 Session

Session session = createSession();

// 创建 MailInfo 列表

List mailInfoList = new ArrayList();

// 收取邮件

Store store = null;

Folder folder = null;

try {

// 获取 Store,并连接 Store(登录)

store = session.getStore(PROTOCOL);

store.connect(username, password);

// 获取 Folder(收件箱)

folder = store.getFolder(MailConstant.Fetcher.FOLDER);

// 判断是 只读方式 还是 读写方式 打开收件箱

if (MailConstant.Fetcher.FOLDER_READONLY) {

folder.open(Folder.READ_ONLY);

} else {

folder.open(Folder.READ_WRITE);

}

// 获取邮件总数

int size = folder.getMessageCount();

// 获取并遍历邮件列表

Message[] messages = folder.getMessages();

if (ArrayUtil.isNotEmpty(messages)) {

for (int i = size - 1; i > size - count - 1; i--) {

// 创建并累加 MailInfo

Message message = messages[i];

if (message instanceof MimeMessage) {

MailInfo mailInfo = createMailInfo((MimeMessage) message);

mailInfoList.add(mailInfo);

}

}

}

} catch (Exception e) {

logger.error("错误:收取邮件出错!", e);

} finally {

try {

// 关闭收件箱

if (folder != null) {

folder.close(false);

}

// 注销

if (store != null) {

store.close();

}

} catch (MessagingException e) {

logger.error("错误:释放资源出错!", e);

}

}

return mailInfoList;

}

@Override

public MailInfo fetchLatest() {

List mailInfoList = fetch(1);

return CollectionUtil.isNotEmpty(mailInfoList) ? mailInfoList.get(0) : null;

}

...

可见,实现部分是将 JavaMail API 的一个封装,获取指定数量的邮件其实是根据发送日期进行了一个倒序排列(注意 for 循环中的 i 是从后往前递减的),而获取最新一封邮件实际上是前者的一个特例。

以上用到了一些私有方法,现描述如下:

...

private Session createSession() {

// 初始化 Session 配置项

Properties props = new Properties();

// 判断是否支持 SSL 连接

if (MailConstant.Fetcher.IS_SSL) {

props.put("mail." + PROTOCOL + ".ssl.enable", true);

}

// 设置 主机名 与 端口号

props.put("mail." + PROTOCOL + ".host", MailConstant.Fetcher.HOST);

props.put("mail." + PROTOCOL + ".port", MailConstant.Fetcher.PORT);

// 创建 Session

Session session = Session.getDefaultInstance(props);

// 判断是否开启 debug 模式

if (MailConstant.IS_DEBUG) {

session.setDebug(true);

}

return session;

}

private String[] parseTo(MimeMessageParser parser) throws Exception {

return doParse(parser.getTo());

}

private String[] parseCc(MimeMessageParser parser) throws Exception {

return doParse(parser.getCc());

}

private String[] parseBcc(MimeMessageParser parser) throws Exception {

return doParse(parser.getBcc());

}

private String[] doParse(List

addressList) {

List list = new ArrayList();

if (CollectionUtil.isNotEmpty(addressList)) {

for (Address address : addressList) {

list.add(MailUtil.decodeEmailAddress(address.toString()));

}

}

return list.toArray(new String[0]);

}

private MailInfo createMailInfo(MimeMessage message) throws Exception {

// 创建 MailInfo

MailInfo mailInfo = new MailInfo();

// 解析邮件内容

MimeMessageParser parser = new MimeMessageParser(message).parse();

// 设置 MailInfo 相关属性

mailInfo.setSubject(parser.getSubject());

if (parser.hasHtmlContent()) {

mailInfo.setContent(parser.getHtmlContent());

} else if (parser.hasPlainContent()) {

mailInfo.setContent(parser.getPlainContent());

}

mailInfo.setFrom(parser.getFrom());

mailInfo.setTo(parseTo(parser));

mailInfo.setCc(parseCc(parser));

mailInfo.setBcc(parseBcc(parser));

mailInfo.setDate(DateUtil.formatDatetime(message.getSentDate().getTime()));

return mailInfo;

}

}

在编写代码时,建议大家保持方法的简短,将可以重用的代码或业务独立的代码抽取为私有方法,这也是《重构-改善既有代码的设计》这本书里一再强调的地方。

如何使用 MailFetcher 这个接口呢?

第四步:收取邮件测试

public class FetchMailTest {

private static final String username = "hy_think@163.com";

private static final String password = "xxx";

private static final MailFetcher mailFetcher = new DefaultMailFetcher(username, password);

@Test

public void fetchTest() {

List mailInfoList = mailFetcher.fetch(5);

for (MailInfo mailInfo : mailInfoList) {

System.out.println(mailInfo.getSubject());

}

}

@Test

public void fetchLatestTest() {

MailInfo mailInfo = mailFetcher.fetchLatest();

System.out.println(mailInfo.getSubject());

}

}

可见,只需要提供 username 与 password,就可以使用 MailFetcher 了。

经过这两篇文章,大致描述了一下发送与收取邮件的主要开发过程,当然上面的都是主角,还有写配角也提供了重要的作用,现描述如下:

config-mail.properties

通过一个 properties 文件提供邮件相关配置。

mail.is_debug=false

sender.protocol=smtp

sender.protocol.ssl=true

sender.protocol.host=smtp.163.com

sender.protocol.port=465

sender.from=管理员

sender.auth=true

sender.auth.username=huang_yong_2006@163.com

sender.auth.password=xxx

fetcher.protocol=pop3

fetcher.protocol.ssl=true

fetcher.protocol.host=pop.163.com

fetcher.protocol.port=995

fetcher.folder=INBOX

fetcher.folder.readonly=true

MailConstant.java

通过一个常量类(实际上是一个接口),获取 config-mail.properties 文件中相关配置项,方便在代码中使用。

public interface MailConstant {

Properties config = FileUtil.loadPropFile("config-mail.properties");

boolean IS_DEBUG = CastUtil.castBoolean(config.getProperty("mail.is_debug"));

interface Sender {

String PROTOCOL = config.getProperty("sender.protocol");

boolean IS_SSL = CastUtil.castBoolean(config.getProperty("sender.protocol.ssl"));

String HOST = config.getProperty("sender.protocol.host");

int PORT = CastUtil.castInt(config.getProperty("sender.protocol.port"));

String FROM = config.getProperty("sender.from");

boolean IS_AUTH = CastUtil.castBoolean(config.getProperty("sender.auth"));

String AUTH_USERNAME = config.getProperty("sender.auth.username");

String AUTH_PASSWORD = config.getProperty("sender.auth.password");

}

interface Fetcher {

String PROTOCOL = config.getProperty("fetcher.protocol");

boolean IS_SSL = CastUtil.castBoolean(config.getProperty("fetcher.protocol.ssl"));

String HOST = config.getProperty("fetcher.protocol.host");

int PORT = CastUtil.castInt(config.getProperty("fetcher.protocol.port"));

String FOLDER = config.getProperty("fetcher.folder");

boolean FOLDER_READONLY = CastUtil.castBoolean(config.getProperty("fetcher.folder.readonly"));

}

}

MailUtil.java

通过一个工具类,将代码中比较通用的功能进行封装,这里主要提供了邮箱地址的编码与解码方法。由于考虑到邮箱地址中如果出现中文,可能会导致乱码。

public class MailUtil {

private static final Logger logger = Logger.getLogger(MailUtil.class);

// 定义一个邮箱地址的正则表达式:姓名

private static final Pattern pattern = Pattern.compile("(.+)(<.>)");

private static enum CodecType {

ENCODE, DECODE

}

// 编码邮箱地址

public static String encodeAddress(String address) {

return codec(CodecType.ENCODE, address);

}

// 解码邮箱地址

public static String decodeAddress(String address) {

return codec(CodecType.DECODE, address);

}

private static String codec(CodecType codecType, String address) {

// 需要对满足匹配条件的邮箱地址进行 UTF-8 编码,否则姓名将出现中文乱码

Matcher addressMatcher = pattern.matcher(address);

if (addressMatcher.find()) {

try {

if (codecType == CodecType.ENCODE) {

address = MimeUtility.encodeText(addressMatcher.group(1), "UTF-8", "B") + addressMatcher.group(2);

} else {

address = MimeUtility.decodeText(addressMatcher.group(1)) + addressMatcher.group(2);

}

} catch (UnsupportedEncodingException e) {

logger.error("错误:邮箱地址编解码出错!", e);

}

}

return address;

}

}

到目前为止,Smart Email 插件的开发过程已全部结束,欢迎您的点评,并期待您的建议!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值