JavaMail发送邮件(超详细)
一:邮件发送的基本概念
本文我将阐述使用JavaMail方式发送和接收Email的详细说明,本博客本着以后遇到类似的邮件发送需求可以直接把代码粘过去直接使用,快捷方便省时间,对于刚接触的JavaMail的朋友们还是把文章过一遍,虽然本文不是最好的,但是我可以保证你能成功发送邮件;
关于邮件还有一些基本知识我将在下面简单介绍,关于邮件发送和接收的流程可以参考 邮件基本概念及发送方式
1:邮件中的几个名词
- 发件人:
- 指的是用哪个邮箱进行发送邮件的人
- 收件人:
- 指的是接收发件人发过来邮件的人,代表这封邮件面向的读者。可以是零个到多个。
- 抄送人:
- 指的是发件人把邮件发送给收件人的同时并抄送一份发给抄送人;此时抄送人可以看到收件人、抄送人的邮箱
- 密送人:
- 指的是发件人把邮件发送给收件人的同时并抄送一份发给密送人;此时抄送人可以看到收件人、抄送人的邮箱,无法看到密送人的邮箱
- 说明:抄送人和密送人一般用于项目组A给项目组B发送邮件确认流程,这时项目组A还要告知领导我已经把方案流程发送给项目组B了;
- 这时我就要把流程方案抄送一份给领导,就可以用到抄送和密送,此邮件领导是不用回复的
2:发送邮件的几种方式
JavaMail 具体使用说明参考Oracle官网给出的API:链接地址
Jakarta Mail 具体使用说明参考Jakarta官方给出的API:链接地址
- 1:javax.*
- 也是java标准的一部分,但是没有包含在标准库中,一般属于标准库的扩展。通常属于某个特定领域,不是一般性的api。
- 所以以扩展的方式提供api,以避免jdk的标准库过大。当然某些早期的javax,后来被并入到标准库中,所有也应该属于新版本JDK的标准库。
- 比如jmx,java5以前是以扩展方式提供,但是jdk5以后就做为标准库的一部分了,所有javax.management也是jdk5的标准库的一部分。
- 2:com.sun.*
- 是sun的hotspot虚拟机中java.* 和javax.*的实现类。因为包含在rt中,所以我们也可以调用。但是因为不是sun对外公开承诺的接口,
- 所以根据根据实现的需要随时增减,因此在不同版本的hotspot中可能是不同的,而且在其他的jdk实现中是没有的,调用这些类,
- 可能不会向后兼容,一般不推荐使用
- 下面介绍的发送邮件的几种方式:
- ①:使用javax.mail的坐标依赖包(导包时看清楚是不是javax.mail的)
- <!--JavaMail基本包-->
- <dependency>
- <groupId>javax.mail</groupId>
- <artifactId>mail</artifactId>
- <version>1.4.7</version>
- </dependency>
- <!--邮件发送的扩展包-->
- <dependency>
- <groupId>javax.activation</groupId>
- <artifactId>activation</artifactId>
- <version>1.1.1</version>
- </dependency>
- 注:坐标必须为两个,因为javax.mail没有携带依赖包javax.activation(发送)
- ②:使用com.sun.mail的坐标依赖包(不推荐使用,这里我没讲了,下面是它的坐标,坐标内携带javax.activation依赖)
- <!-- https://mvnrepository.com/artifact/com.sun.mail/javax.mail -->
- <!--使用Sun提供的Email工具包-->
- <dependency>
- <groupId>com.sun.mail</groupId>
- <artifactId>javax.mail</artifactId>
- <version>1.6.2</version>
- </dependency>
- ③:使用Jakarta Mail发送邮件(和javax.mail使用方式基本一样,不过它内部携带了javax.activation依赖包)
- <dependency>
- <groupId>com.sun.mail</groupId>
- <artifactId>jakarta.mail</artifactId>
- <version>1.6.7</version>
- </dependency>
- ③:使用SpringBoot集成邮件发送(跳转此博客)
- 注:SpringBoot集成邮件发送底层使用Jakarta Mail技术
- 注:javax.JavaMail最后一个版本发布于2018年8月,后期发送邮件最好使用Jakarta Mail(它是javaMail的前身);
总结:不借助SpringBoot的情况下使用 javax.JavaMail 或 Jakarta Mail 方式
二:JavaMailAPI简单说明
1:Session类
javax.mail.Session类用于定义整个应用程序所需的环境信息,以及收集客户端与邮件服务器建立网络连接的会话信息,例如邮件服务器的主机名、端口号、采用的邮件发送和接收协议等。Session 对象根据这些信息构建用于邮件收发的Transport和Store对象,以及为客户端创建Message对象时提供信息支持。
- Session getInstance(Properties props)
- Session getInstance(Properties props, Authenticator authenticator)
- 说明:获取一个新的Session对象
- 参数:
- props:为Session会话域提供默认值
- mail.store.protocol:接收邮件时分配给协议的名称
- mail.transport.protocol:发送邮件时分配给协议的名称
- mail.host:邮箱服务器地址
- mail.user:发件人名称
- mail.from:发件人邮箱
- authenticator:
- 用于在需要用户名和密码时回调应用程序
2:Message类
javax.mail.Message类是创建和解析邮件的核心API,这是一个抽象类,通常使用它的子类javax.mail.internet.MimeMessage类。它的实例对象表示一份电子邮件。客户端程序发送邮件时,首先使用创建邮件的JavaMail API创建出封装了邮件数据的Message对象,然后把这个对象传递给邮件发送API(Transport 类) 发送,客户端程序接收邮件时,邮件接收API把接收到的邮件数据封装在Message类的实例中,客户端程序在使用邮件解析API从这个对象中解析收到的邮件数据。
3:Transport类
javax.mail.Transport类是发送邮件的核心API类,它的实例对象代表实现了某个邮件发送协议的邮件发送对象,例如SMTP协议,客户端程序创建好Message对象后,只需要使用邮件发送API得到Transport对象,然后把Message对象传递给Transport 对象,并调用它的发送方法,就可以把邮件发送给指定的SMTP服务器。
4:Store类
javax.mail.Store类是接收邮件的核心API类,它的实例对象代表实现了某个邮件接收协议的邮件接收对象,例如POP3协议,客户端程序接收邮件时,只需要使用邮件接收API得到Store对象,然后调用Store对象的接收方法,就可以从指定的POP3服务器获得邮件数据,并把这些邮件数据封装到表示邮件的 Message 对象中
三:使用javax中的JavaMail
1:基本坐标导入
- <!--JavaMail基本包-->
- <dependency>
- <groupId>javax.mail</groupId>
- <artifactId>mail</artifactId>
- <version>1.4.7</version>
- </dependency>
- <!--邮件发送的扩展包-->
- <dependency>
- <groupId>javax.activation</groupId>
- <artifactId>activation</artifactId>
- <version>1.1.1</version>
- </dependency>
2:使用JavaMail发送HTML格式邮件
- public class JavaxJavaMailClient {
- public String emailHost = "smtp.163.com"; //发送邮件的主机
- public String transportType = "smtp"; //邮件发送的协议
- public String fromUser = "antladdie"; //发件人名称
- public String fromEmail = "antladdie@163.com"; //发件人邮箱
- public String authCode = "xxxxxxxxxxxxxxxx"; //发件人邮箱授权码
- public String toEmail = "xiaofeng504@qq.com"; //收件人邮箱
- public String subject = "电子专票开具"; //主题信息
- @Test
- public void ClientTestA() throws UnsupportedEncodingException, javax.mail.MessagingException {
- //初始化默认参数
- Properties props = new Properties();
- props.setProperty("mail.transport.protocol", transportType);
- props.setProperty("mail.host", emailHost);
- props.setProperty("mail.user", fromUser);
- props.setProperty("mail.from", fromEmail);
- //获取Session对象
- Session session = Session.getInstance(props, null);
- //开启后有调试信息
- session.setDebug(true);
- //通过MimeMessage来创建Message接口的子类
- MimeMessage message = new MimeMessage(session);
- //下面是对邮件的基本设置
- //设置发件人:
- //设置发件人第一种方式:直接显示:antladdie <antladdie@163.com>
- //InternetAddress from = new InternetAddress(sender_username);
- //设置发件人第二种方式:发件人信息拼接显示:蚂蚁小哥 <antladdie@163.com>
- String formName = MimeUtility.encodeWord("蚂蚁小哥") + " <" + fromEmail + ">";
- InternetAddress from = new InternetAddress(formName);
- message.setFrom(from);
- //设置收件人:
- InternetAddress to = new InternetAddress(toEmail);
- message.setRecipient(Message.RecipientType.TO, to);
- //设置抄送人(两个)可有可无抄送人:
- List<InternetAddress> addresses = Arrays.asList(new InternetAddress("1457034247@qq.com"), new InternetAddress("575814158@qq.com"));
- InternetAddress[] addressesArr = (InternetAddress[]) addresses.toArray();
- message.setRecipients(Message.RecipientType.CC, addressesArr);
- //设置密送人 可有可无密送人:
- //InternetAddress toBCC = new InternetAddress(toEmail);
- //message.setRecipient(Message.RecipientType.BCC, toBCC);
- //设置邮件主题
- message.setSubject(subject);
- //设置邮件内容,这里我使用html格式,其实也可以使用纯文本;纯文本"text/plain"
- message.setContent("<h1>蚂蚁小哥祝大家工作顺利!</h1>", "text/html;charset=UTF-8");
- //保存上面设置的邮件内容
- message.saveChanges();
- //获取Transport对象
- Transport transport = session.getTransport();
- //smtp验证,就是你用来发邮件的邮箱用户名密码(若在之前的properties中指定默认值,这里可以不用再次设置)
- transport.connect(null, null, authCode);
- //发送邮件
- transport.sendMessage(message, message.getAllRecipients()); // 发送
- }
- }
3:使用JavaMail发送HTML内携带图片邮件格式
- public class JavaxJavaMailClient {
- public String emailHost = "smtp.163.com"; //发送邮件的主机
- public String transportType = "smtp"; //邮件发送的协议
- public String fromUser = "antladdie"; //发件人名称
- public String fromEmail = "antladdie@163.com"; //发件人邮箱
- public String authCode = "xxxxxxxxxxxxxxxx"; //发件人邮箱授权码
- public String toEmail = "xiaofeng504@qq.com"; //收件人邮箱
- public String subject = "电子专票开具"; //主题信息
- @Test
- public void ClientTestB() throws IOException, javax.mail.MessagingException {
- // 1:初始化默认参数
- Properties props = new Properties();
- props.setProperty("mail.transport.protocol", transportType);
- props.setProperty("mail.host", emailHost);
- props.setProperty("mail.user", fromUser);
- props.setProperty("mail.from", fromEmail);
- // 2:获取Session对象
- Session session = Session.getInstance(props, null);
- session.setDebug(true);
- // 3:创建MimeMessage对象
- MimeMessage message = new MimeMessage(session);
- // 4:设置发件人、收件人、主题、(内容后面设置)
- String formName = MimeUtility.encodeWord("蚂蚁小哥") + " <" + fromEmail + ">";
- InternetAddress from = new InternetAddress(formName);
- message.setFrom(from);
- InternetAddress to = new InternetAddress(toEmail);
- message.setRecipient(Message.RecipientType.TO, to);
- //设置邮件主题
- message.setSubject(subject);
- //邮件发送时间
- message.setSentDate(new Date());
- // 5:设置多资源内容
- //=============== 构建邮件内容:多信息片段关联邮件 使用Content-Type:multipart/related ===============//
- // 5.1:构建一个多资源的邮件块 用来把 文本内容资源 和 图片资源关联;;;related代表多资源关联
- MimeMultipart text_img_related = new MimeMultipart("related");
- //text_img_related.setSubType("related");
- //注:这里为什么填写related的请去查阅Multipart类型或者去文章开头跳转我之前上一篇邮件介绍
- // 5.2:创建图片资源
- MimeBodyPart img_body = new MimeBodyPart();
- DataHandler dhImg = new DataHandler(JavaxJavaMailClient.class.getResource("static/b.png"));
- img_body.setDataHandler(dhImg); //设置dhImg图片处理
- img_body.setContentID("imgA"); //设置资源图片名称ID
- // 5.3:创建文本资源,文本资源并引用上面的图片ID(因为这两个资源我做了关联)
- MimeBodyPart text_body = new MimeBodyPart();
- text_body.setContent("<img src='cid:imgA' width=100/> 我是蚂蚁小哥!!","text/html;charset=UTF-8");
- // 5.4:把创建出来的两个资源合并到多资源模块了
- text_img_related.addBodyPart(img_body);
- text_img_related.addBodyPart(text_body);
- //===========================================================================================//
- // 6:设置我们处理好的资源(存放到Message)
- message.setContent(text_img_related);
- // 7:保存上面设置的邮件内容
- message.saveChanges();
- // 8:获取Transport对象
- Transport transport = session.getTransport();
- //9:smtp验证,就是你用来发邮件的邮箱用户名密码(若在之前的properties中指定默认值,这里可以不用再次设置)
- transport.connect(null, null, authCode);
- //10:发送邮件
- transport.sendMessage(message, message.getAllRecipients()); // 发送
- }
- }
4:使用JavaMail发送HTML带图片+附件格式邮件
- public class JavaxJavaMailClient {
- public String emailHost = "smtp.163.com"; //发送邮件的主机
- public String transportType = "smtp"; //邮件发送的协议
- public String fromUser = "antladdie"; //发件人名称
- public String fromEmail = "antladdie@163.com"; //发件人邮箱
- public String authCode = "xxxxxxxxxxxxxxxx"; //发件人邮箱授权码
- public String toEmail = "xiaofeng504@qq.com"; //收件人邮箱
- public String subject = "电子专票开具"; //主题信息
- @Test
- public void ClientTestC() throws IOException, javax.mail.MessagingException {
- // 1:初始化默认参数
- Properties props = new Properties();
- props.setProperty("mail.transport.protocol", transportType);
- props.setProperty("mail.host", emailHost);
- props.setProperty("mail.user", fromUser);
- props.setProperty("mail.from", fromEmail);
- // 2:获取Session对象
- Session session = Session.getInstance(props, null);
- session.setDebug(true);
- // 3:创建MimeMessage对象
- MimeMessage message = new MimeMessage(session);
- // 4:设置发件人、收件人、主题、(内容后面设置)
- String formName = MimeUtility.encodeWord("蚂蚁小哥") + " <" + fromEmail + ">";
- InternetAddress from = new InternetAddress(formName);
- message.setFrom(from);
- InternetAddress to = new InternetAddress(toEmail);
- message.setRecipient(Message.RecipientType.TO, to);
- //设置邮件主题
- message.setSubject(subject);
- //邮件发送时间
- message.setSentDate(new Date());
- //*****邮件内容携带 附件 + (HTML内容+图片)使用Content-Type:multipart/mixed ******//
- // 5:设置一个多资源混合的邮件块 设置此类型时可以同时存在 附件和邮件内容 mixed代表混合
- MimeMultipart mixed = new MimeMultipart("mixed");
- // 5.1:创建一个附件资源
- MimeBodyPart file_body = new MimeBodyPart();
- DataHandler dhFile = new DataHandler(JavaxJavaMailClient.class.getResource("static/a.zip"));
- file_body.setDataHandler(dhFile); //设置dhFile附件处理
- file_body.setContentID("fileA"); //设置资源附件名称ID
- //file_body.setFileName("拉拉.zip"); //设置中文附件名称(未处理编码)
- file_body.setFileName(MimeUtility.encodeText("一个附件.zip")); //设置中文附件名称
- // 5.2:先把附件资源混合到 mixed多资源邮件模块里
- mixed.addBodyPart(file_body);
- // 5.3:创建主体内容资源存储对象
- MimeBodyPart content = new MimeBodyPart();
// 把主体内容混合到资源存储对象里
mixed.addBodyPart(content);- // 5.4:设置多资源内容
- //=============== 构建邮件内容:多信息片段邮件 使用Content-Type:multipart/related ===============//
- // 5.4.1:构建一个多资源的邮件块 用来把 文本内容资源 和 图片资源合并;;;related代表多资源关联
- MimeMultipart text_img_related = new MimeMultipart("related");
- //text_img_related.setSubType("related");
- //注:这里为什么填写related的请去查阅Multipart类型或者去文章开头跳转我之前上一篇邮件介绍
- // 5.4.2:把关联的把多资源邮件块 混合到mixed多资源邮件模块里
- content.setContent(text_img_related);
- // 5.4.3:创建图片资源
- MimeBodyPart img_body = new MimeBodyPart();
- DataHandler dhImg = new DataHandler(JavaxJavaMailClient.class.getResource("static/b.png"));
- img_body.setDataHandler(dhImg); //设置dhImg图片处理
- img_body.setContentID("imgA"); //设置资源图片名称ID
- // 5.4.4:创建文本资源,文本资源并引用上面的图片ID(因为这两个资源我做了关联)
- MimeBodyPart text_body = new MimeBodyPart();
- text_body.setContent("<img src='cid:imgA' width=100/> 我是蚂蚁小哥!!","text/html;charset=UTF-8");
- // 5.4.5:把创建出来的两个资源合并到多资源模块了
- text_img_related.addBodyPart(img_body);
- text_img_related.addBodyPart(text_body);
- //===========================================================================================//
- // 6:设置我们处理好的资源(存放到Message)
- message.setContent(mixed);
- // 7:保存上面设置的邮件内容
- message.saveChanges();
- // 8:获取Transport对象
- Transport transport = session.getTransport();
- //9:smtp验证,就是你用来发邮件的邮箱用户名密码(若在之前的properties中指定默认值,这里可以不用再次设置)
- transport.connect(null, null, authCode);
- //10:发送邮件
- transport.sendMessage(message, message.getAllRecipients()); // 发送
- }
- }
5:使用JavaMail和Thymeleaf模板发送HTML内嵌图片格式
下面我将使用JavaMail通过Thymeleaf模板的方式发送HTML内容(HTML内容存在资源图片关联)+附件内容,其实这是方式都是通过2~4小节的慢慢改造,学会这种方式,那么我们发送公司业务上的一些邮件是没有太大压力的;下面我不在展示是全部代码,只把补充的代码展示出来,其它的参照第4节代码
- <!--导入thymeleaf坐标-->
- <dependency>
- <groupId>org.thymeleaf</groupId>
- <artifactId>thymeleaf</artifactId>
- <version>3.0.12.RELEASE</version>
- </dependency>


