此文的配套視頻: 詳情
RESTFull 風格API 接口簡介
RESTFull 風格API 接口簡介
背景:
網絡應用程序,分為前端和后端兩個部分。當前的發展趨勢,就是前端設備層出不窮(手機、平板、桌面電腦、其他專用設備......)。
因此,必須有一種統一的機制,方便不同的前端設備與后端進行通信。這導致API構架的流行,甚至出現"API First"的設計思想。RESTful API是目前比較成熟的一套互聯網應用程序的API設計理論。
RESTful架構,就是目前最流行的一種互聯網軟件架構。它結構清晰、符合標准、易於理解、擴展方便,所以正得到越來越多網站的采用。
但是,到底什么是RESTful架構,並不是一個容易說清楚的問題。下面,我就談談我理解的RESTful架構。
一、起源
REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的。
Fielding是一個非常重要的人,他是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的第一任主席。所以,他的這篇論文一經發表,就引起了關注,並且立即對互聯網開發產生了深遠的影響。
他這樣介紹論文的寫作目的:
"本文研究計算機科學兩大前沿----軟件和網絡----的交叉點。長期以來,軟件研究主要關注軟件設計的分類、設計方法的演化,很少客觀地評估不同的設計選擇對系統行為的影響。而相反地,網絡研究主要關注系統之間通信行為的細節、如何改進特定通信機制的表現,常常忽視了一個事實,那就是改變應用程序的互動風格比改變互動協議,對整體表現有更大的影響。我這篇文章的寫作目的,就是想在符合架構原理的前提下,理解和評估以網絡為基礎的應用軟件的架構設計,得到一個功能強、性能好、適宜通信的架構。"
(This dissertation explores a junction on the frontiers of two research disciplines in computer science: software and networking. Software research has long been concerned with the categorization of software designs and the development of design methodologies, but has rarely been able to objectively evaluate the impact of various design choices on system behavior. Networking research, in contrast, is focused on the details of generic communication behavior between systems and improving the performance of particular communication techniques, often ignoring the fact that changing the interaction style of an application can have more impact on performance than the communication protocols used for that interaction. My work is motivated by the desire to understand and evaluate the architectural design of network-based application software through principled use of architectural constraints, thereby obtaining the functional, performance, and social properties desired of an architecture. )
二、REST名稱的起源
Fielding將他對互聯網軟件的架構原則,定名為REST,即Representational State Transfer的縮寫。我對這個詞組的翻譯是"表現層狀態轉化"。
如果一個架構符合REST原則,就稱它為RESTful架構。
要理解RESTful架構,最好的方法就是去理解Representational State Transfer這個詞組到底是什么意思,它的每一個詞代表了什么涵義。如果你把這個名稱搞懂了,也就不難體會REST是一種什么樣的設計。
三、什么是資源(Resources)
REST的名稱"表現層狀態轉化"中,省略了主語。"表現層"其實指的是"資源"(Resources)的"表現層"。
所謂"資源",就是網絡上的一個實體,或者說是網絡上的一個具體信息。它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的實在。你可以用一個URI(統一資源定位符)指向它,每種資源對應一個特定的URI。要獲取這個資源,訪問它的URI就可以,因此URI就成了每一個資源的地址或獨一無二的識別符。
所謂"上網",就是與互聯網上一系列的"資源"互動,調用它的URI。
四、表現層(Representation)
"資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表現層"(Representation)。
比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以采用二進制格式;圖片可以用JPG格式表現,也可以用PNG格式表現。
URI只代表資源的實體,不代表它的形式。嚴格地說,有些網址最后的".html"后綴名是不必要的,因為這個后綴名表示格式,屬於"表現層"范疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段才是對"表現層"的描述。
五、狀態轉化(State Transfer)
訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,勢必涉及到數據和狀態的變化。
互聯網通信協議HTTP協議,是一個無狀態協議。這意味着,所有的狀態都保存在服務器端。因此,如果客戶端想要操作服務器,必須通過某種手段,讓服務器端發生"狀態轉化"(State Transfer)。而這種轉化是建立在表現層之上的,所以就是"表現層狀態轉化"。
客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源。
六、綜述
綜合上面的解釋,我們總結一下什么是RESTful架構:
(1)每一個URI代表一種資源;
(2)客戶端和服務器之間,傳遞這種資源的某種表現層;
(3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"。
七、誤區
RESTful架構有一些典型的設計誤區。
最常見的一種設計錯誤,就是URI包含動詞。因為"資源"表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。
舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然后用GET方法表示show。
如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:
POST /accounts/1/transfer/500/to/2
正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
另一個設計誤區,就是在URI中加入版本號:
http://www.example.com/app/1.0/foo
因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該采用同一個URI。版本號可以在HTTP請求頭信息的Accept字段中進行區分(參見Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
RESTfull風格簡介
REST(Representational State Transfer)是Roy Fielding提出的一個描述互聯系統架構風格的名詞。REST定義了一組體系架構原則,可以根據這些原則設計Web服務。
RESTfull風格使用不同的HTTP方法來進行不同的操作,並且使用HTTP狀態碼來表示不同的結果。例如HTTP的GET方法用來獲取資源,HTTP的DELETE方法用來刪除資源。
在HTTP協議中,大致的請求方法如下:
(1)GET:通過請求URI得到資源。
(2)POST:用於添加新的資源。
(3)PUT:用於修改某個資源,若不存在則添加。
(4)DELETE:刪除某個資源。
(5)OPTIONS:詢問可以執行哪些方法。
(6)HEAD:類似於GET,但是不返回body信息,用於檢查資源是否存在,以及得到資源的元數據。
(7)CONNECT:用於代理進行傳輸,如使用SSL。
(8)TRACE:用於遠程診斷服務器。
在RESTfull風格中,資源的CRUD操作包括創建、讀取、更新和刪除(Create、Read、Update、Delete),與HTTP方法之間有一個簡單的對應關系:
(1)若要在服務器上創建資源,則應該使用POST方法。
(2)若要在服務器上檢索某個資源,則應該使用GET方法。
(3)若要在服務器上更新某個資源,則應該使用PUT方法。
(4)若要在服務器上刪除某個資源,則應該使用DELETE方法。
Swagger是什么?
導語:
相信無論是前端還是后端開發,都或多或少地被接口文檔折磨過。前端經常抱怨后端給的接口文檔與實際情況不一致。后端又覺得編寫及維護接口文檔會耗費不少精力,經常來不及更新。其實無論是前端調用后端,還是后端調用后端,都期望有一個好的接口文檔。但是這個接口文檔對於程序員來說,就跟注釋一樣,經常會抱怨別人寫的代碼沒有寫注釋,然而自己寫起代碼起來,最討厭的,也是寫注釋。所以僅僅只通過強制來規范大家是不夠的,隨着時間推移,版本迭代,接口文檔往往很容易就跟不上代碼了。
發現了痛點就要去找解決方案。解決方案用的人多了,就成了標准的規范,這就是Swagger的由來。
1.SWAGGER官網主要提供了幾種開源工具
現在SWAGGER官網主要提供了幾種開源工具,提供相應的功能。可以通過配置甚至是修改源碼以達到你想要的效果。
Swagger Codegen: 通過Codegen 可以將描述文件生成html格式和cwiki形式的接口文檔,同時也能生成多鍾語言的服務端和客戶端的代碼。支持通過jar包,docker,node等方式在本地化執行生成。也可以在后面的Swagger Editor中在線生成。
Swagger UI:提供了一個可視化的UI頁面展示描述文件。接口的調用方、測試、項目經理等都可以在該頁面中對相關接口進行查閱和做一些簡單的接口請求。該項目支持在線導入描述文件和本地部署UI項目。
Swagger Editor: 類似於markendown編輯器的編輯Swagger描述文件的編輯器,該編輯支持實時預覽描述文件的更新效果。也提供了在線編輯器和本地部署編輯器兩種方式。
Swagger Inspector: 感覺和postman差不多,是一個可以對接口進行測試的在線版的postman。比在Swagger UI里面做接口請求,會返回更多的信息,也會保存你請求的實際請求參數等數據。
Swagger Hub:集成了上面所有項目的各個功能,你可以以項目和版本為單位,將你的描述文件上傳到Swagger Hub中。在Swagger Hub中可以完成上面項目的所有工作,需要注冊賬號,分免費版和收費版。
2.基於Spring框架的Springfox
通過Swagger這套規范,你只需要按照它的規范去定義接口及接口相關的信息。再通過Swagger衍生出來的一系列項目和工具,就可以做到生成各種格式的接口文檔,生成多種語言的客戶端和服務端的代碼,以及在線接口調試頁面等等。這樣,如果按照新的開發模式,在開發新版本或者迭代版本的時候,只需要更新Swagger描述文件,就可以自動生成接口文檔和客戶端服務端代碼,做到調用端代碼、服務端代碼以及接口文檔的一致性。
使用原始的Swagger,需要編寫這個yml或json格式的描述文件,本身也是有一定負擔的工作。所以作為Java屆服務端的大一統框架Spring,迅速將Swagger規范納入自身的標准,建立了Spring-swagger項目,后面改成了現在的Springfox。
通過在項目中引入Springfox,可以掃描相關的代碼,生成該描述文件,進而生成與代碼一致的接口文檔和客戶端代碼。這種通過代碼生成接口文檔的形式,在后面需求持續迭代的項目中,顯得尤為重要和高效。
在springcloud項目引入swagger
在pom.xml文件中添加maven依賴
<!-- Swagger核心包 start -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Swagger核心包 end -->
編寫專用的配置類
package com.crazymaker.springcloud.standard.config;
import com.crazymaker.springcloud.common.constants.SessionConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.LinkedList;
/**
* swagger配置
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig
{
@Bean
public Docket templateApi()
{
ParameterBuilder tokenPar = new ParameterBuilder();
tokenPar.name(SessionConstants.AUTHORIZATION_HEAD).description("token")
.modelRef(new ModelRef("string")).parameterType("header").required(false).build();
// ParameterBuilder adminTokenPar = new ParameterBuilder();
// adminTokenPar.name(SessionConstants.ADMIN_AUTHORIZATION_HEAD).description("管理控制台 token 令牌" )
// .modelRef(new ModelRef("string" )).parameterType("header" ).required(false).build();
ParameterBuilder user = new ParameterBuilder();
user.name(SessionConstants.USER_IDENTIFIER).description("user-id")
.modelRef(new ModelRef("string")).parameterType("header").required(false).build();
LinkedList<Parameter> list = new LinkedList<>();
list.add(tokenPar.build());
// list.add(adminTokenPar.build());
list.add(user.build());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.useDefaultResponseMessages(false)
.select()
//控制暴露出去的路徑下的實例
//如果某個接口不想暴露,可以使用以下注解
//@ApiIgnore 這樣,該接口就不會暴露在 swagger2 的頁面下
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.build().globalOperationParameters(list);
}
//構建 api文檔的詳細信息函數
private ApiInfo apiInfo()
{
return new ApiInfoBuilder()
//頁面標題
.title("瘋狂創客圈 springcloud + Nginx 高並發核心編程")
//描述
.description("Zuul+Swagger2 構建 RESTful APIs")
//條款地址
.termsOfServiceUrl("https://www.cnblogs.com/crazymakercircle/")
.contact(new Contact("瘋狂創客圈", "https://www.cnblogs.com/crazymakercircle/", ""))
.version("1.0")
.build();
}
}
通過WebMvcConfigurer加入Jar包中的靜態資源
package com.crazymaker.springcloud.standard.config;
import com.crazymaker.springcloud.common.context.SessionHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* WebConfiguration
*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer
{
// 配置靜態資源的,比如html,js,css,等等
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
// 解決swagger無法訪問
registry.addResourceHandler("swagger-ui.html" )
.addResourceLocations("classpath:/META-INF/resources/" );
// 解決swagger的js文件無法訪問
registry.addResourceHandler("/webjars/**" )
.addResourceLocations("classpath:/META-INF/resources/webjars/" );
}
}
在Rest接口上加入Swagger注解
package com.crazymaker.springcloud.user.info.controller;
import com.alibaba.fastjson.JSONObject;
import com.crazymaker.springcloud.common.dto.UserDTO;
import com.crazymaker.springcloud.common.result.RestOut;
import com.crazymaker.springcloud.user.info.service.impl.FrontUserEndSessionServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Api(value = "用戶信息、基礎學習DEMO", tags = {"用戶信息、基礎學習DEMO"})
@RestController
@RequestMapping("/api/user" )
public class UserController
{
@Resource
private FrontUserEndSessionServiceImpl userService;
/**
* 注入全局的加密器
*/
@Resource
PasswordEncoder passwordEncoder;
@ApiOperation(value = "接口的功能介紹",notes = "提示接口使用者注意事項",httpMethod = "GET")
@ApiImplicitParam(dataType = "string",name = "name",value = "姓名",required = true)
@RequestMapping(value = "/")
public String hello(String name) {
return "hello "+ name;
}
@GetMapping("/detail/v1" )
@ApiOperation(value = "獲取用戶信息" )
public RestOut<UserDTO> getUser(@RequestParam(value = "userId", required = true) Long userId)
{
UserDTO dto = userService.getUser(userId);
if (null == dto)
{
return RestOut.error("沒有找到用戶" );
}
return RestOut.success(dto).setRespMsg("操作成功" );
}
@GetMapping("/passwordEncoder/v1" )
@ApiOperation(value = "密碼加密" )
public RestOut<String> passwordEncoder(
@RequestParam(value = "raw", required = true) String raw)
{
// passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encode = passwordEncoder.encode(raw);
return RestOut.success(encode);
}
}
測試:
此時啟動項目,輸入http://localhost:7702/uaa-provider/swagger-ui.html#/,能夠看到如下頁面,說明已經配置成功了: