# 對(duì)接后臺(tái)
本節(jié)我們嘗試完成個(gè)人中心后臺(tái)的對(duì)接。
## 接口信息
我們?yōu)榇蠹姨峁┝巳缦陆涌趤?lái)獲取當(dāng)前登錄用戶的基本信息:
```bash
GET /teacher/me
```
#### 參數(shù) Parameters
| type | name | Description | Schema |
| ---------- | ------------------------------- | ------------ | ---------------- |
| **Header** | **認(rèn)證x-auth-token** *requried* | 獲取登錄信息 | 當(dāng)前登錄教師實(shí)體 |
#### 返回值 Responses
| HTTP Code | Description | Schema |
| --------- | ------------------------------------------------------ | ------ |
| **200** | 認(rèn)證x-auth-token合法 | Ok |
| **401** | 未攜帶x-auth-token信息,未攜帶的x-auth-token信息不正確 | 未授權(quán) |
接口中提到了x-auth-token信息,我們暫時(shí)先不考慮它,看看按以前請(qǐng)求思想會(huì)發(fā)生什么。
## HttpClient
```typescript
+++ b/first-app/src/app/personal-center/personal-center.component.ts
@@ -1,5 +1,6 @@
import {Component, OnInit} from '@angular/core';
import {Teacher} from '../entity/teacher';
+import {HttpClient} from '@angular/common/http';
@Component({
selector: 'app-personal-center',
@@ -9,18 +10,14 @@ import {Teacher} from '../entity/teacher';
export class PersonalCenterComponent implements OnInit {
me = {} as Teacher;
- constructor() {
+ constructor(private httpClient: HttpClient) {
}
ngOnInit(): void {
- this.me = new Teacher(
- 1,
- 'zhangsan@yunzhi.club',
- '張三',
- 'password',
- null as unknown as boolean,
- 'zhangsan'
- );
+ const url = 'http://angular.api.codedemo.club:81/teacher/me';
+ this.httpClient.get<Teacher>(url)
+ .subscribe(teacher => this.me = teacher,
+ error => console.log('請(qǐng)求發(fā)生錯(cuò)誤', error));
}
}
```
單元測(cè)試中的動(dòng)態(tài)測(cè)試模塊:
```typescript
+++ b/first-app/src/app/personal-center/personal-center.component.spec.ts
@@ -2,6 +2,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
import {PersonalCenterComponent} from './personal-center.component';
import {SexPipe} from './sex.pipe';
+import {HttpClientModule} from '@angular/common/http';
describe('PersonalCenterComponent', () => {
let component: PersonalCenterComponent;
@@ -9,7 +10,8 @@ describe('PersonalCenterComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [PersonalCenterComponent, SexPipe]
+ declarations: [PersonalCenterComponent, SexPipe],
+ imports: [HttpClientModule]
})
.compileComponents();
});
@@ -20,7 +22,7 @@ describe('PersonalCenterComponent', () => {
fixture.detectChanges();
});
- it('should create', () => {
+ fit('should create', () => {
expect(component).toBeTruthy();
fixture.autoDetectChanges();
});
```
測(cè)試結(jié)果:

## 登錄
與前面章節(jié)中對(duì)教師的操作不同,后臺(tái)獲取當(dāng)前登錄教師的接口必須在**用戶登錄成功**后,才能夠獲取到。也就是說(shuō)只有登錄的用戶才有資格訪問(wèn)該接口。
那么如果在請(qǐng)求該接口前先完成用戶的登錄,是否可以請(qǐng)求成功呢?心動(dòng)不如行動(dòng),我們參考登錄組件簡(jiǎn)單寫(xiě)下代碼:
```typescript
+++ b/first-app/src/app/personal-center/personal-center.component.ts
@@ -1,5 +1,6 @@
import {Component, OnInit} from '@angular/core';
import {Teacher} from '../entity/teacher';
+import {HttpClient, HttpHeaders} from '@angular/common/http';
@Component({
selector: 'app-personal-center',
@@ -9,18 +10,29 @@ import {Teacher} from '../entity/teacher';
export class PersonalCenterComponent implements OnInit {
me = {} as Teacher;
- constructor() {
+ constructor(private httpClient: HttpClient) {
}
ngOnInit(): void {
- this.me = new Teacher(
- 1,
- 'zhangsan@yunzhi.club',
- '張三',
- 'password',
- null as unknown as boolean,
- 'zhangsan'
- );
- }
+ const authString = 'zhangsan:codedemo.club';
+ const authToken = btoa(authString);
+ let httpHeaders = new HttpHeaders();
+ httpHeaders = httpHeaders.append('Authorization', 'Basic ' + authToken);
+ this.httpClient
+ .get<Teacher>(
+ 'http://angular.api.codedemo.club:81/teacher/login',
+ {headers: httpHeaders})
+ .subscribe(() => {
+ console.log('登錄成功,接著嘗試獲取當(dāng)前登錄用戶');
+ const url = 'http://angular.api.codedemo.club:81/teacher/me';
+ this.httpClient.get<Teacher>(url)
+ .subscribe(teacher => {
+ console.log('請(qǐng)求當(dāng)前登錄用戶成功');
+ this.me = teacher;
+ },
+ error => console.log('請(qǐng)求當(dāng)前登錄用戶發(fā)生錯(cuò)誤', error));
+ },
+ error => console.log('發(fā)生錯(cuò)誤, 登錄失敗', error));
+ }
}
```

> 注意:此時(shí)的用戶名可能已經(jīng)不是`zhangsan`,請(qǐng)參考登錄一節(jié)查看當(dāng)前可用的用戶名。
可見(jiàn):即使是在請(qǐng)求當(dāng)前用戶前發(fā)起登錄請(qǐng)求,在登錄成功的情況下仍然沒(méi)有被后臺(tái)認(rèn)為是**已認(rèn)證用戶**。
## 再學(xué)COOKIE
而值得一提的是:在前后臺(tái)統(tǒng)一的項(xiàng)目中,先完成用戶登錄再進(jìn)行請(qǐng)求,是可以請(qǐng)求成功的。為什么會(huì)發(fā)生這種情況呢?
在前后臺(tái)地址相同的情況下,瀏覽器會(huì)自動(dòng)處理用于認(rèn)為的cookie信息。包括:將后臺(tái)返回的cookie信息緩存到瀏覽器中,以及再次請(qǐng)求中自動(dòng)在header中攜帶當(dāng)前已緩存的cookie信息。
比如我們?cè)跒g覽器中直接訪問(wèn) [http://angular.api.codedemo.club:81](http://angular.api.codedemo.club:81),則會(huì)在請(qǐng)求頭中發(fā)現(xiàn)如下信息(如果沒(méi)有,就再次訪問(wèn)一次):

該信息則由瀏覽器自動(dòng)處理。
但如果我們?cè)诋?dāng)前項(xiàng)目中向樣的地址發(fā)起請(qǐng)求,則會(huì)發(fā)現(xiàn)請(qǐng)求頭中并沒(méi)有攜帶cookie信息,反而增加了Origin信息:

實(shí)際上,即使是我們使用類(lèi)似于登錄時(shí)的代碼,手動(dòng)的在header中添加cookie信息,瀏覽器在發(fā)起請(qǐng)求時(shí)也會(huì)將此信息刪除。
```typescript
+++ b/first-app/src/app/personal-center/personal-center.component.ts
@@ -18,6 +18,7 @@ export class PersonalCenterComponent implements OnInit {
const authToken = btoa(authString);
let httpHeaders = new HttpHeaders();
httpHeaders = httpHeaders.append('Authorization', 'Basic ' + authToken);
+ httpHeaders = httpHeaders.append('Cookie', 'test cookie');
this.httpClient
.get<Teacher>(
```