- <!doctype html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport"
- content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>Document</title>
- <style>
- * {padding: 0;margin: 0;}
- h5 {width: 300px;height: 40px;margin: 10px auto;
- text-align: center;font: normal 500 14px/40px '微软雅黑';
- color: rgba(0, 0, 0, .8);border: 1px dashed #1c8b9e;
- border-radius: 5px;box-shadow: 10px 10px 30px 2px #f00;}
- img {width: 300px;height: 40px;}
- div {text-align: center;border: 1px solid #f00;margin: auto;}
- </style>
- </head>
- <body>
- <h5>感谢<span th:text="${name}"></span>同志对我们的肯定和支持!</h5>
- <!--注意这个资源一定要引用邮件发送关联的图片资源-->
- <div><img src="cid:imgA" alt=""></div>
- </body>
- </html>
emailTemplate.html模板代码,就以此模板发送资源
- /***
- * 模板解析方法,解析出一个String的html返回
- * @return
- */
- public String templateHtml(){
- //设置类加载模板处理器
- ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
- //设置前缀后缀
- resolver.setPrefix("/static/");
- resolver.setSuffix(".html");
- //创建模板引擎处理器
- TemplateEngine engine = new TemplateEngine();
- //设置引擎使用的模板文件
- engine.setTemplateResolver(resolver);
- //创建Context来为模板设置填充数据
- Context context = new Context();
- //填充模板里的数据
- context.setVariable("name","蚂蚁小哥");
- //具体处理,把模板和数据合并成一个新的文本
- //注:文件我直接放在resources/templates文件根目录下,如果有多层目录,需要写明文件位置(或者设置过前缀和后缀)
- return engine.process("emailTemplate", context);
- }
6:使用JavaMail接收邮件(未解析)
为了可以更好的测试邮件的接收,我这里使用163邮箱,把之前的邮件全部删除了,用QQ有限发送了两封邮件给163邮箱,下面这个只是其中一封邮件内容
- public class JavaxJavaMailClient {
- public String emailHost = "pop.163.com"; //接收邮件的主机
- public String storeType = "pop3"; //邮件接收的协议
- public String fromEmail = "antladdie@163.com"; //接收邮件的邮箱
- public String authCode = "QWETREWWEWERTWWW"; //接收邮件的邮箱授权码
- @Test
- public void ClientTestD() throws MessagingException, IOException {
- // 1:初始化默认参数
- Properties props = new Properties();
- props.setProperty("mail.host", emailHost);
- props.setProperty("mail.store.protocol", storeType);
- props.setProperty("mail.user", fromEmail);
- // 2:获取连接
- Session session = Session.getInstance(props);
- session.setDebug(false);
- // 3:获取Store对象
- Store store = session.getStore();
- store.connect(null, authCode); //POP3服务器登录认证,user我们在properties中已经指定默认
- // 4:获取收件箱内容:(电子邮件)收件箱 folder:邮件夹
- Folder folder = store.getFolder("INBOX");
- // 设置对邮件帐户的访问权限
- // Folder.READ_ONLY (只读或者1) Folder.READ_WRITE(只写或者2)
- folder.open(Folder.READ_WRITE);
- // 5:得到邮箱帐户中的所有邮件
- Message[] messages = folder.getMessages();
- //循环遍历邮件
- for (Message message : messages) {
- String subject = message.getSubject(); // 获得邮件主题
- Address from = message.getFrom()[0]; // 获得发送者地址
- System.out.println("邮件的主题为: " + subject + "发件人地址为: " + from);
- System.out.println("邮件的内容为:");
- // 输出邮件的全部内容到控制台
- message.writeTo(System.out);
- }
- // 关闭邮件夹对象
- folder.close(false);
- store.close(); // 关闭连接对象
- }
- }
补:打印出的第一条邮件内容:
- 邮件的主题为: 测试接收邮件(无附件)① 发件人地址为: =?gb18030?B?sai/vLTL0KO1xLH4?= <xiaofeng6699@vip.qq.com>
- 邮件的内容为:
- Received: from xmbg7.mail.qq.com (unknown [101.91.43.54])
- by mx47 (Coremail) with SMTP id YcCowAAHhSLsVZxhew5GCw--.44623S3;
- Tue, 23 Nov 2021 10:46:04 +0800 (CST)
- DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=vip.qq.com;
- s=s201512; t=1637635564;
- bh=hx6m189GKLvHwYK7EPgjUY6W63aDJzkYLaZobsCH5fw=;
- h=From:To:Subject:Date;
- b=enOyaq4hkcY0sVHuLe3O6f0wz9gctOI9knFJ438sldmBa4gFpX7I6Ucv9Npe0BtTz
- +89KfHw6wUpfiBmgo119MRTkR8m1gE8BXyFCUgdq2qaYHPkf0sNAaZDtDGan7rRQuG
- oQmP+mXeqP3/KHtJNAMYEAJQF03qYipFKcPXA964=
- X-QQ-FEAT: zaIfg0hwV2qA1LHh5sNQ3yDC93UmKYlF+NlagoYC+rE=
- X-QQ-SSF: 000100000000004000000000000000Z
- X-QQ-XMAILINFO: NBrlLnjHQiaZ3bWTg2+kaq2WcXqqqVbSme9UoRGcm/EacxcEzzBQa78nxQndrm
- eF/UAg21TqvTk30iPgxBkFSCSf9NIRTG3wpo2TUtMU4b/2LEU+Be3z3i/hOQaoe09Y2PYIfBfkona
- FEzXSl8LNQHYStRGXPoD/TwYeiQfXWnMIOnEU3EgJpJ5uznkMMFN6wldar9WyhOYPE3h2nLLSuSN4
- RT4Prw9m7uF/IJoSE5jMEI2hO3EE7YetoenF7EULwI+s5KyKyH9Y+hMDbVsZv5mnQ+aBLJJSsBJba
- Rb4Z7Di6PZfKS9MN2IQvcKNzNlgQ5mIhSiwLIlukwMz5W+XnOVp7gLIn4HXK5b4WmqclfbrHV0b1q
- SM/HdMWzjT29cV1ZM7A2+uiCPFR9Tf1V0EDOhJGN+GqCgV4Z8ETjrWz47C4E3B993M+HiNQj1PY03
- UhD+8DsrsGqZqVTKoRP3SQKrZSygkyrYpBkevCoRex26JH67dHQxRXUGgCci1UZ8IA9/kMa28JQ+h
- fM0Cly0ZQtW32QAJSPUcHdrhxkfWQo3mfeVHg6NEp18arwXhEPp4Io8PTcmCGq6G0k5ZsjMVXaUtN
- DRUFEVQVo2jxint/dyQoGoxsNVkUdqYijW+VTMSdMkC4wnuEqjZ5a+8aiD1uMGm/eEkwW2qF4tZi7
- XZ5MKHKvvze1HSPsol0cuP6og8hbc490IH1Lz4Slz/HSTWL95IwkqvniKJtVKldTpa+0GYEY1dPIx
- KAmwahttxUgWv39mZ4VJgxL5Ozb+IX4tZ4JyTsjx4sk8RCHkEQVHsXZn+nSrQwPvlmMlaQ5sp/N7W
- Jt7yf2TpaHTWhtTek3P08MNRmkPqMgJSKCrn+3t/TJq8HdAKQi2NY0B0Mnseh5S7pWEu3i5xh1Rh
- X-HAS-ATTACH: no
- X-QQ-BUSINESS-ORIGIN: 2
- X-Originating-IP: 122.97.149.60
- X-QQ-STYLE:
- X-QQ-mid: webmail623t1637635564t3630643
- From: "=?gb18030?B?sai/vLTL0KO1xLH4?=" <xiaofeng6699@vip.qq.com>
- To: "=?gb18030?B?YW50bGFkZGll?=" <antladdie@163.com>
- Subject: =?gb18030?B?suLK1L3TytXTyrz+KM7euL28/imi2Q==?=
- Mime-Version: 1.0
- Content-Type: multipart/alternative;
- boundary="----=_NextPart_619C55EB_107CACC0_425E3EAC"
- Content-Transfer-Encoding: 8Bit
- Date: Tue, 23 Nov 2021 10:46:03 +0800
- X-Priority: 3
- Message-ID: <tencent_269D4D83D43DB8D6F68434FA99562BC26708@qq.com>
- X-QQ-MIME: TCMime 1.0 by Tencent
- X-Mailer: QQMail 2.x
- X-QQ-Mailer: QQMail 2.x
- X-CM-TRANSID:YcCowAAHhSLsVZxhew5GCw--.44623S3
- Authentication-Results: mx47; spf=pass smtp.mail=xiaofeng6699@vip.qq.c
- om; dkim=pass header.i=@vip.qq.com
- X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73
- VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxU4vPfDUUUU
- This is a multi-part message in MIME format.
- ------=_NextPart_619C55EB_107CACC0_425E3EAC
- Content-Type: text/plain;
- charset="gb18030"
- Content-Transfer-Encoding: base64
- SSdtIGdvaW5nIHRvIGRvIGEgbWFpbGJveCBhY2NlcHRhbmNlIHRlc3QuztLSqtf20ru49tPK
- z+S908rVsuLK1A==
- ------=_NextPart_619C55EB_107CACC0_425E3EAC
- Content-Type: text/html;
- charset="gb18030"
- Content-Transfer-Encoding: base64
- PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNo
- YXJzZXQ9R0IxODAzMCI+PHNwYW4gc3R5bGU9ImZvbnQtZmFtaWx5OiBQaW5nRmFuZ1NDLVJl
- Z3VsYXIsICZxdW90O01pY3Jvc29mdCBZYWhlaSZxdW90OywgJnF1b3Q7XFw1RkFFyO3Rxbra
- JnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7Ij5JJ20gZ29pbmcgdG8gZG8g
- YSBtYWlsYm94IGFjY2VwdGFuY2UgdGVzdC48L3NwYW4+PGRpdj48Zm9udCBmYWNlPSJQaW5n
- RmFuZ1NDLVJlZ3VsYXIsIE1pY3Jvc29mdCBZYWhlaSwgXFw1RkFFyO3RxbraLCBzYW5zLXNl
- cmlmIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOiAxNnB4OyI+ztLSqtf20ru49tPKz+S908rV
- suLK1Dwvc3Bhbj48L2ZvbnQ+PC9kaXY+
- ------=_NextPart_619C55EB_107CACC0_425E3EAC--
7:使用JavaMail接收邮件(解析邮件内容)


- import javax.mail.*;
- import javax.mail.internet.InternetAddress;
- import javax.mail.internet.MimeMessage;
- import javax.mail.internet.MimeMultipart;
- import javax.mail.internet.MimeUtility;
- import java.io.*;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * @Author AnHui_XiaoYang
- * @Email 939209948@qq.com
- * @Date 2021/11/24 13:52
- * @Description 解析邮箱服务器返回的邮件
- */
- public class MailParsingTool {
- /***
- * 获取邮箱的基本邮件信息
- * @param folder 收件箱对象
- * @return 返回邮箱基本信息
- */
- public static Map<String, Integer> emailInfo(Folder folder) throws MessagingException {
- Map<String, Integer> emailInfo = new HashMap<>();
- // 由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount得到的是收件箱的邮件总数
- emailInfo.put("unreadMessageCount", folder.getUnreadMessageCount()); //未读邮件数
- // 由于POP3协议无法获知邮件的状态,所以下面(删除、新邮件)得到的结果始终都是为0
- emailInfo.put("deletedMessageCount", folder.getDeletedMessageCount()); //删除邮件数
- emailInfo.put("newMessageCount", folder.getNewMessageCount()); //新邮件
- // 获得收件箱中的邮件总数
- emailInfo.put("messageCount", folder.getMessageCount()); //邮件总数
- return emailInfo;
- }
- /***
- * 获得邮件主题
- * @param msg 邮件内容
- * @return 解码后的邮件主题
- * @throws UnsupportedEncodingException
- * @throws MessagingException
- */
- public static String getSubject(MimeMessage msg) throws UnsupportedEncodingException, MessagingException {
- return decodeText(msg.getSubject());
- }
- /***
- * 获得邮件发件人
- * @param msg 邮件内容
- * @return 姓名 <Email地址>
- * @throws MessagingException
- * @throws UnsupportedEncodingException
- */
- public static String getFrom(MimeMessage msg) throws MessagingException, UnsupportedEncodingException {
- String from = "";
- Address[] froms = msg.getFrom();
- if (froms.length < 1) {
- throw new MessagingException("没有发件人!");
- }
- InternetAddress address = (InternetAddress) froms[0];
- String person = address.getPersonal();
- if (person != null) {
- person = decodeText(person) + " ";
- } else {
- person = "";
- }
- from = person + "<" + address.getAddress() + ">";
- return from;
- }
- /***
- * 根据收件人类型,获取邮件收件人、抄送和密送地址。如果收件人类型为空,则获得所有的收件人
- * type可选值
- * <p>Message.RecipientType.TO 收件人</p>
- * <p>Message.RecipientType.CC 抄送</p>
- * <p>Message.RecipientType.BCC 密送</p>
- * @param msg 邮件内容
- * @param type 收件人类型
- * @return 收件人1 <邮件地址1>, 收件人2 <邮件地址2>, ...
- * @throws MessagingException
- */
- public static String getReceiveAddress(MimeMessage msg, Message.RecipientType type) throws MessagingException {
- StringBuilder recipientAddress = new StringBuilder();
- Address[] addresss = null;
- if (type == null) {
- addresss = msg.getAllRecipients();
- } else {
- addresss = msg.getRecipients(type);
- }
- if (addresss == null || addresss.length < 1) {
- if (type == null) {
- throw new MessagingException("没有收件人!");
- } else if ("Cc".equals(type.toString())) {
- throw new MessagingException("没有抄送人!");
- } else if ("Bcc".equals(type.toString())) {
- throw new MessagingException("没有密送人!");
- }
- }
- for (Address address : addresss) {
- InternetAddress internetAddress = (InternetAddress) address;
- recipientAddress.append(internetAddress.toUnicodeString()).append(",");
- }
- //删除最后一个逗号
- recipientAddress.deleteCharAt(recipientAddress.length() - 1);
- return recipientAddress.toString();
- }
- /***
- * 获得邮件发送时间
- * @param msg 邮件内容
- * @return 默认返回:yyyy年mm月dd日 星期X HH:mm
- * @throws MessagingException
- */
- public static String getSentDate(MimeMessage msg, String pattern) throws MessagingException {
- Date receivedDate = msg.getSentDate();
- if (receivedDate == null)
- return "";
- if (pattern == null || "".equals(pattern))
- pattern = "yyyy年MM月dd日 E HH:mm ";
- return new SimpleDateFormat(pattern).format(receivedDate);
- }
- /***
- * 判断邮件是否已读 www.2cto.com
- * @param msg 邮件内容
- * @return 如果邮件已读返回true, 否则返回false
- * @throws MessagingException
- */
- public static boolean isSeen(MimeMessage msg) throws MessagingException {
- return msg.getFlags().contains(Flags.Flag.SEEN);
- }
- /***
- * 判断邮件是否需要阅读回执
- * @param msg 邮件内容
- * @return 需要回执返回true, 否则返回false
- * @throws MessagingException
- */
- public static boolean isReplySign(MimeMessage msg) throws MessagingException {
- boolean replySign = false;
- String[] headers = msg.getHeader("Disposition-Notification-To");
- if (headers != null)
- replySign = true;
- return replySign;
- }
- /***
- * 获得邮件的优先级
- * @param msg 邮件内容
- * @return 1(High):紧急 3:普通(Normal) 5:低(Low)
- * @throws MessagingException
- */
- public static String getPriority(MimeMessage msg) throws MessagingException {
- String priority = "普通";
- String[] headers = msg.getHeader("X-Priority");
- if (headers != null) {
- String headerPriority = headers[0];
- if (headerPriority.contains("1") || headerPriority.contains("High"))
- priority = "紧急";
- else if (headerPriority.contains("5") || headerPriority.contains("Low"))
- priority = "低";
- else
- priority = "普通";
- }
- return priority;
- }
- /***
- * 获得邮件文本内容
- * @param part 邮件体
- * @param content 存储邮件文本内容的字符串
- * @throws MessagingException
- * @throws IOException
- */
- public static void getMailTextContent(Part part, StringBuffer content) throws MessagingException, IOException {
- //如果是文本类型的附件,通过getContent方法可以取到文本内容,但这不是我们需要的结果,所以在这里要做判断
- boolean isContainTextAttach = part.getContentType().indexOf("name") > 0;
- if (part.isMimeType("text/*") && !isContainTextAttach) {
- content.append(part.getContent().toString());
- } else if (part.isMimeType("message/rfc822")) {
- getMailTextContent((Part) part.getContent(), content);
- } else if (part.isMimeType("multipart/*")) {
- Multipart multipart = (Multipart) part.getContent();
- int partCount = multipart.getCount();
- for (int i = 0; i < partCount; i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
- getMailTextContent(bodyPart, content);
- }
- }
- }
- /***
- * 文本解码
- * @param encodeText 解码MimeUtility.encodeText(String text)方法编码后的文本
- * @return 解码后的文本
- * @throws UnsupportedEncodingException
- */
- public static String decodeText(String encodeText) throws UnsupportedEncodingException {
- if (encodeText == null || "".equals(encodeText)) {
- return "";
- } else {
- return MimeUtility.decodeText(encodeText);
- }
- }
- /***
- * 判断邮件中是否包含附件 (Part为Message接口)
- * @param part 邮件内容
- * @return 邮件中存在附件返回true,不存在返回false
- * @throws MessagingException
- * @throws IOException
- */
- public static boolean isContainAttachment(Part part) throws MessagingException, IOException {
- boolean flag = false;
- if (part.isMimeType("multipart/*")) {
- MimeMultipart multipart = (MimeMultipart) part.getContent();
- int partCount = multipart.getCount();
- for (int i = 0; i < partCount; i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
- String disp = bodyPart.getDisposition();
- if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
- flag = true;
- } else if (bodyPart.isMimeType("multipart/*")) {
- flag = isContainAttachment(bodyPart);
- } else {
- String contentType = bodyPart.getContentType();
- if (contentType.contains("application")) {
- flag = true;
- }
- if (contentType.contains("name")) {
- flag = true;
- }
- }
- if (flag) break;
- }
- } else if (part.isMimeType("message/rfc822")) {
- flag = isContainAttachment((Part) part.getContent());
- }
- return flag;
- }
- /***
- * 保存附件
- * @param part 邮件中多个组合体中的其中一个组合体
- * @param destDir 附件保存目录
- * @throws UnsupportedEncodingException
- * @throws MessagingException
- * @throws FileNotFoundException
- * @throws IOException
- */
- public static void saveAttachment(Part part, String destDir) throws UnsupportedEncodingException, MessagingException,
- FileNotFoundException, IOException {
- if (part.isMimeType("multipart/*")) {
- Multipart multipart = (Multipart) part.getContent(); //复杂体邮件
- //复杂体邮件包含多个邮件体
- int partCount = multipart.getCount();
- for (int i = 0; i < partCount; i++) {
- //获得复杂体邮件中其中一个邮件体
- BodyPart bodyPart = multipart.getBodyPart(i);
- //某一个邮件体也有可能是由多个邮件体组成的复杂体
- String disp = bodyPart.getDisposition();
- if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
- InputStream is = bodyPart.getInputStream();
- saveFile(is, destDir, decodeText(bodyPart.getFileName()));
- } else if (bodyPart.isMimeType("multipart/*")) {
- saveAttachment(bodyPart, destDir);
- } else {
- String contentType = bodyPart.getContentType();
- if (contentType.contains("name") || contentType.contains("application")) {
- saveFile(bodyPart.getInputStream(), destDir, decodeText(bodyPart.getFileName()));
- }
- }
- }
- } else if (part.isMimeType("message/rfc822")) {
- saveAttachment((Part) part.getContent(), destDir);
- }
- }
- /***
- * 读取输入流中的数据保存至指定目录
- * @param is 输入流
- * @param fileName 文件名
- * @param destDir 文件存储目录
- * @throws FileNotFoundException
- * @throws IOException
- */
- private static void saveFile(InputStream is, String destDir, String fileName)
- throws FileNotFoundException, IOException {
- BufferedInputStream bis = new BufferedInputStream(is);
- BufferedOutputStream bos = new BufferedOutputStream(
- new FileOutputStream(destDir + fileName));
- int len = -1;
- while ((len = bis.read()) != -1) {
- bos.write(len);
- bos.flush();
- }
- bos.close();
- bis.close();
- }
- }
邮件解析工具类 MailParsingTool
- public class JavaxJavaMailClient {
- public String emailHost = "pop.163.com"; //接收邮件的主机
- public String storeType = "pop3"; //邮件接收的协议
- public String fromEmail = "antladdie@163.com"; //接收邮件的邮箱
- public String authCode = "NUOVRPTNUOJIEIYJ"; //接收邮件的邮箱授权码
- @Test
- public void ClientTestD() throws MessagingException, IOException {
- // 1:初始化默认参数
- Properties props = new Properties();
- props.setProperty("mail.host", emailHost);
- props.setProperty("mail.store.protocol", storeType);
- props.setProperty("mail.user", fromEmail);
- // 2:获取连接
- Session session = Session.getInstance(props);
- session.setDebug(false);
- // 3:获取Store对象
- Store store = session.getStore();
- store.connect(null, authCode); //POP3服务器登录认证,user我们在properties中已经指定默认
- // 4:获取收件箱内容:(电子邮件)收件箱 folder:邮件夹
- Folder folder = store.getFolder("INBOX");
- // 设置对邮件帐户的访问权限
- // Folder.READ_ONLY (只读或者1) Folder.READ_WRITE(只写或者2)
- folder.open(Folder.READ_WRITE);
- //获取邮箱基本信息
- Map<String, Integer> map = MailParsingTool.emailInfo(folder);
- System.out.println(map);
- // 得到收件箱中的所有邮件,并解析
- Message[] messages = folder.getMessages();
- parseMessage(messages);
- // 关闭邮件夹对象
- folder.close(false);
- store.close(); // 关闭连接对象
- }
- public static void parseMessage(Message... messages) throws MessagingException, IOException {
- //判断邮件是否为空
- if (messages == null || messages.length < 1) {
- throw new MessagingException("未找到要解析的邮件!");
- }
- // 解析所有邮件
- for (int i = 0, count = messages.length; i < count; i++) {
- MimeMessage msg = (MimeMessage) messages[i];
- System.out.println("-----------解析第" + msg.getMessageNumber() + "封邮件---------------");
- System.out.println("主题: " + MailParsingTool.getSubject(msg));
- System.out.println("发件人: " + MailParsingTool.getFrom(msg));
- System.out.println("收件人:" + MailParsingTool.getReceiveAddress(msg, Message.RecipientType.TO));
- System.out.println("发送时间:" + MailParsingTool.getSentDate(msg, null));
- System.out.println("是否已读:" + MailParsingTool.isSeen(msg));
- System.out.println("邮件优先级:" + MailParsingTool.getPriority(msg));
- System.out.println("是否需要回执:" + MailParsingTool.isReplySign(msg));
- System.out.println("邮件大小:" + msg.getSize() * 1024 + "kb");
- boolean isContainerAttachment = MailParsingTool.isContainAttachment(msg);
- System.out.println("是否包含附件:" + isContainerAttachment);
- if (isContainerAttachment) {
- //获取文件的存储目录
- String path = JavaxJavaMailClient.class.getClassLoader().getResource("").getPath();
- //获取文件的前缀
- String strFile = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
- MailParsingTool.saveAttachment(msg, path + strFile + "_"); //保存附件
- }
- //用来存储正文的对象
- StringBuffer content = new StringBuffer();
- //处理邮件正文
- MailParsingTool.getMailTextContent(msg, content);
- System.out.println("邮件正文:" + content);
- System.out.println("-----------第" + msg.getMessageNumber() + "封邮件解析结束------------");
- System.out.println();
- }
- }
- }
8:结尾
不知不觉中我们就会使用JavaMail发送和接收邮件,不过我要告诉大家的是javax.JavaMail最后一个版本发布于2018年8月,此后则停止更新;如果在新项目中使用到了邮件发送我推荐大家使用Jakarta Mail,它是JavaMail的前身;其实Jakarta Mail使用起来和JavaMail基本上一样,把上面代码拷贝过去照样运行,不过我下一篇主要介绍SpringBoot集成JakartaMail
JavaMail发送邮件(超详细)的更多相关文章
- 使用JavaMail发送邮件-no object DCH for MIME type multipart/mixed报错解决
最近需要实现一个使用Spring schedule按一定时间间隔自动触发条件发送邮件的功能,在开发的过程中,是按照先测试能发出text/html文本邮件,然后测试添加附件发送邮件,我碰到的问题是,文本 ...
- 使用spring的JavaMail发送邮件
以前我们使用JavaMail发送邮件,步骤挺多的.现在的项目跟Spring整合的比较多.所以这里主要谈谈SpringMail发送. 导入jar包. 配置applicationContext-email ...
- JavaMail发送邮件
发送邮件包含的内容有: from字段 --用于指明发件人 to字段 --用于指明收件人 subject字段 --用于说明邮件主题 cc字段 -- 抄送,将邮件发送给收件人的同时抄 ...
- JavaMail发送邮件第一版
首先,我们先来了解一个基本的知识点,用什么工具来发邮件? 简单的说一下,目前用的比较多的客户端:OutLook,Foxmail等 顺便了解一下POP3.SMTP协议的区别: POP3,全名为" ...
- 【转】(超详细)jsp与servlet之间页面跳转及参数传递实例
初步学习JavaEE,对其中jsp与Servlet之间的传值没弄清楚,查看网上资料,发现一篇超详细的文章,收获大大,特此记录下来.具体链接:http://blog.csdn.net/ssy_shand ...
- web应用中使用JavaMail发送邮件
现在很多的网站都提供有用户注册功能, 通常我们注册成功之后就会收到一封来自注册网站的邮件.邮件里面的内容可能包含了我们的注册的用户名和密码以及一个激活账户的超链接等信息.今天我们也来实现一个这样的功能 ...
- JavaMail发送邮件的笔记及Demo
最近碰到一个需求,就是注册用户时候需要向用户发送激活邮箱,于是照着网上搜来的demo自己试着运行了一下,发件时我用的是网易163邮箱,收件时用QQ邮箱,运行后报了一个错误: 网络上搜索解决方式,多次尝 ...
- web应用中使用JavaMail发送邮件 。。转载
现在很多的网站都提供有用户注册功能, 通常我们注册成功之后就会收到一封来自注册网站的邮件.邮件里面的内容可能包含了我们的注册的用户名和密码以及一个激活账户的超链接等信息.今天我们也来实现一个这样的功能 ...
- (转载)JavaWeb学习总结(五十三)——Web应用中使用JavaMail发送邮件
博客源地址:http://www.cnblogs.com/xdp-gacl/p/4220190.html 现在很多的网站都提供有用户注册功能, 通常我们注册成功之后就会收到一封来自注册网站的邮件.邮件 ...
- 超强、超详细Redis数据库入门教程
这篇文章主要介绍了超强.超详细Redis入门教程,本文详细介绍了Redis数据库各个方面的知识,需要的朋友可以参考下 [本教程目录] 1.redis是什么2.redis的作者何许人也3.谁在使用red ...
随机推荐
- Go语言核心36讲(Go语言进阶技术一)--学习笔记
07 | 数组和切片 我们这次主要讨论 Go 语言的数组(array)类型和切片(slice)类型. 它们的共同点是都属于集合类的类型,并且,它们的值也都可以用来存储某一种类型的值(或者说元素). 不 ...
- Java(9)数组详解
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201564.html 博客主页:https://www.cnblogs.com/testero ...
- LDAP-初见
目录 什么是LDAP? LDAP 协议能解决什么问题? Spring Boot中使用LDAP来统一管理用户信息 添加用户 连接LDAP服务端 什么是LDAP? LDAP 的全称是 Lightweigh ...
- 为Kubernetes集群添加用户认证
Kubernetes中的用户 K8S中有两种用户(User)--服务账号(ServiceAccount)和普通意义上的用户(User) ServiceAccount是由K8S管理的,而User通常是在 ...
- Map中getOrDefault()与数值进行比较
一般用哈希表计数时,value类型通常为Integer.如果想比较某个key出现的次数,使用get(key)与某个数值进行比较是有问题的.当哈希表中并不包含该key时,因为此时get方法返回值是nul ...
- Scrum Meeting 0607
零.说明 日期:2021-6-7 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 困难 qsy PM&前端 重新设计产品 ...
- UltraSoft - Beta - Scrum Meeting 12
Date: May 28th, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 会议记录修复了课程中心导入作业时出现重复的问题完成了消息中心界面的交互 Liuzh ...
- OO第四单元及学期总结
OO第四单元及学期总结 第四单元两次作业的架构设计 第一次作业 类图: 树形结构:使用Operation类管理UMLOperation以及parent为该UMLOperation的参数(UMLpara ...
- 2021.9.7考试总结[NOIP模拟49]
T1 Reverse $BFS$暴力$O(n^2)$ 过程中重复枚举了很多点,考虑用链表记录当前点后面可到达的第一个未更新点. 搜索时枚举翻转子串的左端点,之后便可以算出翻转后$1$的位置. $cod ...
- Python课程笔记(九)
本次课程主要学习了Excel和JSON格式的一些读写操作.课程代码 一.Excel数据读写操作 1.安装模块 pip install xlrd pip install xlwt 网不好可以采用三方库: ...