文章目錄
- 接口文檔
- 內容
- 前言
- 1. 前后端分離開發
- 1.1 介紹
- 1.2 開發流程
- 1.3 前端技術棧
- 2. Yapi
- 2.1 介紹
- 2.2 使用
- 2.2.1 準備
- 2.2.2 定義接口
- 2.2.3 導出接口文檔
- 2.2.4 導入接口文檔
- 3. Swagger
- 3.1 介紹
- 3.2 使用方式
- 3.3 查看接口文檔
- 3.4 常用注解
- 3.4.1 問題說明
- 3.4.2 注解介紹
- 3.4.3 注解測試
- 4. 項目部署
- 4.1 部署架構
- 4.2 環境說明
- 4.3 前端部署
- 4.4 反向代理配置
- 4.5 服務端部署
- 4.6 圖片展示問題處理
接口文檔
內容
-
前后端分離開發
-
Yapi
-
Swagger
-
項目部署
前言
當前項目中,前端代碼和后端代碼混合在一起,是存在問題的,存在什么問題呢?
主要存在以下幾點問題:
1). 開發人員同時負責前端和后端代碼開發,分工不明確
2). 開發效率低
3). 前后端代碼混合在一個工程中,不便于管理
4). 對開發人員要求高(既會前端,又會后端),人員招聘困難
為了解決上述提到的問題,現在比較主流的開發方式,就是前后端分離開發,前端人員開發前端的代碼,后端開發人員開發服務端的業務功能,分工明確,各司其職。我們本章節,就是需要將之前的項目進行優化改造,變成前后端分離開發的項目。
1. 前后端分離開發
1.1 介紹
前后端分離開發,就是在項目開發過程中,對于前端代碼的開發由專門的前端開發人員負責,后端代碼則由后端開發人員負責,這樣可以做到分工明確、各司其職,提高開發效率,前后端代碼并行開發,可以加快項目開發進度。
目前,前后端分離開發方式已經被越來越多的公司所采用,成為當前項目開發的主流開發方式。
前后端分離開發后,從工程結構上也會發生變化,即前后端代碼不再混合在同一個maven工程中,而是分為 前端工程 和 后端工程 。
前后端分離之后,不僅工程結構變化,后期項目上線部署時,與之前也不同:
1). 之前: 前后端代碼都混合在一起,我們只需要將前端和后端的代碼統一打成jar包,直接運行就可以了。
2). 現在: 拆分為前后端分離的項目后,最終部署時,后端工程會打成一個jar包,運行在Tomcat中(springboot內嵌的tomcat)。前端工程的靜態資源,會直接部署在Nginx中進行訪問。
1.2 開發流程
前后端分離開發后,面臨一個問題,就是前端開發人員和后端開發人員如何進行配合來共同開發一個項目?可以按照如下流程進行:
1). 定制接口: 這里所說的接口不是我們之前在service, mapper層定義的interface; 這里的接口(API接口)就是一個http的請求地址,主要就是去定義:請求路徑、請求方式、請求參數、響應數據等內容。(具體接口文檔描述的信息, 如上圖)
2). 前后端并行開發: 依據定義好的接口信息,前端人員開發前端的代碼,服務端人員開發服務端的接口; 在開發中前后端都需要進行測試,后端需要通過對應的工具來進行接口的測試,前端需要根據接口定義的參數進行Mock數據模擬測試。
3). 聯調: 當前后端都開發完畢并且自測通過之后,就可以進行前后端的聯調測試了,在這一階段主要就是校驗接口的參數格式。
4). 提測: 前后端聯調測試通過之后,就可以將項目部署到測試服務器,進行自動化測試了。
1.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等),并將其轉換和打包為合適的格式供瀏覽器使用。
2. Yapi
2.1 介紹
YApi 是高效、易用、功能強大的 api 管理平臺,旨在為開發、產品、測試人員提供更優雅的接口管理服務。可以幫助開發者輕松創建、發布、維護 API,YApi 還為用戶提供了優秀的交互體驗,開發人員只需利用平臺提供的接口數據寫入工具以及簡單的點擊操作就可以實現接口的管理。
YApi讓接口開發更簡單高效,讓接口的管理更具可讀性、可維護性,讓團隊協作更合理。
源碼地址: https://github.com/YMFE/yapi
官方文檔: https://hellosean1025.github.io/yapi/
要使用YApi,項目組需要自己進行部署,在本項目中我們可以使用課程提供的平臺進行測試,域名: https://mock-java.itheima.net/
2.2 使用
2.2.1 準備
注冊賬號,登錄平臺
2.2.2 定義接口
登錄到Yapi平臺之后,我們可以創建項目,在項目下創建接口分類,在對應的分類中添加接口。
1). 創建項目
2). 添加分類
在當前項目中,有針對于員工、菜品、套餐、訂單的操作,我們在進行接口維護時,可以針對接口進行分類,如果沒有對應的分類,我們自己添加分類。
3). 添加接口
接口基本信息錄入之后,添加提交,就可以看到該接口的基本信息:
但是目前,接口中我們并未指定請求參數,響應數據等信息,我們可以進一步點擊編輯,對該接口 詳情進行編輯處理。
4). 運行接口
Yapi也提供了接口測試功能,當我們接口編輯完畢后,后端服務的代碼開發完畢,啟動服務,就可以使用Yapi進行接口測試了。
注意: 由于菜品分頁查詢接口,是需要登錄后才可以訪問的,所以在測試該接口時,需要先請求員工管理接口中的登錄接口,登錄完成后,再訪問該接口。
在Yapi平臺中,將接口文檔定義好了之后,前后端開發人員就需要根據接口文檔中關于接口的描述進行前端和后端功能的開發。
2.2.3 導出接口文檔
在Yapi平臺中我們不僅可以在線閱讀文檔,還可以將Yapi中維護的文檔直接導出來,可以導出md,json,html格式,在導出時自行選擇即可 。
而在導出的html文件或md文件中,主要描述的就是接口的基本信息, 包括: 請求路徑、請求方式、接口描述、請求參數、返回數據等信息。展示形式如下:

2.2.4 導入接口文檔
上述我們講解了接口文檔的導出,我們也可以將外部的接口文檔導入到Yapi的平臺中,這樣我們就不用一個接口一個接口的添加了。我們可以將課程資料中提供的json格式的接口文檔直接導入Yapi平臺中來。

導入過程中出現的確認彈窗,選擇"確認"。

導入成功之后,我們就可以在Yapi平臺查看到已導入的接口。
3. Swagger
3.1 介紹
官網:https://swagger.io/
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>
3.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.3 查看接口文檔
經過上面的集成配置之后,我們的項目集成Swagger及Knife4j就已經完成了,接下來我們可以重新啟動項目,訪問接口文檔,訪問鏈接為: http://localhost:8080/doc.html
我們可以看到,在所有的Controller中提供的所有的業務增刪改查的接口,全部都已經自動生成了,我們通過接口文檔可以看到請求的url、請求方式、請求參數、請求實例、響應的參數,響應的示例。 并且呢,我們也可以通過這份在線的接口文檔,對接口進行測試。
注意: 由于我們服務端的Controller中的業務增刪改查的方法,都是必須登錄之后才可以訪問的,所以,我們在測試時候,也是需要先訪問登錄接口。登錄完成之后,我們可以再訪問其他接口進行測試。
我們不僅可以在瀏覽器瀏覽生成的接口文檔,Knife4j還支持離線文檔,對接口文檔進行下載,支持下載的格式有:markdown、html、word、openApi。
3.4 常用注解
3.4.1 問題說明
在上面我們直接訪問Knife4j的接口文檔頁面,可以查看到所有的接口文檔信息,但是我們發現,這些接口文檔分類及接口描述都是Controller的類名(駝峰命名轉換而來)及方法名,而且在接口文檔中,所有的請求參數,響應數據,都沒有中文的描述,并不知道里面參數的含義,接口文檔的可讀性很差。
3.4.2 注解介紹
為了解決上述的問題,Swagger提供了很多的注解,通過這些注解,我們可以更好更清晰的描述我們的接口,包含接口的請求參數、響應數據、數據模型等。核心的注解,主要包含以下幾個:
注解 | 位置 | 說明 |
---|---|---|
@Api | 類 | 加載Controller類上,表示對類的說明 |
@ApiModel | 類(通常是實體類) | 描述實體類的作用 |
@ApiModelProperty | 屬性 | 描述實體類的屬性 |
@ApiOperation | 方法 | 說明方法的用途、作用 |
@ApiImplicitParams | 方法 | 表示一組參數說明 |
@ApiImplicitParam | 方法 | 用在@ApiImplicitParams注解中,指定一個請求參數的各個方面的屬性 |
3.4.3 注解測試
1). 實體類
可以通過 @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;
}
2). 響應實體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(); //動態數據//省略靜態方法 ....
}
3). 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);}
}
4). 重啟服務測試
我們上述通過Swagger的注解,對實體類及實體類中的屬性,以及Controller和Controller的方法進行描述,接下來,我們重新啟動服務,然后看一下自動生成的接口文檔有何變化。
在接口文檔的頁面中,我們可以看到接口的中文描述,清晰的看到每一個接口是做什么的,接口方法參數什么含義,參數是否是必填的,響應結果的參數是什么含義等,都可以清楚的描述出來。
總之,我們要想清晰的描述一個接口,就需要借助于Swagger給我們提供的注解。
4. 項目部署
在本章節,我們要做的是項目的部署,包含前端項目的部署,及后端項目的部署。
4.1 部署架構
PC端: 主要是為餐廳的員工及管理員使用的后臺管理系統,對分類、菜品、套餐信息進行維護。
移動端: 可以基于微信公眾號或小程序實現,我們課上并未實現,這部分的工作是前端開發人員需要開發的。
前端部署服務器: Nginx
后端部署服務器: Tomcat(內嵌)
4.2 環境說明
由于我們的服務器數量有限,就使用這三臺服務器,具體的軟件規劃如下:
服務器 | 軟件 | 名稱 |
---|---|---|
192.168.138.100 | Nginx(部署前端項目、配置反向代理),MySQL(主從復制的主庫) | 服務器A |
192.168.138.101 | JDK1.8、Git、Maven、jar(項目jar包基于內嵌Tomcat運行)、MySQL(主從復制的從庫) | 服務器B |
172.17.2.94 | Redis(緩存中間件) | 服務器C |
由于我們前面的課程中Nginx、MySQL的主從復制、Redis、JDK、Git、Maven都已經演示過安裝及配置了,這里我們就不再演示軟件的安裝了。
4.3 前端部署
1). 在服務器A(192.168.138.100)中安裝Nginx,將課程資料中的dist目錄上傳到Nginx的html目錄下
將整個dist目錄上傳至/usr/local/nginx/html目錄下
2). 修改Nginx配置文件nginx.conf
將nginx.conf配置文件中,將原有的監聽80, 82, 8080端口號 的虛擬主機注釋掉,引入如下的配置信息:
server {listen 80;server_name localhost;location / {root html/dist;index index.html;}location ^~ /api/ {rewrite ^/api/(.*)$ /$1 break;proxy_pass http://192.168.138.101:8080;}location = /50x.html {root html;}}
3). 通過nginx訪問前端工程
http://192.168.138.100
4.4 反向代理配置
前端工程部署完成之后,我們可以正常的訪問到系統的登錄頁面,點擊登錄按鈕,可以看到服務端發起的請求,請求信息如下:
而大家知道,在我們之前開發的工程中,是沒有/api這個前綴的,那這個時候,在不修改服務端代碼的情況下,如何處理該請求呢?
實際上,通過nginx的就可以輕松解決這個問題。
在上述我們配置的nginx.conf中,除了配置了靜態資源的加載目錄以外,我們還配置了一段反向代理的配置,配置信息如下:
location ^~ /api/ {rewrite ^/api/(.*)$ /$1 break;proxy_pass http://192.168.138.101:8080;
}
這一段配置代表,如果請求當前nginx,并且請求的路徑如果是 /api/ 開頭,將會被該location處理。而在該location中,主要配置了兩塊兒信息: rewrite(url重寫) 和 proxy_pass(反向代理)。 接下來我們就來解析一下這兩項的配置。
1). 路徑重寫rewrite
rewrite ^/api/(.*)$ /$1 break;
這里寫的是一個正則表達式,代表如果請求路徑是以 /api/
開頭,后面的請求路徑任意,此時將原始的url路徑重寫為 /$1
,這里的$1
指代的就是通配符 .* 這一塊的內容。比如:
/api/employee/login ------> ^/api/(.*)$ --------> 此時 (.*) 匹配的就是 employee/login ------> 最終重寫為/$1 : /employee/login
2). 反向代理
proxy_pass http://192.168.138.101:8080;
路徑重寫后的請求,將會轉發到后端的 http://192.168.138.101:8080 服務器中。 而這臺服務器中,就部署的是我們的后端服務。
4.5 服務端部署
1). 在服務器B(192.168.138.101)中安裝jdk、git、maven、MySQL,使用git clone命令將git遠程倉庫的代碼克隆下來
A. 確認JDK環境
B. 確認Git環境
C. 確認Maven環境
D. 將我們開發完成的代碼推送至遠程倉庫,并在服務器B中克隆下來
#創建java代碼存放目錄
mkdir -p /usr/local/javaapp#切換目錄
cd /usr/local/javaapp#克隆代碼 , 需要使用自己的遠程倉庫
git clone https://gitee.com/ChuanZhiBoKe/reggie_take_out.git
2). 將資料中提供的reggieStart.sh文件上傳到服務器B,通過chmod命令設置執行權限
3). 執行reggieStart.sh腳本文件,自動部署項目
執行完shell腳本之后,我們可以通過 ps -ef|grep java 指令,查看服務是否啟動。
4). 訪問系統測試
http://192.168.138.101/
4.6 圖片展示問題處理
在上述的測試中,我們發現菜品的圖片無法正常展示。原因是因為,在我們的配置文件中,圖片信息依然是從 D:/img 中加載的,但是在Linux服務器中,是不存在D盤的。
1). 修改文件存儲目錄
將文件存儲目錄修改為:
reggie:path: /usr/local/img/
修改完成之后,需要將變動的代碼提交到本地倉庫,并推送至遠程倉庫。
2). 執行shell腳本,進行自動化部署
3). 將本地的測試圖片文件夾img(整個文件夾)上傳到服務器B的/usr/local目錄下
4).訪問測試
http://192.168.138.101/