美文网首页
commons-email 发送HTML邮件(结合freemar

commons-email 发送HTML邮件(结合freemar

作者: 威宸 | 来源:发表于2020-04-03 10:28 被阅读0次

    需求:批量发邮件给用户,邮件是html的,有个按钮点了可以下载附件或html之类的

    想法:java基本的发邮件的工具是JavaMail,而我用的是alpha的comnoms-email。然后呢,html的内容也不少,不可能用StringBuilder去拼一个字符串吧,所以我就使用freemarker模板,去生成html。后面又说邮件要分状态,某个状态下是下载附件,这个是已经上传到阿里oss上了,直接打开那个oss地址就行;但某个状态是要本地生成一个html,然后上传到oss上,然后拿回调的oss地址去下载。很6的是这个本地生成的html其实是已经有了的,但是它是在前端Vue项目里的,所以说可以复用。但是呢,我还真没试过在freemaker里去搞vue,有这功夫谁会在模板引擎里用,直接前后台分离啊歪。好吧,需求如此,开干。

    首先是,集成下所需的jar包:

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-email</artifactId>
                <version>1.4</version>
            </dependency>
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.30</version>
            </dependency>
    

    然后,实现发邮件的功能,同时生成html邮件。使用Juint运行commons-email官方示例。

        @Test
        public void test() throws EmailException {
            HtmlEmail email = new HtmlEmail();
            // hostName为你发送邮件的账号的smtp服务器,一般每种邮箱都有特定的smtp服务器
            // 比如:smtp.163.com是163邮箱的,端口25,身份认证是:账号和登录密码
            // smtp.qq.com是qq邮箱的,端口465,身份认证是:账号和QQ邮箱授权码
            email.setHostName("smtp.163.com");
            email.setSmtpPort(25);
            // 账号和密码
            email.setAuthentication("***@163.com", "***");
            // 将要发送邮件的接受人和称呼
            email.addTo("**@qq.com", "weic");
            // 发送人和称呼
            email.setFrom("*@163.com", "Me");
            // 邮件主题
            email.setSubject("Test email with inline image");
            // 字符编码
            email.setCharset("UTF-8");
             // embed the image and get the content id
            URL url = new URL("http://www.apache.org/images/asf_logo_wide.gif");
            String cid = email.embed(url, "Apache logo");
    
            // set the html message
            email.setHtmlMsg("<html>The apache logo - <img src=\"cid:"+cid+"\"></html>");
            // set the alternative message
            email.setTextMsg("你的email不支持html格式");
            // send the email
            email.send();
        }
    
    

    运行下,可以看看你是否接收到邮件


    官方实例演示邮件.png

    现在思考下一个需求点,邮件不再是一个简单的html页面,可能需要一些花里胡哨的操作。那可以使用freemarker模板引擎生成对应的html,总好过自己去搞html字符串。

    首先,在resources文件下新建一个email/template文件,然后将我们用到的ftl模板放入。需要注意的是如果我们是在test下跑的junit,需要在test中java的同级目录新建一个resources文件,然后通过Mark将其标记为资源文件,之后重复resources的流程。


    IDEA设置文件夹为资源文件.png

    index.ftl

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>Hello World!</title>
    </head>
    <body>
    <div class="container">
        <div class="header">
            <div>
                <span>用于测试的Html邮件</span>
            </div>
        </div>
        <div class="content">
            <div class="card">
                <div class="card-head">
                    <div class="card-head-left">
                        <span style="margin-right: 10px">${name}的名片</span>
                    </div>
                    <div class="card-head-right">
                        <a style="text-decoration: none;" href="${downloadUrl}">${btnText}</a>
                    </div>
                </div>
                <div class="card-content">
                    <div class="card-content-avatar">
                        <p></p>
                    </div>
    
                    <div class="card-content-text">
                        <div class="item">
                            <p class="p-text">${name}</p>
                            <#--<p class="p-text">${sex}</p>-->
                            <#--<p class="p-text">${age}岁</p>-->
                        </div>
                        <div class="item">
                            <p class="p-text">电话:</p>
                            <p class="p-text">10086</p>
                        </div>
                        <div class="item">
                            <p class="p-text">邮箱:</p>
                            <p class="p-text">****@**.com</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    </body>
    </html>
    <style>
        .container{
            /* Safari */
            display: -webkit-flex;
            display: flex;
            flex-direction: column;
            flex-wrap: nowrap;
            justify-content: center;
            align-items: center;
        }
        .header{
            display: flex;
            flex-flow: row;
            align-items: center;
            justify-content: flex-start;
            width: 744px;
            margin-bottom: 20px;
        }
        .grey-mini-text{
            font-size: 12px;
            color: grey;
        }
        .content{
            display: flex;
            flex-direction: column;
            align-items:  flex-start;
            justify-content: flex-start;
            width: 774px;
        }
        .contant-span{
            margin-bottom: 20px;
        }
        .card{
            width: 744px;
            box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
            text-align: center;
            float: left;
            margin-right: 10px;
            padding: 5px;
            padding-top: 15px;
            border-radius: 10px;
        }
        .card-head{
            display: flex;
            flex-flow: row;
            align-items: center;
            padding: 0 20px;
        }
        .card-head-left{
            flex: 1;
            display: flex;
            flex-direction: row;
            align-items: flex-start;
            justify-content: flex-start;
        }
        .card-head-right{
            flex: 1;
            display: flex;
            flex-direction: row;
            align-items: flex-end;
            justify-content: flex-end;
        }
        .card-head-right a{
            background: -webkit-linear-gradient(top,#fd3608 0%,#fd3608 90%,#fd3608 100%);
            border: 1px solid #fd3608;
            border-radius: 10px;
            color: white;
            padding: 12px 35px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 18px;
            cursor: pointer;
            float: left;
        }
        .card-head-right a:hover {
            background-color: #fd3608;
        }
        .card-head-right a:active {
            background-color: #fd3608;
        }
        .card-head-left span{
            color: grey;
        }
        .card-content{
            margin-top: 10px;
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
        }
        .card-content-avatar{
            flex: 1;
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }
        .card-content-avatar p{
            width: 100px;
            height: 100px;
            border-radius: 50px;
            background: grey url("") no-repeat center;
            background-size: 100px;
        }
        .card-content-text{
            flex: 2;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: flex-start;
        }
        .card-content-text .item{
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }
    
        .p-text{
            margin-right: 5px;
        }
    </style>
    

    juint test方法

            HtmlEmail email = new HtmlEmail();
            // hostName为你发送邮件的账号的smtp服务器,一般每种邮箱都有特定的smtp服务器
            // 比如:smtp.163.com是163邮箱的,端口25,身份认证是:账号和登录密码
            // smtp.qq.com是qq邮箱的,端口465,身份认证是:账号和QQ邮箱授权码
            email.setHostName("smtp.163.com");
            email.setSmtpPort(25);
            // 账号和密码
            email.setAuthentication("***@163.com", "***");
            // 将要发送邮件的接受人和称呼
            email.addTo("**@qq.com", "weic");
            // 发送人和称呼
            email.setFrom("*@163.com", "Me");
            // 邮件主题
            email.setSubject("Test email with inline image");
            // 字符编码
            email.setCharset("UTF-8");
            // 开启ssl
            email.setSSLCheckServerIdentity(true)
            // 读取SendEmail的根目录,这里就是target的目录
            String baseURL = Objects.requireNonNull(SendEmail.class.getClassLoader().getResource("")).getPath();
            StringWriter html = new StringWriter();
            try {
                // freemarker指定版本
                Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
                //指定对象模板存放的位置
                cfg.setDirectoryForTemplateLoading(new File(baseURL+"/email/template"));
                //设置字符集
                cfg.setDefaultEncoding("utf-8");
                // 通过map设置参数
                Map<String, Object> rootMap = new HashMap<>(20);
                rootMap.put("logo", "http://www.apache.org/images/asf_logo_wide.gif");
                rootMap.put("name", "Hello World!");
                rootMap.put("btnText", "点击一下");
                rootMap.put("downloadUrl", "");
                // 读取模板
                Template template = cfg.getTemplate("index.ftl");
                // 写入参数
                template.process(rootMap, html);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TemplateException e) {
                e.printStackTrace();
            }
    
            // set the html message
            email.setHtmlMsg(html.toString());
            // set the alternative message
            email.setTextMsg("你的email不支持html格式");
            // send the email
            email.send();
    

    看下效果:


    邮件一.png

    在代码中我们有一个downloadUrl参数,他的作用是点击下载一个html,但是呢,这个html需要本地去生成,然后上传到oss上。也就是说我们需要oss回调的地址,作为download的地址。

        ...
        String baseURL = Objects.requireNonNull(SendEmail.class.getClassLoader().getResource("")).getPath();
        // 在本地生成一个html
        File afile = new File(baseURL + "test.html");
        try {
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
            //指定对象模板存放的位置
            cfg.setDirectoryForTemplateLoading(new File(baseURL+"/email/template"));
            //设置字符集
            cfg.setDefaultEncoding("utf-8");
            Template template = cfg.getTemplate("resume.ftl");
            // 为resume.ftl模板写入数据
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(afile), "UTF-8"));
            template.process(result, out);
            out.flush();
            out.close();
            // 通过oss上传html
            OSS ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
            // 通过雪花算法生成唯一文件名
            String fileName = Snowflake.generateId() + ".html";
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, fileName , afile);
            ossClient.putObject(putObjectRequest);
            ossClient.shutdown();
            // 获取地址,注:oss的相关配置应提前准备好,webHost地址为oss地址前缀
            onlinePath = webHost + fileName;
            // 如果文件存在删除
            if (afile.exists()) {
                afile.delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 如果文件存在删除
            if (afile.exists()) {
                afile.delete();
            }
        }
        ...
    

    resume.ftl

    !DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <!-- import CSS -->
        <link rel = "stylesheet" href = "https://unpkg.com/element-ui/lib/theme-chalk/index.css" >
    </head>
    <body>
    <div id="app">
    </div>
    </body>
    <!-- import Vue before Element -->
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <!-- import JavaScript -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script>
        // 将传入的参数放到window
        window.baseInfoData = '${baseInfo}';
        window.intentionInfoData = '${intentionInfo}';
        // 收到触发vue初始化事件
        window.onload = function(){
            vm.initInfo();
        }
        let vm = new Vue({
            el: '#app',
            data: function () {
                return {
                    baseInfoData: [
                        {
                            name1: '姓名',
                            value1: '',
                            name2: '性别',
                            value2: ''
                        },
                        {
                            name1: '出生日期',
                            value1: '',
                            name2: '户籍所在地',
                            value2: ''
                        }
                    ]
                };
            },
            methods: {
                initInfo: function () {
                    // 将对象转成json,js手动转回来
                    let baseInfo = JSON.parse(window.baseInfoData);
                }
            }
        });
    

    具体的resume.ftl这里就不给出了,毕竟是别人公司直接复制过来的代码。这里只说下遇到的问题和解决方法。

    问题

    一、在freemarker作为模板写入图片,或者说使用email里email.embed()方法会使得图片变为附件,所以最后还是决定使用oss地址来显示图片。

    二、如果收信人第一次收到邮件,没有信任这个邮件,会导致图片显示不出来。用户查看邮件的时候可以信任发邮件方。暂没有什么解决方法。

    三、resume.ftl模板是从vue中复制过来的,使用了CDN去引用vue和element-ui,如果将数据直接通过#{}引用到data里,并不会触发双向绑定,同时也不会触发mounted方法去初始化vue。如果你是将一个对象通过map注入,那么他在页面里就变成了一个字符串,真真的字符串无法使用。。。解决方法:将变量赋值给window里,这样保证不会没有,然后通过window.onload方法手动调用初始化方法。对象就通过json转成字符串,然后在js里转回来

    四、如果你部署的服务器开启了ssl,必须在;发邮件的代码里手动开启下ssl配置,否则发不出邮件。。。当然官方推荐使用ssl或STARTTLS 去发邮件

    Email.setSSLCheckServerIdentity(true)
    

    四、邮件不支持js等一些特殊标签及在div里加click点击事件无效;解决方法:使用a标签,设置好样式,可以实现同一效果。

    贴出commons-email官方地址:http://commons.apache.org/proper/commons-email/userguide.html

    官方文档是最给力的!

    我算是发现了,我啊戒不了游戏,也戒不了小说。像苦行僧一样无欲无求直奔技术的前路,对我来说是不现实的。。。那好吧,转化思路,游戏、打,小说、看,但是呢,我们做个标准,每周写一个博客(可以记录工作、学习的笔记);每个月找一本工作相关书籍,看完他,每周看一章;每周逛逛Leetcode的探索章节,写几道题。时间只能中午挤出来,上班任务完成后摸鱼出来,外加视情况而定的业余时间抽出了。其他的想干嘛干嘛,他**,逼不了就逼不了,相反效果都出来了,再逼估计都快得抑郁症了。。。

    相关文章

      网友评论

          本文标题:commons-email 发送HTML邮件(结合freemar

          本文链接:https://www.haomeiwen.com/subject/zsgcphtx.html