整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

「Springboot」Springboot整合邮件服务(HTML/附件/模板-QQ网易)

邮件服务是常用的服务之一,作用很多,对外可以给用户发送活动、营销广告等;对内可以发送系统监控报告与告警。

本文将介绍Springboot如何整合邮件服务,并给出不同邮件服务商的整合配置。

如图所示:

开发过程

Springboot搭建

Springboot的搭建非常简单,我们使用 https://start.spring.io/来构建,十分方便,选择需要用到的模块,就能快速完成项目的搭建:

引入依赖

为了使用邮件服务,我们需要引入相关的依赖,对于Springboot加入下面的依赖即可:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置文件

需要配置邮件服务提供商的相关参数,如服务地址、用户名及密码等。下面的例子是QQ的配置,其中密码并不是QQ密码,而是QQ授权码,后续我们再讲怎么获得。

Springboot的配置文件application.yml如下:

server:
 port: 8080
spring:
 profiles:
 active: qq
---
spring:
 profiles: qq
 mail:
 host: smtp.qq.com
 username: xxx@qq.com
 password: xxx
 properties:
 mail:
 smtp:
 auth: true
 starttls:
 enable: true
 required: true
---
spring:
 profiles: netEase
 mail:
 host: smtp.163.com
 username: xxx@163.com
 password: xxx
 properties:
 mail:
 smtp:
 auth: true
 starttls:
 enable: true
 required: true

实现发送服务

JavaMailSender注入,组装Message后,就可以发送最简单的文本邮件了。

@Autowired
private JavaMailSender emailSender;
public void sendNormalText(String from, String to, String subject, String text) {
 SimpleMailMessage message = new SimpleMailMessage();
 message.setFrom(from);
 message.setTo(to);
 message.setSubject(subject);
 message.setText(text);
 emailSender.send(message);
}

调用接口

服务调用实现后,通过Controller对外暴露REST接口,具体代码如下:

@Value("${spring.mail.username}")
private String username;
@Autowired
private MailService mailService;
@GetMapping("/normalText")
public Mono<String> sendNormalText() {
 mailService.sendNormalText(username, username,
 "Springboot Mail(Normal Text)",
 "This is a mail from Springboot!");
 return Mono.just("sent");
}

把实现的MailService注入到Controller里,调用对应的方法即可。本次的邮件发送人和收件人都是同一个帐户,实际实现可以灵活配置。

通过Postman调用接口来测试一下能不能正常发送:

成功返回"sent",并收到了邮件,测试通过。

多种类型邮件

简单文本邮件

简单文本邮件如何发送,刚刚已经讲解,不再赘述。

HTML邮件

纯文本虽然已经能满足很多需求,但很多时候也需要更加丰富的样式来提高邮件的表现力。这时HTML类型的邮件就非常有用。

Service代码如下:

public void sendHtml(String from, String to, String subject, String text) throws MessagingException {
 MimeMessage message = emailSender.createMimeMessage();
 MimeMessageHelper helper = new MimeMessageHelper(message, true);
 helper.setFrom(from);
 helper.setTo(to);
 helper.setSubject(subject);
 helper.setText(text, true);
 emailSender.send(message);
}

与简单的文本不同的是,本次用到了MimeMessage和MimeMessageHelper,这是非常有用的类,后续我们经常会用到,组合使用能大大丰富邮件表现形式。

Controller的代码如下:

@GetMapping("/html")
public Mono<String> sendHtml() throws MessagingException {
 mailService.sendHtml(username, username,
 "Springboot Mail(HTML)",
 "<h1>This is a mail from Springboot!</h1>");
 return Mono.just("sent");
}

带附件邮件

邮件发送文件再正常不过,发送附件需要使用MimeMessageHelper.addAttachment(String attachmentFilename, InputStreamSource inputStreamSource)方法,第一个参数为附件名,第二参数为文件流资源。Service代码如下:

public void sendAttachment(String from, String to, String subject, String text, String filePath) throws MessagingException {
 MimeMessage message = emailSender.createMimeMessage();
 MimeMessageHelper helper = new MimeMessageHelper(message, true);
 helper.setFrom(from);
 helper.setTo(to);
 helper.setSubject(subject);
 helper.setText(text, true);
 FileSystemResource file = new FileSystemResource(new File(filePath));
 helper.addAttachment(filePath, file);
 emailSender.send(message);
}

Controller代码如下:

@GetMapping("/attachment")
public Mono<String> sendAttachment() throws MessagingException {
 mailService.sendAttachment(username, username,
 "Springboot Mail(Attachment)",
 "<h1>Please check the attachment!</h1>",
 "/Pictures/postman.png");
 return Mono.just("sent");
}

带静态资源邮件

我们访问的网页其实也是一个HTML,是可以带很多静态资源的,如图片、视频等。Service代码如下:

public void sendStaticResource(String from, String to, String subject, String text, String filePath, String contentId) throws MessagingException {
 MimeMessage message = emailSender.createMimeMessage();
 MimeMessageHelper helper = new MimeMessageHelper(message, true);
 helper.setFrom(from);
 helper.setTo(to);
 helper.setSubject(subject);
 helper.setText(text, true);
 FileSystemResource file = new FileSystemResource(new File(filePath));
 helper.addInline(contentId, file);
 emailSender.send(message);
}

其中,contentId为HTML里静态资源的ID,需要对应好。

Controller代码如下:

@GetMapping("/inlinePicture")
public Mono<String> sendStaticResource() throws MessagingException {
 mailService.sendStaticResource(username, username,
 "Springboot Mail(Static Resource)",
 "<html><body>With inline picture<img src='cid:picture' /></body></html>",
 "/Pictures/postman.png",
 "picture");
 return Mono.just("sent");
}

模板邮件

Java的模板引擎很多,著名的有Freemarker、Thymeleaf、Velocity等,这不是本点的重点,所以只以Freemarker为例使用。

Service代码如下:

@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
public void sendTemplateFreemarker(String from, String to, String subject, Map<String, Object> model, String templateFile) throws Exception {
 MimeMessage message = emailSender.createMimeMessage();
 MimeMessageHelper helper = new MimeMessageHelper(message, true);
 helper.setFrom(from);
 helper.setTo(to);
 helper.setSubject(subject);
 Template template = freeMarkerConfigurer.getConfiguration().getTemplate(templateFile);
 String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
 helper.setText(html, true);
 emailSender.send(message);
}

注意需要注入FreeMarkerConfigurer,然后使用FreeMarkerTemplateUtils解析模板,返回String,就可以作为内容发送了。

Controller代码如下:

@GetMapping("/template")
public Mono<String> sendTemplateFreemarker() throws Exception {
 Map<String, Object> model = new HashMap<>();
 model.put("username", username);
 model.put("templateType", "Freemarker");
 mailService.sendTemplateFreemarker(username, username,
 "Springboot Mail(Template)",
 model,
 "template.html");
 return Mono.just("sent");
}

注意模板文件template.html要放在resources/templates/目录下面,这样才能找得到。

模板内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
<h1>Hello ${username}</h1>
<h1>This is a mail from Springboot using ${templateType}</h1>
</body>
</html>

其中${username}和${templateType}为需要替换的变量名,Freemarker提供了很多丰富的变量表达式,这里不展开讲了。

集成不同邮件服务商

邮件服务的提供商很多,国内最常用的应该是QQ邮箱和网易163邮箱了。

QQ

集成QQ邮件需要有必备的账号,还要开通授权码。开通授权码后配置一下就可以使用了,官方的文档如下:

https://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001256&&id=28

需要注意的是,开通授权码是需要使用绑定的手机号发短信到特定号码的,如果没有绑定手机或者绑定手机不可用,那都会影响开通。

开通之后,授权码就要以作为密码配置到文件中。

163

网易的开通方式与QQ没有太大差别,具体的指导可以看如下官方文档:

