Do you think it is inappropriate for the email sender to be fixed in the yml file? SpringBoot dynamically sets the sender of the message

Keywords: Java Spring Boot Back-end

On the day of the bright moon, I don't know if you miss anyone

preface

I've actually written it before Spring boot sends mail asynchronously However, today, when I need to send e-mail in a small project, I suddenly feel that there is only one sender of e-mail, and it is very inappropriate to write it in the yml file, so I think about how to integrate it into a dynamic one.

I've turned over many blogs before writing, and the pits I should step on are almost the same. I wrote an article after I realized it. If you have problems, you can communicate together.

Keep your voice down bb (I'm really numb to CSDN. It's called CV Dafa site for short. Although I'm also a small blogger and a senior user of CSDN, I can't say much about this article, except that I can say come on).

So there is the following article

1, Demand analysis

By default, everyone will already send the SpringBoot integrated email. Ha, no, click the link above.

Let me start with what I want to achieve:

  1. There can be multiple mail senders, and the yml file is configured (that is, when none is available in the database, the mail senders configured in the yml file are used)
  2. After the project is started, I can also temporarily add mail senders or disable a mail sender (the operation can take effect without restarting the project)
  3. The content of the sent email is html; In addition, send mail asynchronously (optional, everyone will)

The idea is actually quite simple. Just reinitialize the JavaSendMailImpl class every time we add or modify the email sender configuration. There's nothing to say in this place, just don't let the framework configure automatically for us, we can do it manually.

2, Detailed steps

2.1 coding

1) yml profile

spring:  
  mail:
    host: smtp.163.com
    username: nxxxxxx@163.com
    password: IXXXXXXXXXN(Enable the authorization code after allowing the third party to log in)
    default-encoding: utf-8
    protocol: smtps
    properties:
      mail:
        smtp:
          port: 465
          auth: true
          starttls:
            enable: true
            required: true

Note: as for the configuration of e-mail protocol protocol: smtp, I also configured smtp at the beginning. The error I reported was a no provider for smtp error. I also wrote that I always used this smtp protocol before, but when I reported this error, I searched and found a blog saying,

SMTPS protocol

SMTPS (SMTP over SSL) is a variant of SMTP protocol based on SSL security protocol. It inherits the high security and reliability of asymmetric encryption of SSL security protocol and can prevent mail leakage. Like SMTP protocol, SMTPS is also used to send mail, but it is safer to prevent mail from being intercepted and leaked by hackers, and can also realize the anti repudiation function of mail sender. Prevent the sender from deleting the sent email after sending it, and refuse to admit that such an email has been sent. Ports 465 and 587 are open based on the SMTPS protocol.

Port 465 (SMTPS): it is one of the ports used by the SMTPS protocol service. It is encrypted transmission (SSL/TLS) during mail transmission. Compared with the SMTP protocol, the attacker cannot obtain the mail content, and the mail is protected from the beginning.

So in fact, the configuration we use should be stmps.

In addition, create a properties resource class to correspond to the configuration file one by one

/**
 * @author crush
 */
@Data
@Component
@ConfigurationProperties(prefix = "spring.mail")
public class MailProperties {
        /**  * user name */
        private String username;
        /** * Authorization code */
        private String password;
        /** * host */
        private String host;
        /** * port */
        private Integer port;
        /*** agreement */
        private String protocol;
        /** * Default encoding*/
        private String defaultEncoding;
}

2.2. Table construction

According to the yml file, we roughly know what kind of data table to create.

These can be customized and built according to your own needs.

Create a pojo class according to the data table.

/**
 * @Author: crush
 * @Date: 2021-11-26 18:28
 * version 1.0
 */
@Data
@Accessors(chain = true)
@TableName("tb_email")
public class MailPO {

    private String emailHost;
    private String emailUsername;
    private String emailPassword;
    private Integer emailPort=465;
    /** * agreement */
    private String protocol="smtps";

    /** * Default encoding */
    private String defaultEncoding="utf-8";
    /**
     * Usage status, 1: in use, 2: disabled, 3: Disabled
     * TODO Later, you should change to enumeration class for implementation
     */
    private Integer state=1;
    /** * Creation time */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /*** Modification time */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime  updateTime;
}

If you do not use mybatis plus, you can remove the creation time and modification time. @ TableField(fill = FieldFill.INSERT) is an annotation in mybatis plus. In addition, my primary key is set to self increment, so it is empty. As for the returned class, I used it under the vo package.

2.3 mapper and service layers

@Repository
public interface MailMapper extends BaseMapper<MailPO> {
}

service

/**
 * @Author: crush
 * @Date: 2021-11-26 15:55
 * version 1.0
 */
public interface MailService {

    void send(MailDTO mailDTO);

    boolean addMailPerson(MailPO mailPO);
}

impl

import cn.hutool.core.util.IdUtil;
/**
 * @author crush
 * Mailbox sending implementation class
 */
@Service
public class MailServiceImpl implements MailService {

    @Autowired
    MailSenderConfig senderConfig;

    @Autowired
    MailProperties mailProperties;

