• Springboot邮件发送思路分析


    毕业设计里需要邮件发送,所以学习,总的来讲,我考虑以下几点,

    • 代码量少,代码简单.配置少,一看就懂,使用 JavaMail 太麻烦了.
    • 异步执行,添加员工之后会发送入职邮件,
    • 多线程处理,设计里有一个公告推送的功能,就是发布一个公告会给所以员工发一份公告内容的邮件.

    方法一:之前电脑里装了Python环境,所以最开始用Python脚本的方式实现,主要是觉得Python太精干了,在Service里调用执行Python脚本.需要的参数以命令行的方式传参,线程池使用

    ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>();
    • 这样的考虑是因为Runtime.getRuntime().exec()来调用脚本,他会生成一个新的进程去运行调用的程序。不在当前进程中,是异步执行,不调用waitFor()方法.
    • 脚本的话,后来修改好修改,不会影响生产部署.
    • 这样做了一下,单邮件发送确实OK,方便,简单.但是多线程处理的话,cup就爆了,而且不同的系统编码格式,路径表示不同,在开发环境OK(Win10),在生产环境(Linux)不行,
    • 后来查发现,在java中,调用runtime线程执行脚本是非常消耗资源的,不建议频繁使用!,而且需要考虑阻塞问题.如果发送多条的话很麻烦.所以这种方法放弃.

    python脚本格式:

    import smtplib
    from email.mime.text import MIMEText
    from email.header import Header
    
    # 第三方 SMTP 服务
    mail_host = "smtp.163.com"  # 设置服务器
    mail_user = "AAAAAA@163.com"  # 发邮件的账户名
    mail_pass = "******"  # 授权码
    
    sender = 'AAAAAA@163.com'
    receivers = ['BBBBBB@qq.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
    
    # 三个参数:第一个为文本内容,第二个设置格式,plain:文本,html:HTML格式,第三个 utf-8 设置编码
    message = MIMEText('本次邮件的内容', 'plain', 'utf-8')
    message['From'] = Header("AAAAAA@163.com")  # 邮件中的发件人
    message['To'] = Header("BBBBBB@qq.com")  # 邮件中的收件人
    
    subject = '邮件主题'
    message['Subject'] = Header(subject, 'utf-8')
    try:
        smtpObj = smtplib.SMTP()
        smtpObj.connect(mail_host, 25)  # 25 为 SMTP 端口号
        smtpObj.login(mail_user, mail_pass)
        # 发件人邮箱账号、收件人邮箱账号、发送邮件
        smtpObj.sendmail(sender, receivers, message.as_string())
        print("邮件发送成功")
    
    except smtplib.SMTPException:
        print("Error: 无法发送邮件")

    以命令行的方式发送,类比在cmd里执行 Python 文件地址 要传递的参数的格式 ,这里要注意参数有大小有限制.

    Java代码

        final static Logger logger = LoggerFactory.getLogger(EmailUtils.class);
        final static String PATH = "src\main\java\com\liruilong\hros\script\";
    
        public static void sendEmail(EmailModel emailModel) {
    
            String emailTo = emailModel.getEmployee().getEmail();
            String username = emailModel.getEmployee().getName();
            String titles = emailModel.getTitle();
            String pathPy = PATH + emailModel.getPath();
    //命令行输入的字符串,  [python, srcmainjavacomliruilonghrosscriptsendemailpy.py, 1224973008@qq.com, 测试, 12312, <p>123123</p>] String[] args
    = new String[]{"python", pathPy, emailTo, username, titles}; logger.info(Arrays.toString(args)); try { Process process = Runtime.getRuntime().exec(args); } catch (IOException e) { e.printStackTrace(); } }

     Python代码:使用sys模块的 sys.argv[]传递参数,

    格式为: 文件地址 参数1 参数2 ....   

    命令行参数:[ srcmainjavacomliruilonghrosscriptsendemailpy.py, 1224973008@qq.com, 测试, 12312, <p>123123</p>]

    # smtplib 用于邮件的发信动作
    import smtplib
    import sys
    from email.mime.text import MIMEText
    # email 用于构建邮件内容
    from email.header import Header
    
    # 用于构建邮件头
    
    # 发信方的信息:发信邮箱,QQ 邮箱授权码
    from_addr = 'liruilong108@foxmail.com'
    password = 'xznjnyvgnvmuicji'
    
    # 收信方邮箱,
    to_addr = sys.argv[1]
    #to_addr ='1224965096@qq.com'
    # 发信服务器
    smtp_server = 'smtp.qq.com'
    
    # 邮箱正文内容,第一个参数为内容,第二个参数为格式(plain 为纯文本),第三个参数为编码
    msg = MIMEText(
    '<p class="ql-align-justify"><strong>尊敬的<u>&nbsp;&nbsp;&nbsp;'+ sys.argv[2]+'&nbsp;&nbsp;&nbsp;</u>女士/先生:</strong></p><p class="ql-align-justify">    感谢并欢迎您加入XXXX有限公司这支优秀的团队,成为我们亲密的工作伙伴!</p>' , 'HTML', 'utf-8')
    
    # 邮件头信息
    msg['From'] = Header(from_addr)
    msg['To'] = Header(to_addr)
    #邮件titl
    msg['Subject'] = Header(sys.argv[3])
    
    # 开启发信服务,这里使用的是加密传输
    server = smtplib.SMTP_SSL()
    server.connect(smtp_server, 465)
    # 登录发信邮箱
    server.login(from_addr, password)
    # 发送邮件
    server.sendmail(from_addr, to_addr, msg.as_string())
    # 关闭服务器
    server.quit()

     方法二,使用springboot集成的邮件发送依赖,spring-boot-starter-mail, 使用MailSenderAutoConfiguration 和自带线程池注解的方法:

    • 后来发现直接使用spring-boot-starter-mail,的依赖包也挺方便,需要配置的不是太多.

     这个有时间整理

    RunTime.getRuntime().exec()运行脚本命令介绍和阻塞:

    java在企业级项目开发中,无论是强制性的功能需要,还是为了简便java的实现,需要调用服务器命令脚本来执行。在java中,RunTime.getRuntime().exec()就实现了这个功能。

        用法:         public Process exec(String command)-----在单独的进程中执行指定的字符串命令。
     
           public Process exec(String [] cmdArray)---在单独的进程中执行指定命令和变量
     
                           public Process exec(String command, String [] envp)----在指定环境的独立进程中执行指定命令和变量
     
                           public Process exec(String [] cmdArray, String [] envp)----在指定环境的独立进程中执行指定的命令和变量
     
                           public Process exec(String command,String[] envp,File dir)----在有指定环境和工作目录的独立进程中执行指定的字符串命令
     
                           public Process exec(String[] cmdarray,String[] envp,File dir)----在指定环境和工作目录的独立进程中执行指定的命令和变量
     
     
    举例:
     
             1.  RunTime.getRuntime().exec(String  command);
     
                             在windows下相当于直接调用   /开始/搜索程序和文件  的指令,比如                              
                             Runtime.getRuntime().exec("notepad.exe");  -------打开windows下记事本。
     
     
            2.  public Process exec(String [] cmdArray);
                 Linux下:
                 Runtime.getRuntime().exec(new String[]{"/bin/sh","-c", ";
                 Windows下:
                 Runtime.getRuntime().exec(new String[]{ "cmd", "/c", cmds});
     
     
    深入:  
                Process的几种方法:
                            1.destroy():杀掉子进程
     
    2.exitValue():返回子进程的出口值,值 0 表示正常终止
     
    3.getErrorStream():获取子进程的错误流
     
    4.getInputStream():获取子进程的输入流
     
    5.getOutputStream():获取子进程的输出流
     
    6.waitFor():导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程,根据惯例,0 表示正常终止
     
              注意:在java中,调用runtime线程执行脚本是非常消耗资源的,所以切忌不要频繁使用!
             
                        在调用runtime去执行脚本的时候,其实就是JVM开了一个子线程去调用JVM所在系统的命令,其中开了三个通道: 输入流、输出流、错误流,其中输出流就是子线程走调用的通道。
                        大家都知道,waitFor是等待子线程执行命令结束后才执行, 但是在runtime中,打开程序的命令如果不关闭,就不算子线程结束。比如以下代码。
                             代码:private static Process p = null;
                                                p = Runtime.getRuntime().exec("notepad.exe");
        p.waitFor();         
    System.out.println("--------------------------------------------我被执行了");
                         以上代码中,打开windows中记事本。如果我们不手动关闭记事本,那么输出语句就不会被执行,这点是需要理解的。  
     
      process的阻塞:
                          在runtime执行大点的命令中,输入流和错误流会不断有流进入存储在JVM的缓冲区中,如果缓冲区的流不被读取被填满时,就会造成runtime的阻塞。所以在进行比如:大文件复制等的操作时,我们还需要不断的去读取JVM中的缓冲区的流,来防止Runtime的死锁阻塞。
        
                        代码:linux中拷贝文件防止阻塞的写法
     
     
     
     
    参考博客:
     
     
  • 相关阅读:
    bcdedit无法打开启动配置数据存储拒绝访问
    linux 实现两个文件指定列的替换
    Err:12 http://security.ubuntu.com/ubuntu focalupdates/main amd64 openjdk11jreheadless amd64 11.0.11+90ubuntu2~20.04 404 Not Found [IP: 91.189.88.152 80]
    SQL的经典语句(太全了)
    反射
    多线程互操作
    C# 2.0 套接字编程实例初探
    sqlserver 导出 insert (转)
    同步方法和异步方法的区别 (转)
    数组转成 位数组 这个很好用。
  • 原文地址:https://www.cnblogs.com/liruilong/p/12335134.html
Copyright © 2020-2023  润新知