单体开发进阶
SpringBoot的Web开发之路
大家平时中用的CRUD和传统的开发,2天时间我们搞定了!(是每个程序员必经之路!)
能够独立开发出一个简单的CRUD的系统即可(OA系统、CRM管理系统、基于表单的CRUD的系统!
80%的应用都是在做昨天的应用工作!)
分布式开发:后端提供接口 + 前端接收信息渲染!
单体开发:后端提供数据 + 前端获取数据渲染!
整体来说,从开发流程,单体和 分布式本质上没有区别,只是用到技术和思想上略有不同!
分布式开发的本质:网络是不可靠的!服务之间的通信,服务崩了怎么办,客户端怎么访问,服务的注
册与发现!
今日内容
1、精通Swagger
2、异步任务(JUC、多线程)
3、定时任务(定时备份日志…)
4、邮件任务(扩展技能!微信的前身,邮件系统!)
5、富文本编辑器(MD前后端联调,开发博客使用!)
精通Swagger
学习目标
1、了解Swagger的概念和作用
2、在项目中集成并使用Swagger!
什么是Swagger
相信无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。其实无论是前端调用后端,还是后端调用后端,都期望有一个好的接口文档。但是这个接口文档对于程序员来说,就跟注释一样,经常会抱怨别人写的代码没有写注释,然而自己写起代码起来,最讨厌的,也是写注释。所以仅仅只通过强制来规范大家是不够的,随着时间推移,版本迭代,接口文档往往很容易就跟不上代码了。
发现了痛点就要去找解决方案。解决方案用的人多了,就成了标准的规范,这就是Swagger的由来。通过这套规范,你只需要按照它的规范去定义接口及接口相关的信息。再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,生成多种语言的客户端和服务端的代码,以及在线接口调试页面等等。这样,如果按照新的开发模式,在开发新版本或者迭代版本的时候,只需要更新Swagger描述文件,就可以自动生成接口文档和客户端服务端代码,做到调用端代码、服务端代码以及接口文档的一致性。
但即便如此,对于许多开发来说,编写这个yml或json格式的描述文件,本身也是有一定负担的工作,特别是在后面持续迭代开发的时候,往往会忽略更新这个描述文件,直接更改代码。久而久之,这个描述文件也和实际项目渐行渐远,基于该描述文件生成的接口文档也失去了参考意义。所以作为Java届服务端的大一统框架Spring,迅速将Swagger规范纳入自身的标准,建立了Spring-swagger项目,后面改成了现在的Springfox。通过在项目中引入Springfox,可以扫描相关的代码,生成该描述文件,进而生成与代码一致的接口文档和客户端代码。这种通过代码生成接口文档的形式,在后面需求持续迭代的项目中,显得尤为重要和高效。
前后端分离:
前端 --> 前端的控制层,视图层. (专业的前端团队开发)
后端 --> 后端控制层、服务层、数据访问层.(专业的后端团队开发)
前后端的交互是通过 API 来进行的?关于API的约定该怎么处理呢?
早期:后端编写文档(协同文档),前端根据文档解析接口然后渲染视图!
问题:前后端集成,前端或者后端无法做到:及时协商!最终可能导致问题集中爆发或者项目延时!
现在得开源项目中,都有集成Swagger!
什么是Swagger?
号称世界上最流行的API框架!
Restful api 自动生成文档 ,和代码对应的
直接运行测试接口,不用下载 postman
支持多种语言:(Java、Php等)
官网:https://swagger.io/
集成Swagger
基础集成
1、导入依赖
<!--导入依赖-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
2、编写配置
@Configuration
public class SwaggerConfig {
// 注册bean Docket
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2);
}
}
// 主启动类开启配置!
@SpringBootApplication
// 使Swagger生效,默认是不开启!@EnableSwagger2
@EnableSwagger2
public class SpringbootPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPlusApplication.class, args);
}
}
3、启动测试 : http://localhost:8080/swagger-ui.html
spring boot 加入拦截器后swagger不能访问问题
未加入拦截器时,swagger可以正常访问接口信息,但是加入拦截器之后swagger就不能访问了
原因分析
不能访问的原因的swagger的内置接口被拦截器拦下来了
图片中可以看到swagger的所有请求的url信息,只要把他们加到拦截器的排除列表中即可
package com.trimps928.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author liubing
* @version 2018-06-26
* 拦截器配置
**/
@Configuration
public class MyWebAppConfig extends WebMvcConfigurationSupport {
@Bean
LoginInterceptor localInterceptor() {
return new LoginInterceptor();
}
//将swagger相关的链接排除出去
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
}
//添加静态资源访问 这两者都需要的
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
注意点:写Controller的之后一定要精确到方法! 不能写@RequestMapping 因为这是由下面六个组成的,而不是精确到方法的
配置Swagger
1、配置文档信息
@Bean
public Docket docket(){
return
new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()); // 配置文档信息!
}
// 配置文档信息 apiInfo
private ApiInfo apiInfo(){
Contact contact = new
Contact("coding","https://www.icodingedu.com/course/52","24736743@qq.com");
return new ApiInfo(
"SpringBoot-Plus 接口文档信息",
"所有的测试请求地址",
"v1.0",
"https://www.icodingedu.com/course/52", //组织连接
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>());
}
2、配置接口扫描,需要哪些被扫描到文档中
@Bean
public Docket docket(){
return
new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.coding.controller"))
.build();
}
其与的几个方法说明:
any() # 扫描所有,项目的所有接口都会被扫描的
none() # 不扫描接口
basePackage() # 根据包路径扫描
withMethodAnnotation(GetmMapping.class) # 通过方法注解扫描! 比如 GetMapper.class
withClassAnnotation(Controller.class) # 通过类上的注解扫描!
学习方式:上课听(思考自己不会的问题或者有疑问问题,写在记事本上,下课之后去钻研!)
3、设置哪些接口不被扫描!
@Bean
public Docket docket(){
return
new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()) // 配置文档信息!
.select()
.apis(RequestHandlerSelectors.basePackage("com.coding.controller"))
// 配置path过滤请求!只扫描以 /kuang 开头的请求!
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
配置Swagger的开关
test、dev 环境下才可以显示 swagger-ui , prod 不显示!
@Bean
public Docket docket(){
return
new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) // 如果是false就无法在浏览器中访问!
.select()
.apis(RequestHandlerSelectors.basePackage("com.coding.controller"))
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
这里的 false 应该是一个变量:技巧点!通过Profiles类来获取限定咋们的开发环境!
@Bean
public Docket docket(Environment environment){
// 设置要显示的swagger环境
Profiles of = Profiles.of("dev", "test");
// 判断是否处于该环境!
boolean b = environment.acceptsProfiles(of);
return
new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()) // 配置文档信息!
.enable(b) // 如果是false就无法在浏览器中访问!
.select()
.apis(RequestHandlerSelectors.basePackage("com.coding.controller"))
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
配置API分组
这个大家现在过个眼熟即可,能掌握最好,后面我们使用MP一键生成!
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}
@Bean
public Docket docket(){
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("null")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.hl.mywebsystem.controller"))
.build();
return docket;
}
公司都是盈利为目的:你算一下你的工作能带来资金能比你工资多吗?
实体配置
1、新建一个实体类
// 和注释差不多,但是会被Swagger识别
@ApiModel("用户实体")
public class User {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
}
2、请求的接口配置!如果能够看到实体类配置!如果这个实体类在请求的返回值或者泛型中,那么就会
被映射!
// 只有返回值用到才会显示
@GetMapping("/getUser")
public User getUser(){
return new User();
}
接口上的配置
@Api(tags="AAAAA测试")
@RestController
public class HelloController {
// 后面我们自己开发项目的时候,名主要是写 方法注释和参数注释!
@ApiOperation("coding的接口")
@PostMapping("/coding")
public String coding(@ApiParam("这个名字会被返回!") String username){
return username;
}
@GetMapping("/kuang/hello")
public String hello(){
return "hello,Swagger";
}
// 只有返回值用到才会显示
@GetMapping("/getUser")
public User getUser(){
return new User();
}
扩展:皮肤包!
1、默认的 http://localhost:8081/swagger-ui.html
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
2、BootStrap-ui http://localhost:8080/doc.html
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui
-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
3、Layui的框架 http://localhost:8080/docs.html
<!-- https://mvnrepository.com/artifact/com.github.caspar-chen/swagger-ui-layer -
->
<dependency>
<groupId>com.github.caspar-chen</groupId>
<artifactId>swagger-ui-layer</artifactId>
<version>1.1.3</version>
</dependency>
4、mg-ui http://localhost:8080/document.html
<dependency>
<groupId>com.zyplayer</groupId>
<artifactId>swagger-mg-ui</artifactId>
<version>1.0.6</version>
</dependency>
异步任务
异步处理请求!
无返回值的异步任务
1、在业务方法中,加上@Async注解
// 模拟一个延时的后台方法
@Async // 告诉Spring这是一个异步方法!默认使用了线程池,效率很好!
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据处理中.....");
}
2、开启注解的支持
@SpringBootApplication
@EnableSwagger2 // 使Swagger生效,默认是不开启!@EnableSwagger2
@EnableAsync // 开启异步注解的支持
public class SpringbootPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPlusApplication.class, args);
}
}
测试,秒级刷新,后台OK!
有返回值的异步任务
返回值用Futrue变量封装起来,下面是service层的代码
@Async
public Future doReturn(int i){
try {
// 这个方法需要调用500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 消息汇总
return new AsyncResult<>(String.format(“这个是第{%s}个异步调用的证书”, i));
}
读取的时候,记得要批量读取不能单独读取, 下面是controller层的代码
@GetMapping("/hi")
public Map<String, Object> testAsyncReturn() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
Map<String, Object> map = new HashMap<>();
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<String> future = asyncService.doReturn(i);
futures.add(future);
}
List<String> response = new ArrayList<>();
for (Future future : futures) {
String string = (String) future.get();
response.add(string);
}
map.put("data", response);
map.put("消耗时间", String.format("任务执行成功,耗时{%s}毫秒", System.currentTimeMillis() - start));
return map;
}
在浏览器输入地址:http://localhost:8080/hi
结果如下: 耗时500多毫秒的意思代表,springboot自带异步任务线程池是小于10的大小的
{“data”:[“这个是第{0}个异步调用的证书”,“这个是第{1}个异步调用的证书”,“这个是第{2}个异步调用的证书”,“这个是第{3}个异步调用的证书”,“这个是第{4}个异步调用的证书”,“这个是第{5}个异步调用的证书”,“这个是第{6}个异步调用的证书”,“这个是第{7}个异步调用的证书”,“这个是第{8}个异步调用的证书”,“这个是第{9}个异步调用的证书”],“消耗时间”:“任务执行成功,耗时{508}毫秒”}
重要的事情说三遍:
一定要批量读取结果, 否则不能达到异步的效果!!
一定要批量读取结果, 否则不能达到异步的效果!!
一定要批量读取结果, 否则不能达到异步的效果!!
1、异步方法和调用类不要在同一个类中
2、注解扫描时,要注意过滤,避免重复实例化,因为存在覆盖问题,@Async就失效了
否则效果如下:
{“data”:[“这个是第{0}个异步调用的证书”,“这个是第{1}个异步调用的证书”,“这个是第{2}个异步调用的证书”,“这个是第{3}个异步调用的证书”,“这个是第{4}个异步调用的证书”,“这个是第{5}个异步调用的证书”,“这个是第{6}个异步调用的证书”,“这个是第{7}个异步调用的证书”,“这个是第{8}个异步调用的证书”,“这个是第{9}个异步调用的证书”],“消耗时间”:“任务执行成功,耗时{5012}毫秒”}
定时任务
工作中一定会用到定时任务,每天发送一个日志信息,Spring也提供了对应的支持!
Cron 表达式
计划任务,是任务在约定的时间执行已经计划好的工作,这是表面的意思。在Linux中,我们经常用到
cron 服务器来完成这项工作。cron服务器可以根据配置文件约定的时间来执行特定的任务。
秒 分 小时 日期 月份 星期
除了以上的作用值之外,
还有 * ,任意时间!
? :天/星期冲突匹配
W 工作日(朝九晚五、双休,节假日!)
#星期, 4#2 ,第二个星期三
我们平时生成这个表达式,可以去找在线生成的网站!
常用的例子:
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
写点代码测试一下!
自动化定时任务!
1、编写Service
package com.coding.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
// 定时任务类
@Service // 放到Spring容器中
public class ScheduledService {
// 秒 分 时 日 月 周几
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("Hello。。。。。。。。");
}
}
2、开启注解的支持!
@SpringBootApplication
@EnableSwagger2 // 使Swagger生效,默认是不开启!@EnableSwagger2
@EnableAsync // 开启异步注解的支持
@EnableScheduling // 开启定时任务支持
public class SpringbootPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPlusApplication.class, args);
}
}
邮件任务
所有的网站,差不多都用邮件收发功能!基于SpringBoot!
1.导入启动器
2.配置文件
3.测试使用
邮件的开发者权限获取
QQ 的是比较复杂,因为有安全验证
得到授权码:相当于你的密码!可以登录你的邮箱!
其与的就直接写代码就好!
测试
1、导入启动器 ,java发送邮件:javax.mail 但是SpringBoot启动器中导入的是 jakarta.mail?
java有两段api,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2、配置文件,收件人,发件人,邮件主题,邮件内容!
分析源码: MailSenderAutoConfiguration,通过导入的组件发现配置类:
@Bean
JavaMailSenderImpl mailSender(Session session) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setDefaultEncoding(this.properties.getDefaultEncoding().name());
sender.setSession(session);
return sender;
配置类:MailProperties
# 发件人的信息(一般是公司的账号)
spring.mail.username=24736743@qq.com
spring.mail.password=vtnofwxdkmuqcagf
spring.mail.host=smtp.qq.com
# QQ比较特殊,需要配置ssl 安全连接, 如果是其他的邮箱不用配置!
spring.mail.properties.mail.smtp.ssl.enable=true
3、测试使用!
@SpringBootTest
class SpringbootPlusApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
// 发送简单的邮件! 万物皆对象,邮件也不例外
SimpleMailMessage message = new SimpleMailMessage();// 简单的邮箱消息
message.setSubject("通知,明天不上课"); // 邮件主题
message.setText("明天要出差!"); // 简单的文本
message.setTo("24736743@qq.com","785897496@qq.com","2360004365@qq.com");
message.setFrom("24736743@qq.com");
mailSender.send(message); // 发送邮件!
}
// 复杂的邮件
@Test
void test2() throws MessagingException {
// 复杂的邮件,通过一个辅助类来完成 MimeMessage
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
// 基本信息
helper.setSubject("复杂的邮件测试");
helper.setText("<h1 style='color:red'>今天有点意思</h1>",true);
// 发送附件
helper.addAttachment("1.jpg",new
File("C:\\Users\\Administrator\\Desktop\\Bilibili\\赞赏码.jpg"));
helper.addAttachment("2.jpg",new
File("C:\\Users\\Administrator\\Desktop\\Bilibili\\赞赏码.jpg"));
// 发送对象!
String[] userList =
{"24736743@qq.com","785897496@qq.com","2360004365@qq.com"};
helper.setTo(userList);
helper.setFrom("24736743@qq.com");
mailSender.send(mimeMessage); // 发送邮件!
}
}
下去一定要自己实现一下,可以自己写一个前端页面,给自己的博客网站,增加一个写邮件的功能!