這是瀏覽器出于保護(hù)普通的用戶,在安全方面做的考慮。
瀏覽器將這種在`http://localhost:4200`下請(qǐng)求`http://angular.api.codedemo.club:81`的請(qǐng)求稱為**跨域**請(qǐng)求。也就是說(shuō),瀏覽器將`http://localhost:4200`與`http://angular.api.codedemo.club:81`認(rèn)為是兩個(gè)**域**。瀏覽器進(jìn)行跨域判斷時(shí),將對(duì)以下三個(gè)因素進(jìn)行判斷:
1. 協(xié)議是否相同。比如在`http://www.codedemo.club`與`https://www.codedemo.club`的協(xié)議一個(gè)是`http`,一個(gè)是`https`,則認(rèn)為協(xié)議不同。將被識(shí)別為跨域。
2. 域名是否相同。比如https://www.baidu.com與http://www.google.com,由于域名分別是`www.baidu.com`及`www.google.com`,所以認(rèn)為域名不同。將被識(shí)別為跨域。
3. 端口號(hào)是否相同。瀏覽器發(fā)起請(qǐng)求時(shí),會(huì)默認(rèn)省略http協(xié)議的80端口,以及https協(xié)議的443端口。所以訪問(wèn):http://www.baidu.com,則相當(dāng)于訪問(wèn):http://www.baidu.com:80;訪問(wèn)https://www.baidu.com,則相當(dāng)于訪問(wèn):https://www.baidu.com:443。除此以外,我們還可以為其指定特定的端口,比如我們的后臺(tái)地址為:`http://angular.api.codedemo.club:81`。該地址與`http://angular.api.codedemo.club`,一個(gè)端口為81,另一個(gè)為默認(rèn)的80,不同。將被識(shí)別為跨域。
瀏覽器在發(fā)起請(qǐng)求時(shí),如果發(fā)現(xiàn)請(qǐng)求的地址與當(dāng)前訪問(wèn)的地址在協(xié)議、域名以及端口號(hào)上有任意一點(diǎn)不同的時(shí)候,則將此次請(qǐng)求識(shí)別為跨域請(qǐng)求。
跨域更像是電影中的諜戰(zhàn)片,瀏覽器發(fā)現(xiàn)有人向外部傳遞信息時(shí),自動(dòng)的過(guò)濾掉被其認(rèn)為是敏感信息的Cookie,以達(dá)到身份認(rèn)證信息不被壞人獲取的目的(其實(shí)目的不是這樣的,但這不重要,只要知道Cookie是敏感信息,瀏覽器過(guò)濾掉了就可以)。
瀏覽器過(guò)濾掉了原來(lái)自動(dòng)攜帶的用于認(rèn)證的Cookie,這使得在跨域請(qǐng)求中,必須采取其的認(rèn)證方式。
## 域
什么是域呢?如果你對(duì)網(wǎng)絡(luò)知識(shí)感興趣,或是嘗試建立過(guò)個(gè)人站點(diǎn),那么對(duì)它肯定不陌生。在建立個(gè)人站點(diǎn)時(shí),重要的一環(huán)就是購(gòu)買(mǎi)域名,比如`codedemo.club`就是一個(gè)域名。
域?yàn)閐omain的譯文,原意為`范圍、領(lǐng)土`,簡(jiǎn)單來(lái)說(shuō)就是用它來(lái)表示一定的范圍。在現(xiàn)實(shí)生活中,我們接觸了很多域。比如國(guó)家是一個(gè)域、省是一個(gè)域、市縣等都是一個(gè)域。有了域,我們就能快速的根據(jù)域來(lái)找現(xiàn)實(shí)生活中的事物通訊,比如由于河北工業(yè)大學(xué)座落天津市這個(gè)域,所以我們?cè)谂c河北工業(yè)大學(xué)通訊時(shí),可以先指定其域名**天津**,可見(jiàn)域就是指范圍。當(dāng)前我們的現(xiàn)實(shí)世界里有個(gè)頂級(jí)的大域是宇宙,然后第二個(gè)域是地球,接下來(lái)的域有國(guó)家、省、市等。
在計(jì)算機(jī)的網(wǎng)絡(luò)世界里同樣也是如此,網(wǎng)絡(luò)里也有根域名、二級(jí)域名、三級(jí)域名:

