关于项目

这是我的毕业设计(2018),邮件客户端

包含收发邮件、通讯录、多账户登录、本地数据保存等功能

github:github.com/ooooevan/Vm…

使用的相关模块

  • 用vue-cli构建electron-vue项目
  • 用node-imap模块接收邮件
  • 用nodemailer发送邮件
  • 用element-ui做样式框架
  • 用lowdb做本地数据存储
  • 用iconv-lite、quoted-printable、utf8等处理编码
  • 用vue-quill-editor做富文本编辑器

调试运行

npm run dev    # 调试运行,localhost:9080npm run build  # 打包,安装包在build目录
复制代码

页面截图

项目目录

最外层结构是由electron-vue创建,主要看src的结构

─ src├── main│  ├── index.js                         #主进程,创建渲染进程├── models                              #定义模型,用于封装对象├── renrender                           #渲染进程,里面就是一个vue项目目录│  ├── common                           #一些重要的js函数与公共样式│      ├── javascript│          ├── cache.js                 #硬盘存取相关函数│          ├── config.js                #存放配置及正则表达式│          ├── getEmail.js              #获取email的函数│          ├── parseEmail.js            #解析email的函数│          ├── sendEmail.js             #发送email的函数│      ├── style│  ├── components                       #存放组件│  ├── pages                            #存放页面│  ├── router                           #路由│  ├── store                            #vuex的store相关文件│  ├── app.vue                          #vue页面最外层结构│  ├── main.js                          #vue项目入口├── index.ejs                           #electron页面入口
复制代码

开发过程

关于electron和vue

electron将chromium和nodejs合并到同一运行时环境中,可以用html、css、javascript来构建跨平台的桌面应用。说白了就是我们写网页的同时还可以调用nodejs的api(如调用fs模块存储数据到电脑),然后electron帮我们打包成一个跨平台的桌面应用。 vue是当前主流mvvm框架之一,这里就不多介绍了,用到了vuex、router等,不懂的话需要先去了解一下才能看懂项目

本项目用vue-vli初始化electron-vue,开发方便

vue init simulatedgreg/electron-vue Vmail
复制代码

打包选的是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

  1. text,主要有text/html和text/plain,内容需要用Content-Transfer-Encoding解码,常见传输编码为base64和quoted-printable
  2. multipart,又分为mixed、alternative和related。multipart有boundary分割符,将邮件体分割成不同段
    • mixed是有附件的类型
    • alternative是纯文本和超文本同时存在的类型
    • related是资源内嵌类型,如内容为html,但html里有图片,把图片提取出来以附件形式发送
  3. image、application,一般是出现在附件中的格式

第二步看boundary

只有multipart类型才有boundary,因为这种类型比较复杂,需要用boundary分段解析

Content-Type: multipart/mixed;boundary="----=_NextPart_5A640E3E_0AF97620_651579F6"
复制代码

这里的boundary是一串字符,但是分割不是直接用boundary,而是用父段和子段来分割

父段: '--' + boundary + '--'

子段: '--' + boundary

据我观察,父段只出现0次或1次并且在最后的位置(可能我遇到的邮件类型有限),所以内容就是分割后的数组的第一个元素:emailText = emailText.split(fatherBoundary)[0].trim() 子段将内容分为不同的段,每个子段需要单独重新解析,因为里面也有自己的Content-typeboundary,所以一封邮件可能出现两个不同的boundary

第三步解析 若分割后的段是html或附件等,直接根据charset和encoding等解析;若仍是multipart类型,则用同样的思路再次解析(关于编码解析下面有说)

下面举个例子(已删除部分不必要的):

From: "=?gb18030?B?amlhbmJvKw==?=" <490549111@qq.com>
To: "=?gb18030?B?amlhbmJvKw==?=" <635638508@qq.com>
Subject: 123
Content-Type: multipart/mixed;boundary="----=_NextPart_5A5F05FC_0A84FD10_42CF1A15"
Content-Transfer-Encoding: 8Bit
Date: Wed, 17 Jan 2018 16:14:52 +0800This is a multi-part message in MIME format.------=_NextPart_5A5F05FC_0A84FD10_42CF1A15
Content-Type: multipart/alternative;boundary="----=_NextPart_5A5F05FC_0A84FD10_3247BB56";------=_NextPart_5A5F05FC_0A84FD10_3247BB56
Content-Type: text/plain;charset="gb18030"
Content-Transfer-Encoding: base64MTINCjM=------=_NextPart_5A5F05FC_0A84FD10_3247BB56
Content-Type: text/html;charset="gb18030"
Content-Transfer-Encoding: base64PGRpdj4xMjwvZGl2PjxkaXY+MzwvZGl2Pg==------=_NextPart_5A5F05FC_0A84FD10_3247BB56--------=_NextPart_5A5F05FC_0A84FD10_42CF1A15
Content-Type: application/octet-stream;charset="gb18030";name="=?gb18030?B?08q8/s/qx+kucG5n?="
Content-Disposition: attachment; filename="=?gb18030?B?08q8/s/qx+kucG5n?="
Content-Transfer-Encoding: base64iVBORw0KGgoAAAANSUhEUgAAA2QAAAHRCAIAAACKEu1wAAAAAXNSR0IArs4c6QAAA...(很长,省略)------=_NextPart_5A5F05FC_0A84FD10_42CF1A15--复制代码

