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.代码:

复制代码
    @Override
    public 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 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表格数据,并做为邮件的附件发送出去。

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生成代码,现在继续写邮件发送代码:

复制代码
    @Autowired
    private JavaMailSender springMailSender;    

    @Override
    public 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");

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

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

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

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

3.参考文章:

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

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

相关推荐
悟空码字1 小时前
Spring Boot 整合 MongoDB 最佳实践:CRUD、分页、事务、索引全覆盖
java·spring boot·后端
皮皮林5512 天前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602734 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840825 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解5 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解5 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记5 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者6 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840826 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解6 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端