在上篇中描述了发送邮件的主要过程,今天我想和大家分享一下 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 插件的开发过程已全部结束,欢迎您的点评,并期待您的建议!

java邮箱插件,初步实现 Mail 插件 —— 收取邮件相关推荐

  1. springboot mail插件/JavaMailSenderImpl 发邮件。163网易企业邮箱调用的坑。

    前言 spring boot 2.0.0.RELEASE spring 5.0.4.RELEASE 网易企业邮箱 2019年版 jdk 1.8 发邮件的说明 按照加密方式方式,可以分为: 明文 TLS ...

  2. 基于Java的Minecraft游戏后端自定义插件 的Java实践项目整理

    Minecraft_Plugin 1.项目概述 2.系统设计 3.功能模块成果展示 4.个人总结 1.项目概述 1.1 项目简介 小组准备编写一个具有武器战斗,道具收集.互动社交和任务悬赏四大系统的聚 ...

  3. 【Java 虚拟机原理】垃圾回收算法( Java VisualVM 工具 | 安装 Visual GC 插件 | 使用 Java VisualVM 分析 GC 内存 )

    文章目录 一.Java VisualVM 工具安装 Visual GC 插件 二.使用 Java VisualVM 分析 GC 内存 一.Java VisualVM 工具安装 Visual GC 插件 ...

  4. Java VisualVM 插件地址,安装Visual VM插件,修改下载插件地址使插件可以直接在JVisualVM中进行下载

    Java VisualVM 插件地址 打开Java VisualVM检查更新插件时,默认的连接连不上,通过浏览器访问之后发现默认的服务器已经404,新地址已经迁移到github,下面这个地址里面有不同 ...

  5. java代码自动生成的插件_如何使用插件生成自定义Java 8代码

    java代码自动生成的插件 大多数程序员讨厌的一件事就是编写样板代码. 无休止的时间花费在设置实体类和配置数据库连接上. 为了避免这种情况,您可以让Speedment Open Source之类的程序 ...

  6. vscode java settings设置_兼容vscode插件的主题服务

    背景介绍 今年有幸参与了 IDE 共建项目组,负责主题服务的设计实现.说到主题服务,我们可能立马会想到 VSCODE 丰富的主题生态.VSCODE 有着庞大的插件市场,主题插件是其中非常重要的一部分. ...

  7. java插件开发_编写一个IDEA插件之:自动生成Java代码

    我很喜欢IDEA的一键自动生成代码功能,例如自动生成构造方法.字段的Get/Set方法.ToString方法等等,除此之外,也有一些插件提供自动生成代码的功能,例如我们所熟悉的GsonFormat插件 ...

  8. 阿里插件机制android,Android插件 - 阿里規約 Alibaba Java Coding Guidelines

    因為項目開發完畢之后,涉及到項目的優化問題,除了資源的共性抽取和方法的抽取之外,還需規范我們的代碼,所以大牛推薦了Alibaba Java Coding Guidelines(阿里規約插件),本以為出 ...

  9. java自定义maven插件_自定义Maven插件

    第一.自定义Maven插件概述 Mojo:Maven plain Old Java Object.每一个 Mojo 就是 Maven 中的一个执行目标(executable goal),而插件则是对单 ...

最新文章

  1. FE.ES-终结0.1+0.2,答到点上的那种
  2. linux man命令无效,Linux man命令的具体使用
  3. python判断计算机是否有网络连接
  4. 八皇后问题的Java递归算法
  5. 程序猿,该在一个公司呆多久?
  6. 十分钟轻松搞懂CSS的五大定位方式!(建议收藏)
  7. Linux 文件相关的常用命令
  8. Python可视化库Matplotlib的使用
  9. LeetCode:每日一题(2020.4.8)
  10. python jupyter notebook 上传文件_使用jupyter notebook将文件保存为Markdown,HTML等文件格式...
  11. UISearchBar 点击取消回到原来位置时会跳动的解决方法
  12. 十大管理之项目干系人管理知识点
  13. LDOMs 也被叫做OVM-SPARC 常用的命令集合
  14. ip icmp flood 等 常见的攻击
  15. 芝加哥大学校长:诺贝尔奖人才是怎样炼成的?
  16. 2019年8月21日 星期三(韩天峰的个人简历)
  17. iOS获取设备ID总结
  18. vue实现考勤排班日历(备忘)
  19. KSO-Sqlserver事务的实现
  20. 开源风控系统radar部署

热门文章

  1. Java springboot压缩文件上传,解压,删除压缩包
  2. 新开普生产实习【智能家居灯控系统】
  3. mac linux 编译环境配置,mac开发环境配置
  4. 对于天天酷跑项目的思路基于java
  5. 【Qt5开发及实例】20、实现一个飞舞的蝴蝶
  6. 基于python的影评数据分析_[Python] 通过采集两万条数据,对《无名之辈》影评分析...
  7. Visual Studio Code debug Python 代码很牛逼。
  8. HistCite Pro简要使用说明
  9. oracle杨树,Oracle存储过程的格式
  10. 【Tomcat 】Tomcat 架构原理解析到架构设计借鉴