当前位置: 首页 > news >正文

Springboot 使用JavaMailSender发送邮件 + Excel附件

目录

1.生成Excel表格

1.依赖设置

2.代码:

2.邮件发送

1.邮件发送功能实现-带附件

 2.踩过的坑

1.附件名中文乱码问题

3.参考文章:


需求描述:项目审批完毕后,需要发送邮件通知相关人员,并且要附带数据库表生成的Excel表格,这就要求不光是邮件发送功能,还要临时生成Excel表格做为附件

1.生成Excel表格

使用huTool工具包的Excel表格生成功能

1.依赖设置

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.22</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.2</version>
</dependency>

Hutool-all中包含了Hutool的所有工具类,由于需要生成Excel文件需要依赖poi

2.代码:

    @Overridepublic void publish(xxxxxxPublishVo publishVo) {..................................................../*** 生成Excel表格*///在内存操作,写到输出流中ExcelWriter writer = ExcelUtil.getWriter(true);//自定义标题别名writer.addHeaderAlias("projectCode", "xx编号");writer.addHeaderAlias("projectName", "xx名称");writer.addHeaderAlias("targetType", "xx类型");writer.addHeaderAlias("targetName", "xx名称");writer.addHeaderAlias("targetForMp", "xxxx目标");writer.addHeaderAlias("symbols", "xx限制符");//获取数据QpmxxxxTargetMg query = new QpmxxxxTargetMg();query.setProjectCode(publishVo.getProjectCode());List<xxxxxxListDTO> data =  selectxxxxxxByCondition(query);//整理数据,以便于生成Excel表格List<Object> dataNew = new ArrayList<>();Set<String> stageCollectSet = new HashSet<>();for (xxxxxxListDTO target: data){List<xxxxxxTargetMgStage> stageList = target.getxxxxxxStageList();Map<String, Object> addProperties = new HashMap<>();for (xxxxxxMgStage stage: stageList){if (!stageCollectSet.contains(stage)){stageCollectSet.add(stage.getStage());//给Excel增加列writer.addHeaderAlias(stage.getStage(), stage.getStage() + "目标");}//为对象动态增加属性addProperties.put(stage.getStage(), stage.getStageTarget());}//生成新的包含了新增字段的对象Object targetNew = ReflectUtil.getTarget(target, addProperties);dataNew.add(targetNew);}//只保留别名的数据writer.setOnlyAlias(true);writer.write(dataNew,true);ByteArrayOutputStream outputStream = new ByteArrayOutputStream();// excel写入到输出流writer.flush(outputStream,true);...........................................................................

上述代码中,调用了工具类ReflectUtil给对象动态增加属性。由于数据中有子类,需要获取到子类中的某个字段并生成Excel表格,所以Excel表格构造就需要对数据对象进行改造,简单来说就是需要给对象动态增加新的属性(成员对象的属性),如下所示示例:

把studentList里面的关键属性数据,新增给Test类

示例不是很合适,凑合着用吧

class Test {

    private String class;

    .............................................

    private List<Student> studentList;

}

工具类ReflectUtil,用于给对象动态增加新的属性:

import com.google.common.collect.Maps;
import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;/*** 为实体类动态增加属性,用于生成Excel表格时的特殊情况,例如表格中的列需要动态增加*/
public class ReflectUtil {static Logger logger = LoggerFactory.getLogger(ReflectUtil.class);public static Object getTarget(Object dest, Map<String, Object> addProperties) {// get property mapPropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);Map<String, Class> propertyMap = Maps.newHashMap();for (PropertyDescriptor d : descriptors) {if (!"class".equalsIgnoreCase(d.getName())) {propertyMap.put(d.getName(), d.getPropertyType());}}// add extra propertiesfor (Map.Entry<String, Object> entry : addProperties.entrySet()) {propertyMap.put(entry.getKey(), entry.getValue().getClass());}
//        addProperties.forEach((k, v) -> propertyMap.put(k, v.getClass()));// new dynamic beanDynamicBean dynamicBean = new DynamicBean(dest.getClass(), propertyMap);// add old valuefor (Map.Entry<String, Class> entry : propertyMap.entrySet()) {try {// filter extra propertiesif (!addProperties.containsKey(entry.getKey())) {dynamicBean.setValue(entry.getKey(), propertyUtilsBean.getNestedProperty(dest, entry.getKey()));}} catch (Exception e) {logger.error(e.getMessage(), e);}};// add extra valuefor (Map.Entry<String, Object> entry : addProperties.entrySet()) {try {dynamicBean.setValue(entry.getKey(), entry.getValue());} catch (Exception e) {logger.error(e.getMessage(), e);}};Object target = dynamicBean.beanMap;return target;}public static class DynamicBean {/*** 目标对象*/private Object target;/*** 属性集合*/private BeanMap beanMap;public DynamicBean(Class superclass, Map<String, Class> propertyMap) {this.target = generateBean(superclass, propertyMap);this.beanMap = BeanMap.create(this.target);}/*** bean 添加属性和值** @param property* @param value*/public void setValue(String property, Object value) {beanMap.put(property, value);}/*** 获取属性值** @param property* @return*/public Object getValue(String property) {return beanMap.get(property);}/*** 获取对象** @return*/public Object getTarget() {return this.target;}/*** 根据属性生成对象** @param superclass* @param propertyMap* @return*/private Object generateBean(Class superclass, Map<String, Class> propertyMap) {BeanGenerator generator = new BeanGenerator();if (null != superclass) {generator.setSuperclass(superclass);}BeanGenerator.addProperties(generator, propertyMap);return generator.create();}}
}

至此,我们就生成了Excel表格,并且把数据写入到了输出流中。

下面我们需要从输出流中拿到Excel表格数据,并做为邮件的附件发送出去。

2.邮件发送

1.邮件发送功能实现-带附件

Spring Email 抽象的核心是 JavaMailSender接口,通过实现JavaMailSender接口把 Email 发送给邮件服务器,由邮件服务器实现邮件发送的功能。

Spring 自带了一个 JavaMailSender的实现 JavaMailSenderImpl。SpringBoot 应用在发送 Email 之前,我们需要在配置文件中对JavaMailSender进行属性配置,这样就可以利用Springboot的自动装配机制,将 JavaMailSenderImpl 装配为 Spring容器的一个 bean。

spring.mail.host: xxxxxxx.com
# 设置端口
spring.mail.port: 25
# 设置用户名
spring.mail.username: xxxxxxxxxx
# 设置密码,该处的密码是QQ邮箱开启SMTP的授权码而非QQ密码
spring.mail.password: xxxxxxxxx
# 设置是否需要认证,如果为true,那么用户名和密码就必须的,
# 如果设置false,可以不设置用户名和密码,当然也得看你的对接的平台是否支持无密码进行访问的。
spring.mail.properties.mail.smtp.auth: false
# STARTTLS[1]  是对纯文本通信协议的扩展。它提供一种方式将纯文本连接升级为加密连接(TLS或SSL),而不是另外使用一个端口作加密通信。
spring.mail.properties.mail.smtp.starttls.enable: true
spring.mail.properties.mail.smtp.starttls.required: fasle
spring.mail.properties.mail.imap.starttls.socketFactory.fallback: false
spring.mail.properties.mail.smtp.starttls.socketFactory.class: com.ey.model.MailCommand

继上面完整的Excel生成代码,现在继续写邮件发送代码:

    @Autowiredprivate JavaMailSender springMailSender;    @Overridepublic void publish(xxxxxxPublishVo publishVo) {//........这里不再复制上面的代码,只从Excel表格写入输出流开始.........// excel写入输出流writer.flush(outputStream,true);//邮件附件名称String fileName = String.format("制定xx目标-%s.xlsx",UUID.randomUUID());//这个地方无需再配置,springboot自动装配,配置信息在nacos配置中心
//        springMailSender.setDefaultEncoding("UTF-8");
//        springMailSender.setHost("mx.goertek.com");
//        springMailSender.setPort(25);
//        springMailSender.setProtocol(JavaMailSenderImpl.DEFAULT_PROTOCOL);
//        springMailSender.setUsername("tims.sys@goertek.com");
//        springMailSender.setPassword("Khkd0804");
//        Properties p = new Properties();
//        p.setProperty("mail.smtp.timeout", "25000");
//        p.setProperty("mail.smtp.auth", "true");
//        p.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
//        springMailSender.setJavaMailProperties(p);MimeMessage mimeMessage = springMailSender.createMimeMessage();System.getProperties().setProperty("mail.mime.splitlongparameters", "false");MimeMessageHelper messageHelper = null;try {messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");} catch (MessagingException e) {throw new RuntimeException(e);}try {LoginUser userInfo = UserUtil.getCurrentUser();
//            String currentUserEmail = userInfo.getEmaila();
//            messageHelper.setFrom(currentUserEmail);messageHelper.setFrom(mailUserName);//设置收件人String[] emailArr = publishVo.getEmails().replaceAll("\\s+", "").split(",");messageHelper.setTo(emailArr);//设置抄送人if (!StringUtils.isBlank(publishVo.getCcEmails())){String[] ccEmailArr = publishVo.getCcEmails().replaceAll("\\s+", "").split(",");messageHelper.setCc(ccEmailArr);}messageHelper.setSubject("项目-" + publishVo.getProjectName().concat(": 制定品质目标完毕"));messageHelper.setText("项目-" + publishVo.getProjectName().concat(": 制定品质目标完毕"));try {//messageHelper.addInline("doge.gif", new File("xx/xx/doge.gif"));messageHelper.addAttachment(MimeUtility.encodeWord(fileName,"utf-8","B"), new ByteArrayResource(outputStream.toByteArray()));} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}springMailSender.send(mimeMessage);} catch (MessagingException e) {throw new RuntimeException(e);}

 2.踩过的坑

上述邮件发送功能实现过程中踩过的坑:

1.附件名中文乱码问题

附件的名字是中文,发送成功后,在邮件中的附件名字中文乱码,怎样解决这个问题?

1. 设置系统值:

System.setProperty("mail.mime.splitlongparameters", "false");

2. 这里,在创建对象的时候定义编码格式(utf-8):

MimeMessageHelper messageHelper = new MimeMessageHelper(mes, true, "utf-8");

3. 其次,在添加附件的时候,附件名是需要定义编码:

messageHelper.addAttachment(MimeUtility.encodeWord(附件名,"utf-8","B"), 附件输入流));

3.参考文章:

使用hutool工具进行导入导出excel表格_hutool excel-CSDN博客

springboot:实现excel生成并且通过邮件发送 - 哔哩哔哩 

相关文章:

Springboot 使用JavaMailSender发送邮件 + Excel附件

目录 1.生成Excel表格 1.依赖设置 2.代码&#xff1a; 2.邮件发送 1.邮件发送功能实现-带附件 2.踩过的坑 1.附件名中文乱码问题 3.参考文章&#xff1a; 需求描述&#xff1a;项目审批完毕后&#xff0c;需要发送邮件通知相关人员&#xff0c;并且要附带数据库表生成的…...

软件工程——期末复习知识点汇总

本帖的资料来源于某国内顶流高校的期末考试资料&#xff0c;仅包含核心的简答题&#xff0c;大家结合个人情况&#xff0c;按需复习~ 总的来说&#xff0c;大层面重点包括如下几个方面&#xff1a; 软件过程需求工程 设计工程软件测试软件项目管理软件过程管理 1.掌握软件生命…...

postgresSQL 数据库本地创建表空间读取本地备份tar文件与SQL文件

使用pgAdmin4&#xff0c;你安装PG得文件夹****/16/paAdmin 4 /runtime/pgAdmin4.exe 第一步&#xff1a;找到Tablespaces 第二步&#xff1a;创建表空间名称 第三步&#xff1a;指向数据文件 第四步&#xff1a;找到Databases&#xff0c;创建表空间 第五步&#xff1a;输入数…...

Elasticsearch跨集群检索配置

跨集群检索字面意思&#xff0c;同一个检索语句&#xff0c;可以检索到多个ES集群中的数据&#xff0c;ES集群默认是支持跨集群检索的&#xff0c;只需要动态的增加入节点即可&#xff0c;下面跟我一起来体验下ES的跨集群检索的魅力。 Elasticsearch 跨集群检索推荐的是不同集群…...

第九章 软件BUG和管理

一、学习目的与要求 软件测试的目的就是为了发现软件BUG。通过本章的学习&#xff0c;应了解软件BUG的产生和影响&#xff0c;掌握软件开发过程中产生的BUG种类&#xff0c;掌握使BUG重现的技术&#xff0c;了解软件BUG报告单应该包括的主要内容及软件BUG的管理流程。 二、考核…...

大厂面试题-Java并发编程基础篇(二)

目录 一、wait和notify这个为什么要在synchronized代码块中&#xff1f; 二、ThreadLocal是什么&#xff1f;它的实现原理呢&#xff1f; 三、基于数组的阻塞队列ArrayBlockingQueue原理 四、怎么理解线程安全&#xff1f; 五、请简述一下伪共享的概念以及如何避免 六、什…...

测绘屠夫报表系统V1.0.0-beta

1. 简介 测绘屠夫报表系统&#xff0c;能够根据变形监测数据&#xff1a;水准、平面、轴力、倾斜等数据&#xff0c;生成对应的报表&#xff0c;生成报表如下图。如需进一步了解&#xff0c;可以加QQ&#xff1a;3339745885。视频教程可以在bilibili观看。 2. 软件主界面 3. …...

『力扣刷题本』:移除链表元素

一、题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]示例 2&#xff1a; 输入&a…...

图像特征Vol.1:计算机视觉特征度量|第一弹:【纹理区域特征】

目录 一、前言二、纹理区域度量2.1&#xff1a;边缘特征度量2.2&#xff1a;互相关和自相关特征2.3&#xff1a;频谱方法—傅里叶谱2.4&#xff1a;灰度共生矩阵(GLCM)2.5&#xff1a;Laws纹理特征2.6&#xff1a;局部二值模式&#xff08;LBP&#xff09; 一、前言 &#x1f…...

day01:数据库DDL

一:基础概念 数据库:存储数据的仓库&#xff0c;数据是有组织的进行存储 数据库管理系统:操纵和管理数据库的大型软件 SQL&#xff1a;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库统一标准 关系图 二:数据模型 关系型数据库:建…...

9、定义错误页

在layouts目录下新建error.vue&#xff0c;可以通过layout函数使用布局文件&#xff0c;通过props: [“error”]能拿到错误信息对象。 <template><div>{{ error.statusCode }}: {{ error.message }}</div> </template><script> export default {…...

有关多线程环境下的Volatile、lock、Interlocked和Synchronized们

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不仅…...

spring boot利用redis作为缓存

一、缓存介绍 在 Spring Boot 中&#xff0c;可以使用 Spring Cache abstraction 来实现缓存功能。Spring Cache abstraction 是 Spring 框架提供的一个抽象层&#xff0c;它对底层缓存实现&#xff08;如 Redis、Ehcache、Caffeine 等&#xff09;进行了封装&#xff0c;使得在…...

Android Studio 查看Framework源码

1、背景 安卓系统源码量很庞大&#xff0c;选择好的开发工具和方式去开发可以提升开发效率&#xff0c;常用的开发工具有Source Insight 、Visual Studio Code、Android Studio&#xff0c;vscode适合C和C代码开发&#xff0c;java层代码无法跳转和提示&#xff0c;因此&#…...

FileInputStream文件字节输入流

一.概念 以内存为基准&#xff0c;把磁盘文件中的数据以字节形式读入内存中 二.构造器 public FileInputStream(File file) public FileInputStream(String pathname) 这两个都是创建字节输入流管道与源文件接通 三.方法 public int read() :每次读取一个字节返回&#xff0c;如…...

【Qt】窗口和对话框区别、主窗口和二级窗口区别、QMainWindow和QDialog区别

窗口和对话框&#xff08;Window and Dialog Widgets&#xff09; 未嵌入在父界面中的界面称为窗口。&#xff08;通常&#xff0c;窗口具有边框和标题栏&#xff0c;尽管也可以使用合适的窗口标志创建没有此类标志的窗口&#xff09;。 在Qt中&#xff0c;QMainWindow和QDial…...

Python参数种类介绍

Python参数种类介绍 相比于一些其他编程语言&#xff0c;Python提供了更多的参数种类选项。这是Python的一大特点&#xff0c;使用不同的参数类型&#xff0c;可以提高函数的可读性和可维护性。例如&#xff0c;使用关键字参数可以使函数调用更加清晰&#xff0c;不需要记住参数…...

react事件机制

React 事件机制 React的事件机制是React框架中非常重要的一部分&#xff0c;用于处理用户交互和用户界面上的事件。React的事件机制在底层使用了虚拟DOM以及合成事件来提高性能和跨浏览器兼容性。以下是关于React事件机制的详细信息&#xff1a; 合成事件&#xff08;Syntheti…...

JAVA删除excel指定列

首先POI没有提供删除列的API&#xff0c;所以就需要用其他的方式实现。 在 java - Apache POI xls column Remove - Stack Overflow 这里找到了实现方式&#xff1a; 先将该列所有值都清空&#xff0c;然后将该列之后的所有列往前移动。 下面的工具类中 deleteColumns(Inpu…...

Netty编码器和解码器

文章目录 一、Decoder原理与实践1、ByteToMessageDecoder解码器2、自定义整数解码器1&#xff09;常规方式2&#xff09;ReplayingDecoder解码器 3、分包解码器3、MessageToMessageDecoder解码器 二、Netty内置的Decoder1、LineBasedFrameDecoder解码器2、DelimiterBasedFrameD…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...