    @Autowired
    MailMapper mailMapper;

    // A thread pool has been configured here before. There is one in the above link, so I won't say it
    // @Async("taskExecutor")
    @Override
    public void send(MailDTO mailDTO) {
        String context = "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\" />\n" +
                "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n" +
                "    <title>xxxx mail</title>\n" +
                "    <style>\n" +
                "        body {\n" +
                "            margin: 0;\n" +
                "            padding: 0;\n" +
                "        }\n" +
                "        \n" +
                "        .email {\n" +
                "            position: relative;\n" +
                "            width: 100%;\n" +
                "            /* background-color: rgba(0, 0, 0, 1); */\n" +
                "        }\n" +
                "        \n" +
                "        .main {\n" +
                "            left: 0;\n" +
                "            right: 0;\n" +
                "            margin: auto;\n" +
                "            width: 80%;\n" +
                "            max-width: 800px;\n" +
                "            box-sizing: content-box;\n" +
                "        }\n" +
                "        \n" +
                "        .main .title {\n" +
                "            /* color: white; */\n" +
                "            display: inline-flex;\n" +
                "            align-items: center;\n" +
                "        }\n" +
                "        \n" +
                "        .main .title span {\n" +
                "            margin: 0 10px;\n" +
                "        }\n" +
                "        \n" +
                "        .main table {\n" +
                "            width: 100%;\n" +
                "        }\n" +
                "        \n" +
                "        .main table tbody td {\n" +
                "            /* background-color: white; */\n" +
                "            padding: 20px;\n" +
                "            text-align: left;\n" +
                "            border-bottom: 1px solid rgb(161, 161, 161);\n" +
                "        }\n" +
                "        \n" +
                "        tfoot td p {\n" +
                "            color: rgb(161, 161, 161);\n" +
                "            font-size: 13px;\n" +
                "        }\n" +
                "        \n" +
                "        a {\n" +
                "            color: rgb(161, 161, 161);\n" +
                "            text-decoration: none;\n" +
                "        }\n" +
                "        \n" +
                "        a:hover {\n" +
                "            border-bottom: 1px solid rgb(161, 161, 161);\n" +
                "        }\n" +
                "    </style>\n" +
                "</head>\n" +
                "\n" +
                "<body>\n" +
                "    <div class=\"email\">\n" +
                "        <div class=\"main\">\n" +
                "            <table>\n" +
                "                <thead>\n" +
                "                    <tr>\n" +
                "                        <td>\n" +
                "                            <h1 class=\"title\">\n" +
                "                                <img width=\"60\" src=\"xxxxx\" alt=\"\" />\n" +
                "                                <span>" + mailDTO.getTitle() + "</span>\n" +
                "                            </h1>\n" +
                "                        </td>\n" +
                "                    </tr>\n" +
                "                </thead>\n" +
                "                <tbody>\n" +
                "                    <tr>\n" +
                "                        <td>\n" +
                "                            " + mailDTO.getContent() + "\n" +
                "                        </td>\n" +
                "                    </tr>\n" +
                "                </tbody>\n" +
                "                <tfoot>\n" +
                "                    <tr>\n" +
                "                        <td>\n" +
                "                            <p>The mail is sent automatically by the system. Please do not reply directly.</p>\n" +
                "                            <p>Official website:\n" +
                "                                <a href=\"https://blog.csdn.net/weixin_ 45821811? SPM = 1000.2115.3001.5343 \ "> Ning Zaichun blog < / a > \ n"+
                "                            </p>\n" +
                "                        </td>\n" +
                "                    </tr>\n" +
                "                </tfoot>\n" +
                "            </table>\n" +
                "        </div>\n" +
                "    </div>\n" +
                "</body>\n" +
                "\n" +
                "</html>";

        JavaMailSenderImpl mailSender = senderConfig.getSender();
        //Create a SimpleMailMessage object
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //You need to create a MimeMessageHelper object with related parameters similar to simple mail
        try {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
                //Sender
                helper.setFrom(mailSender.getUsername());
                //The recipient can be an array, but I only need a single one, so I don't do much.
                helper.setTo(mailDTO.getMail());
                helper.setSubject("Verification Code");
                //Format the message content as html
                // send out
                helper.setText( context, true);
                mailSender.send(mimeMessage);
            } catch (MessagingException e) {
                e.printStackTrace();
        }

    }

    // Add the initialization information and re initialize it.
    @Override
    public boolean addMailPerson(MailPO mailPO) {
        if(mailMapper.insert(mailPO)>0){
            senderConfig.clear();
            senderConfig.buildMailSender();
            return true;
        }
        return false;
    }
}

MailDto used

/**
 * @author crush
 * Mailbox sending - front end transmission parameters
 */
@Data
public class MailDTO implements Serializable {

    /*** Accept email account*/
    private String mail;
    /*** Mailbox title*/
    private String title;
    /** * What to send*/
    private String content;
}

2.4 MailSenderConfig configuration class

/**
 * @author crush
 */
@Slf4j
@Component
@AllArgsConstructor
public class MailSenderConfig {

