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