由于之前用的是Abp框架,框架封装的十分成熟,所以很多时候用起来都是知其然而不知其所以然。比如刚开始还没有意识到每次请求都传了token,因为在http方法的调用中,并没有看到它添加header,知道后面看network才知道每次都传了token,搜了全部的文件,关于token的,只有封装好的settoken方法和gettoken方法,到底是怎么加入header的十分费解,后来,想了想估计是拦截器的作用,找了找abp源码,果然有httpInceptor.js,代码一读,问题自然迎刃而解。
关于这个东西的概念,官网是有十分清楚的解释的,通俗的来说就是你在每次发起http请求时,最后的都要经过拦截器来加工一次,然后返回的http response也是要先经过拦截器处理加工。
自定义拦截器需要创建一个服务,服务中必须有的方法是intercept方法。
@Injectable()
export class InterceptorService implements HttpInterceptor {
constructor(
private message: NzMessageService,
private sessionService: SessionService) {
}
id: string = "";
intercept(req: HttpRequest, next: HttpHandler): Observable> {
if (this.id == "" && !req.url.includes('json')) {
this.id = this.message.loading('请稍后...', { nzDuration: 0 }).messageId;
}
let secureReq: HttpRequest = req;
// const url = `${config.apiUrl}/api/`; //添加api统一前缀
let modifiedHeaders = new HttpHeaders();
let token = document.cookie;
if (token) {
modifiedHeaders = req.headers.set('Authorization', `Bearer ${token}`);
}
secureReq = req.clone({
url: req.url,
headers: modifiedHeaders
});
const started = Date.now();
let ok: string;
if (req.url.includes('json')) {
return next.handle(secureReq);
}
else {
return next.handle(secureReq)
.pipe(
catchError((res: HttpResponse) => {
console.log(res);
let msg = "";
switch (res.status) {
case 401:
msg = "身份验证过期,请重新进入页面";
break;
case 200:
msg = "身份验证过期,请重新进入页面";
break;
case 404:
msg = "找不到地址";
break;
case 403:
msg = "业务错误";
break;
case 500:
msg = "服务器发生错误,请重试";
break;
}
this.showError(msg);
return Observable.create(res);
}),
finalize(() => {
const elapsed = Date.now() - started; //可计算出请求所消耗时间
const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`;
console.log(msg);
}),
mergeMap(
// Succeeds when there is a response; ignore other events
(event: any) => {
if (event.status == 200) {
this.id = "";
this.message.remove();
}
return Observable.create(observer => observer.next(event));
}),
);
}
showError(message: string) {
this.message.remove();
this.id = "";
this.message.error(message, { nzDuration: 2000 })
}
}
这是我在项目中实际用的interceptor,我做的比较多的修改是在请求进来的时候,加载了ng-zorro的message的loading消息,这是个全局的提示消息,然后从cookie中读取的之前存入的token,将其放入header中,执行next.handle()方法,执行http请求,通过catcherror来捕获异常,根据异常的代码不同,来提示不同的错误。对了,这个地方用的pipe,catchError,finalize都是是rxjs6的新语法。
写完自己的拦截器,接下来就需要去项目中入口的module.ts文件中配置了,
providers: [
...
{ provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true },
],
配置完成后,重新生成就生效了。
这个地方就是根据不同的项目有不同的实现方法了,我这个项目中,后台用的技术是.Net Core,内部集成了swagger,这也为我提供了便利,我直接用NSwagStudio,来生成我的ts代码。贴个软件的图
左边的地址栏里填写swagger的json地址(一般来说只用修改前面的地址和端口就行了),填写完了过后,create local copy,swagger的json文件就生成出来了,然后右侧的outputs中,可以选择三种语言,这里我只是需要Typescript,所以后面两项我就没有勾选了,然后再下面的TypeScript的tab栏里面有一些配置,比如typescript的版本,angular和rxjs的版本,根据我的项目,我选择了相对应的版本,然后generate output就可以了。
这个地方比较难以理解的就是api的基地址,在生成的代码中是以这种形式获取的。
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
参考了一下之前用的abp框架,还是摸索出来了这个到底该怎么用。
首先需要在根模块进行配置
providers: [
...
{ provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl },
],
这里需要先从生成的文件中import API_BASE_URL。
import { API_BASE_URL } from './common-service';
然后这个地方用到的useFactory,用的getRemoteServiceBaseUrl方法,是需要我们自己实现的,我这里就在module文件中实现了。
export function getRemoteServiceBaseUrl(): string {
return AppConsts.baseURL;
}
返回的是我之前在初始化的时候配置在我的一个常量文件中的url。这样操作完成后就大功告成了。
上面用的url,如果是写死在ts文件中是可以行得通的,但为了保证有些时候接口地址可能会变,如果是要改部署到环境上的项目,那就需要再生成,再部署一次才行,相当的麻烦,所以可以尝试着把地址配在一个json文件中,每次进来的时候读取一次就行了,哪怕发生变化,也只需要在json文件中做改动。
在assets文件夹下新建appconfig.json,文件中写好配置。
由于需要在项目启动时读取这个文件,所以需要在初始化的时候进行读取,这里需要在根模块进行一些改动
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
deps: [Injector, PlatformLocation],
multi: true
},
这里的APP_INITIALIZER和Injector是angular本身就自带的,需要从angular/core中import。
import { APP_INITIALIZER, Injector } from '@angular/core';
实现的这个这个方法,appInitializerFactory则需要自己定义,同样,我也在根模块中定义了。
export function appInitializerFactory(
injector: Injector,
platformLocation: PlatformLocation
) {
return () => {
return new Promise((resolve, reject) => {
let httpclient: HttpClient = injector.get(HttpClient);
httpclient.get('assets/appconfig.json').toPromise().then((res: any) => {
AppConsts.baseURL = res.remoteServiceBaseUrl;
resolve(true);
},
(err) => {
reject(err);
})
});
}
}
这里尝试了很多种返回方法,最后还是只有返回Promise才生效。赋值给AppConsts.baseURL,就可以联系之前的读取基地址相关联了。