    private final List<JavaMailSenderImpl> senderList;

    private final MailProperties mailProperties;

    private final MailMapper mailMapper;

    /**
     * Initialize sender
     * PostConstruct Annotations are used for methods that need to perform any initialization after dependency injection is complete. This method must be called before classes are put into use.
     * At the beginning, I thought this method (@ PostConstruct) was inappropriate, that is, it didn't feel like it could be used immediately after modification.
      * However, after writing, I found that it is OK to reinitialize every time a new sender is added.
      * Then I started the event listener with@ PostConstruct didn't test it later.
      * After adding and modifying the theory, you can call this initialization method.
     */
//    @PostConstruct
    public void buildMailSender() {
        log.info("initialization mailSender");
        List<MailPO> mails = mailMapper.selectList(new QueryWrapper<MailPO>().eq("state", 1));
        /**
         * Demand: it was originally intended to be a dynamic e-mail sender, because if you always use an e-mail to send verification code or disturbing SMS, once the speed is too frequent, it will cause e-mail sending errors.
         * Idea: get all available email senders from the database, then package them, and then make random selection when sending emails.
         * Another way is that it is dynamic.
         * Finally, add a bottom line. If the sender cannot be queried in the database, we use the send mail configuration in the configuration file.
         */
        if(mails!=null&&!mails.isEmpty()){
            mails.forEach(mail -> {
                JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
                javaMailSender.setDefaultEncoding(mail.getDefaultEncoding());
                javaMailSender.setHost(mail.getEmailHost());
                javaMailSender.setPort(mail.getEmailPort());
                javaMailSender.setProtocol(mail.getProtocol());
                javaMailSender.setUsername(mail.getEmailUsername());
                javaMailSender.setPassword(mail.getEmailPassword());
                // Add data
                senderList.add(javaMailSender);
            });
        }
        else{
            JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
            javaMailSender.setDefaultEncoding(mailProperties.getDefaultEncoding());
            javaMailSender.setHost(mailProperties.getHost());
            javaMailSender.setPort(mailProperties.getPort());
            javaMailSender.setProtocol(mailProperties.getProtocol());
            javaMailSender.setUsername(mailProperties.getUsername());
            javaMailSender.setPassword(mailProperties.getPassword());
            // Add data
            senderList.add(javaMailSender);
        }

    }

    /**
     * Get MailSender
     *
     * @return CustomMailSender
     */
    public JavaMailSenderImpl getSender() {
        if (senderList.isEmpty()) {
            buildMailSender();
        }
        // A JavaMailSender is returned randomly
        return senderList.get(new Random().nextInt(senderList.size()));
    }

    /**
     * Clean up sender
     */
    public void clear() {
        senderList.clear();
    }
}

2.5. Listener

If you don't have anything to say, you can go in directly through idea to see the doc annotation on the source code. Study together next time.

/**
 * Initialization operation
 * At present, only the operation of dynamically setting the mail sender is defined
 * @Author: crush
 * @Date: 2021-11-26 19:51
 * version 1.0
 */
@Slf4j
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class StartListener implements ApplicationListener<ApplicationStartedEvent> {
    
    MailSenderConfig mailSenderConfig;

    public StartListener(MailSenderConfig mailSenderConfig) {
        this.mailSenderConfig = mailSenderConfig;
    }

    @SneakyThrows
    @Override
    public void onApplicationEvent(@NotNull ApplicationStartedEvent event) {
        this.mailSenderConfig.buildMailSender();
    }
}

2.6,controller

/**
 * @Author: crush
 * @Date: 2021-11-26 16:10
 * version 1.0
 */
@RestController
@RequestMapping("/email")
public class MailController {

    @Autowired
    private MailService mailService;

    @PostMapping("/send")
    public String send(@RequestBody MailDTO mailDTO){
        mailService.send(mailDTO);
        return "Send successfully!!! There may be a slight delay, please check the mailbox information!!";
    }

    @PostMapping("/addConfig")
    public String addMailPerson(@RequestBody MailPO mailPO){
        String message=mailService.addMailPerson(mailPO)?"Added successfully!!! However, please note that there may be delays":"Failed to add, please try again later!!";
        return message;
    }

}

3, Testing

The template is roughly in the following status.

It was added

One more time.

I click send email again, because it is a random number. We test it several times, and we will always use the wrong email sender. If we use it, it means that we have succeeded.

Because the random input added must have failed. But we can be sure that we used the email senders we joined after the project was launched. You can give it a try.

It's over. It's over.

No small demo, no source code.

Post language

Let's cheer together!!! If there are any deficiencies in the article, please point out them in time. Thank you very much.

I feel shallow on paper. I absolutely know that I have to practice it.

Hello, I'm blogger Ning Zaichun: homepage

A young man who likes literature and art but embarks on the road of programming.

Hope: when we meet on another day, we have achieved something.

It's rare to go back to the back-end and pick up the back-end again. After that, I will continue to write Vue. I'm sure I'll finish the column.

Posted by ec on Tue, 30 Nov 2021 17:53:04 -0800