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


    目录

    1.生成Excel表格

    1.依赖设置

    2.代码:

    2.邮件发送

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

     2.踩过的坑

    1.附件名中文乱码问题

    3.参考文章:


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

    1.生成Excel表格

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

    1.依赖设置


        cn.hutool
        hutool-all
        5.7.22


        org.apache.poi
        poi-ooxml
        5.2.2

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

    2.代码:

    要加try finally,在finally将输出流和其他需要关闭的都进行关闭,防止发生内存泄漏 

    1. @Override
    2. public void publish(xxxxxxPublishVo publishVo) {
    3. ....................................................
    4. /**
    5. * 生成Excel表格
    6. */
    7. //在内存操作,写到输出流中
    8. ExcelWriter writer = ExcelUtil.getWriter(true);
    9. //自定义标题别名
    10. writer.addHeaderAlias("projectCode", "项目编号");
    11. writer.addHeaderAlias("projectName", "项目名称");
    12. writer.addHeaderAlias("targetType", "指标类型");
    13. writer.addHeaderAlias("targetName", "指标名称");
    14. writer.addHeaderAlias("targetForMp", "转量产品质目标");
    15. writer.addHeaderAlias("symbols", "目标限制符");
    16. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    17. try{
    18. //获取数据
    19. Qpm******Mg query = new Qpm******Mg();
    20. query.setProjectCode(publishVo.getProjectCode());
    21. List<Qpm******MGListDTO> data = selectQpm******MgByCondition(query);
    22. //整理数据,以便于生成Excel表格
    23. List<Object> dataNew = new ArrayList<>();
    24. Set<String> stageCollectSet = new HashSet<>();
    25. //后续排序的时候需要使用
    26. // List<Qpm******MgStage> noRepeatList = new ArrayList<>();
    27. //先获取所有的阶段:D*T,E*T等
    28. for (Qpm******MGListDTO target : data) {
    29. List<Qpm******MgStage> stageList = target.get******StageList();
    30. for (Qpm******MgStage stage : stageList) {
    31. if (!stageCollectSet.contains(stage.getStage())) {
    32. stageCollectSet.add(stage.getStage());
    33. //给Excel增加列
    34. writer.addHeaderAlias(stage.getStage(), stage.getStage() + "目标");
    35. }
    36. }
    37. }
    38. //处理Excel表格数据
    39. for (Qpm******MGListDTO target : data) {
    40. List<Qpm******MgStage> stageList = target.get******StageList();
    41. Map<String, Object> addProperties = new HashMap<>();
    42. for (String stageStr : stageCollectSet) {
    43. addProperties.put(stageStr, "");
    44. }
    45. for (Qpm******MgStage stage : stageList) {
    46. //为对象动态增加属性
    47. //这里要处理值为null的情况
    48. if(null == stage.getxxxTarget()){
    49. addProperties.put(stage.getStage(), "");
    50. }else{
    51. addProperties.put(stage.getStage(), stage.getxxxTarget());
    52. // noRepeatList.add(stage);
    53. }
    54. }
    55. //生成新的包含了新增字段的对象
    56. Object targetNew = ReflectUtil.getTarget(target, addProperties);
    57. dataNew.add(targetNew);
    58. }
    59. //只保留别名的数据
    60. writer.setOnlyAlias(true);
    61. writer.write(dataNew, true);
    62. // excel写入输出流
    63. writer.flush(outputStream, true);
    64. ...........................................................................

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

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

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

    class Test {

        private String class;

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

        private List studentList;

    }

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

    1. import com.google.common.collect.Maps;
    2. import net.sf.cglib.beans.BeanGenerator;
    3. import net.sf.cglib.beans.BeanMap;
    4. import org.apache.commons.beanutils.PropertyUtilsBean;
    5. import org.slf4j.Logger;
    6. import org.slf4j.LoggerFactory;
    7. import java.beans.PropertyDescriptor;
    8. import java.lang.reflect.Method;
    9. import java.util.HashMap;
    10. import java.util.Map;
    11. /**
    12. * 为实体类动态增加属性,用于生成Excel表格时的特殊情况,例如表格中的列需要动态增加
    13. */
    14. public class ReflectUtil {
    15. static Logger logger = LoggerFactory.getLogger(ReflectUtil.class);
    16. public static Object getTarget(Object dest, Map<String, Object> addProperties) {
    17. // get property map
    18. PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
    19. PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);
    20. Map<String, Class> propertyMap = Maps.newHashMap();
    21. for (PropertyDescriptor d : descriptors) {
    22. if (!"class".equalsIgnoreCase(d.getName())) {
    23. propertyMap.put(d.getName(), d.getPropertyType());
    24. }
    25. }
    26. // add extra properties
    27. for (Map.Entry<String, Object> entry : addProperties.entrySet()) {
    28. propertyMap.put(entry.getKey(), entry.getValue().getClass());
    29. }
    30. // addProperties.forEach((k, v) -> propertyMap.put(k, v.getClass()));
    31. // new dynamic bean
    32. DynamicBean dynamicBean = new DynamicBean(dest.getClass(), propertyMap);
    33. // add old value
    34. for (Map.Entry<String, Class> entry : propertyMap.entrySet()) {
    35. try {
    36. // filter extra properties
    37. if (!addProperties.containsKey(entry.getKey())) {
    38. dynamicBean.setValue(entry.getKey(), propertyUtilsBean.getNestedProperty(dest, entry.getKey()));
    39. }
    40. } catch (Exception e) {
    41. logger.error(e.getMessage(), e);
    42. }
    43. }
    44. ;
    45. // add extra value
    46. for (Map.Entry<String, Object> entry : addProperties.entrySet()) {
    47. try {
    48. dynamicBean.setValue(entry.getKey(), entry.getValue());
    49. } catch (Exception e) {
    50. logger.error(e.getMessage(), e);
    51. }
    52. }
    53. ;
    54. Object target = dynamicBean.beanMap;
    55. return target;
    56. }
    57. public static class DynamicBean {
    58. /**
    59. * 目标对象
    60. */
    61. private Object target;
    62. /**
    63. * 属性集合
    64. */
    65. private BeanMap beanMap;
    66. public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
    67. this.target = generateBean(superclass, propertyMap);
    68. this.beanMap = BeanMap.create(this.target);
    69. }
    70. /**
    71. * bean 添加属性和值
    72. *
    73. * @param property
    74. * @param value
    75. */
    76. public void setValue(String property, Object value) {
    77. beanMap.put(property, value);
    78. }
    79. /**
    80. * 获取属性值
    81. *
    82. * @param property
    83. * @return
    84. */
    85. public Object getValue(String property) {
    86. return beanMap.get(property);
    87. }
    88. /**
    89. * 获取对象
    90. *
    91. * @return
    92. */
    93. public Object getTarget() {
    94. return this.target;
    95. }
    96. /**
    97. * 根据属性生成对象
    98. *
    99. * @param superclass
    100. * @param propertyMap
    101. * @return
    102. */
    103. private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
    104. BeanGenerator generator = new BeanGenerator();
    105. if (null != superclass) {
    106. generator.setSuperclass(superclass);
    107. }
    108. BeanGenerator.addProperties(generator, propertyMap);
    109. return generator.create();
    110. }
    111. }
    112. }

    至此,我们就生成了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生成代码,现在继续写邮件发送代码:

    1. @Autowired
    2. private JavaMailSender springMailSender;
    3. @Override
    4. public void publish(xxxxxxPublishVo publishVo) {
    5. /**
    6. * 生成Excel表格
    7. */
    8. //在内存操作,写到输出流中
    9. ExcelWriter writer = ExcelUtil.getWriter(true);
    10. //自定义标题别名
    11. writer.addHeaderAlias("projectCode", "项目编号");
    12. writer.addHeaderAlias("projectName", "项目名称");
    13. writer.addHeaderAlias("targetType", "指标类型");
    14. writer.addHeaderAlias("targetName", "指标名称");
    15. writer.addHeaderAlias("targetForMp", "转量产品质目标");
    16. writer.addHeaderAlias("symbols", "目标限制符");
    17. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    18. try{
    19. //获取数据
    20. Qpm******Mg query = new Qpm******Mg();
    21. query.setProjectCode(publishVo.getProjectCode());
    22. List<Qpm******MGListDTO> data = selectQpm******MgByCondition(query);
    23. //整理数据,以便于生成Excel表格
    24. List<Object> dataNew = new ArrayList<>();
    25. Set<String> stageCollectSet = new HashSet<>();
    26. //后续排序的时候需要使用
    27. // List<Qpm******MgStage> noRepeatList = new ArrayList<>();
    28. //先获取所有的阶段:D*T,E*T等
    29. for (Qpm******MGListDTO target : data) {
    30. List<Qpm******MgStage> stageList = target.get******StageList();
    31. for (Qpm******MgStage stage : stageList) {
    32. if (!stageCollectSet.contains(stage.getStage())) {
    33. stageCollectSet.add(stage.getStage());
    34. //给Excel增加列
    35. writer.addHeaderAlias(stage.getStage(), stage.getStage() + "目标");
    36. }
    37. }
    38. }
    39. //处理Excel表格数据
    40. for (Qpm******MGListDTO target : data) {
    41. List<Qpm******MgStage> stageList = target.get******StageList();
    42. Map<String, Object> addProperties = new HashMap<>();
    43. for (String stageStr : stageCollectSet) {
    44. addProperties.put(stageStr, "");
    45. }
    46. for (Qpm******MgStage stage : stageList) {
    47. //为对象动态增加属性
    48. addProperties.put(stage.getStage(), stage.getStageTarget());
    49. // noRepeatList.add(stage);
    50. }
    51. //生成新的包含了新增字段的对象
    52. Object targetNew = ReflectUtil.getTarget(target, addProperties);
    53. dataNew.add(targetNew);
    54. }
    55. //只保留别名的数据
    56. writer.setOnlyAlias(true);
    57. writer.write(dataNew, true);
    58. // excel写入输出流
    59. writer.flush(outputStream, true);
    60. //这个地方无需再配置,springboot自动装配,配置信息在nacos配置中心
    61. // springMailSender.setDefaultEncoding("UTF-8");
    62. // springMailSender.setHost("mx.goertek.com");
    63. // springMailSender.setPort(25);
    64. // springMailSender.setProtocol(JavaMailSenderImpl.DEFAULT_PROTOCOL);
    65. // springMailSender.setUsername("tims.sys@goertek.com");
    66. // springMailSender.setPassword("Khkd0804");
    67. // Properties p = new Properties();
    68. // p.setProperty("mail.smtp.timeout", "25000");
    69. // p.setProperty("mail.smtp.auth", "true");
    70. // p.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    71. // springMailSender.setJavaMailProperties(p);
    72. MimeMessage mimeMessage = springMailSender.createMimeMessage();
    73. System.getProperties().setProperty("mail.mime.splitlongparameters", "false");
    74. MimeMessageHelper messageHelper = null;
    75. messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
    76. // LoginUser userInfo = UserUtil.getCurrentUser();
    77. // String currentUserEmail = userInfo.getEmaila();
    78. // messageHelper.setFrom(currentUserEmail);
    79. messageHelper.setFrom(mailUserName);
    80. //设置收件人
    81. String[] emailArr = publishVo.getEmails().replaceAll("\\s+", "").split(",");
    82. messageHelper.setTo(emailArr);
    83. //设置抄送人
    84. if (!StringUtils.isBlank(publishVo.getCcEmails())){
    85. String[] ccEmailArr = publishVo.getCcEmails().replaceAll("\\s+", "").split(",");
    86. messageHelper.setCc(ccEmailArr);
    87. }
    88. messageHelper.setSubject("【G**S通知】项目-" + publishVo.getProjectName().concat(": 制定**目标完毕"));
    89. if (StringUtils.isEmpty(publishVo.getContent())){
    90. messageHelper.setText("项目-" + publishVo.getProjectName().concat(": 制定**目标完毕"));
    91. }else {
    92. messageHelper.setText(publishVo.getContent());
    93. }
    94. //messageHelper.addInline("doge.gif", new File("xx/xx/doge.gif"));
    95. messageHelper.addAttachment(MimeUtility.encodeWord(fileName,"utf-8","B"), new ByteArrayResource(outputStream.toByteArray()));
    96. springMailSender.send(mimeMessage);
    97. } catch (MessagingException e) {
    98. throw new RuntimeException(e);
    99. } catch (UnsupportedEncodingException e) {
    100. throw new RuntimeException(e);
    101. } finally {
    102. // 关闭writer,释放内存,防止内存泄漏
    103. writer.close();
    104. //关闭输出流,防止内存泄漏
    105. try {
    106. if(null != outputStream) {
    107. outputStream.close();
    108. }
    109. } catch (IOException e) {
    110. throw new RuntimeException(e);
    111. }
    112. }
    113. }

     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生成并且通过邮件发送 - 哔哩哔哩

    重要: Hutool Java 工具类库Excel导出,配置宽度自适应极度舒适_hutool导出excel设置列宽_夜雨微澜醉挽清风的博客-CSDN博客 

  • 相关阅读:
    《Python 计算机视觉编程》学习笔记(三)
    ZigBee 3.0实战教程-Silicon Labs EFR32+EmberZnet-3-02:不同BootLoader之间的区别/如何选择
    进入C++
    面向对象的基础知识
    4ARM-PEG-OH 四臂PEG羟基
    SpringBoot + 一个注解,轻松实现 Redis 分布式锁
    Java8中的函数式接口(你知道几个?)
    ai配音怎么弄?推荐一款免费好用的ai配音软件
    C语言源代码系列-管理系统之机房机位预定系统
    【毕业设计】基于Stm32的人体心率脉搏无线监测系统 - 单片机 物联网
  • 原文地址:https://blog.csdn.net/wdquan19851029/article/details/134078150