目錄
前言
一、前后端分離開發
1.? 介紹
2. 開發流程
3. 前端技術棧
(1)開發工具:
(2)技術框架:
二、Yapi
1. 介紹
2. 使用
(1)準備
(2)定義接口
(3)導出接口文檔
(4)導入接口文檔
三、Swagger
1. 介紹
2. 使用方式
(1)導入knife4j的maven坐標
(2)導入knife4j相關配置類
(3)設置靜態資源映射
(4)在LoginCheckFilter中設置不需要處理的請求路徑
3. 查看接口文檔
4.?常用注解
(1)問題說明
(2)注解介紹
(3)注解測試
前言
當前項目中,前端代碼和后端代碼混合在一起,是存在問題的,存在什么問題呢?
主要存在以下幾點問題:
(1) 開發人員同時負責前端和后端代碼開發,分工不明確
(2)開發效率低
(3)前后端代碼混合在一個工程中,不便于管理
(4)對開發人員要求高(既會前端,又會后端),人員招聘困難
為了解決上述提到的問題,現在比較主流的開發方式,就是前后端分離開發,前端人員開發前端的代碼,后端開發人員開發服務端的業務功能,分工明確,各司其職。我們本章節,就是需要將之前的項目進行優化改造,變成前后端分離開發的項目。
一、前后端分離開發
1.? 介紹
前后端分離開發,就是在項目開發過程中,對于前端代碼的開發由專門的前端開發人員負責,后端代碼則由后端開發人員負責,這樣可以做到分工明確、各司其職,提高開發效率,前后端代碼并行開發,可以加快項目開發進度。
目前,前后端分離開發方式已經被越來越多的公司所采用,成為當前項目開發的主流開發方式。
前后端分離開發后,從工程結構上也會發生變化,即前后端代碼不再混合在同一個maven工程中,而是分為 前端工程 和 后端工程 。
前后端分離之后,不僅工程結構變化,后期項目上線部署時,與之前也不同:
?之前: 前后端代碼都混合在一起,我們只需要將前端和后端的代碼統一打成jar包,直接運行就可以了。
?現在: 拆分為前后端分離的項目后,最終部署時,后端工程會打成一個jar包,運行在Tomcat中(springboot內嵌的tomcat)。前端工程的靜態資源,會直接部署在Nginx中進行訪問。
2. 開發流程
前后端分離開發后,面臨一個問題,就是前端開發人員和后端開發人員如何進行配合來共同開發一個項目?可以按照如下流程進行:
a. 定制接口: 這里所說的接口不是我們之前在service, mapper層定義的interface; 這里的接口(API接口)就是一個http的請求地址,主要就是去定義:請求路徑、請求方式、請求參數、響應數據等內容。(具體接口文檔描述的信息, 如上圖)
b. 前后端并行開發: 依據定義好的接口信息,前端人員開發前端的代碼,服務端人員開發服務端的接口; 在開發中前后端都需要進行測試,后端需要通過對應的工具來進行接口的測試,前端需要根據接口定義的參數進行Mock數據模擬測試。
c. 聯調: 當前后端都開發完畢并且自測通過之后,就可以進行前后端的聯調測試了,在這一階段主要就是校驗接口的參數格式。
d. 提測: 前后端聯調測試通過之后,就可以將項目部署到測試服務器,進行自動化測試了。
3. 前端技術棧
(1)開發工具:
Visual Studio Code (簡稱VsCode)
Hbuilder
(2)技術框架:
A. Node.js: Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環境。(類似于java語言中的JDK)。
B. Vue : 目前最火的的一個前端javaScript框架。
C. ElementUI: 一套為開發者、設計師和產品經理準備的基于 Vue 2.0 的桌面端組件庫,通過ElementUI組件可以快速構建項目頁面。
D. Mock: 生成隨機數據,攔截 Ajax 請求,前端可以借助于Mock生成測試數據進行功能測試。
E. Webpack: webpack 是一個現代 JavaScript 應用程序的模塊打包器(module bundler),分析你的項目結構,找到JavaScript模塊以及其它的一些瀏覽器不能直接運行的拓展語言(Sass,TypeScript等),并將其轉換和打包為合適的格式供瀏覽器使用。
二、Yapi
1. 介紹
YApi 是高效、易用、功能強大的 api 管理平臺,旨在為開發、產品、測試人員提供更優雅的接口管理服務。可以幫助開發者輕松創建、發布、維護 API,YApi 還為用戶提供了優秀的交互體驗,開發人員只需利用平臺提供的接口數據寫入工具以及簡單的點擊操作就可以實現接口的管理。
YApi讓接口開發更簡單高效,讓接口的管理更具可讀性、可維護性,讓團隊協作更合理。
源碼地址: GitHub - YMFE/yapi: YApi 是一個可本地部署的、打通前后端及QA的、可視化的接口管理平臺
官方文檔: YApi 接口管理平臺
要使用YApi,項目組需要自己進行部署,在本項目中我們可以使用課程提供的平臺進行測試,域名: https://mock-java.itheima.net/
2. 使用
(1)準備
注冊賬號,登錄平臺
(2)定義接口
登錄到Yapi平臺之后,我們可以創建項目,在項目下創建接口分類,在對應的分類中添加接口。
a. 創建項目
b. 添加分類
在當前項目中,有針對于員工、菜品、套餐、訂單的操作,我們在進行接口維護時,可以針對接口進行分類,如果沒有對應的分類,我們自己添加分類。
c. 添加接口
接口基本信息錄入之后,添加提交,就可以看到該接口的基本信息:
但是目前,接口中我們并未指定請求參數,響應數據等信息,我們可以進一步點擊編輯,對該接口 詳情進行編輯處理。
d. 運行接口
Yapi也提供了接口測試功能,當我們接口編輯完畢后,后端服務的代碼開發完畢,啟動服務,就可以使用Yapi進行接口測試了。
注意: 由于菜品分頁查詢接口,是需要登錄后才可以訪問的,所以在測試該接口時,需要先請求員工管理接口中的登錄接口,登錄完成后,再訪問該接口。
在Yapi平臺中,將接口文檔定義好了之后,前后端開發人員就需要根據接口文檔中關于接口的描述進行前端和后端功能的開發。
(3)導出接口文檔
在Yapi平臺中我們不僅可以在線閱讀文檔,還可以將Yapi中維護的文檔直接導出來,可以導出md,json,html格式,在導出時自行選擇即可 。
而在導出的html文件或md文件中,主要描述的就是接口的基本信息, 包括: 請求路徑、請求方式、接口描述、請求參數、返回數據等信息。展示形式如下:
(4)導入接口文檔
上述我們講解了接口文檔的導出,我們也可以將外部的接口文檔導入到Yapi的平臺中,這樣我們就不用一個接口一個接口的添加了。我們可以將課程資料中提供的json格式的接口文檔直接導入Yapi平臺中來。
導入過程中出現的確認彈窗,選擇"確認"。
導入成功之后,我們就可以在Yapi平臺查看到已導入的接口。
三、Swagger
1. 介紹
官網:API Documentation & Design Tools for Teams | Swagger
Swagger 是一個規范和完整的框架,用于生成、描述、調用和可視化 RESTful 風格的 Web 服務。功能主要包含以下幾點:
A. 使得前后端分離開發更加方便,有利于團隊協作
B. 接口文檔在線自動生成,降低后端開發人員編寫接口文檔的負擔
C. 接口功能測試
使用Swagger只需要按照它的規范去定義接口及接口相關的信息,再通過Swagger衍生出來的一系列項目和工具,就可以做到生成各種格式的接口文檔,以及在線接口調試頁面等等。
直接使用Swagger, 需要按照Swagger的規范定義接口, 實際上就是編寫Json文件,編寫起來比較繁瑣、并不方便, 。而在項目中使用,我們一般會選擇一些現成的框架來簡化文檔的編寫,而這些框架是基于Swagger的,如knife4j。knife4j是為Java MVC框架集成Swagger生成Api文檔的增強解決方案。而我們要使用kinfe4j,需要在pom.xml中引入如下依賴即可:
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</version>
</dependency>
2. 使用方式
接下來,我們就將我們的項目集成Knife4j,來自動生成接口文檔。這里我們還是需要再創建一個新的分支v1.2,在該分支中進行knife4j的集成,集成測試完畢之后,沒有問題,我們再將v1.2分支合并到master。
使用knife4j,主要需要操作以下幾步:
(1)導入knife4j的maven坐標
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</version>
</dependency>
(2)導入knife4j相關配置類
這里我們就不需要再創建一個新的配置類了,我們直接在WebMvcConfig配置類中聲明即可。
A. 在該配置類中加上兩個注解 @EnableSwagger2 @EnableKnife4j ,開啟Swagger和Knife4j的功能。
B. 在配置類中聲明一個Docket類型的bean, 通過該bean來指定生成文檔的信息。
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {/*** 設置靜態資源映射* @param registry*/@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("開始進行靜態資源映射...");registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}/*** 擴展mvc框架的消息轉換器* @param converters*/@Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {log.info("擴展消息轉換器...");//創建消息轉換器對象MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//設置對象轉換器,底層使用Jackson將Java對象轉為jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//將上面的消息轉換器對象追加到mvc框架的轉換器集合中converters.add(0,messageConverter);}@Beanpublic Docket createRestApi() {// 文檔類型return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.itheima.reggie.controller")).paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("瑞吉外賣").version("1.0").description("瑞吉外賣接口文檔").build();}
}
注意: Docket聲明時,指定的有一個包掃描的路徑,該路徑指定的是Controller所在包的路徑。因為Swagger在生成接口文檔時,就是根據這里指定的包路徑,自動的掃描該包下的@Controller, @RestController, @RequestMapping等SpringMVC的注解,依據這些注解來生成對應的接口文檔。
(3)設置靜態資源映射
由于Swagger生成的在線文檔中,涉及到很多靜態資源,這些靜態資源需要添加靜態資源映射,否則接口文檔頁面無法訪問。因此需要在 WebMvcConfig類中的addResourceHandlers方法中增加如下配置。
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
(4)在LoginCheckFilter中設置不需要處理的請求路徑
需要將Swagger及Knife4j相關的靜態資源直接放行,無需登錄即可訪問,否則我們就需要登錄之后,才可以訪問接口文檔的頁面。
在原有的不需要處理的請求路徑中,再增加如下鏈接:
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"
3. 查看接口文檔
經過上面的集成配置之后,我們的項目集成Swagger及Knife4j就已經完成了,接下來我們可以重新啟動項目,訪問接口文檔,訪問鏈接為: http://localhost:8080/doc.html
我們可以看到,在所有的Controller中提供的所有的業務增刪改查的接口,全部都已經自動生成了,我們通過接口文檔可以看到請求的url、請求方式、請求參數、請求實例、響應的參數,響應的示例。 并且呢,我們也可以通過這份在線的接口文檔,對接口進行測試。
注意: 由于我們服務端的Controller中的業務增刪改查的方法,都是必須登錄之后才可以訪問的,所以,我們在測試時候,也是需要先訪問登錄接口。登錄完成之后,我們可以再訪問其他接口進行測試。
我們不僅可以在瀏覽器瀏覽生成的接口文檔,Knife4j還支持離線文檔,對接口文檔進行下載,支持下載的格式有:markdown、html、word、openApi。
4.?常用注解
(1)問題說明
在上面我們直接訪問Knife4j的接口文檔頁面,可以查看到所有的接口文檔信息,但是我們發現,這些接口文檔分類及接口描述都是Controller的類名(駝峰命名轉換而來)及方法名,而且在接口文檔中,所有的請求參數,響應數據,都沒有中文的描述,并不知道里面參數的含義,接口文檔的可讀性很差。
(2)注解介紹
為了解決上述的問題,Swagger提供了很多的注解,通過這些注解,我們可以更好更清晰的描述我們的接口,包含接口的請求參數、響應數據、數據模型等。核心的注解,主要包含以下幾個:
注解 | 位置 | 說明 |
---|---|---|
@Api | 類 | 加載Controller類上,表示對類的說明 |
@ApiModel | 類(通常是實體類) | 描述實體類的作用 |
@ApiModelProperty | 屬性 | 描述實體類的屬性 |
@ApiOperation | 方法 | 說明方法的用途、作用 |
@ApiImplicitParams | 方法 | 表示一組參數說明 |
@ApiImplicitParam | 方法 | 用在@ApiImplicitParams注解中,指定一個請求參數的各個方面的屬性 |
(3)注解測試
a. 實體類:
可以通過 @ApiModel , @ApiModelProperty 來描述實體類及屬性
@Data
@ApiModel("套餐")
public class Setmeal implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty("主鍵")private Long id;//分類id@ApiModelProperty("分類id")private Long categoryId;//套餐名稱@ApiModelProperty("套餐名稱")private String name;//套餐價格@ApiModelProperty("套餐價格")private BigDecimal price;//狀態 0:停用 1:啟用@ApiModelProperty("狀態")private Integer status;//編碼@ApiModelProperty("套餐編號")private String code;//描述信息@ApiModelProperty("描述信息")private String description;//圖片@ApiModelProperty("圖片")private String image;@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;
}
b. 響應實體R:
@Data
@ApiModel("返回結果")
public class R<T> implements Serializable{@ApiModelProperty("編碼")private Integer code; //編碼:1成功,0和其它數字為失敗@ApiModelProperty("錯誤信息")private String msg; //錯誤信息@ApiModelProperty("數據")private T data; //數據@ApiModelProperty("動態數據")private Map map = new HashMap(); //動態數據//省略靜態方法 ....
}
?c. Controller類及其中的方法:
描述Controller、方法及其方法參數,可以通過注解: @Api, @APIOperation, @ApiImplicitParams, @ApiImplicitParam
@RestController
@RequestMapping("/setmeal")
@Slf4j
@Api(tags = "套餐相關接口")
public class SetmealController {@Autowiredprivate SetmealService setmealService;@Autowiredprivate CategoryService categoryService;@Autowiredprivate SetmealDishService setmealDishService;/*** 新增套餐* @param setmealDto* @return*/@PostMapping@CacheEvict(value = "setmealCache",allEntries = true)@ApiOperation(value = "新增套餐接口")public R<String> save(@RequestBody SetmealDto setmealDto){log.info("套餐信息:{}",setmealDto);setmealService.saveWithDish(setmealDto);return R.success("新增套餐成功");}/*** 套餐分頁查詢* @param page* @param pageSize* @param name* @return*/@GetMapping("/page")@ApiOperation(value = "套餐分頁查詢接口")@ApiImplicitParams({@ApiImplicitParam(name = "page",value = "頁碼",required = true),@ApiImplicitParam(name = "pageSize",value = "每頁記錄數",required = true),@ApiImplicitParam(name = "name",value = "套餐名稱",required = false)})public R<Page> page(int page,int pageSize,String name){//分頁構造器對象Page<Setmeal> pageInfo = new Page<>(page,pageSize);Page<SetmealDto> dtoPage = new Page<>();LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();//添加查詢條件,根據name進行like模糊查詢queryWrapper.like(name != null,Setmeal::getName,name);//添加排序條件,根據更新時間降序排列queryWrapper.orderByDesc(Setmeal::getUpdateTime);setmealService.page(pageInfo,queryWrapper);//對象拷貝BeanUtils.copyProperties(pageInfo,dtoPage,"records");List<Setmeal> records = pageInfo.getRecords();List<SetmealDto> list = records.stream().map((item) -> {SetmealDto setmealDto = new SetmealDto();//對象拷貝BeanUtils.copyProperties(item,setmealDto);//分類idLong categoryId = item.getCategoryId();//根據分類id查詢分類對象Category category = categoryService.getById(categoryId);if(category != null){//分類名稱String categoryName = category.getName();setmealDto.setCategoryName(categoryName);}return setmealDto;}).collect(Collectors.toList());dtoPage.setRecords(list);return R.success(dtoPage);}/*** 刪除套餐* @param ids* @return*/@DeleteMapping@CacheEvict(value = "setmealCache",allEntries = true)@ApiOperation(value = "套餐刪除接口")public R<String> delete(@RequestParam List<Long> ids){log.info("ids:{}",ids);setmealService.removeWithDish(ids);return R.success("套餐數據刪除成功");}/*** 根據條件查詢套餐數據* @param setmeal* @return*/@GetMapping("/list")@Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")@ApiOperation(value = "套餐條件查詢接口")public R<List<Setmeal>> list(Setmeal setmeal){LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());queryWrapper.orderByDesc(Setmeal::getUpdateTime);List<Setmeal> list = setmealService.list(queryWrapper);return R.success(list);}
}
d. 重啟服務測試:
我們上述通過Swagger的注解,對實體類及實體類中的屬性,以及Controller和Controller的方法進行描述,接下來,我們重新啟動服務,然后看一下自動生成的接口文檔有何變化。
在接口文檔的頁面中,我們可以看到接口的中文描述,清晰的看到每一個接口是做什么的,接口方法參數什么含義,參數是否是必填的,響應結果的參數是什么含義等,都可以清楚的描述出來。
總之,我們要想清晰的描述一個接口,就需要借助于Swagger給我們提供的注解。