http://help.mail.163.com/faqDetail.do?code=d7a5dc8471cd0c0e8b4b8f4f8e49998b374173cfe9171305fa1ce630d7f67ac2cda80145a1742516

同样也是需要绑定手机进行操作。

总结

本次例子发送后收到邮件如图所示:

邮件功能强大,Springboot也非常容易整合。技术利器,善用而不滥用。

面完成了API服务(虽然这个API没什么用)。接下去来个WEB服务,在前面项目中加上个页面。这章目标是通过访问一个URL展示一个界面,从服务端传递参数值到界面中展示动态数据。这里还会涉及到webjars的应用。

目录与文件

在项目src/main/resources目录下创建两个目录,分别是templates各static,templates 存放模板文件,static 存放静态文件。

目录

在static目录放入一张图片,图片名称为001.jpg,启动项目。打开浏览器访问http://localhost/001.jpg

访问资源

spring boot会默认从static查找静态资源,在这里还可以放css,js,html等文件,都可以直接访问到。但是,这里的html文件只能是静态页面,服务端不能传值到界面中。

 templates中加入一个index.html,内容如下<!DOCTYPE html>
<html lang="zh">
<head>
 <meta charset="UTF-8">
 <title>这里是标题</title>
</head>
<body>
 <div>
 <p>这是首页</p>
 </div>
</body>
</html>

重启服务,浏览器中访问http://localhost/index.html

404

找不到页面,如果index.html放到static目录中是可以访问的。templates目录中的文件是不能直接访问。下面讲到这么访问templates中的文件。

当前目录

目录

使用模板

访问templates文件,首先要引入模板引擎。这里使用thymeleaf,在pom.xml文件中包含thymeleaf组件。

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-thymeleaf</artifactId>
 </dependency>

如图

增加package:com.biboheart.demos.web,在package中创建class:PageController

package com.biboheart.demos.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PageController {
 @RequestMapping(value = {"/", "/home"})
 public String home() {
 return "index";
 }
}

@Controller表示这是一个SpringMVC的控制器

@RequestMapping(value = {"/", "/home"}) 表示访问路径"/"或"/home"都指向home函数,home返回"index"页面,即templates/index.html模板生成的页面。

重新启动服务,在浏览器中访问 http://localhost/home

home页面

这时候,页面展示的是index.html的内容。向页面传值

这里用Model对象传值到模板中

调整controller的实现

package com.biboheart.demos.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PageController {
 @RequestMapping(value = {"/", "/home"})
 public String home(Model model, String name) {
 model.addAttribute("name", name);
 return "index";
 }
}

在函数中增加两个参数,Model model用于向模板传递值,name用于接收请求参数。model.addAttribute("name", name);将接收到的值通过model传递到模板中。

模板文件index.html中接收并显示name的值。

<!DOCTYPE html>
<html lang="zh-CN"
 xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="UTF-8">
 <title>这里是标题</title>
</head>
<body>
 <div>
 <p>这是首页</p>
 <p>欢迎你:<span th:text="${name}"></span></p>
 </div>
</body>
</html>

重新启动服务,在浏览器中访问http://localhost/home?name=biboheart

参数

index.html中的<span th:text="${name}"></span>,展示Model中的name的值。使用webjars

在pom.xml中包含webjars,并且包含jquery,bootstrap

 <dependency>
 <groupId>org.webjars</groupId>
 <artifactId>webjars-locator</artifactId>
 <version>0.34</version>
 </dependency>
 <dependency>
 <groupId>org.webjars</groupId>
 <artifactId>jquery</artifactId>
 <version>3.3.1</version>
 </dependency>
 <dependency>
 <groupId>org.webjars</groupId>
 <artifactId>bootstrap</artifactId>
 <version>3.3.7-1</version>
 </dependency>

界面中使用bootstrap

<!DOCTYPE html>
<html lang="zh-CN"
 xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="UTF-8">
 <title>这里是标题</title>
 <script src="/webjars/jquery/jquery.min.js"></script>
 <script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
 <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.min.css"/>
