|
@@ -15,8 +15,9 @@ import { environment } from '@env/environment';
|
|
|
import { NzNotificationService } from 'ng-zorro-antd/notification';
|
|
import { NzNotificationService } from 'ng-zorro-antd/notification';
|
|
|
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
|
|
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
|
|
|
import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators';
|
|
import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators';
|
|
|
|
|
+import {LocalStorageService} from "../../service/local-storage.service";
|
|
|
|
|
|
|
|
-const CODEMESSAGE: { [key: number]: string } = {
|
|
|
|
|
|
|
+const CODE_MESSAGE: { [key: number]: string } = {
|
|
|
200: '服务器成功返回请求的数据。',
|
|
200: '服务器成功返回请求的数据。',
|
|
|
201: '新建或修改数据成功。',
|
|
201: '新建或修改数据成功。',
|
|
|
202: '一个请求已经进入后台排队(异步任务)。',
|
|
202: '一个请求已经进入后台排队(异步任务)。',
|
|
@@ -40,15 +41,10 @@ const CODEMESSAGE: { [key: number]: string } = {
|
|
|
@Injectable()
|
|
@Injectable()
|
|
|
export class DefaultInterceptor implements HttpInterceptor {
|
|
export class DefaultInterceptor implements HttpInterceptor {
|
|
|
private refreshTokenEnabled = environment.api.refreshTokenEnabled;
|
|
private refreshTokenEnabled = environment.api.refreshTokenEnabled;
|
|
|
- private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
|
|
|
|
|
private refreshToking = false;
|
|
private refreshToking = false;
|
|
|
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
|
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
|
|
|
|
|
|
|
- constructor(private injector: Injector) {
|
|
|
|
|
- if (this.refreshTokenType === 'auth-refresh') {
|
|
|
|
|
- this.buildAuthRefresh();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ constructor(private injector: Injector, private storageSvc: LocalStorageService) { }
|
|
|
|
|
|
|
|
private get notification(): NzNotificationService {
|
|
private get notification(): NzNotificationService {
|
|
|
return this.injector.get(NzNotificationService);
|
|
return this.injector.get(NzNotificationService);
|
|
@@ -67,12 +63,11 @@ export class DefaultInterceptor implements HttpInterceptor {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private checkStatus(ev: HttpResponseBase): void {
|
|
private checkStatus(ev: HttpResponseBase): void {
|
|
|
- if ((ev.status >= 200 && ev.status < 300) || ev.status === 401) {
|
|
|
|
|
|
|
+ if (ev.status >= 200 && ev.status < 500) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- const errortext = CODEMESSAGE[ev.status] || ev.statusText;
|
|
|
|
|
- this.notification.error(`请求错误 ${ev.status}: ${ev.url}`, errortext);
|
|
|
|
|
|
|
+ const errorText = CODE_MESSAGE[ev.status] || ev.statusText;
|
|
|
|
|
+ this.notification.error(`抱歉服务器繁忙 ${ev.status}: ${ev.url}`, errorText);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -109,6 +104,7 @@ export class DefaultInterceptor implements HttpInterceptor {
|
|
|
this.refreshToking = false;
|
|
this.refreshToking = false;
|
|
|
this.refreshToken$.next(res);
|
|
this.refreshToken$.next(res);
|
|
|
// 重新保存新 token
|
|
// 重新保存新 token
|
|
|
|
|
+ this.storageSvc.storageAuthorizationToken(res);
|
|
|
this.tokenSrv.set(res);
|
|
this.tokenSrv.set(res);
|
|
|
// 重新发起请求
|
|
// 重新发起请求
|
|
|
return next.handle(this.reAttachToken(req));
|
|
return next.handle(this.reAttachToken(req));
|
|
@@ -124,138 +120,69 @@ export class DefaultInterceptor implements HttpInterceptor {
|
|
|
/**
|
|
/**
|
|
|
* 重新附加新 Token 信息
|
|
* 重新附加新 Token 信息
|
|
|
*
|
|
*
|
|
|
- * > 由于已经发起的请求,不会再走一遍 `@delon/auth` 因此需要结合业务情况重新附加新的 Token
|
|
|
|
|
*/
|
|
*/
|
|
|
private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
|
|
private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
|
|
|
- // 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor`
|
|
|
|
|
- const token = this.tokenSrv.get()?.token;
|
|
|
|
|
|
|
+ let token = this.storageSvc.getAuthorizationToken();
|
|
|
return req.clone({
|
|
return req.clone({
|
|
|
setHeaders: {
|
|
setHeaders: {
|
|
|
- token: `Bearer ${token}`
|
|
|
|
|
|
|
+ 'Authorization': `Bearer ${token}`
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // #endregion
|
|
|
|
|
-
|
|
|
|
|
- // #region 刷新Token方式二:使用 `@delon/auth` 的 `refresh` 接口
|
|
|
|
|
-
|
|
|
|
|
- private buildAuthRefresh(): void {
|
|
|
|
|
- if (!this.refreshTokenEnabled) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- this.tokenSrv.refresh
|
|
|
|
|
- .pipe(
|
|
|
|
|
- filter(() => !this.refreshToking),
|
|
|
|
|
- switchMap(res => {
|
|
|
|
|
- console.log(res);
|
|
|
|
|
- this.refreshToking = true;
|
|
|
|
|
- return this.refreshTokenRequest();
|
|
|
|
|
- })
|
|
|
|
|
- )
|
|
|
|
|
- .subscribe(
|
|
|
|
|
- res => {
|
|
|
|
|
- // TODO: Mock expired value
|
|
|
|
|
- res.expired = +new Date() + 1000 * 60 * 5;
|
|
|
|
|
- this.refreshToking = false;
|
|
|
|
|
- this.tokenSrv.set(res);
|
|
|
|
|
- },
|
|
|
|
|
- () => this.toLogin()
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // #endregion
|
|
|
|
|
|
|
|
|
|
private toLogin(): void {
|
|
private toLogin(): void {
|
|
|
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
|
|
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
|
|
|
this.goTo(this.tokenSrv.login_url!);
|
|
this.goTo(this.tokenSrv.login_url!);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
|
|
|
|
- this.checkStatus(ev);
|
|
|
|
|
- // 业务处理:一些通用操作
|
|
|
|
|
- switch (ev.status) {
|
|
|
|
|
- case 200:
|
|
|
|
|
- // 业务层级错误处理,以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理
|
|
|
|
|
- // 例如响应内容:
|
|
|
|
|
- // 错误内容:{ status: 1, msg: '非法参数' }
|
|
|
|
|
- // 正确内容:{ status: 0, response: { } }
|
|
|
|
|
- // 则以下代码片断可直接适用
|
|
|
|
|
- // if (ev instanceof HttpResponse) {
|
|
|
|
|
- // const body = ev.body;
|
|
|
|
|
- // if (body && body.status !== 0) {
|
|
|
|
|
- // this.injector.get(NzMessageService).error(body.msg);
|
|
|
|
|
- // // 注意:这里如果继续抛出错误会被行254的 catchError 二次拦截,导致外部实现的 Pipe、subscribe 操作被中断,例如:this.http.get('/').subscribe() 不会触发
|
|
|
|
|
- // // 如果你希望外部实现,需要手动移除行254
|
|
|
|
|
- // return throwError({});
|
|
|
|
|
- // } else {
|
|
|
|
|
- // // 忽略 Blob 文件体
|
|
|
|
|
- // if (ev.body instanceof Blob) {
|
|
|
|
|
- // return of(ev);
|
|
|
|
|
- // }
|
|
|
|
|
- // // 重新修改 `body` 内容为 `response` 内容,对于绝大多数场景已经无须再关心业务状态码
|
|
|
|
|
- // return of(new HttpResponse(Object.assign(ev, { body: body.response })));
|
|
|
|
|
- // // 或者依然保持完整的格式
|
|
|
|
|
- // return of(ev);
|
|
|
|
|
- // }
|
|
|
|
|
- // }
|
|
|
|
|
- break;
|
|
|
|
|
- case 401:
|
|
|
|
|
- if (this.refreshTokenEnabled && this.refreshTokenType === 're-request') {
|
|
|
|
|
- return this.tryRefreshToken(ev, req, next);
|
|
|
|
|
- }
|
|
|
|
|
- this.toLogin();
|
|
|
|
|
- break;
|
|
|
|
|
- case 403:
|
|
|
|
|
- case 404:
|
|
|
|
|
- case 500:
|
|
|
|
|
- // this.goTo(`/exception/${ev.status}?url=${req.urlWithParams}`);
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- if (ev instanceof HttpErrorResponse) {
|
|
|
|
|
- console.warn(
|
|
|
|
|
- '未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起,请参考 https://ng-alain.com/docs/server 解决跨域问题',
|
|
|
|
|
- ev
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- if (ev instanceof HttpErrorResponse) {
|
|
|
|
|
- return throwError(ev);
|
|
|
|
|
- } else {
|
|
|
|
|
- return of(ev);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private getAdditionalHeaders(headers?: HttpHeaders): { [name: string]: string } {
|
|
|
|
|
|
|
+ private fillHeaders(headers?: HttpHeaders): { [name: string]: string } {
|
|
|
const res: { [name: string]: string } = {};
|
|
const res: { [name: string]: string } = {};
|
|
|
const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang;
|
|
const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang;
|
|
|
if (!headers?.has('Accept-Language') && lang) {
|
|
if (!headers?.has('Accept-Language') && lang) {
|
|
|
res['Accept-Language'] = lang;
|
|
res['Accept-Language'] = lang;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+ let token = this.storageSvc.getAuthorizationToken();
|
|
|
|
|
+ if (token !== null) {
|
|
|
|
|
+ res['Authorization'] = `Bearer ${token}`;
|
|
|
|
|
+ }
|
|
|
return res;
|
|
return res;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
|
|
- // 统一加上服务端前缀
|
|
|
|
|
let url = req.url;
|
|
let url = req.url;
|
|
|
if (!url.startsWith('https://') && !url.startsWith('http://')) {
|
|
if (!url.startsWith('https://') && !url.startsWith('http://')) {
|
|
|
const { baseUrl } = environment.api;
|
|
const { baseUrl } = environment.api;
|
|
|
url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url);
|
|
url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- const newReq = req.clone({ url, setHeaders: this.getAdditionalHeaders(req.headers) });
|
|
|
|
|
|
|
+ const newReq = req.clone({ url, setHeaders: this.fillHeaders(req.headers) });
|
|
|
return next.handle(newReq).pipe(
|
|
return next.handle(newReq).pipe(
|
|
|
- mergeMap(ev => {
|
|
|
|
|
- // 允许统一对请求错误处理
|
|
|
|
|
- if (ev instanceof HttpResponseBase) {
|
|
|
|
|
- return this.handleData(ev, newReq, next);
|
|
|
|
|
|
|
+ mergeMap(httpEvent => {
|
|
|
|
|
+ if (httpEvent instanceof HttpResponseBase) {
|
|
|
|
|
+ // 处理token过期自动刷新
|
|
|
|
|
+ switch (httpEvent.status) {
|
|
|
|
|
+ case 401:
|
|
|
|
|
+ if (this.refreshTokenEnabled) {
|
|
|
|
|
+ return this.tryRefreshToken(httpEvent, req, next);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.toLogin();
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 403 | 404 | 500:
|
|
|
|
|
+ this.goTo(`/exception/${httpEvent.status}?url=${req.urlWithParams}`);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return of(httpEvent);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return of(httpEvent);
|
|
|
}
|
|
}
|
|
|
- // 若一切都正常,则后续操作
|
|
|
|
|
- return of(ev);
|
|
|
|
|
}),
|
|
}),
|
|
|
- catchError((err: HttpErrorResponse) => this.handleData(err, newReq, next))
|
|
|
|
|
|
|
+ catchError((err: HttpErrorResponse) => {
|
|
|
|
|
+ this.checkStatus(err);
|
|
|
|
|
+ console.warn(`${err.status} == ${err.message}`)
|
|
|
|
|
+ return throwError(err);
|
|
|
|
|
+ })
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|