关于项目
这是我的毕业设计(2018),邮件客户端
包含收发邮件、通讯录、多账户登录、本地数据保存等功能
使用的相关模块
- 用vue-cli构建electron-vue项目
- 用node-imap模块接收邮件
- 用nodemailer发送邮件
- 用element-ui做样式框架
- 用lowdb做本地数据存储
- 用iconv-lite、quoted-printable、utf8等处理编码
- 用vue-quill-editor做富文本编辑器
调试运行
|
|
页面截图
项目目录
最外层结构是由electron-vue创建,主要看src的结构
|
|
开发过程
关于electron和vue
electron将chromium和nodejs合并到同一运行时环境中,可以用html、css、javascript来构建跨平台的桌面应用。说白了就是我们写网页的同时还可以调用nodejs的api(如调用fs模块存储数据到电脑),然后electron帮我们打包成一个跨平台的桌面应用。
vue是当前主流mvvm框架之一,这里就不多介绍了,用到了vuex、router等,不懂的话需要先去了解一下才能看懂项目
本项目用vue-vli初始化electron-vue,开发方便
|
|
打包选的是electron-builder,这个工具可以直接打包安装包,而electron-packager打包成可执行文件
项目思路分解
项目主体是邮件,比较重要的有四步:获取解析邮件、存储邮件、显示邮件和发送邮件
获取和发送邮件要根据邮件协议来分析
获取与解析邮件
读取邮件的协议有pop3(Post Office Protocol)、imap(Internet Message Access Protocol)。pop3简单但交互性较弱。imap较复杂,可交互性强,是一个联机协议,如可以获取邮件后将邮件置为已读,而pop3协议是只读的。
如果要自己实现获取协议会比较麻烦,去github逛了一圈,发现node-imap这个库挺不错的,就用了它
关于密码要先去邮件服务器开通获取,如qq:邮箱->设置->账号->imap服务,开启(需要自己手动保存密码)
项目中获取邮件有两个方法:一个是获取一个完整的邮件(getEmailDetail),一个获取一组邮件头(getEmailList)。如在登陆成功后,会自动获取邮件列表显示出来,此时是调用getEmailList。当点击某一个邮件时,会自动获取一个完整的邮件,调用getEmailDetail。node-imap
这个库包含着两个功能,还支持很多不同参数,可自行去github熟悉。
下面重点讲解析一封邮件
邮件有邮件头和邮件体两部分。邮件头的格式基本都是一样的,而邮件体格式就多种多样了,因为有很多类型如:纯文本,html页面,包含附件等等
第一步是看Content-Type
- text,主要有text/html和text/plain,内容需要用Content-Transfer-Encoding解码,常见传输编码为base64和quoted-printable
- multipart,又分为mixed、alternative和related。multipart有boundary分割符,将邮件体分割成不同段
- mixed是有附件的类型
- alternative是纯文本和超文本同时存在的类型
- related是资源内嵌类型,如内容为html,但html里有图片,把图片提取出来以附件形式发送
- image、application,一般是出现在附件中的格式
第二步看boundary
只有multipart类型才有boundary,因为这种类型比较复杂,需要用boundary分段解析
|
|
这里的boundary是一串字符,但是分割不是直接用boundary,而是用父段和子段来分割
父段: '--' + boundary + '--'
子段: '--' + boundary
据我观察,父段只出现0次或1次并且在最后的位置(可能我遇到的邮件类型有限),所以内容就是分割后的数组的第一个元素:emailText = emailText.split(fatherBoundary)[0].trim()
子段将内容分为不同的段,每个子段需要单独重新解析,因为里面也有自己的Content-type
和boundary
,所以一封邮件可能出现两个不同的boundary
第三步解析
若分割后的段是html或附件等,直接根据charset和encoding等解析;若仍是multipart类型,则用同样的思路再次解析(关于编码解析下面有说)
下面举个例子(已删除部分不必要的):
|
|
分析:
- Content-type是
multipart/mixed
,说明是包含附件类型 - 父段出现在最后,分割后的数组取第一项即可
- 根据子段分割,段内有各自的boundary、Content-type和charset等。
- 第一段是一个alternative的小邮件,包含纯文本和超文本。再根据boundary分割即可
- 解析后可的到纯文本内容为
12\r\n3
- 解析后可的到超文本内容为
<div>12</div><div>3</div>
- 解析后可的到纯文本内容为
- 第二段是application类型附件,根据charset和encoding解析得到文件名是
邮件详情.png
- 第一段是一个alternative的小邮件,包含纯文本和超文本。再根据boundary分割即可
编写时要注意的问题
- 遇到过Content-type为
multipart/related;type="multipart/alternative";boundary="----=_NextPart_5A6951CD_6F185580_3879981A
,这样要算related,不能算alternative,按复杂的那个算 - 观察发现related类型和mixed类型的解析规则一样
- 有些邮件一些值不全(如没有charset),需要设置默认值
- base64值解析错误,是因为base64有换行符,需要去掉
存储邮件
分析了邮件的类型,那存储邮件就不难了。下面是刚解析完的邮件对象格式
|
|
我们需要根据邮件Content-type
进行转换再存储,不然的话就要在显示时在判断不同类型不同处理。显然存储前处理更好
- 若是html类型,则将bodyHtml单独存入一个文件,bodyHtml的值为文件路径。这里需要考虑有的html并不是完整页面而是一个片段
- 若是mixed类型,将attachment存入单独文件,同样存为文件路径
- 对于alternative和related,到这里已经不用单独考虑。因为alternative是纯文本和超文本共存,也就是重复的,超文本是html片段,包含格式,而纯文本只有文字,直保留超文本即可;related是和mixed解析规则一样,并需要将资源拼合成完整html,将html单独存储即可。
|
|
显示邮件
进行了很规则的存储,所以显示时逻辑就很清晰了
- bodyHtml以
.html
结尾,则是html路径,用webview的src引入 - bodyHtml不是路径,则将html片段插入
- 没有bodyHtml,则将bodyText插入
- 有attachment则显示,没有就不显示
|
|
发送邮件
发送邮件有stmp(Simple Mail Transfer Protocol),github有现成较成熟的nodemailer,无论发送html还是附件,都非常简单。
开发遇到的问题
编码
邮件最开始获取的是流,需要一个编码转为最初的字符串。我用的是gb18030解码
关于gb系列编码可以自行了解,简单提一下:最初只有ascii,中国想显示中文,就有了gb2312、gbk等,从简体中文慢慢加入繁体字等,最后更新的版本是gb18030,所以是gb系列直接用gb18030即可,因为它向下兼容。
解析最初的流好像用gb18030或utf-8都可以,因为各个部分都已经用base64或其他编码转为ascii码了。
|
|
上面的字符串反应了两个事:
1、字符集为gb18030,即本邮件由gb18030编码
2、B代表base64
,后面的字符用base64编码
解析的思路是先用base64转为buffer,在用gb18030字符集转为字符串
解析的方法是iconv.decode(iconv.encode('amlhbmJvKw==?=','base64'),'gb18030')
|
|
同样的道理,这是utf-8字符集的base64编码
解析的方法是iconv.decode(iconv.encode('B?6Zi/6YeM5LqR?=','base64'),'utf-8')
|
|
如果gb18030和utf-8混用了,那就出现乱码了,因为他们字符集不一样,同一个编码代表的文字不一样。
上面的第一行输出�����ɰ�����
。第二行输出闃块噷閮庨樋閲屼簯
对于boundary段内的内容如:
|
|
里面清楚写了字符集和传输编码,按它规则解析即可得到纯文本qwezxc
使用imap的node-imap相关
使用node-imap模块,一方面是较灵活,另一方面是可以同步状态。最大的问题是发现同步状态失败,根据文档下面这样就可以标记邮件已读,但是怎么都失败。。可能是支持性不够
|
|
文档api比较多,参数也多,但是很多得到的结果不一致,要多观察多测试,查出哪个是要用的api。
electron最小化,全屏按钮和无边框
默认的窗口是有系统边框的,我不喜欢,只要再创建渲染进程是配置去掉即可
|
|
没有了边框,就要手动添加界面拖动。
|
|
这样,header就可以拖动了。但要注意,只有写行内样式才起效。同时会导致里面标签的hover不触发,要想触发,就要将这个标签设置不可拖动
|
|
下面是最小化和全屏的部分代码
|
|
判断全屏还有browserWindow.isFullScreen()
,发现双击拖动栏全屏不能正确返回,browserWindow.isMaximized()
可以正确判断。
关于事件监听,要用addEventListener,不能用onresize,因为多个地方用到了resize,用onresize会相互覆盖
打包
项目是用electron-builder打包,第一次build要下载依赖。因为资源在墙外,要翻墙,否则报错。也可以尝试手动下载,根据命令行的提示下载对应的文件
npm应该设置镜像,配置文件为~/.npmrc
|
|
https://www.cnblogs.com/chenweixuan/p/7693718.html
http://blog.csdn.net/bailong1/article/details/78657605
其他
下图是硬盘存储结构
其中config.js存储所有用户和当前用户
每个邮箱目录都有一个index文件,存着各种邮件列表,具体html或附件都单独提取出来了
开始是用qq邮箱测试,之后用其他邮箱测试,基本是没问题的,因为大多数都是根据标准来收发邮件。测试了qq、163、aliyun等都基本没问题。(163需要多一个授权步骤)
项目缺点
项目做的比较粗糙,有些功能时不完善的。比如草稿箱,写邮件时的右侧快捷选择收件人,webview不能自适应高度等。功能也不多,如没有快捷回复邮件功能等。
总结
此次项目,业务不难,主要是邮件解析部分比较绕。我没有查阅权威的文档,所以可能有缺陷。
匹配字符串用到了大量正则表达式,我写的正则也不是很好
对于vue项目结构,vuex等知识不是重点,所以我一笔带过
以上就是对项目的总结,如果有错,望指正