分析:

  1. Content-type是multipart/mixed,说明是包含附件类型
  2. 父段出现在最后,分割后的数组取第一项即可
  3. 根据子段分割,段内有各自的boundary、Content-type和charset等。
    1. 第一段是一个alternative的小邮件,包含纯文本和超文本。再根据boundary分割即可

      1. 解析后可的到纯文本内容为12\r\n3
      2. 解析后可的到超文本内容为<div>12</div><div>3</div>
    2. 第二段是application类型附件,根据charset和encoding解析得到文件名是邮件详情.png

编写时要注意的问题

  1. 遇到过Content-type为multipart/related;type="multipart/alternative";boundary="----=_NextPart_5A6951CD_6F185580_3879981A,这样要算related,不能算alternative,按复杂的那个算
  2. 观察发现related类型和mixed类型的解析规则一样
  3. 有些邮件一些值不全(如没有charset),需要设置默认值
  4. base64值解析错误,是因为base64有换行符,需要去掉

存储邮件

分析了邮件的类型,那存储邮件就不难了。下面是刚解析完的邮件对象格式

── attr   #有邮件uid等简单信息
── body├──attachment   #附件├──bodyHtml      #超文本├──bodyText      #纯文本
── emailText        #完整的邮件文本
── header           #头部信息
复制代码

我们需要根据邮件Content-type进行转换再存储,不然的话就要在显示时在判断不同类型不同处理。显然存储前处理更好

  1. 若是html类型,则将bodyHtml单独存入一个文件,bodyHtml的值为文件路径。这里需要考虑有的html并不是完整页面而是一个片段
  2. 若是mixed类型,将attachment存入单独文件,同样存为文件路径
  3. 对于alternative和related,到这里已经不用单独考虑。因为alternative是纯文本和超文本共存,也就是重复的,超文本是html片段,包含格式,而纯文本只有文字,直保留超文本即可;related是和mixed解析规则一样,并需要将资源拼合成完整html,将html单独存储即可。
if (contentType.match(htmlTypeReg)) {...}  //单独存储html
else if (contentType.match(mixedMultipart)) {...} // 单独存储附件
if (!contentType.match(htmlTypeReg)) {...}  //非htmlTypeReg也进行单独存储html
复制代码

显示邮件

进行了很规则的存储,所以显示时逻辑就很清晰了

  1. bodyHtml以.html结尾,则是html路径,用webview的src引入
  2. bodyHtml不是路径,则将html片段插入
  3. 没有bodyHtml,则将bodyText插入
  4. 有attachment则显示,没有就不显示
if (bodyHtml.indexOf(HTML) && bodyHtml.indexOf(HTML) + HTML.length === bodyHtml.length) {...} //html是路径
else if (bodyHtml) {...} //html不是路径
else {...} //没有html,只能取bodyText
if (detail.body.attachment && detail.body.attachment.length) {...} //显示附件
复制代码

发送邮件

发送邮件有stmp(Simple Mail Transfer Protocol),github有现成较成熟的nodemailer,无论发送html还是附件,都非常简单。

开发遇到的问题

编码

邮件最开始获取的是流,需要一个编码转为最初的字符串。我用的是gb18030解码 关于gb系列编码可以自行了解,简单提一下:最初只有ascii,中国想显示中文,就有了gb2312、gbk等,从简体中文慢慢加入繁体字等,最后更新的版本是gb18030,所以是gb系列直接用gb18030即可,因为它向下兼容。

解析最初的流好像用gb18030或utf-8都可以,因为各个部分都已经用base64或其他编码转为ascii码了。

From: "=?gb18030?B?amlhbmJvKw==?=" <490549111@qq.com>
复制代码

上面的字符串反应了两个事: 1、字符集为gb18030,即本邮件由gb18030编码 2、B代表base64,后面的字符用base64编码

解析的思路是先用base64转为buffer,在用gb18030字符集转为字符串 解析的方法是iconv.decode(iconv.encode('amlhbmJvKw==?=','base64'),'gb18030')

From: =?UTF-8?B?6Zi/6YeM5LqR?= <system@notice.aliyun.com>
复制代码

