重拾后端之Spring Boot(一):REST API的搭建可以這樣簡單
重拾后端之Spring Boot(二):MongoDb的無縫集成
重拾后端之Spring Boot(三):找回熟悉的Controller,Service
重拾后端之Spring Boot(四):使用 JWT 和 Spring Security 保護(hù) REST API
重拾后端之Spring Boot(五):跨域雾棺、自定義查詢及分頁
重拾后端之Spring Boot(六):熱加載白对、容器和多項(xiàng)目
跨域
前面我們初步做出了一個(gè)可以實(shí)現(xiàn)受保護(hù)的 REST API怔匣,但是我們沒有涉及一個(gè)前端領(lǐng)域很重要的問題击胜,那就是跨域請求( cross-origin HTTP request )亏狰。先來回顧一些背景知識:
跨域請求
定義:當(dāng)我們從本身站點(diǎn)請求不同域名或端口的服務(wù)所提供的資源時(shí),就會發(fā)起跨域請求偶摔。
例如最常見的我們很多的 css 樣式文件是會鏈接到某個(gè)公共 CDN 服務(wù)器上暇唾,而不是在本身的服務(wù)器上,這其實(shí)就是典型的一個(gè)跨域請求辰斋。但瀏覽器由于安全原因限制了在腳本( script )中發(fā)起的跨域 HTTP 請求策州。也就是說 XMLHttpRequest
和 Fetch
等是遵循“同源規(guī)則”的,即只能訪問自己服務(wù)器的指定端口的資源(同一服務(wù)器不同端口也會視為跨域)宫仗。但這種限制在今天够挂,我們的應(yīng)用需要訪問多種外部 API 或 資源的時(shí)候就不能滿足開發(fā)者的需求了,因此就產(chǎn)生了若干對于跨域的解決方案藕夫,JSONP 是其中一種孽糖,但在今天來看主流的更徹底的解決方案是 CORS ( Cross-Origin Resource Sharing )。
跨域資源共享 ( CORS )
這種機(jī)制將跨域的訪問控制權(quán)交給服務(wù)器汁胆,這樣可以保證安全的跨域數(shù)據(jù)傳輸∷笮眨現(xiàn)代瀏覽器一般會將 CORS 的支持封裝在 HTTP API 之中( 比如 XMLHttpRequest
和 Fetch
)霜幼,這樣可以有效控制使用跨域請求的風(fēng)險(xiǎn)嫩码,因?yàn)槟憷@不過去,總得要使用 API 吧罪既。
概括來說铸题,這個(gè)機(jī)制是增加一系列的 HTTP 頭來讓服務(wù)器可以描述哪些源是允許使用瀏覽器來訪問資源的。而且對于簡單的請求和復(fù)雜請求琢感,處理機(jī)制是不一樣的丢间。
簡單請求僅允許三個(gè) HTTP 方法:GET,POST 以及 HEAD驹针,另外只能支持若干 header 參數(shù):Accept 烘挫, Accept-Language , Content-Language , Content-Type (值只能是 application/x-www-form-urlencoded
饮六、multipart/form-data
和 text/plain
)其垄, DPR , Downlink 卤橄, Save-Data 绿满, Viewport-Width 和 Width。
對于簡單請求來說窟扑,比如下面這樣一個(gè)簡單的GET請求:從 http://me.domain
發(fā)起到 http://another.domain/data/blablabla
的資源請求
GET /data/blablabla/ HTTP/1.1
// 請求的域名
Host: another.domain
...//省略其它部分喇颁,重點(diǎn)是下面這句,說明了發(fā)起請求者的來源
Origin: http://me.domain
應(yīng)用了 CORS 的對方服務(wù)器返回的響應(yīng)應(yīng)該像下面這個(gè)樣子嚎货,當(dāng)然這里 Access-Control-Allow-Origin: *
中的 *
表示任何網(wǎng)站都可以訪問該資源橘霎,如果要限制只能從 me.domain
訪問,那么需要改成 Access-Control-Allow-Origin: http://me.domain
HTTP/1.1 200 OK
...//省略其它部分
Access-Control-Allow-Origin: *
...//省略其它部分
Content-Type: application/json
那么對于復(fù)雜請求怎么辦呢殖属?這需要一次預(yù)檢請求和一次實(shí)際的請求茎毁,也就是說需要兩次和對方服務(wù)器的請求/響應(yīng)。預(yù)檢請求是以 OPTION 方法進(jìn)行的忱辅,因?yàn)?OPTION 方法不會改變?nèi)魏钨Y源七蜘,所以這個(gè)預(yù)檢請求是安全的,它的職責(zé)在于發(fā)送實(shí)際請求將會使用的 HTTP 方法以及將要發(fā)送的 HEADER 中將攜帶哪些內(nèi)容墙懂,這樣對方服務(wù)器可以根據(jù)預(yù)檢請求的信息決定是否接受橡卤。
// 預(yù)檢請求
OPTIONS /resources/post/ HTTP/1.1
Host: another.domain
...// 省略其它部分
Origin: http://me.domain
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
服務(wù)器對預(yù)檢請求的響應(yīng)如下:
HTTP/1.1 200 OK
// 省略其它部分
Access-Control-Allow-Origin: http://me.domain
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
// 省略其它部分
Content-Type: text/plain
接下來的正式請求就和上面的簡單請求差不多了,就不贅述了损搬。
在 Spring Boot 中如何啟用 CORS
啰嗦了這么多碧库,終于進(jìn)入正題,但我一直覺得不能光知其然而不知其所以然巧勤,所以各位就忍了吧嵌灰。加入 CORS 的支持在 Spring Boot 中簡單到不忍直視,添加一個(gè)配置類即可:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 設(shè)置你要允許的網(wǎng)站域名颅悉,如果全允許則設(shè)為 *
config.addAllowedOrigin("http://localhost:4200");
// 如果要限制 HEADER 或 METHOD 請自行更改
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
// 這個(gè)順序很重要哦沽瞭,為避免麻煩請?jiān)O(shè)置在最前
bean.setOrder(0);
return bean;
}
}
如果我們使用 POSTMAN 訪問一下 API,會發(fā)現(xiàn)得到一個(gè) Invalid CORS request
的響應(yīng)剩瓶,因?yàn)槲覀兊?API 只授權(quán)給了 localhost:4200
![用 POSTMAN 無法得到請求結(jié)果](http://static.zybuluo.com/wpcfan/5f43go66ch3ayhog5tmoo2a3/image_1be65oklk1h66dd213dvc6j1kt89.png)
當(dāng)然驹溃,如果我們使用 CURL 的話是可以訪問的,這是因?yàn)?CURL 不是瀏覽器延曙。
嗯嗯豌鹤,這樣就結(jié)束了,這節(jié)好水枝缔,但就是這么簡單啊布疙。
自定義查詢
我們回過頭來再來看看數(shù)據(jù)查詢,大部分情況下 Spring Data 提供的按方法名進(jìn)行查詢的方式足夠簡單也足夠強(qiáng)大,但總歸還是有很多局限灵临。為了說明這個(gè)問題拣挪,也順便為我的新 Angular 項(xiàng)目打造一個(gè) API,我們把 API 的需求改一下【阒睿現(xiàn)在我們要做的不是一個(gè)簡單的 Todo 了菠劝,而是類似 Teambition 或 Worktile 那樣的企業(yè)協(xié)作平臺,當(dāng)然我們不會做的那么復(fù)雜睁搭,是個(gè)簡化版本赶诊。那么這時(shí)我們的對象模型是這樣的:
![主要的領(lǐng)域?qū)ο?><div id=](http://static.zybuluo.com/wpcfan/jk771ouciv5okk7babornbkv/image_1be7d1sevbdc1knd1gmj1q8v1mnbm.png)
我們首先看一下 Project 這個(gè)對象,我們來構(gòu)建它的 API园骆,增刪改沒啥可講舔痪。但是查詢上會有點(diǎn)不一樣,首先我們并不希望把所有的 Project 都查出來锌唾,而是該用戶參與的項(xiàng)目要提供一個(gè) API 給客戶端锄码。
Project 和 User 按關(guān)系型數(shù)據(jù)庫的看法是個(gè)多對多的關(guān)系,在MongoDB中這其實(shí)有多種做法晌涕,可以在 User 對象中設(shè)置一個(gè) Project 的集合屬性滋捶,也可以在 Project 中設(shè)置 User 的集合屬性 (在我們的例子里是 memebers ),還可以兩者結(jié)合余黎,就是在 User 和 Projet 中互相有對方的集合屬性重窟。具體采用哪種需要看業(yè)務(wù)場景和性能需求,比如如果任何一個(gè)項(xiàng)目的成員數(shù)如果不會很大惧财,那么在 Project 中嵌入 User 集合就比較劃算巡扇;如果項(xiàng)目的成員較多,但一個(gè)成員歸屬的項(xiàng)目不會很多的情況下垮衷,就可以把 Project 的集合嵌入到 User 中厅翔。我們這里采用了第一種做法。
那么接下來我們來寫該用戶參與的項(xiàng)目的查詢搀突。當(dāng)然我們可以按照 Spring Data 強(qiáng)大的按方法名稱來生成對應(yīng)查詢的方式來做:尋找 members 屬性中包含該用戶的集合
Set<Project> findByMembersContaining(User user)
看起來還可以刀闷,挺簡單,但是如果我們說再加兩個(gè)條件要篩選 project.enabled == true
(我們不會物理刪除項(xiàng)目描姚,而是設(shè)置其標(biāo)志位 enabled涩赢,所以這就是篩選未刪除的項(xiàng)目) 和 project.archived == false
(項(xiàng)目完結(jié)后需要?dú)w檔戈次,這就是篩選未歸檔的)轩勘。這兩個(gè)條件一加上,好家伙怯邪,我們的方法名變成了下面這個(gè)樣子绊寻,不忍直視啊:
Set<Project> findByMembersContainingAndEnabledAndArchived(User user, boolean enabled, boolean archived)
當(dāng)然好用還是好用了,但是這個(gè)方法名也太長了澄步,好在 Spring Data 中提供很多種方式自定義查詢冰蘑,我們介紹一種相對簡單的:利用 @Query
注解來進(jìn)行查詢,方法名字就沒有那么雷人了:
@Query("{'owner.$id': ?#{[0]}, 'enabled': ?#{[1]}, 'archived': ?#{[2]}}")
Set<Project> findRelated(User user, boolean enabled, boolean archived)
這個(gè)注解中的內(nèi)容是一個(gè) JSON 對象村缸,就和我們在 MongoDB 的控制臺查詢的find()中的內(nèi)容是一樣的祠肥,只不過將雙引號換成單引號,將需要變量用 [0]
梯皿、[1]
和 [2]
的形式表示第一仇箱、第二和第三個(gè)參數(shù)。那么 ?#{}
是表示里面的內(nèi)容是個(gè) SpEL ( Spring 的表達(dá)式語言) 表達(dá)式东羹。
所以實(shí)踐中剂桥,我們可以在 MongoDB 的控制臺去實(shí)驗(yàn)語句是否好用,然后在 Spring 中編寫表達(dá)式属提。
db.project.find(
{
"owner.$id": ObjectId("58f5a904edc76ab0e033cfc3"),
"enabled": true,
"archived": false
})
![在MongoDB的console查詢](http://static.zybuluo.com/wpcfan/k42aa0yiuthuauym39gkwvn8/image_1be7i1npstg6mu21o2110cq2eu13.png)
數(shù)據(jù)的分頁
很多時(shí)候我們希望 API 返回的數(shù)據(jù)是可以分頁的权逗,這個(gè)分頁問題在 Spring Boot 有怎樣便捷的方法呢?我們是否需要再定義一堆什么 pageSize冤议,pageCount斟薇,start, off 的參數(shù)呢恕酸?答案是完全沒必要奔垦,分頁這個(gè)事情對于 Spring Boot 來說很簡單,只需改變各層級原有方法中返回的 List 或 Set 對象為 Page 對象尸疆,傳入?yún)?shù)多一個(gè) Pageable 類型的參數(shù)即可椿猎。
// Controller
@RequestMapping(method = RequestMethod.GET)
public Page<Project> findRelated(
@RequestHeader(value = "userId") String userId,
@RequestParam(value = "enabled", defaultValue = "true", required = false) boolean enabled,
@RequestParam(value = "archived", defaultValue = "false", required = false) boolean archived,
Pageable pageable) {
return service.findRelated(userId, enabled, archived, pageable);
}
// Repository
@Query("{'owner.$id': ?#{[0]}, 'enabled': ?#{[1]}, 'archived': ?#{[2]}}")
Page<Project> findRelated(ObjectId userId, boolean enabled, boolean archived, Pageable pageable);
現(xiàn)在呢,我們就可以這樣使用了 GET http://localhost:8090/projects/?page=0&size=3
表示取每頁三個(gè)數(shù)據(jù)取第一頁寿弱。
本章代碼:https://github.com/wpcfan/spring-boot-tut/tree/chap05
重拾后端之Spring Boot(一):REST API的搭建可以這樣簡單
重拾后端之Spring Boot(二):MongoDb的無縫集成
重拾后端之Spring Boot(三):找回熟悉的Controller犯眠,Service
重拾后端之Spring Boot(四):使用 JWT 和 Spring Security 保護(hù) REST API
重拾后端之Spring Boot(五):跨域、自定義查詢及分頁
有問題的童鞋可以加入我的小密圈討論: http://t.xiaomiquan.com/jayRnaQ (該鏈接7天內(nèi)(5月14日前)有效)
另外症革,我的 《Angular 從零到一》出版了筐咧,下面是書籍的內(nèi)容簡介:
本書系統(tǒng)介紹Angular的基礎(chǔ)知識與開發(fā)技巧,可幫助前端開發(fā)者快速入門噪矛。共有9章量蕊,第1章介紹Angular的基本概念,第2~7章從零開始搭建一個(gè)待辦事項(xiàng)應(yīng)用艇挨,然后逐步增加功能残炮,如增加登錄驗(yàn)證、將應(yīng)用模塊化缩滨、多用戶版本的實(shí)現(xiàn)势就、使用第三方樣式庫泉瞻、動態(tài)效果制作等。第8章介紹響應(yīng)式編程的概念和Rx在Angular中的應(yīng)用苞冯。第9章介紹在React中非常流行的Redux狀態(tài)管理機(jī)制袖牙,這種機(jī)制的引入可以讓代碼和邏輯隔離得更好,在團(tuán)隊(duì)工作中強(qiáng)烈建議采用這種方案舅锄。本書不僅講解Angular的基本概念和最佳實(shí)踐鞭达,而且分享了作者解決問題的過程和邏輯,講解細(xì)膩皇忿,風(fēng)趣幽默碉怔,適合有面向?qū)ο缶幊袒A(chǔ)的讀者閱讀。
歡迎大家圍觀禁添、訂購撮胧、提出寶貴意見。
京東鏈接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0