單體開發進階
SpringBoot的Web開發之路
衆所周知,CRUD是每個程序員的必經之路。作爲一個初級程序員,只要能夠獨立開發出一個簡單的CRUD系統即可,例如OA系統、CRM管理系統、基於表單的CRUD系統等。
簡單來說,分佈式開發是後端提供接口,前端接收信息進行渲染;單體開發是後端提供數據,前端獲取數據進行渲染。
從開發流程來說,單體開發和分佈式開發本質上並沒有區別,只是用到的技術和思想上略有不同。
分佈式開發的本質:網絡是不可靠的,我們需要解決服務之間的通信問題,例如服務崩潰了的解決方案,客戶端如何去進行訪問,服務的註冊和發現等。
Swagger
什麼是Swagger?
在前後端分離的項目當中,開發人員的分工大概如下:
- 前端主要專注於控制層和視圖層,由專業的前端團隊進行開發。
- 後端主要專注於後端控制層、服務層和數據訪問層,由專業的後端團隊進行開發。
那麼問題來了,前後端的交互一般都是通過API來進行的,關於API的約定應該如何處理呢?
在早期的時候,一般由後端編寫協同文檔,前端根據文檔解析接口然後渲染視圖。但問題也很明顯,前端和後端之間無法做到及時協商,最終可能導致問題集中爆發或者項目延時。
爲了解決這些問題,Swagger應運而生。
什麼是Swagger?
Swagger是一個規範和完整的框架,用於生成、描述、調用和可視化 RESTful風格的Web服務。總體目標是使客戶端和文件系統作爲服務器以同樣的速度來更新。文件的方法,參數和模型緊密集成到服務器端的代碼,允許API來始終保持同步。
Swagger的作用主要有兩點:接口的文檔在線自動生成和功能測試。
現在的開源項目中,都會集成Swagger。
Swagger的主要特點
- 號稱世界上最流行的API框架。
- Restful API自動生成文檔,和代碼對應。
- 直接運行測試接口,不需要下載postman了。
- 支持多種語言,如Java、Php等。
Swagger官網地址::https://swagger.io/
集成Swagger
基礎集成
1、導入Swagger2依賴。
<!--swaggger2依賴-->
<!-- 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、編寫Swagger配置類,註冊Docket到Spring容器中,在主啓動類上添加@EnableSwagger2
開啓配置使Swagger生效。
//Swagger配置類
@Configuration
public class SwaggerConfig {
// 註冊bean Docket
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2);
}
}
//主啓動類
@SpringBootApplication
@EnableSwagger2 //使swagger生效,默認不開啓
public class SpringbootPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPlusApplication.class, args);
}
}
3、啓動項目進行測試,Swagger API文檔默認訪問地址:http://localhost:8080/swagger-ui.html
注意在Controller的方法請求路徑映射不要使用@RequestMapping
來配置,應該直接使用具體請求方法的註解,如@GetMapping
、@PostMapping
等。因爲使用@RequestMapping
配置在API文檔上一個方法默認會生成七種請求的API文檔信息,如下圖所示。
配置Swagger
1、在SwaggerConfig類中配置文檔信息,構造ApiInfo對象。
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())//配置文檔信息
}
//配置文檔信息apiInfo
private ApiInfo apiInfo(){
Contact contact = new Contact("wunian", "https://www.jianshu.com/u/5e27029e243f", "[email protected]");
return new ApiInfo(
"SpringBoot-Plus接口文檔信息",//文檔標題
"所有的測試請求地址",//文檔描述
"v1.0",//文檔版本
"https://www.jianshu.com/u/5e27029e243f",//組織鏈接
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.wunian.controller"))//設置掃描指定包下的類
.build();
}
RequestHandlerSelectors類的所有靜態方法說明:
any()
:掃描所有,項目的所有接口都會被掃描。none()
:不掃描接口。basePackage()
:根據包路徑掃描。withMethodAnnotation(GetMapping.class)
:通過方法註解掃描。withClassAnnotation(Controller.class)
:通過類上的註解掃描。
3、設置哪些接口不被掃描。
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.wunian.controller"))//設置掃描指定包下的類
//配置path過濾請求,只掃描以/kuang開頭的請求
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
PathSelectors類的所有靜態方法說明:
ant(String antPattern)
:只掃描指定的路徑下的請求。any()
:任何請求都會被掃描。none()
:不掃描請求。regex(String pathRegex)
:通過正則表達式來匹配請求。
配置Swagger開關
如果我們要讓swagger-ui頁面只在test和dev環境下顯示,prod環境不顯示就需要配置enable(false)方法。
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false)//如果是false就無法在瀏覽器中訪問swagger-ui.html
.select()
.apis(RequestHandlerSelectors.basePackage("com.wunian.controller"))//
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
不過直接在enable方法中傳入false也不合適,它應該是一個變量,我們可以通過Profiles類來獲取限定的開發環境,並且調用Environment對象的acceptsProfiles方法來判斷當前環境是否是限定的開發環境。
@Bean
public Docket docket(Environment environment){
//設置要顯示的swagger環境
Profiles of = Profiles.of("dev", "test");
//判斷是否處於該環境
boolean isEnable=environment.acceptsProfiles(of);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(isEnable) //如果是false,就無法在瀏覽器中訪問
.select()
.apis(RequestHandlerSelectors.basePackage("com.wunian.controller"))
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
配置API分組
這個配置只需要瞭解即可,以後可以使用MyBatis-Plus一鍵生成。
@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");
}
實體配置
1、新建一個實體類,類上使用@ApiModel註解來添加該實體類的描述信息,此註解其實就是註釋,只不過它會被Swagger識別。
//和註釋差不多,但是會被swagger識別
@ApiModel("用戶實體") //實體類的描述信息
public class User {//只有在controller中返回值用到,這個類纔會顯示在swagger中
@ApiModelProperty("用戶名")
private String username;
@ApiModelProperty("密碼")
private String password;
}
2、請求的接口配置,如果要在swagger-ui.html中看到實體類的配置,那麼這個實體類一定是在請求的返回值或者泛型中,只有這樣它纔會被映射。
//只有返回值用到纔會顯示
@GetMapping("/getUser")
public User getUser(){
return new User();
}
接口上的配置
在類上使用@Api註解來添加類的描述信息,在方法上使用@ApiOperation註解來添加方法的描述信息,在方法的形參上使用@ApiParam註解來添加參數的描述信息。
@Api(tags="Hello測試類") //類的描述信息
@RestController
public class HelloController {
@GetMapping("/kuang/hello")
public String hello(){
return "Hello Swagger";
}
//只有返回值用到纔會顯示
@GetMapping("/getUser")
public User getUser(){
return new User();
}
// 後面我們自己開發項目的時候,主要是寫方法註釋和參數註釋!
@ApiOperation("coding的接口") //方法和接口的描述信息
@PutMapping("/coding")
public String coding(@ApiParam("用戶名") String username){//參數的描述信息
return username;
}
}
Swagger接口文檔的皮膚包(擴展)
1、默認的皮膚包: http://localhost:8080/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、ui-layer: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註解,此註解相當於告訴Spring這是一個異步任務,默認使用線程池開啓異步任務,效率很高。
@Service
public class AsyncService {
//模擬一個延時的後臺方法
@Async //告訴spring這是一個異步方法,默認使用了線程池
public void hello(){
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("數據處理中......");
}
}
2、在主啓動類上添加@EnableAsync開啓異步任務支持。
@SpringBootApplication
@EnableSwagger2 //使swagger生效,默認不開啓
@EnableAsync //開啓異步任務的支持
public class SpringbootPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPlusApplication.class, args);
}
}
3、啓動項目進行測試,頁面如果出現秒級刷新,說明測試成功。
定時任務
Cron表達式
計劃任務,是指任務在約定的時間執行已經計劃好的工作,這只是表面的意思。在Linux中,我們經常用到Cron服務器來完成這項工作。Cron服務器可以根據配置文件約定的時間來執行特定的任務。
實際上在分前面還有秒,範圍在0-59之間。
一些特殊符號的含義:
*
表示任意時間。-
表示區間。L
表示最後。?
用在日或星期上,表示不確定值、不限制值。W
表示工作日,這裏的工作日指的是朝九晚五,雙休和節假日。#
用於星期上,#
後面的數字表示第幾周,如果不存在這個周的值,則不執行。
這些特殊符號其實不用刻意去記憶,我們想要編寫Cron表達式只需要去找在線生成Cron表達式的網站去生成就好了。
常用的Cron表達式:
(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,在定時方法上添加@Scheduled註解,該註解傳入一個cron參數,此參數傳入一個Cron表達式。
@Service //放到spring容器中
public class ScheduledService {
//工作中的定時任務都可以使用這樣的方法
//秒 分 時 日 月 周幾
@Scheduled(cron = "0 * * * * 0-7") //每天每時每分整點執行
public void hello(){
System.out.println("Hello Scheduled");
}
}
2、在主啓動類上添加@EnableScheduling註解開啓定時任務支持。
@SpringBootApplication
@EnableSwagger2 //使swagger生效,默認不開啓
@EnableAsync //開啓異步任務的支持
@EnableScheduling//開啓定時任務支持
public class SpringbootPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPlusApplication.class, args);
}
}
3、啓動項目進行測試,可以看到每一分鐘的整點都會輸出"Hello Scheduled"。
郵件任務
所有的網站,幾乎都有郵件收發功能。我們這裏以QQ郵箱爲例,基於SpringBoot來實現發送郵件功能。
郵箱的開發者權限獲取
相比於其他郵箱,QQ郵箱較爲複雜,因爲它有安全驗證。我們需要登錄QQ郵箱去開啓一下SMTP服務,具體操作步驟如下圖所示。
然後我們就可以獲得一個授權碼了,它相當於我們的郵箱賬號的密碼,通過它可以登錄我們的郵箱。
對於不同的郵箱系統,網站服務器地址是不一樣的:
- QQ郵箱:smtp.qq.com
- 網易郵箱:smtp.163.com
- 新浪郵箱:smtp.sina.com
測試
1、導入啓動器,java中發送郵件的包是javax.mail,但是SpringBoot啓動器中導入的包是jakarta.mail。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2、配置發件人信息,詳細的配置項可以查看MailProperties配置類。
#發件人的信息
[email protected]
spring.mail.password=mxhdobcadmkudacd
spring.mail.host=smtp.qq.com
#QQ比較特殊,需要配置ssl安全連接,其他郵箱不需要配置
spring.mail.properties.mail.smtp.ssl.enable=true
4、測試發送簡單郵件和複雜郵件。
@SpringBootTest
class SpringbootPlusApplicationTests {
@Autowired
JavaMailSender sender;
@Test
void sendMailTest(){//測試簡單的郵件發送
//發送簡單的郵件
SimpleMailMessage message = new SimpleMailMessage();//簡單的郵件消息
message.setSubject("你收到了一份面試邀請");//郵件主題
message.setText("纔怪,測試郵件哦!");
message.setFrom("[email protected]");//發件人
message.setTo("[email protected]","[email protected]");//收件人
sender.send(message); //發送郵件
}
@Test
void sendMimeMailTest() throws MessagingException {//測試複雜的郵件發送
//複雜的郵件,通過一個輔助類來完成 MimeMessageHelper
MimeMessage mimeMessage = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
//基本信息
helper.setSubject("支付寶到賬提醒:尊敬的客戶,您3月份...");
helper.setText("<h1 style='color:red'>支付寶到賬1,0000,000元,請注意查收!</h1>",true);
//發送附件
helper.addAttachment("1.jpg",new File("C:\\Users\\Administrator\\Desktop\\1.jpg"));
helper.setFrom("[email protected]");
helper.setTo(new String[]{"[email protected]","[email protected]"});
sender.send(mimeMessage); //發送郵件
}
}