同样的道理,这是utf-8字符集的base64编码 解析的方法是iconv.decode(iconv.encode('B?6Zi/6YeM5LqR?=','base64'),'utf-8')

iconv.decode(iconv.encode('阿里郎阿里云','gb18030'),'utf-8')
iconv.decode(iconv.encode('6Zi/6YeM6YOO6Zi/6YeM5LqR','base64'),'gb18030')
复制代码

如果gb18030和utf-8混用了,那就出现乱码了,因为他们字符集不一样,同一个编码代表的文字不一样。

上面的第一行输出�����ɰ�����。第二行输出闃块噷閮庨樋閲屼簯

对于boundary段内的内容如:

------=_NextPart_5A640E3E_0AF97620_02509F49
Content-Type: text/plain;charset="gb18030"
Content-Transfer-Encoding: base64cXdlenhj
复制代码

里面清楚写了字符集和传输编码,按它规则解析即可得到纯文本qwezxc

使用imap的node-imap相关

使用node-imap模块,一方面是较灵活,另一方面是可以同步状态。最大的问题是发现同步状态失败,根据文档下面这样就可以标记邮件已读,但是怎么都失败。。可能是支持性不够

imap.openBox('INBOX', false, cb);       //openBox不能是readOnly,置false
let f = imap.fetch(results, { bodies: '', markSeen: true }); //markSeen为true
复制代码

文档api比较多,参数也多,但是很多得到的结果不一致,要多观察多测试,查出哪个是要用的api。

electron最小化,全屏按钮和无边框