每個(gè)域名下都可以有很多臺(tái)計(jì)算機(jī),每臺(tái)計(jì)算機(jī)在當(dāng)前域下都有一個(gè)唯一的名字,我們把域名下計(jì)算機(jī)的名字稱作主機(jī)名。在進(jìn)行http請(qǐng)求時(shí),實(shí)際上是域中的某個(gè)計(jì)算機(jī)發(fā)起請(qǐng)求,所以在請(qǐng)求時(shí)要指名這個(gè)主機(jī)名。
### 域名
知道了什么是域,域名就簡(jiǎn)單了。這就像是張三是張三的名字,李四是李四的名字,河北工業(yè)大學(xué)是河北工業(yè)大學(xué)的名字一樣。為了區(qū)分不同的域,就需要對(duì)不同的域起名字。所以域名就是域的名字。
## CORS
所以瀏覽器認(rèn)為的跨域的請(qǐng)求,實(shí)際上是考慮了域、協(xié)議以及端口號(hào)。即使在同一個(gè)域下,如果協(xié)議或是端口號(hào)不同,也么被瀏覽器認(rèn)為是跨域訪問(wèn)。我們把域、協(xié)議以及端口號(hào)也稱為**同源**的三要素,即如果兩個(gè)請(qǐng)求的域、協(xié)議以及端口號(hào)均相同時(shí),則認(rèn)為兩個(gè)請(qǐng)求是同源的,而非同源則被認(rèn)為是跨域的。所以,實(shí)際上**跨域**這個(gè)專有名詞是不確切、不足以正確的表達(dá)其中的含義的,正確的專有名詞應(yīng)該是**跨源**,全稱為**跨源資源共享**,對(duì)應(yīng)英文原文為:Cross-Origin Resource Sharing,稱寫(xiě)為`CORS`。
而在前后臺(tái)分離的項(xiàng)目中,前臺(tái)與后臺(tái)的分離架構(gòu)導(dǎo)致了前后臺(tái)必然不同源,而不同源時(shí)瀏覽器必然不攜帶cookie信息,也就無(wú)法實(shí)現(xiàn)**自動(dòng)認(rèn)證**。所以在CORS中,我們一般使用其它的認(rèn)證方法。在下個(gè)小節(jié)中,我們將對(duì)其中的一種進(jìn)行介紹。
| 名稱 | 地址 | |
| ---------------------- | ------------------------------------------------------------ | ---- |
| 跨域資源共享 CORS 詳解 | [https://www.ruanyifeng.com/blog/2016/04/cors.html](https://www.ruanyifeng.com/blog/2016/04/cors.html) | |
| 跨源資源共享(CORS) | [https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS) | |
| 域名 | [https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D](https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D) | |
| 本節(jié)源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step4.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step4.3.zip) | |
- 序言
- 第一章 Hello World
- 1.1 環(huán)境安裝
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教師管理
- 2.1 教師列表
- 2.1.1 初始化原型
- 2.1.2 組件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 請(qǐng)求后臺(tái)數(shù)據(jù)
- 2.2.1 HttpClient
- 2.2.2 請(qǐng)求數(shù)據(jù)
- 2.2.3 模塊與依賴注入
- 2.2.4 異步與回調(diào)函數(shù)
- 2.2.5 集成測(cè)試
- 2.2.6 本章小節(jié)
- 2.3 新增教師
- 2.3.1 組件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 對(duì)接后臺(tái)
- 2.3.4 路由
- 2.4 編輯教師
- 2.4.1 組件初始化
- 2.4.2 獲取路由參數(shù)
- 2.4.3 插值與模板表達(dá)式
- 2.4.4 初識(shí)泛型
- 2.4.5 更新教師
- 2.4.6 測(cè)試中的路由
- 2.5 刪除教師
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome圖標(biāo)庫(kù)
- 2.6.3 firefox
- 2.7 總結(jié)
- 第三章 用戶登錄
- 3.1 初識(shí)單元測(cè)試
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 著陸組件
- 3.5 @Output
- 3.6 TypeScript 類(lèi)
- 3.7 瀏覽器緩存
- 3.8 總結(jié)
- 第四章 個(gè)人中心
- 4.1 原型
- 4.2 管道
- 4.3 對(duì)接后臺(tái)
- 4.4 x-auth-token認(rèn)證
- 4.5 攔截器
- 4.6 小結(jié)
- 第五章 系統(tǒng)菜單
- 5.1 延遲及測(cè)試
- 5.2 手動(dòng)創(chuàng)建組件
- 5.3 隱藏測(cè)試信息
- 5.4 規(guī)劃路由
- 5.5 定義菜單
- 5.6 注銷(xiāo)
- 5.7 小結(jié)
- 第六章 班級(jí)管理
- 6.1 新增班級(jí)
- 6.1.1 組件初始化
- 6.1.2 MockApi 新建班級(jí)
- 6.1.3 ApiInterceptor
- 6.1.4 數(shù)據(jù)驗(yàn)證
- 6.1.5 教師選擇列表
- 6.1.6 MockApi 教師列表
- 6.1.7 代碼重構(gòu)
- 6.1.8 小結(jié)
- 6.2 教師列表組件
- 6.2.1 初始化
- 6.2.2 響應(yīng)式表單
- 6.2.3 getTestScheduler()
- 6.2.4 應(yīng)用組件
- 6.2.5 小結(jié)
- 6.3 班級(jí)列表
- 6.3.1 原型設(shè)計(jì)
- 6.3.2 初始化分頁(yè)
- 6.3.3 MockApi
- 6.3.4 靜態(tài)分頁(yè)
- 6.3.5 動(dòng)態(tài)分頁(yè)
- 6.3.6 @Input()
- 6.4 編輯班級(jí)
- 6.4.1 測(cè)試模塊
- 6.4.2 響應(yīng)式表單驗(yàn)證
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定義FormControl
- 6.4.6 代碼重構(gòu)
- 6.4.7 小結(jié)
- 6.5 刪除班級(jí)
- 6.6 集成測(cè)試
- 6.6.1 惰性加載
- 6.6.2 API攔截器
- 6.6.3 路由與跳轉(zhuǎn)
- 6.6.4 ngStyle
- 6.7 初識(shí)Service
- 6.7.1 catchError
- 6.7.2 單例服務(wù)
- 6.7.3 單元測(cè)試
- 6.8 小結(jié)
- 第七章 學(xué)生管理
- 7.1 班級(jí)列表組件
- 7.2 新增學(xué)生
- 7.2.1 exports
- 7.2.2 自定義驗(yàn)證器
- 7.2.3 異步驗(yàn)證器
- 7.2.4 再識(shí)DI
- 7.2.5 屬性型指令
- 7.2.6 完成功能
- 7.2.7 小結(jié)
- 7.3 單元測(cè)試進(jìn)階
- 7.4 學(xué)生列表
- 7.4.1 JSON對(duì)象與對(duì)象
- 7.4.2 單元測(cè)試
- 7.4.3 分頁(yè)模塊
- 7.4.4 子組件測(cè)試
- 7.4.5 重構(gòu)分頁(yè)
- 7.5 刪除學(xué)生
- 7.5.1 第三方dialog
- 7.5.2 批量刪除
- 7.5.3 面向?qū)ο?/a>
- 7.6 集成測(cè)試
- 7.7 編輯學(xué)生
- 7.7.1 初始化
- 7.7.2 自定義provider
- 7.7.3 更新學(xué)生
- 7.7.4 集成測(cè)試
- 7.7.5 可訂閱的路由參數(shù)
- 7.7.6 小結(jié)
- 7.8 總結(jié)
- 第八章 其它
- 8.1 打包構(gòu)建
- 8.2 發(fā)布部署
- 第九章 總結(jié)