Skip to content

Commit

Permalink
Implement HTTP interceptors for request handling, authentication, and…
Browse files Browse the repository at this point in the history
… error management
  • Loading branch information
vnobo committed Nov 1, 2024
1 parent 16618d2 commit 87d71bb
Show file tree
Hide file tree
Showing 27 changed files with 608 additions and 275 deletions.
2 changes: 1 addition & 1 deletion boot/platform/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spring:
max-idle-time: 10m
validation-query: select 1
sql.init:
mode: never
mode: always
platform: postgres
encoding: utf-8
data.redis:
Expand Down
10 changes: 9 additions & 1 deletion ng-ui/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,17 @@
{
"glob": "**/*",
"input": "public"
},
{
"glob": "**/*",
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
"output": "/assets/"
}
],
"styles": ["src/styles.scss"],
"styles": [
"src/theme.less",
"src/styles.scss"
],
"scripts": [],
"server": "src/main.server.ts",
"prerender": true,
Expand Down
2 changes: 1 addition & 1 deletion ng-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.5.2"
}
}
}
313 changes: 51 additions & 262 deletions ng-ui/src/app/app.component.html

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions ng-ui/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
:host {
display: flex;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.app-layout {
height: 100vh;
}

.menu-sidebar {
position: relative;
z-index: 10;
min-height: 100vh;
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
}

.header-trigger {
height: 64px;
padding: 20px 24px;
font-size: 20px;
cursor: pointer;
transition: all .3s, padding 0s;
}

.trigger:hover {
color: #1890ff;
}

.sidebar-logo {
position: relative;
height: 64px;
padding-left: 24px;
overflow: hidden;
line-height: 64px;
background: #001529;
transition: all .3s;
}

.sidebar-logo img {
display: inline-block;
height: 32px;
width: 32px;
vertical-align: middle;
}

.sidebar-logo h1 {
display: inline-block;
margin: 0 0 0 20px;
color: #fff;
font-weight: 600;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}

nz-header {
padding: 0;
width: 100%;
z-index: 2;
}

.app-header {
position: relative;
height: 64px;
padding: 0;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
}

nz-content {
margin: 24px;
}

.inner-content {
padding: 24px;
background: #fff;
height: 100%;
}
14 changes: 8 additions & 6 deletions ng-ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { DatePipe } from '@angular/common';
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common';
import { RouterLink, RouterOutlet } from '@angular/router';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzLayoutModule } from 'ng-zorro-antd/layout';
import { NzMenuModule } from 'ng-zorro-antd/menu';

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, DatePipe],
imports: [CommonModule, RouterLink, RouterOutlet, NzIconModule, NzLayoutModule, NzMenuModule],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'ng-ui';
timeNow = new Date();
isCollapsed = false;
}
32 changes: 29 additions & 3 deletions ng-ui/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
import { ApplicationConfig, LOCALE_ID, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { ApplicationConfig, importProvidersFrom, LOCALE_ID, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';

import { provideClientHydration } from '@angular/platform-browser';
import { routes } from './app.routes';
import { icons } from './icons-provider';
import { provideNzIcons } from 'ng-zorro-antd/icon';
import { provideNzI18n, zh_CN } from 'ng-zorro-antd/i18n';
import { FormsModule } from '@angular/forms';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import {
provideHttpClient,
withFetch,
withInterceptors,
withInterceptorsFromDi,
withXsrfConfiguration,
} from '@angular/common/http';
import { indexInterceptor } from './core';

export const appConfig: ApplicationConfig = {
providers: [
{ provide: LOCALE_ID, useValue: 'zh_CN' },
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideRouter(routes, withComponentInputBinding()),
provideClientHydration(),
provideNzIcons(icons),
provideNzI18n(zh_CN),
importProvidersFrom(FormsModule),
provideAnimationsAsync(),
provideHttpClient(
withFetch(),
withInterceptorsFromDi(),
withInterceptors(indexInterceptor),
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN',
}),
),
],
};
5 changes: 4 additions & 1 deletion ng-ui/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Routes } from '@angular/router';

export const routes: Routes = [];
export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: '/welcome' },
{ path: 'welcome', loadChildren: () => import('./pages/welcome/welcome.routes').then(m => m.WELCOME_ROUTES) },
];
29 changes: 29 additions & 0 deletions ng-ui/src/app/core/icons-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
ClearOutline,
CloseOutline,
DashboardOutline,
FormOutline,
GroupOutline,
HomeOutline,
MenuFoldOutline,
MenuOutline,
MenuUnfoldOutline,
SearchOutline,
SettingOutline,
UserOutline,
} from '@ant-design/icons-angular/icons';

