目录
需求描述:项目审批完毕后,需要发送邮件通知相关人员,并且要附带数据库表生成的Excel表格,这就要求不光是邮件发送功能,还要临时生成Excel表格做为附件
使用huTool工具包的Excel表格生成功能
cn.hutool
hutool-all
5.7.22
org.apache.poi
poi-ooxml
5.2.2
Hutool-all中包含了Hutool的所有工具类,由于需要生成Excel文件需要依赖poi
要加try finally,在finally将输出流和其他需要关闭的都进行关闭,防止发生内存泄漏
- @Override
- public void publish(xxxxxxPublishVo publishVo) {
- ....................................................
- /**
- * 生成Excel表格
- */
- //在内存操作,写到输出流中
- ExcelWriter writer = ExcelUtil.getWriter(true);
- //自定义标题别名
- writer.addHeaderAlias("projectCode", "项目编号");
- writer.addHeaderAlias("projectName", "项目名称");
- writer.addHeaderAlias("targetType", "指标类型");
- writer.addHeaderAlias("targetName", "指标名称");
- writer.addHeaderAlias("targetForMp", "转量产品质目标");
- writer.addHeaderAlias("symbols", "目标限制符");
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- try{
- //获取数据
- Qpm******Mg query = new Qpm******Mg();
- query.setProjectCode(publishVo.getProjectCode());
- List<Qpm******MGListDTO> data = selectQpm******MgByCondition(query);
-
- //整理数据,以便于生成Excel表格
- List<Object> dataNew = new ArrayList<>();
- Set<String> stageCollectSet = new HashSet<>();
- //后续排序的时候需要使用
- // List<Qpm******MgStage> noRepeatList = new ArrayList<>();
- //先获取所有的阶段:D*T,E*T等
- for (Qpm******MGListDTO target : data) {
- List<Qpm******MgStage> stageList = target.get******StageList();
- for (Qpm******MgStage stage : stageList) {
- if (!stageCollectSet.contains(stage.getStage())) {
- stageCollectSet.add(stage.getStage());
-
- //给Excel增加列
- writer.addHeaderAlias(stage.getStage(), stage.getStage() + "目标");
- }
- }
- }
- //处理Excel表格数据
- for (Qpm******MGListDTO target : data) {
- List<Qpm******MgStage> stageList = target.get******StageList();
- Map<String, Object> addProperties = new HashMap<>();
- for (String stageStr : stageCollectSet) {
- addProperties.put(stageStr, "");
- }
- for (Qpm******MgStage stage : stageList) {
- //为对象动态增加属性
- //这里要处理值为null的情况
- if(null == stage.getxxxTarget()){
- addProperties.put(stage.getStage(), "");
- }else{
- addProperties.put(stage.getStage(), stage.getxxxTarget());
- // noRepeatList.add(stage);
- }
- }
-
- //生成新的包含了新增字段的对象
- Object targetNew = ReflectUtil.getTarget(target, addProperties);
- dataNew.add(targetNew);
- }
-
- //只保留别名的数据
- writer.setOnlyAlias(true);
- writer.write(dataNew, true);
- // excel写入输出流
- writer.flush(outputStream, true);
- ...........................................................................
上述代码中,调用了工具类ReflectUtil给对象动态增加属性。由于数据中有子类,需要获取到子类中的某个字段并生成Excel表格,所以Excel表格构造就需要对数据对象进行改造,简单来说就是需要给对象动态增加新的属性(成员对象的属性),如下所示示例:
把studentList里面的关键属性数据,新增给Test类
示例不是很合适,凑合着用吧
class Test {
private String class;
.............................................
private List
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 map
- PropertyUtilsBean 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 properties
- for (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 bean
- DynamicBean dynamicBean = new DynamicBean(dest.getClass(), propertyMap);
- // add old value
- for (Map.Entry<String, Class> entry : propertyMap.entrySet()) {
- try {
- // filter extra properties
- if (!addProperties.containsKey(entry.getKey())) {
- dynamicBean.setValue(entry.getKey(), propertyUtilsBean.getNestedProperty(dest, entry.getKey()));
- }
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- }
- ;
- // add extra value
- for (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表格数据,并做为邮件的附件发送出去。
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生成代码,现在继续写邮件发送代码:
- @Autowired
- private JavaMailSender springMailSender;
-
- @Override
- public void publish(xxxxxxPublishVo publishVo) {
- /**
- * 生成Excel表格
- */
- //在内存操作,写到输出流中
- ExcelWriter writer = ExcelUtil.getWriter(true);
- //自定义标题别名
- writer.addHeaderAlias("projectCode", "项目编号");
- writer.addHeaderAlias("projectName", "项目名称");
- writer.addHeaderAlias("targetType", "指标类型");
- writer.addHeaderAlias("targetName", "指标名称");
- writer.addHeaderAlias("targetForMp", "转量产品质目标");
- writer.addHeaderAlias("symbols", "目标限制符");
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- try{
- //获取数据
- Qpm******Mg query = new Qpm******Mg();
- query.setProjectCode(publishVo.getProjectCode());
- List<Qpm******MGListDTO> data = selectQpm******MgByCondition(query);
-
- //整理数据,以便于生成Excel表格
- List<Object> dataNew = new ArrayList<>();
- Set<String> stageCollectSet = new HashSet<>();
- //后续排序的时候需要使用
- // List<Qpm******MgStage> noRepeatList = new ArrayList<>();
- //先获取所有的阶段:D*T,E*T等
- for (Qpm******MGListDTO target : data) {
- List<Qpm******MgStage> stageList = target.get******StageList();
- for (Qpm******MgStage stage : stageList) {
- if (!stageCollectSet.contains(stage.getStage())) {
- stageCollectSet.add(stage.getStage());
-
- //给Excel增加列
- writer.addHeaderAlias(stage.getStage(), stage.getStage() + "目标");
- }
- }
- }
- //处理Excel表格数据
- for (Qpm******MGListDTO target : data) {
- List<Qpm******MgStage> stageList = target.get******StageList();
- Map<String, Object> addProperties = new HashMap<>();
- for (String stageStr : stageCollectSet) {
- addProperties.put(stageStr, "");
- }
- for (Qpm******MgStage stage : stageList) {
- //为对象动态增加属性
- addProperties.put(stage.getStage(), stage.getStageTarget());
- // noRepeatList.add(stage);
- }
-
- //生成新的包含了新增字段的对象
- Object targetNew = ReflectUtil.getTarget(target, addProperties);
- dataNew.add(targetNew);
- }
-
- //只保留别名的数据
- writer.setOnlyAlias(true);
- writer.write(dataNew, true);
- // excel写入输出流
- writer.flush(outputStream, true);
-
- //这个地方无需再配置,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;
- messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
- // 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("【G**S通知】项目-" + publishVo.getProjectName().concat(": 制定**目标完毕"));
- if (StringUtils.isEmpty(publishVo.getContent())){
- messageHelper.setText("项目-" + publishVo.getProjectName().concat(": 制定**目标完毕"));
- }else {
- messageHelper.setText(publishVo.getContent());
- }
- //messageHelper.addInline("doge.gif", new File("xx/xx/doge.gif"));
- messageHelper.addAttachment(MimeUtility.encodeWord(fileName,"utf-8","B"), new ByteArrayResource(outputStream.toByteArray()));
-
- springMailSender.send(mimeMessage);
- } catch (MessagingException e) {
- throw new RuntimeException(e);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- } finally {
- // 关闭writer,释放内存,防止内存泄漏
- writer.close();
-
- //关闭输出流,防止内存泄漏
- try {
- if(null != outputStream) {
- outputStream.close();
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- }
上述邮件发送功能实现过程中踩过的坑:
附件的名字是中文,发送成功后,在邮件中的附件名字中文乱码,怎样解决这个问题?
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"), 附件输入流));
使用hutool工具进行导入导出excel表格_hutool excel-CSDN博客
springboot:实现excel生成并且通过邮件发送 - 哔哩哔哩
重要: Hutool Java 工具类库Excel导出,配置宽度自适应极度舒适_hutool导出excel设置列宽_夜雨微澜醉挽清风的博客-CSDN博客