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 插件的开发过程已全部结束,欢迎您的点评,并期待您的建议!
java邮箱插件,初步实现 Mail 插件 —— 收取邮件相关推荐
- springboot mail插件/JavaMailSenderImpl 发邮件。163网易企业邮箱调用的坑。
前言 spring boot 2.0.0.RELEASE spring 5.0.4.RELEASE 网易企业邮箱 2019年版 jdk 1.8 发邮件的说明 按照加密方式方式,可以分为: 明文 TLS ...
- 基于Java的Minecraft游戏后端自定义插件 的Java实践项目整理
Minecraft_Plugin 1.项目概述 2.系统设计 3.功能模块成果展示 4.个人总结 1.项目概述 1.1 项目简介 小组准备编写一个具有武器战斗,道具收集.互动社交和任务悬赏四大系统的聚 ...
- 【Java 虚拟机原理】垃圾回收算法( Java VisualVM 工具 | 安装 Visual GC 插件 | 使用 Java VisualVM 分析 GC 内存 )
文章目录 一.Java VisualVM 工具安装 Visual GC 插件 二.使用 Java VisualVM 分析 GC 内存 一.Java VisualVM 工具安装 Visual GC 插件 ...
- Java VisualVM 插件地址,安装Visual VM插件,修改下载插件地址使插件可以直接在JVisualVM中进行下载
Java VisualVM 插件地址 打开Java VisualVM检查更新插件时,默认的连接连不上,通过浏览器访问之后发现默认的服务器已经404,新地址已经迁移到github,下面这个地址里面有不同 ...
- java代码自动生成的插件_如何使用插件生成自定义Java 8代码
java代码自动生成的插件 大多数程序员讨厌的一件事就是编写样板代码. 无休止的时间花费在设置实体类和配置数据库连接上. 为了避免这种情况,您可以让Speedment Open Source之类的程序 ...
- vscode java settings设置_兼容vscode插件的主题服务
背景介绍 今年有幸参与了 IDE 共建项目组,负责主题服务的设计实现.说到主题服务,我们可能立马会想到 VSCODE 丰富的主题生态.VSCODE 有着庞大的插件市场,主题插件是其中非常重要的一部分. ...
- java插件开发_编写一个IDEA插件之:自动生成Java代码
我很喜欢IDEA的一键自动生成代码功能,例如自动生成构造方法.字段的Get/Set方法.ToString方法等等,除此之外,也有一些插件提供自动生成代码的功能,例如我们所熟悉的GsonFormat插件 ...
- 阿里插件机制android,Android插件 - 阿里規約 Alibaba Java Coding Guidelines
因為項目開發完畢之后,涉及到項目的優化問題,除了資源的共性抽取和方法的抽取之外,還需規范我們的代碼,所以大牛推薦了Alibaba Java Coding Guidelines(阿里規約插件),本以為出 ...
- java自定义maven插件_自定义Maven插件
第一.自定义Maven插件概述 Mojo:Maven plain Old Java Object.每一个 Mojo 就是 Maven 中的一个执行目标(executable goal),而插件则是对单 ...
最新文章
- FE.ES-终结0.1+0.2,答到点上的那种
- linux man命令无效,Linux man命令的具体使用
- python判断计算机是否有网络连接
- 八皇后问题的Java递归算法
- 程序猿,该在一个公司呆多久?
- 十分钟轻松搞懂CSS的五大定位方式!(建议收藏)
- Linux 文件相关的常用命令
- Python可视化库Matplotlib的使用
- LeetCode:每日一题(2020.4.8)
- python jupyter notebook 上传文件_使用jupyter notebook将文件保存为Markdown,HTML等文件格式...
- UISearchBar 点击取消回到原来位置时会跳动的解决方法
- 十大管理之项目干系人管理知识点
- LDOMs 也被叫做OVM-SPARC 常用的命令集合
- ip icmp flood 等 常见的攻击
- 芝加哥大学校长:诺贝尔奖人才是怎样炼成的?
- 2019年8月21日 星期三(韩天峰的个人简历)
- iOS获取设备ID总结
- vue实现考勤排班日历(备忘)
- KSO-Sqlserver事务的实现
- 开源风控系统radar部署
热门文章
- Java springboot压缩文件上传,解压,删除压缩包
- 新开普生产实习【智能家居灯控系统】
- mac linux 编译环境配置,mac开发环境配置
- 对于天天酷跑项目的思路基于java
- 【Qt5开发及实例】20、实现一个飞舞的蝴蝶
- 基于python的影评数据分析_[Python] 通过采集两万条数据,对《无名之辈》影评分析...
- Visual Studio Code debug Python 代码很牛逼。
- HistCite Pro简要使用说明
- oracle杨树,Oracle存储过程的格式
- 【Tomcat 】Tomcat 架构原理解析到架构设计借鉴