本节内容主要就几个常用的模块进行解读,在众多企业级开发中常常用得到。
例如邮件发送这一功能其实非常常见,在一般的网站设计中会有对注册邮件的激活功能,此时就需要用到这个功能了,一般来说激活邮件需要设置UUID 来进行验证,如果用户点击了链接那么在数据库的状态就转换为1,表示已经激活了。
定时任务也用得比较多,常见于缓存进入数据库等,可以看我这篇关于高并发的章节 高并发Demo
这里讲的批处理主要是针对批量导入数据库的快捷方法(这样就不用进入excel一行一行导入了)
目前很多项目也是需要讲究前后端分离的,所以一个好的接口测试是非常有必要的,之前我们使用了Postman 来进行测试,不过依旧缺少点详细信息,这里使用Swagger2 来进行测试,不过值得一提的是,以前老的配置方法非常坑,我也是花了相当的时间才找到了一个合适的办法。
希望对你有所帮助!
目录
- 邮件发送
- 发送前的准备
- 发送
- 定时任务
- @Scheduled
- Quartz
- 批处理
- Swagger 2
邮件发送
邮件发送是非常常见的功能,注册时的身份验证、重要通知发送都会用到邮件发送。Sun公司提供了JavaMail,但是配置相当繁琐,Spring 提供了JavaMailSender来简化,Spring Boot则提供了MailSenderAutoConfiguration对邮件做了进一步简化。
发送前的准备
这里以QQ邮箱发送邮件为例:需要开通POP3/SMTP服务或者IMAP/SMTP服务。SMTP就是简单邮件传输协议,定义了邮件客户端和SMTP服务器之间、以及SMTP与SMTP之间的通信规则。也就是说aaa@qq.com 用户先将邮件投递给腾讯的SMTP,然后邮件投递到网易的SMTP服务器,此时SMTP服务器就是用来接收邮件的。POP3 邮局协议,定义了客户端与POP3 服务器之间的规则。
这样你就会得到一个授权码了。
发送
添加邮件依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
项目创建成功后,在application.properties 完成邮件基本信息配置:
spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username = 1739994163@qq.com
spring.mail.password=授权码
spring.mail.default-encoding=utf-8
spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.debug=true
这里配置了邮件服务器的地址、端口(可以是465或者587)、用户账号和密码以及默认编码、SSL连接配置,最后开启debug,这样方便开发者查看邮件发送日志。注意,SSL的配置可以在QQ邮箱官方文档查看
发送一个简单的邮件,创建一个MailService用来封装邮件的发送:
@Component
public class MailService {
@Resource
// 在MailSenderPropertiesConfiguration类配置好的,该类在Mail 自动配置类导入,
// 只要注入就可以了
JavaMailSender javaMailSender;
public void sendSimpleMail(String from, String to, String cc, String subject, String content) {
// 5个参数,分别是邮件发送者,收件人,抄送人,邮件主题,邮件内容
SimpleMailMessage simpMsg = new SimpleMailMessage();
simpMsg.setFrom(from);
simpMsg.setTo(to);
simpMsg.setCc(cc);
simpMsg.setSubject(subject);
simpMsg.setText(content);
javaMailSender.send(simpMsg);
}
}
配置完成后,可以在单元测试中写一个测试方法进行测试:
import com.example.testspringboot.service.MailService;
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.test.context.junit4.SpringRunner;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.annotation.Resource;
import java.io.File;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SendmailApplicationTests {
@Resource
MailService mailService;
@Test
public void sendSimpleMail() {
mailService.sendSimpleMail("1739994163@qq.com",
"qazjou@163.com",
"1739994163@qq.com",
"测试邮件主题",
"测试邮件内容");
}
}
执行该方法,便可以看到邮件发送成功了:
发送带附件的邮件:
通过调用Attachment 方法即可添加附件,该方法调用多个方法添加附件,在MailService添加:
public void sendAttachFileMail(String from, String to,
String subject, String content, File file) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message,true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content);
helper.addAttachment(file.getName(), file);
javaMailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
使用了MimeMessageHelper简化了配置,构造方法的第二个参数true 表示构造一个multipart message 类型的邮件,此类型的邮件包含了多个正文、附件等,最后在单元测试进行测试:
@Test
public void sendAttachFileMail() {
mailService.sendAttachFileMail("1739994163@qq.com",
"qazjou@163.com",
"测试邮件主题",
"测试邮件内容",
new File("C:\\Users\\17399\\Desktop\\1.txt"));
}
运行后可见发送成功了。
有的邮件正文可能要插入图片,此时就不是附件的内容了,使用FileSystemResource实现功能:
public void sendMailWithImg(String from, String to,
String subject, String content,
String[] srcPath,String[] resIds) {
if (srcPath.length != resIds.length) {
System.out.println("发送失败");
return;
}
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper =
new MimeMessageHelper(message,true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content,true);
for (int i = 0; i < srcPath.length; i++) {
FileSystemResource res =
new FileSystemResource(new File(srcPath[i]));
helper.addInline(resIds[i], res);
}
javaMailSender.send(message);
} catch (MessagingException e) {
System.out.println("发送失败");
}
}
在发送邮件时候分别传入图片资源路径和资源id,构造静态资源,然后调用addInline方法将资源加入邮件对象中。注意,在调用setText方法时候,第二个参数true 表示邮件正文是HTML格式的,该参数默认为false。
@Test
public void sendMailWithImg() {
mailService.sendMailWithImg("1739994163@qq.com",
"qazjou@163.com",
"测试邮件主题(图片)",
"<div>hello,这是一封带图片资源的邮件:" +
"这是图片1:<div><img src='cid:p01'/></div>" +
"这是图片2:<div><img src='cid:p02'/></div>" +
"</div>",
new String[]{"C:\\Users\\17399\\Desktop\\2.png",
"C:\\Users\\17399\\Desktop\\3.png"},
new String[]{"p01", "p02"});
}
运行结果如图:
定时任务
@Scheduled
定时任务是企业开发最常见的功能之一,如定时统计订单数、数据库备份、定时发送短信和邮件、定时统计博客访客。简单的定时任务直接通过@Scheduled 注解来实现,复杂的定时任务集成Quartz来实现。关于Quartz 可以看我这篇博客,Quartz任务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
开启定时任务:
在项目启动类添加注解开启定时任务:
@EnableScheduling
@SpringBootApplication
定时任务主要 @Component 来进行配置:
@Component
public class MySchedule {
// 来标注一个定时任务,其中fixedDelay=1000 表示在当前任务结束后1s开始另一个任务
// initialDelay=1000 表示首次执行的延迟时间
@Scheduled(fixedDelay = 1000)
public void fixedDelay() {
System.out.println("fixedDelay:"+new Date());
}
@Scheduled(fixedRate = 2000)
public void fixedRate() {
System.out.println("fixedRate:"+new Date());
}
@Scheduled(initialDelay = 1000,fixedRate = 2000)
public void initialDelay() {
System.out.println("initialDelay:"+new Date());
}
// 也可以使用cron 表达式,来表示任务每分钟执行一次
@Scheduled(cron = "0 * * * * ?")
public void cron() {
System.out.println("cron:"+new Date());
}
}
配置完成后启动项目即可,定时任务部分打印日志如下:
Quartz
是一个功能丰富的开源作业调度库,由Java写成,可以集成在任何Java程序中,使用Quartz可以创建简单或者复杂的执行任务,支持数据库、集群、插件等,并且支持cron 表达式,有很高的灵活性,Spring Boot集成Quartz和Spring 集成非常类似,主要提供三个Bean: JobDetail,Trigger以及SchedulerFactory.
创建依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
然后创建两个Job:
MyFirstJob.java:
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyFirstJob {
public void sayHello() {
System.out.println("MyFirstJob:sayHello:"+new Date());
}
}
MySecondJob.java:
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
public class MySecondJob extends QuartzJobBean {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
protected void executeInternal(JobExecutionContext context){
System.out.println("hello:"+name+":"+new Date());
}
}
Job可以一个普通的JavaBean,如果是普通的JavaBean,那么可以添加@Component注解将之注册到Spring 容器中。当然也可以使用继承抽象类QuartzJobBean,则需要实现该类的executeInternal 方法,在任务被调用时候使用。接下来创建对JobDetail和Trigger 进行配置:
@Configuration
public class QuartzConfig {
// 两种方法配置JobDetail,只需要指定Job的实例名和要调用的方法即可,
// 注册这种方法无法在创建JobDetail 传递参数
@Bean
MethodInvokingJobDetailFactoryBean jobDetail1() {
MethodInvokingJobDetailFactoryBean bean =
new MethodInvokingJobDetailFactoryBean();
bean.setTargetBeanName("myFirstJob");
bean.setTargetMethod("sayHello");
return bean;
}
// 指定JobClass 即可,通过JobDtaMap 传递参数到Job中,Job只需要提供属性名和set
@Bean
JobDetailFactoryBean jobDetail2() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setJobClass(MySecondJob.class);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("name","sang");
bean.setJobDataMap(jobDataMap);
bean.setDurability(true);
return bean;
}
// Trigger 有不同的实现方法,这里展示常用的Trigger:
// 这里设置JobDetail ,通过setRepeatCount 配置任务循环次数
@Bean
SimpleTriggerFactoryBean simpleTrigger() {
SimpleTriggerFactoryBean bean =
new SimpleTriggerFactoryBean();
bean.setJobDetail(jobDetail1().getObject());
bean.setRepeatCount(3);
// 启动延迟时间
bean.setStartDelay(1000);
// 任务时间间隔
bean.setRepeatInterval(2000);
return bean;
}
// 主要配置JobDetail 和Cron 表达式
@Bean
CronTriggerFactoryBean cronTrigger() {
CronTriggerFactoryBean bean =
new CronTriggerFactoryBean();
bean.setJobDetail(jobDetail2().getObject());
bean.setCronExpression("* * * * * ?");
return bean;
}
@Bean
SchedulerFactoryBean schedulerFactory() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
SimpleTrigger simpleTrigger = simpleTrigger().getObject();
CronTrigger cronTrigger = cronTrigger().getObject();
bean.setTriggers(simpleTrigger,cronTrigger);
return bean;
}
}
配置完成后,便可以启动项目了,此时需要将前面在启动类的注解删去:
批处理
Spring Batch 是一个开源的、全面的、轻量级的批处理框架,通过给此可以实现强大的批处理应用程序的开发。Spring Batch 提供了记录/追踪,事务管理,作业处理统计等,可以结合定时任务发挥更大的作用。
也提供了ItemReader、ItemProcessor和Item Writer 来完成数据的读取,可将执行状态持久化到数据库中。下面通过简单的数据复制展示Spring Batch。
创建依赖以及数据库相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
添加数据库依赖是为了将批处理的执行状态持久化到数据库中,配置相应的信息:
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
#spring.datasource.url=jdbc:mysql:///ay_user
#spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.url = jdbc:mysql:///ay_user
spring.datasource.username= root
spring.datasource.password=q1
# 表示项目启动时候创建数据表的SQL 脚本位置(由batch提供)
spring.datasource.schema = classpath:/org/springframework/batch/core/schema-mysql.sql
# 表示在项目启动时执行建表SQL
spring.batch.jdbc.initialize-schema=always
# 不自动执行,需要用户手动触发执行
spring.batch.job.enabled=false
接着在项目启动类上添加注解:
@EnableBatchProcessing
@SpringBootApplication
然后配置批处理:
@Configuration
public class CsvBatchJobConfig {
// 注入三个对象进行备用,这里持久化的方案是JDBC
@Resource
JobBuilderFactory jobBuilderFactory;
@Resource
StepBuilderFactory stepBuilderFactory;
@Resource
DataSource dataSource;
// 配置ItemReader,Batch 提供了常用的ItemReader
@Bean
@StepScope
FlatFileItemReader<MUser> itemReader() {
// 加载一个普通文件的ItemReader
FlatFileItemReader<MUser> reader = new FlatFileItemReader<>();
// 第一行是标题所以要跳过
reader.setLinesToSkip(1);
// 找到配置的位置,此时已经写了,放在classpath目录下了
reader.setResource(new ClassPathResource("data.csv"));
// 设置每一行的信息
reader.setLineMapper(new DefaultLineMapper<MUser>(){{
setLineTokenizer(new DelimitedLineTokenizer(){{
setNames("id","username","address","gender");
// 配置列与列之间的间隔符
setDelimiter("\t");
}});
// 设置要映射的实体属性
setFieldSetMapper(new BeanWrapperFieldSetMapper<MUser>(){{
setTargetType(MUser.class);
}});
}});
return reader;
}
// 数据的写出逻辑
@Bean
JdbcBatchItemWriter jdbcBatchItemWriter() {
JdbcBatchItemWriter writer = new JdbcBatchItemWriter();
writer.setDataSource(dataSource);
// 注意占位符的写法: :属性名
writer.setSql("insert into muser(id,username,address,gender) " +
"values(:id,:username,:address,:gender)");
writer.setItemSqlParameterSourceProvider(
new BeanPropertyItemSqlParameterSourceProvider<>());
return writer;
}
// 配置一个Step,通过get来获取一个StepBuilder
@Bean
Step csvStep() {
return stepBuilderFactory.get("csvStep")
.<MUser, MUser>chunk(2)
.reader(itemReader())
.writer(jdbcBatchItemWriter())
.build();
}
@Bean
Job csvJob() {
return jobBuilderFactory.get("csvJob")
.start(csvStep())
.build();
}
}
这里涉及了一个MUser实体类:
public class MUser {
private Integer id;
private String username;
private String address;
private String gender;
// 省略set 和get 方法
}
此时在classpath 下的data.csv文件如下:
现在创建一个Controller ,当用户发起请求时候触发批处理:
注意此时jobLauncher.run括号的第二个参数不要写出null 否则会报错,直接生成new对象即可。
@RestController
public class BatchController {
@Resource
JobLauncher jobLauncher;
@Resource
Job job;
@GetMapping("/testbatch")
public String batchtest() {
try{
// 框架提供,job是刚刚配置的,调用run方法来批处理
jobLauncher.run(job,new JobParameters());
}catch (Exception e) {
e.printStackTrace();
}
return "success";
}
}
最后运行以后,会在batch库中创建多个批处理相关的库,这些表是用来记录批处理的执行状态,此时data.csv数据成功插入user 表。:
此时查看数据,已经成功插入表中:
Swagger 2
前后端分离开发中,为了减少沟通成本,一般需要构建RESTful API 文档来描述所有的接口信息,但仍有一些弊端,说明:
- 接口众多,编写RESTful API 文档工作量大,因为要包含接口的基本信息,例如接口地址、接口请求参数、接口返回值等。
- 维护不方便,一旦接口发生了变化,需要修改文档
- 接口测试不方便,只能借助于第三方工具,例如Postman来测试。
Swagger 2是一个开源软件框架,可以帮助开发人进行设计、构建、记录和使用等,将代码和文档融为一体,可以完美解决上述问题,可以较好地集中在业务当中,同时也可以非常好整合在Spring Boot.
用常规的依赖太老了,不兼容我2.7版本的Spring Boot。
# 这个依赖更好,可以Swagger 2的兼容(可以比较方便的做升级了)
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
创建Swagger2 的配置类:
还有一种办法就是可以在启动类上面,加@EnableOpenApi 就直接可以运行了。(这个方法非常简单)
@Configuration
@EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer {
@Bean
// Docket is a builder which is intended to be the primary interface into the swagger-springmvc framework.
Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.testspringboot.controller"))
.paths(PathSelectors.any()) // 所有包
.build().apiInfo(new ApiInfoBuilder()
.description("接口文档")
.contact(new Contact("Jacin","baidu.com","1@qq.com"))
.version("v1.0")
.title("测试文档")
.license("Apache")
.licenseUrl("apache.org")
.build());
}
}
通过@EnableSwagge2 注解开启 Swagger 2 ,主要是配置一个Docket,通过apis方法配置要扫描的controller 位置,通过paths 方法配置路径。在apiInfo 构建文档的基本信息,例如描述、联系人信息、版本等。
Swagger 2配置完成后,接下来开发接口了:
@RestController
//用来描述整个Controller 信息,默认就是类名
@Api(tags="用户接口")
public class SwaggerController {
// 用在方法上,描述一个信息,value 是简单的描述,notes 用来备注方法的详细作用
@ApiOperation(value = "查询用户",notes = "根据id")
// 方法上,描述方法的参数
// paramType 是方法参数的类型,可选值有path( PathVariable),query(@RequestParam)
// name 表示参数名称,和下面的参数变量对应
// value 是参数描述信息,required 吧iOS是否必填
@ApiImplicitParam(paramType = "path",name="id",value = "用户id",required = true)
@GetMapping("/user/{id}")
public String getUserById(@PathVariable Integer id) {
return "/user/"+ id;
}
// 这个是响应结果的描述,code 是响应码,message是描述信息,有多个那么就放在一个里面
@ApiResponses({
@ApiResponse(code = 200,message = "删除成功"),
@ApiResponse(code = 500,message = "删除失败")
})
@ApiOperation(value = "删除用户",notes = "id删除")
@DeleteMapping("/user/{id}")
public Integer deletUserById(@PathVariable Integer id) {
return id;
}
@ApiOperation(value = "添加用户",notes = "添加一个用户,传入用户名和地址")
@ApiImplicitParams({
@ApiImplicitParam (paramType = "query" ,name = "username",value = "用户名",required = true,defaultValue = "sang"),
@ApiImplicitParam(paramType = "query" ,name = "address",value = "用户地址",required = true,defaultValue = "sang")
})
@PostMapping("/user")
public String addUser(@RequestParam String username,@RequestParam String address) {
return username + ":" + address;
}
@ApiOperation(value = "修改用户",notes = "修改用户,传入用户信息")
// @PostMapping("/user")
// public String updateUser(@RequestBody MUser user) {
// return user.toString();
// }
@GetMapping("/ignore")
// 不对某个接口生成文档
@ApiIgnore
public void ingoreMethod() {
}
@GetMapping("tst")
public String tst() {
return "tst";
}
}
此时输入:http://localhost:8081/swagger-ui/index.html# (注意新版的格式)
这次更新,移除了原来默认的swagger页面路径:http://host/context-path/swagger-ui.html,新增了两个可访问路径:http://host/context-path/swagger-ui/index.html和http://host/context-path/swagger-ui/
不过我一开始使用的是依赖是 (以下方法不一定成功):
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
而且用的http://localhost:8081/swagger-ui/ 发现怎么都进不去,一直报error.
查看swagger-ui 位置:
此时构造一个Webconfig 来实现 WebMvcConfigurer 接口方法:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 解决静态资源⽆法访问
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
// 解决swagger⽆法访问
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
// 解决swagger的js⽂件⽆法访问
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
不过我这里确实没实现出来,应该是spring boot 版本问题。毕竟swagger2 太久没更新了,所以使用新版的swagger2。
以上。