[Spring Boot 6]企业级开发

本节内容主要就几个常用的模块进行解读,在众多企业级开发中常常用得到
例如邮件发送这一功能其实非常常见,在一般的网站设计中会有对注册邮件的激活功能,此时就需要用到这个功能了,一般来说激活邮件需要设置UUID 来进行验证,如果用户点击了链接那么在数据库的状态就转换为1,表示已经激活了。
定时任务也用得比较多,常见于缓存进入数据库等,可以看我这篇关于高并发的章节 高并发Demo
这里讲的批处理主要是针对批量导入数据库的快捷方法(这样就不用进入excel一行一行导入了)
目前很多项目也是需要讲究前后端分离的,所以一个好的接口测试是非常有必要的,之前我们使用了Postman 来进行测试,不过依旧缺少点详细信息,这里使用Swagger2 来进行测试,不过值得一提的是,以前老的配置方法非常坑,我也是花了相当的时间才找到了一个合适的办法。

希望对你有所帮助!

邮件发送

邮件发送是非常常见的功能,注册时的身份验证、重要通知发送都会用到邮件发送。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 文档来描述所有的接口信息,但仍有一些弊端,说明:

  1. 接口众多,编写RESTful API 文档工作量大,因为要包含接口的基本信息,例如接口地址、接口请求参数、接口返回值等。
  2. 维护不方便,一旦接口发生了变化,需要修改文档
  3. 接口测试不方便,只能借助于第三方工具,例如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。

以上。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值