</head>
<body>
 <div class="container">
 <div class="jumbotron">
 <h1>欢迎你:<span th:text="${name}"></span></h1>
 <p>这是首页</p>
 </div>
 </div>
</body>
</html>

重新启动项目,在浏览器中访问http://localhost/home?name=biboheart

bootstrap效果

模板中包含静态资源

将静态资源001.jpg图片在模板中显示,修改后index.html文件如下

者 | 唐亚峰

责编 | 胡巍巍

Spring Boot 是为了简化 Spring 应用的创建、运行、调试、部署等一系列问题而诞生的产物, 自动装配的特性让我们可以更好的关注业务本身而不是外部的 XML 配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 Web 工程。

未接触 SpringBoot 之前,搭建一个普通的 Web 工程往往需要花费30分钟左右,如果遇到点奇葩的问题耽搁的时间会更长一点,但自从用了 SpringBoot 后,真正体会到什么叫分分钟搭建一个 Web,让我拥有更多的时间跟我的小伙伴们唠嗑了。

使用 SpringBoot 后发现一切是如此的简单(还记得读书那会被JAR包,XML 支配的恐惧吗,如今都可以说 good bye)。

设计的目标

  • 为所有使用 Spring 的开发者提供一个更简单,快速的入门体验;
  • 提供一些常见的功能、如监控、Web 容器,健康,安全等功能;
  • 干掉 XML,遵循规范,开箱即用。

前提

SpringBoot 为我们提供了一系列的依赖包,所以需要构建工具的支持: Maven 或 Gradle。由于本人更习惯使用 Maven,所以后续案例都是基于 Maven 与 IntelliJ IDEA,同时这里是基于最新的 SpringBoot2 编写的哦......

创建项目

初次接触,我们先来看看如何创建一个 SpringBoot项目,这里以 IntelliJ IDEA为例,其他的IDE工具小伙伴们自行搜索创建方式。

创建完项目后,各位小伙伴请认真、细心的对比下与传统的 Web 工程有何区别(如:目录结构)。

点击 File->Project

如果用过 Eclipse/IDEA 等工具的,对创建项目肯定不会陌生,但为了照顾第一次使用的我贴上了图文。

选择 Spring Initializr

到这一步选择的时候,如图中选项的是 Spring Initializr(官方的构建插件,需要联网),第二个是自己选择 Maven 构建,为了更好的适合初学者,我们将在本章用插件构建。

填写项目基本信息

  • Group: 组织 ID,一般分为多个段,这里我只说两段,第一段为域,第二段为公司名称。域又分为 org、com、cn等等,其中 org 为非营利组织,,com为商业组织。如阿里、淘宝(com.alibaba/com.taobao)。
  • Artifact:唯一标识符,一般是项目名称。

择包

Spring Initializr 为我们提供了很多的选项,不同的选项有不同的作用,在初期我们只需要依赖 Web->Web就可以了,选择好依赖包之后点击 Next -> Finish。

目录结果

src
 -main
 -java
 -package
 #主函数,启动类,运行它如果运行了 Tomcat、Jetty、Undertow 等容器
 -SpringbootApplication 
 -resouces
 #存放静态资源 js/css/images 等
 - statics
 #存放 html 模板文件
 - templates
 #主要的配置文件,SpringBoot启动时候会自动加载application.yml/application.properties 
 - application.yml
 #测试文件存放目录 
 -test
# pom.xml 文件是Maven构建的基础,里面包含了我们所依赖JAR和Plugin的信息
- pom

pom.xml 依赖

