@@ -15,8 +15,9 @@ import { environment } from '@env/environment';
import { NzNotificationService } from 'ng-zorro-antd/notification' ;
import { BehaviorSubject , Observable , of , throwError } from 'rxjs' ;
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 : '服务器成功返回请求的数据。' ,
201 : '新建或修改数据成功。' ,
202 : '一个请求已经进入后台排队(异步任务)。' ,
@@ -40,15 +41,10 @@ const CODEMESSAGE: { [key: number]: string } = {
@Injectable ( )
export class DefaultInterceptor implements HttpInterceptor {
private refreshTokenEnabled = environment . api . refreshTokenEnabled ;
private refreshTokenType : 're-request' | 'auth-refresh' = environment . api . refreshTokenType ;
private refreshToking = false ;
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 {
return this . injector . get ( NzNotificationService ) ;
@@ -67,12 +63,11 @@ export class DefaultInterceptor implements HttpInterceptor {
}
private checkStatus ( ev : HttpResponseBase ) : void {
if ( ( ev . status >= 200 && ev . status < 3 00) || ev . status === 401 ) {
if ( ev . status >= 200 && ev . status < 5 00) {
return ;
}
const errortext = CODEMESSAGE [ ev . status ] || ev . status Text;
this . notification . error ( ` 请求错误 ${ ev . status } : ${ ev . url } ` , errortext ) ;
const errorText = CODE_MESSAGE [ ev . status ] || ev . statusText ;
this . notification . error ( ` 抱歉服务器繁忙 ${ ev . status } : ${ ev . url } ` , error Text) ;
}
/ * *
@@ -109,6 +104,7 @@ export class DefaultInterceptor implements HttpInterceptor {
this . refreshToking = false ;
this . refreshToken $ . next ( res ) ;
// 重新保存新 token
this . storageSvc . storageAuthorizationToken ( res ) ;
this . tokenSrv . set ( res ) ;
// 重新发起请求
return next . handle ( this . reAttachToken ( req ) ) ;
@@ -124,138 +120,69 @@ export class DefaultInterceptor implements HttpInterceptor {
/ * *
* 重 新 附 加 新 Token 信 息
*
* > 由 于 已 经 发 起 的 请 求 , 不 会 再 走 一 遍 ` @delon/auth ` 因 此 需 要 结 合 业 务 情 况 重 新 附 加 新 的 Token
* /
private reAttachToken ( req : HttpRequest < any > ) : HttpRequest < any > {
// 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor`
const token = this . tokenSrv . get ( ) ? . token ;
let token = this . storageSvc . getAuthorizationToken ( ) ;
return req . clone ( {
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 {
this . notification . error ( ` 未登录或登录已过期,请重新登录。 ` , ` ` ) ;
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 lang = this . injector . get ( ALAIN_I18N_TOKEN ) . currentLang ;
if ( ! headers ? . has ( 'Accept-Language' ) && lang ) {
res [ 'Accept-Language' ] = lang ;
}
let token = this . storageSvc . getAuthorizationToken ( ) ;
if ( token !== null ) {
res [ 'Authorization' ] = ` Bearer ${ token } ` ;
}
return res ;
}
intercept ( req : HttpRequest < any > , next : HttpHandler ) : Observable < HttpEvent < any > > {
// 统一加上服务端前缀
let url = req . url ;
if ( ! url . startsWith ( 'https://' ) && ! url . startsWith ( 'http://' ) ) {
const { baseUrl } = environment . api ;
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 (
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 ) ;
} )
) ;
}
}