# 对接后台
本节我们尝试完成个人中心后台的对接。
## 接口信息
我们为大家提供了如下接口来获取当前登录用户的基本信息:
```bash
GET /teacher/me
```
#### 参数 Parameters
| type | name | Description | Schema |
| ---------- | ------------------------------- | ------------ | ---------------- |
| **Header** | **认证x-auth-token** *requried* | 获取登录信息 | 当前登录教师实体 |
#### 返回值 Responses
| HTTP Code | Description | Schema |
| --------- | ------------------------------------------------------ | ------ |
| **200** | 认证x-auth-token合法 | Ok |
| **401** | 未携带x-auth-token信息,未携带的x-auth-token信息不正确 | 未授权 |
接口中提到了x-auth-token信息,我们暂时先不考虑它,看看按以前请求思想会发生什么。
## 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('请求发生错误', error));
}
}
```
单元测试中的动态测试模块:
```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();
});
```
测试结果:
![image-20210308104228124](https://img.kancloud.cn/67/17/6717ade960183f748ad9d1bc5d2d68a8_2546x366.png)
## 登录
与前面章节中对教师的操作不同,后台获取当前登录教师的接口必须在**用户登录成功**后,才能够获取到。也就是说只有登录的用户才有资格访问该接口。
那么如果在请求该接口前先完成用户的登录,是否可以请求成功呢?心动不如行动,我们参考登录组件简单写下代码:
```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('登录成功,接着尝试获取当前登录用户');
+ const url = 'http://angular.api.codedemo.club:81/teacher/me';
+ this.httpClient.get<Teacher>(url)
+ .subscribe(teacher => {
+ console.log('请求当前登录用户成功');
+ this.me = teacher;
+ },
+ error => console.log('请求当前登录用户发生错误', error));
+ },
+ error => console.log('发生错误, 登录失败', error));
+ }
}
```
![image-20210308105049039](https://img.kancloud.cn/a1/29/a12921dccbfb432925175c5f467f9fdc_1422x208.png)
> 注意:此时的用户名可能已经不是`zhangsan`,请参考登录一节查看当前可用的用户名。
可见:即使是在请求当前用户前发起登录请求,在登录成功的情况下仍然没有被后台认为是**已认证用户**。
## 再学COOKIE
而值得一提的是:在前后台统一的项目中,先完成用户登录再进行请求,是可以请求成功的。为什么会发生这种情况呢?
在前后台地址相同的情况下,浏览器会自动处理用于认为的cookie信息。包括:将后台返回的cookie信息缓存到浏览器中,以及再次请求中自动在header中携带当前已缓存的cookie信息。
比如我们在浏览器中直接访问 [http://angular.api.codedemo.club:81](http://angular.api.codedemo.club:81),则会在请求头中发现如下信息(如果没有,就再次访问一次):
![image-20210306145611485](https://img.kancloud.cn/e2/c0/e2c0d5cc50c206edce54639ac0b1e671_1850x840.png)
该信息则由浏览器自动处理。
但如果我们在当前项目中向样的地址发起请求,则会发现请求头中并没有携带cookie信息,反而增加了Origin信息:
![image-20210306150116177](https://img.kancloud.cn/19/ec/19ecb19a274e7a7caaf454f5fa44e1ac_1904x828.png)
实际上,即使是我们使用类似于登录时的代码,手动的在header中添加cookie信息,浏览器在发起请求时也会将此信息删除。
```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>(
```
![image-20210308110606538](https://img.kancloud.cn/15/54/1554e51e62a8726310d99dde0dd3fffe_1344x334.png)
这是浏览器出于保护普通的用户,在安全方面做的考虑。
浏览器将这种在`http://localhost:4200`下请求`http://angular.api.codedemo.club:81`的请求称为**跨域**请求。也就是说,浏览器将`http://localhost:4200`与`http://angular.api.codedemo.club:81`认为是两个**域**。浏览器进行跨域判断时,将对以下三个因素进行判断:
1. 协议是否相同。比如在`http://www.codedemo.club`与`https://www.codedemo.club`的协议一个是`http`,一个是`https`,则认为协议不同。将被识别为跨域。
2. 域名是否相同。比如https://www.baidu.com与http://www.google.com,由于域名分别是`www.baidu.com`及`www.google.com`,所以认为域名不同。将被识别为跨域。
3. 端口号是否相同。浏览器发起请求时,会默认省略http协议的80端口,以及https协议的443端口。所以访问:http://www.baidu.com,则相当于访问:http://www.baidu.com:80;访问https://www.baidu.com,则相当于访问:https://www.baidu.com:443。除此以外,我们还可以为其指定特定的端口,比如我们的后台地址为:`http://angular.api.codedemo.club:81`。该地址与`http://angular.api.codedemo.club`,一个端口为81,另一个为默认的80,不同。将被识别为跨域。
浏览器在发起请求时,如果发现请求的地址与当前访问的地址在协议、域名以及端口号上有任意一点不同的时候,则将此次请求识别为跨域请求。
跨域更像是电影中的谍战片,浏览器发现有人向外部传递信息时,自动的过滤掉被其认为是敏感信息的Cookie,以达到身份认证信息不被坏人获取的目的(其实目的不是这样的,但这不重要,只要知道Cookie是敏感信息,浏览器过滤掉了就可以)。
浏览器过滤掉了原来自动携带的用于认证的Cookie,这使得在跨域请求中,必须采取其的认证方式。
## 域
什么是域呢?如果你对网络知识感兴趣,或是尝试建立过个人站点,那么对它肯定不陌生。在建立个人站点时,重要的一环就是购买域名,比如`codedemo.club`就是一个域名。
域为domain的译文,原意为`范围、领土`,简单来说就是用它来表示一定的范围。在现实生活中,我们接触了很多域。比如国家是一个域、省是一个域、市县等都是一个域。有了域,我们就能快速的根据域来找现实生活中的事物通讯,比如由于河北工业大学座落天津市这个域,所以我们在与河北工业大学通讯时,可以先指定其域名**天津**,可见域就是指范围。当前我们的现实世界里有个顶级的大域是宇宙,然后第二个域是地球,接下来的域有国家、省、市等。
在计算机的网络世界里同样也是如此,网络里也有根域名、二级域名、三级域名:
![image-20210306151140032](https://img.kancloud.cn/50/c5/50c5536787d130115b5c121895661690_1442x514.png)
每个域名下都可以有很多台计算机,每台计算机在当前域下都有一个唯一的名字,我们把域名下计算机的名字称作主机名。在进行http请求时,实际上是域中的某个计算机发起请求,所以在请求时要指名这个主机名。
### 域名
知道了什么是域,域名就简单了。这就像是张三是张三的名字,李四是李四的名字,河北工业大学是河北工业大学的名字一样。为了区分不同的域,就需要对不同的域起名字。所以域名就是域的名字。
## CORS
所以浏览器认为的跨域的请求,实际上是考虑了域、协议以及端口号。即使在同一个域下,如果协议或是端口号不同,也么被浏览器认为是跨域访问。我们把域、协议以及端口号也称为**同源**的三要素,即如果两个请求的域、协议以及端口号均相同时,则认为两个请求是同源的,而非同源则被认为是跨域的。所以,实际上**跨域**这个专有名词是不确切、不足以正确的表达其中的含义的,正确的专有名词应该是**跨源**,全称为**跨源资源共享**,对应英文原文为:Cross-Origin Resource Sharing,称写为`CORS`。
而在前后台分离的项目中,前台与后台的分离架构导致了前后台必然不同源,而不同源时浏览器必然不携带cookie信息,也就无法实现**自动认证**。所以在CORS中,我们一般使用其它的认证方法。在下个小节中,我们将对其中的一种进行介绍。
| 名称 | 地址 | |
| ---------------------- | ------------------------------------------------------------ | ---- |
| 跨域资源共享 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) | |
| 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step4.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step4.3.zip) | |
- 序言
- 第一章 Hello World
- 1.1 环境安装
- 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 请求后台数据
- 2.2.1 HttpClient
- 2.2.2 请求数据
- 2.2.3 模块与依赖注入
- 2.2.4 异步与回调函数
- 2.2.5 集成测试
- 2.2.6 本章小节
- 2.3 新增教师
- 2.3.1 组件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 对接后台
- 2.3.4 路由
- 2.4 编辑教师
- 2.4.1 组件初始化
- 2.4.2 获取路由参数
- 2.4.3 插值与模板表达式
- 2.4.4 初识泛型
- 2.4.5 更新教师
- 2.4.6 测试中的路由
- 2.5 删除教师
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome图标库
- 2.6.3 firefox
- 2.7 总结
- 第三章 用户登录
- 3.1 初识单元测试
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 着陆组件
- 3.5 @Output
- 3.6 TypeScript 类
- 3.7 浏览器缓存
- 3.8 总结
- 第四章 个人中心
- 4.1 原型
- 4.2 管道
- 4.3 对接后台
- 4.4 x-auth-token认证
- 4.5 拦截器
- 4.6 小结
- 第五章 系统菜单
- 5.1 延迟及测试
- 5.2 手动创建组件
- 5.3 隐藏测试信息
- 5.4 规划路由
- 5.5 定义菜单
- 5.6 注销
- 5.7 小结
- 第六章 班级管理
- 6.1 新增班级
- 6.1.1 组件初始化
- 6.1.2 MockApi 新建班级
- 6.1.3 ApiInterceptor
- 6.1.4 数据验证
- 6.1.5 教师选择列表
- 6.1.6 MockApi 教师列表
- 6.1.7 代码重构
- 6.1.8 小结
- 6.2 教师列表组件
- 6.2.1 初始化
- 6.2.2 响应式表单
- 6.2.3 getTestScheduler()
- 6.2.4 应用组件
- 6.2.5 小结
- 6.3 班级列表
- 6.3.1 原型设计
- 6.3.2 初始化分页
- 6.3.3 MockApi
- 6.3.4 静态分页
- 6.3.5 动态分页
- 6.3.6 @Input()
- 6.4 编辑班级
- 6.4.1 测试模块
- 6.4.2 响应式表单验证
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定义FormControl
- 6.4.6 代码重构
- 6.4.7 小结
- 6.5 删除班级
- 6.6 集成测试
- 6.6.1 惰性加载
- 6.6.2 API拦截器
- 6.6.3 路由与跳转
- 6.6.4 ngStyle
- 6.7 初识Service
- 6.7.1 catchError
- 6.7.2 单例服务
- 6.7.3 单元测试
- 6.8 小结
- 第七章 学生管理
- 7.1 班级列表组件
- 7.2 新增学生
- 7.2.1 exports
- 7.2.2 自定义验证器
- 7.2.3 异步验证器
- 7.2.4 再识DI
- 7.2.5 属性型指令
- 7.2.6 完成功能
- 7.2.7 小结
- 7.3 单元测试进阶
- 7.4 学生列表
- 7.4.1 JSON对象与对象
- 7.4.2 单元测试
- 7.4.3 分页模块
- 7.4.4 子组件测试
- 7.4.5 重构分页
- 7.5 删除学生
- 7.5.1 第三方dialog
- 7.5.2 批量删除
- 7.5.3 面向对象
- 7.6 集成测试
- 7.7 编辑学生
- 7.7.1 初始化
- 7.7.2 自定义provider
- 7.7.3 更新学生
- 7.7.4 集成测试
- 7.7.5 可订阅的路由参数
- 7.7.6 小结
- 7.8 总结
- 第八章 其它
- 8.1 打包构建
- 8.2 发布部署
- 第九章 总结