fix(admin-service): 修复维护拦截器路径检测和错误处理

问题:添加系统维护检测后站内通知功能失效

修复:
1. 使用 request.url 获取完整路径(包含 /api/v1 前缀)
2. 同时支持带前缀和不带前缀的路径检测
3. 添加 try-catch 错误处理,数据库错误时放行请求而非阻断
4. 添加日志记录便于调试

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-02 05:01:32 -08:00
parent c392142562
commit fea0b42223
1 changed files with 56 additions and 34 deletions

View File

@ -6,6 +6,7 @@ import {
HttpException, HttpException,
HttpStatus, HttpStatus,
Inject, Inject,
Logger,
} from '@nestjs/common'; } from '@nestjs/common';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { import {
@ -19,6 +20,8 @@ import {
*/ */
@Injectable() @Injectable()
export class MaintenanceInterceptor implements NestInterceptor { export class MaintenanceInterceptor implements NestInterceptor {
private readonly logger = new Logger(MaintenanceInterceptor.name);
constructor( constructor(
@Inject(SYSTEM_MAINTENANCE_REPOSITORY) @Inject(SYSTEM_MAINTENANCE_REPOSITORY)
private readonly maintenanceRepo: ISystemMaintenanceRepository, private readonly maintenanceRepo: ISystemMaintenanceRepository,
@ -28,23 +31,32 @@ export class MaintenanceInterceptor implements NestInterceptor {
context: ExecutionContext, context: ExecutionContext,
next: CallHandler, next: CallHandler,
): Promise<Observable<any>> { ): Promise<Observable<any>> {
try {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const path = request.path; // 获取请求路径,优先使用 url去除查询参数
const path = (request.url?.split('?')[0] || request.path) as string;
// 白名单路径 - 这些路径在维护期间也可以访问 // 白名单路径 - 这些路径在维护期间也可以访问
// 支持带 /api/v1 前缀和不带前缀的两种情况
const whitelist = [ const whitelist = [
'/mobile/system/maintenance-status', // 维护状态检查 '/api/v1/mobile/system/maintenance-status',
'/health', // 健康检查 '/mobile/system/maintenance-status',
'/api/health', // 健康检查(带前缀) '/api/v1/health',
'/health',
]; ];
// 只拦截移动端 API以 /mobile 开头的路径) // 检查是否是移动端 API需要同时检查带前缀和不带前缀的路径
if (!path.startsWith('/mobile')) { const isMobileApi =
path.includes('/mobile/') ||
path.startsWith('/mobile');
// 非移动端 API直接放行
if (!isMobileApi) {
return next.handle(); return next.handle();
} }
// 检查是否在白名单中 // 检查是否在白名单中
if (whitelist.some((p) => path.startsWith(p))) { if (whitelist.some((p) => path === p || path.startsWith(p + '/'))) {
return next.handle(); return next.handle();
} }
@ -52,6 +64,7 @@ export class MaintenanceInterceptor implements NestInterceptor {
const activeMaintenance = await this.maintenanceRepo.findActiveMaintenance(); const activeMaintenance = await this.maintenanceRepo.findActiveMaintenance();
if (activeMaintenance) { if (activeMaintenance) {
this.logger.warn(`系统维护中,阻断请求: ${path}`);
throw new HttpException( throw new HttpException(
{ {
statusCode: HttpStatus.SERVICE_UNAVAILABLE, statusCode: HttpStatus.SERVICE_UNAVAILABLE,
@ -68,6 +81,15 @@ export class MaintenanceInterceptor implements NestInterceptor {
); );
} }
return next.handle();
} catch (error) {
// 如果是我们抛出的 HttpException直接重新抛出
if (error instanceof HttpException) {
throw error;
}
// 其他错误(如数据库错误),记录日志但不阻断请求
this.logger.error(`维护状态检查失败,放行请求: ${error.message}`);
return next.handle(); return next.handle();
} }
} }
}