export const icons = [
MenuFoldOutline,
MenuUnfoldOutline,
DashboardOutline,
FormOutline,
HomeOutline,
SettingOutline,
UserOutline,
GroupOutline,
MenuOutline,
SearchOutline,
ClearOutline,
CloseOutline,
];
5 changes: 5 additions & 0 deletions ng-ui/src/app/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { indexInterceptor } from './net/http.Interceptor';
export { PageTitleStrategy } from './services/page-title-strategy';
export { AuthService } from './services/auth.service';
export { SessionStorageService } from './storage/session-storage';
export { BrowserStorage } from './storage/browser-storage';
95 changes: 95 additions & 0 deletions ng-ui/src/app/core/net/http.Interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { inject } from '@angular/core';
import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { catchError, finalize, Observable, throwError, timeout } from 'rxjs';
import { Router } from '@angular/router';
import { environment } from '../../../environments/environment';
import { ProgressBar } from '../services/progress-bar';
import { NzMessageService } from 'ng-zorro-antd/message';
import { AuthService } from '../services/auth.service';

/**
* 默认的HTTP拦截器,用于处理所有HTTP请求。
* 在请求发送前显示加载进度条,并在请求完成后隐藏。
* 如果请求的URL包含'assets/',则直接传递请求,不做任何修改。
* 对于其他请求,会添加'X-Requested-With'头部,并根据环境配置拼接完整的URL。
* 设置请求超时时间为5秒,如果请求在5秒内未完成,则触发超时处理。
* 使用finalize操作符确保加载进度条在请求完成后隐藏。
* @param req - HTTP请求对象
* @param next - 下一个HTTP处理函数
* @returns Observable<HttpEvent<unknown>> - HTTP响应事件的Observable对象
*/
function defaultInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
const _loading = inject(ProgressBar);
_loading.show();
if (req.url.indexOf('assets/') > -1) {
return next(req);
}
const originalUrl = req.url.indexOf('http') > -1 ? req.url : environment.host + req.url;
const xRequestedReq = req.clone({
headers: req.headers.append('X-Requested-With', 'XMLHttpRequest'),
url: originalUrl,
});
return next(xRequestedReq).pipe(
timeout({ first: 5_000, each: 10_000 }),
finalize(() => _loading.hide()),
);
}

/**
* 错误处理拦截器,用于处理 HTTP 请求过程中可能出现的错误。
* @param req - HTTP请求对象。
* @param next - 下一个HTTP处理函数。
* @returns 返回一个Observable,包含处理后的HTTP响应或错误信息。
* 错误处理包括:
* - 网络错误(状态码为0):提示用户检查网络连接。
* - 认证失败(状态码为401):登出用户并重定向到登录页面。
* - 其他服务器错误:提示用户稍后重试,并显示错误码和详细信息。
*/
function handleErrorInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn) {
const _message = inject(NzMessageService);
const _auth = inject(AuthService);
const _route = inject(Router);
return next(req).pipe(
catchError(error => {
if (error.status === 0) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error);
_message.error(`网络错误,请检查网络连接后重试!`);
} else {
if (error.status === 401) {
if (error.error.path === '/oauth2/login') {
return throwError(() => error.error);
}
_auth.logout();
_route.navigate([_auth.loginUrl]).then();
_message.error(`访问认证失败,错误码: ${error.status},详细信息: ${error.error.message}`);
return throwError(() => error.error);
} else {
_message.error(`服务器访问错误,请稍后重试. 错误码: ${error.status},详细信息: ${error.error}`);
}
}
console.error(`Backend returned code ${error.status}, body was: `, error.error);
return throwError(() => error);
}),
);
}

/**
* 认证令牌拦截器
* 在发送 HTTP 请求前,检查用户是否已登录。如果已登录,则在请求头中添加认证令牌。
* @param req - 原始 HTTP 请求
* @param next - 下一个处理程序函数
* @returns 返回一个 Observable,包含修改后的 HTTP 事件
*/
function authTokenInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
const _auth = inject(AuthService);
if (!_auth.isLogged()) {
return next(req);
}
const newReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${_auth.authToken()}`),
});
return next(newReq);
}

export const indexInterceptor = [defaultInterceptor, handleErrorInterceptor, authTokenInterceptor];
14 changes: 14 additions & 0 deletions ng-ui/src/app/core/pages.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type CanActivateFn, Router } from '@angular/router';
import { AuthService } from '.';
import { inject } from '@angular/core';

// 定义一个函数,用于判断用户是否已登录
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLogged()) {
return true;
}
auth.redirectUrl = state.url;
return router.parseUrl(auth.loginUrl);
};
Loading

0 comments on commit 87d71bb

Please sign in to comment.