您好,登錄后才能下訂單哦!
引言
基本上當下的應用都會分為前端與后端,當然這種前端定義不在限于桌面瀏覽器、手機、APP等設備。一個良好的后端會通過一套所有前端都通用的 RESTful API 序列接口作為前后端之間的通信。
這其中對于身份認證都不可能再依賴傳統的Session或Cookie;轉而使用諸如OAuth3、JWT等這種更適合API接口的認證方式。當然本文并不討論如何去構建它們。
一、API 設計
首先雖然并不會討論身份認證的技術,但不管是OAuth3還是JWT本質上身份認證都全靠一個 Token 來維持;因此,下面統一以 token 來表示身份認證所需要的值。
一套合理的API規則,會讓前端編碼更優雅。因此,希望在編寫Angular之前,能與后端相互達成一種“協議”也很有必要。可以嘗試從以下幾點進行考慮。
版本號
可以在URL(例:https://demo.com/v1/)或Header(例:headers: { version: 'v1' }
)中體現,相比較我更喜歡前者的直接。
業務節點
以一個節點來表示某個業務,比如:
動作
由HTTP動詞來表示:
統一響應
這一點非常重要,特別是當我們新建一個商品時,商品的屬性非常多,但如果我們缺少某個屬性時。可以使用這樣的一種統一的響應格式:
{ "code": 100, // 0 表示成功 "errors": { // 錯誤明細 "title": "商品名稱必填" } }
其中 code
不管成功與否都會有該屬性。
狀態碼
后端響應一個請求是包括狀態碼和響應內容,而每一種狀態碼又包含著不同的含義。
二、如何訪問Http?
首先,需要導入 HttpClientModule 模塊。
import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ HttpClientModule ] })
然后,在組件類注入 HttpClient。
export class IndexComponent { constructor(private http: HttpClient) { } }
最后,請求點擊某個按鈕發送一次GET請求。
user: Observable<User>; getUser() { this.user = this.http.get<User>('/assets/data/user.json'); }
打印結果:
{{ user | async | json }}
三個簡單的步驟,就是一個完整的HTTP請求步驟。
然后,現實與實際是有一些距離,比如說身份認證、錯誤處理、狀態碼處理等問題,在上面并無任何體現。
可,上面已經足夠優雅,要讓我破壞這種優雅那么此文就變得無意義了!
因此……
三、攔截器
1、HttpInterceptor 接口
正如其名,我們在不改變上面應用層面的代碼下,允許我們把身份認證、錯誤處理、狀態碼處理問題給解決了!
寫一個攔截器也是非常的優雅,只需要實現 HttpInterceptor
接口即可,而且只有一個 intercept
方法。
@Injectable() export class JWTInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { // doing } }
intercept
方法有兩個參數,它幾乎所當下流行的中間件概念一般,req
表示當前請求數據(包括:url、參數、header等),next
表示調用下一個“中間件”。
2、身份認證
req
有一個 clone
方法,允許對當前的請求參數進行克隆并且這一過程會自行根據一些參數推導,不管如何用它來產生一個新的請求數據,并在這個新數據中加入我們期望的數據,比如:token。
const jwtReq = req.clone({ headers: req.headers.set('token', 'xxxxxxxxxxxxxxxxxxxxx') });
當然,你可以再折騰更多請求前的一些配置。
最后,把新請求參數傳遞給下一個“中間件”。
return next.handle(jwtReq);
等等,都 return
了,說好的狀態碼、異常處理呢?
3、異常處理
仔細再瞧 next.handle
返回的是一個 Observable
類型。看到 Observable
我們會想到什么?mergeMap、catch
等一大堆東西。
因此,我們可以利用這些操作符來改變響應的值。
mergeMap
請求過程中會會有一些過程狀態,比如請求前、上傳進度條、請求結束等,Angular在每一次這類動作中都會觸次 next。因此,我們只需要在返回 Observable
對象加上 mergeMap
來觀察這些值的變更,這樣有非常大的自由空間想象。
return next.handle(jwtReq).mergeMap((event: any) => { if (event instanceof HttpResponse && event.body.code !== 0) { return Observable.create(observer => observer.error(event)); } return Observable.create(observer => observer.next(event)); })
只會在請求成功才會返回一個 HttpResponse 類型,因此,我們可以大膽判斷是否來源于 HttpResponse 來表示HTTP請求已經成功。
這里,統一對業務層級的錯誤 code !== 0 產生一個錯誤信號的 Observable。反之,產生一個成功的信息。
catch
catch 來捕獲非200以外的其他狀態碼的錯誤,比如:401。同時,前面的 mergeMap 所產生的錯誤信號,也會在這里被捕獲到。
.catch((res: HttpResponse<any>) => { switch (res.status) { case 401: // 權限處理 location.href = ''; // 重新登錄 break; case 200: // 業務層級錯誤處理 alert('業務錯誤:' + res.body.code); break; case 404: alert('API不存在'); break; } return Observable.throw(res); })
4、完整代碼
至此,攔截器所要包括的身份認證token、統一響應處理、異常處理都解決了。
@Injectable() export class JWTInterceptor implements HttpInterceptor { constructor(private notifySrv: NotifyService) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { console.log('interceptor') const jwtReq = req.clone({ headers: req.headers.set('token', 'asdf') }); return next .handle(jwtReq) .mergeMap((event: any) => { if (event instanceof HttpResponse && event.body.code !== 0) { return Observable.create(observer => observer.error(event)); } return Observable.create(observer => observer.next(event)); }) .catch((res: HttpResponse<any>) => { switch (res.status) { case 401: // 權限處理 location.href = ''; // 重新登錄 break; case 200: // 業務層級錯誤處理 this.notifySrv.error('業務錯誤', `錯誤代碼為:${res.body.code}`); break; case 404: this.notifySrv.error('404', `API不存在`); break; } // 以錯誤的形式結束本次請求 return Observable.throw(res); }) } }
發現沒有,我們并沒有加一大堆并不認識的事物,單純都只是對數據流的各種操作而已。
NotifyService 是一個無須依賴HTML模板、極簡Angular通知組件。
5、注冊攔截器
攔截器構建后,還需要將其注冊至 HTTP_INTERCEPTORS 標識符中。
import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ HttpClientModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true} ] })
以上是攔截器的所有內容,在不改變原有的代碼的情況下,我們只是利用短短幾行的代碼實現了身份認證所需要的TOKEN、業務級統一響應處理、錯誤處理動作。
四、async 管道
一個 Observable
必須被訂閱以后才會真正的開始動作,前面在HTML模板中我們利用了 async
管道簡化了這種訂閱過程。
{{ user | async | json }}
它相當于:
let user: User; get() { this.http.get<User>('/assets/data/user.json').subscribe(res => { this.user = res; }); } {{ user | json }}
然而,async 這種簡化,并不代表失去某些自由度,比如說當在獲取數據過程中顯示【加載中……】,怎么辦?
<div *ngIf="user | async as user; else loading"> {{ user | json }} </div> <ng-template #loading>加載中……</ng-template>
恩!
五、結論
Angular在HTTP請求過程中使用 Observable 異步數據流控制數據,而利用 rxjs 提供的大量操作符,來改變最終值;從而獲得在應用層面最優雅的編碼風格。
當我們說到優雅使用HTTP這件事時,易測試是一個非常重要,因此,我建議將HTTP從組件類中剝離并將所有請求放到 Service 當中。當對某個組件編寫測試代碼時,如果受到HTTP請求結果的限制會讓測試更困難。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。