因为使用了 Spring Initializr 插件,所以如下的配置都不需要我们自己去写啦,需要注意的是版本要选择 RELEASE ,稳定版本 Bug 少。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.battcn</groupId>
 <artifactId>chapter1</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>
 <name>chapter1</name>
 <description>我的用第一个SpringBoot工程</description>
 <!--版本采用的是最新的 2.0.1.RELEASE TODO 开发中请记得版本一定要选择 RELEASE 哦 -->
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.0.1.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 <java.version>1.8</java.version>
 </properties>
 <dependencies>
 <!-- 默认就内嵌了Tomcat 容器,如需要更换容器也极其简单-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!-- 测试包,当我们使用 mvn package 的时候该包并不会被打入,因为它的生命周期只在 test 之内-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <!-- 编译插件 -->
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
</project>

其它的依赖可以参考:官方文档。

主函数入口

注意事项: 一个项目中切记不要出现多个 main 函数,否在在打包的时候 Spring-Boot-Maven-plugin 将找不到主函数(主动指定打包主函数入口除外......)。

/**
* 我的第一个SpringBoot程序
* 其中 @RestController 等同于 (@Controller 与 @ResponseBody)
*
* @author Levin
*/
@RestController
@SpringBootApplication
public class Chapter1Application {
 public static void main(String[] args) {
 SpringApplication.run(Chapter1Application.class, args);
 }
 @GetMapping("/demo1")
 public String demo1() {
 return "Hello battcn";
 }
 @Bean
 public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
 // 目的是
 return args -> {
 System.out.println("来看看 SpringBoot 默认为我们提供的 Bean:");
 String[] beanNames = ctx.getBeanDefinitionNames();
 Arrays.sort(beanNames);
 Arrays.stream(beanNames).forEach(System.out::println);
 };
 }
}

初窥配置文件

从启动日志中可以发现, SpringBoot 默认的端口是 8080 ,那么如果端口被占用了怎么办呢?不要慌,问题不大,配置文件分分钟解决你的困扰......

2018-04-20 16:14:46.725 INFO 11184 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''

修改默认配置。

# 默认的 8080 我们将它改成 9090 
server.port=9090
# 未定义上下文路径之前 地址是 http://localhost:8080 定义了后 http://localhost:9090 你能在tomcat做的事情,配置文件都可以
server.servlet.context-path=/chapter1

再启动一次看看日志。

2018-04-20 16:47:05.716 INFO 12108 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path '/chapter1'

测试

本次测试采用Junit 进行,当然也可以启动项目后直接访问 http://localhost:9090/chapter/demo1 进行测试。

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import java.net.URL;
import static org.junit.Assert.assertEquals;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Chapter1ApplicationTests {
 @LocalServerPort
 private int port;
 private URL base;
 @Autowired
 private TestRestTemplate template;
 @Before
 public void setUp() throws Exception {
 // TODO 因为我们修改了 content-path 所以请求后面要带上
 this.base = new URL("http://localhost:" + port + "/chapter1/demo1");
 }
 @Test
 public void demo1() throws Exception {
 ResponseEntity<String> response = template.getForEntity(base.toString(), String.class);
 assertEquals(response.getBody(), "Hello battcn");
 }
}

拓展知识

自定义Banner

SpringBoot 启动的时候我们可以看到如下内容,这一块其实是可以自定义的哦,而且在 2.X 版本中,它支持的格式从文本扩展到 banner.txt、banner.jpg、banner.gif、banner.jpeg 等等,只需要在 resouces 目录下添加指定命名的文件即可。

 . ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _ 
( ( )___ | '_ | '_| | '_ / _` | 
 \/ ___)| |_)| | | | | || (_| | ) ) ) )
 ' |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot :: (v2.0.1.RELEASE)

总结

目前很多大佬都写过关于 SpringBoot 的教程了,如有雷同,请多多包涵,本教程基于最新的 Spring-Boot-Starter-parent:2.0.1.RELEASE编写,包括新版本的特性都会一起介绍...

作者:唐亚峰 | battcn。分享技术、记录生活、专注 Spring Boot、Spring Cloud、微服务等技术分享,欢迎一起交流探讨。从零开始、以实战落地为主,不定期分享干货。漫漫架构之路,让我们一起见证!

本文系作者投稿,版权归作者所有。文章内容不代表CSDN立场。

征稿啦

CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。

如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。