默认的窗口是有系统边框的,我不喜欢,只要再创建渲染进程是配置去掉即可

  mainWindow = new BrowserWindow({height: 563,width: 1000,useContentSize: true,autoHideMenuBar: true,title: 'Vmail',disableAutoHideCursor: true,frame: false // 没有边框})
复制代码

没有了边框,就要手动添加界面拖动。

<header style="-webkit-app-region: drag">
复制代码

这样,header就可以拖动了。但要注意,只有写行内样式才起效。同时会导致里面标签的hover不触发,要想触发,就要将这个标签设置不可拖动

<div class="refresh fl" style="-webkit-app-region: no-drag">
复制代码

下面是最小化和全屏的部分代码

const { remote } = require('electron')
...
data () {return {isFullScreen: false  //当前是否全屏状态}
},
mounted () {window.addEventListener('resize', () => {  //当resize时检测是否全屏this.isFullScreen = remote.getCurrentWindow().isMaximized()})
},
methods: {close () {remote.getCurrentWindow().close()  //点击关闭,停止渲染进程},minimize () {remote.getCurrentWindow().minimize()  //窗口最小化},full () {const browserWindow = remote.getCurrentWindow()  //全屏toggleif (browserWindow.isMaximized()) {browserWindow.unmaximize()this.isFullScreen = false} else {browserWindow.maximize()this.isFullScreen = true}},
复制代码

判断全屏还有browserWindow.isFullScreen(),发现双击拖动栏全屏不能正确返回,browserWindow.isMaximized()可以正确判断。

关于事件监听,要用addEventListener,不能用onresize,因为多个地方用到了resize,用onresize会相互覆盖

打包

项目是用electron-builder打包,第一次build要下载依赖。因为资源在墙外,要翻墙,否则报错。也可以尝试手动下载,根据命令行的提示下载对应的文件

npm应该设置镜像,配置文件为~/.npmrc

registry=https://registry.npm.taobao.org
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
phantomjs_cdnurl=http://npm.taobao.org/mirrors/phantomjs
ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/
复制代码

www.cnblogs.com/chenweixuan… blog.csdn.net/bailong1/ar…

其他

下图是硬盘存储结构

其中config.js存储所有用户和当前用户

每个邮箱目录都有一个index文件,存着各种邮件列表,具体html或附件都单独提取出来了

开始是用qq邮箱测试,之后用其他邮箱测试,基本是没问题的,因为大多数都是根据标准来收发邮件。测试了qq、163、aliyun等都基本没问题。(163需要多一个授权步骤)

项目缺点

项目做的比较粗糙,有些功能时不完善的。比如草稿箱,写邮件时的右侧快捷选择收件人,webview不能自适应高度等。功能也不多,如没有快捷回复邮件功能等。

总结

此次项目,业务不难,主要是邮件解析部分比较绕。我没有查阅权威的文档,所以可能有缺陷。

匹配字符串用到了大量正则表达式,我写的正则也不是很好

对于vue项目结构,vuex等知识不是重点,所以我一笔带过

以上就是对项目的总结,如果有错,望指正

参考: MIME---multipart类型

邮件编码Content-Transfer-Encoding的各种形式

electron-vue邮件客户端总结相关推荐

  1. mail linux 客户端,Nylas Mail: 一个 Linux 的免费邮件客户端

    Nylas Mail An Amazing Free Email Client For Linux 有一个经常被提及的问题是 Ubuntu 是否还应该提供默认的电子邮件客户端.就个人而言,我已经很长时 ...

  2. android仿微信图片编辑器,electron/vue可编辑框contenteditable|仿微信截图

    基于Electron+vue实现div可编辑contenteditable插入表情|electron-vue截图功能 为了避免使用 vue 手动建立起 electron 应用程序.electron-v ...

  3. 记一次Electron+vue实现动态打印小票

    1.业务场景 公司有做一个Electron+Vue的收银产品,收银以后需要将订单信息通过小票打印出来,记录一下做这个需求遇到的一些问题跟bug 2.解决思路 通过webview或者新窗口将需要打印的内 ...

  4. Nylas Mail: 一个Linux的免费邮件客户端

    有一个经常被提及的问题是 Ubuntu 是否还应该提供默认的电子邮件客户端.就个人而言,我已经很长时间没有使用 Thunderbird 了.我相信这不是一个第一次被问到的问题,但我相信这是一个把它解决 ...

  5. 一款 Linux 邮件客户端—Nylas Mail

    导读 Linux 上面有许多邮件客户端,Geary.Empathy.Evolution 和 Thunderbird 本身已经为很多用户提供了很好的服务,但是我发现了值得一试的软件: Nylas Mai ...

  6. 邮件客户端WebMail Pro v7.7.5发布,在线订购限时75折优惠!

    2019独角兽企业重金招聘Python工程师标准>>> AfterLogic WebMail Pro是基于网页并以脚本开发的邮件客户端.能以前端模式与现有的邮件服务器或内置邮件服务器 ...

  7. 如何在 Mutt 邮件客户端中使用密文密码

    如何在 Mutt 邮件客户端中使用密文密码 Mutt 是一个 Linux/UNIX 终端环境下的开源的邮件客户端.Mutt 以及 Alpine 在 Linux 命令行爱好者中有着最忠诚的追随者,这不是 ...

  8. linux 邮件客户端 n1,N1:下一代开源邮件客户端

    N1 Open Source email client 当我们谈论到Linux中的邮件客户端,通常 Thunderbird.Geary 和 Evolution 就会出现在我们的脑海.作为对这些大咖们的 ...

  9. C#获取邮件客户端保存的邮箱密码

    有时候邮件客户端记录了邮箱密码,但自己却忘记了,此时可以使用C#建立一个临时的"邮件服务器",截取密码: IPEndPoint ipEndPoint = new IPEndPoin ...

最新文章

  1. 魅族手机使用应用沙盒一键修改位置数据
  2. ThinkSNS+ 是如何计算字符显示长度的
  3. linux桌面文件夹改图标,Linux 给桌面程序设置个性化图标
  4. boost::log::sinks::simple_event_log_backend用法的测试程序
  5. Linux与Windows比较出的20个优势
  6. [leetcode] 105.从前序与中序遍历构造二叉树
  7. 2021年全国大学生电子设计竞赛重新启动通知及进度安排
  8. OPENSSL ENGINE机制
  9. CUDA:在GPU上实现核函数的嵌套以及编译运行
  10. 深入解析Mysql 主从同步延迟原理及解决方案
  11. git学习 远程仓库02
  12. mysql在windows配置多节点_Windows环境配置MySQL集群
  13. a 标签 jq js 打开新页面跳转
  14. thingworx- 安装thingworx120天试用版步骤
  15. 新买的硬盘接在计算机上,电脑如何对刚买来的新硬盘分区
  16. 手把手教你使用Python批量创建复工证明
  17. 量化投资学习——关于XTP交易柜台
  18. Optional orElseThrow 错误: 未报告的异常错误X; 必须对其进行捕获或声明以便抛出...
  19. 带着老娘和女儿看《孔子》
  20. 英文中 vi和vt的区别

热门文章

  1. 基于stm32移植uC/OS-III以及使用Keil仿真和逻辑分析仪抓取波形
  2. unity3d软阴影和硬阴影的原理_在广告摄影中阴影和高光的重要作用和控制技巧...
  3. java setattribute_java – HttpSession setAttribute并不总是插入新对象
  4. denied 登陆后access_Linux SSH登陆出现Access Denied错误的解决方法
  5. 汽车交通主题的微信公众号图文这样排版阅读量翻十倍
  6. EC-PCA: 利润中心分配分摊流程-3KE5 / 4KE5
  7. 一根网线同时走宽带和iptv(单线复用)
  8. LOL无限火力是哪个服务器先上线,LOL无限火力什么时候上线 2020年无限火力上线时间...
  9. 2023软件测试行情不行了?
  10. iOS密码查找工具:4uKey Password Manager for Mac