import { Injectable, NestInterceptor, ExecutionContext, CallHandler, ForbiddenException, Logger, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { PrePlantingClient } from './pre-planting.client'; /** * 预种授权申请拦截器 * * 仅拦截用户端授权申请路由(POST /authorizations/...) * 规则: * - 无预种记录(纯认种用户)→ 直接放行 * - 有预种记录且已合并成树 → 放行 * - 有预种记录但未合并 → 拦截 * - planting-service 不可达 → 放行(fail-open,不影响现有功能) */ @Injectable() export class PrePlantingAuthorizationInterceptor implements NestInterceptor { private readonly logger = new Logger(PrePlantingAuthorizationInterceptor.name); private readonly protectedPaths = [ '/authorizations/community', '/authorizations/province', '/authorizations/city', '/authorizations/self-apply', ]; constructor(private readonly client: PrePlantingClient) {} async intercept( context: ExecutionContext, next: CallHandler, ): Promise> { const req = context.switchToHttp().getRequest(); // 仅拦截 POST 请求 if (req.method !== 'POST') { return next.handle(); } // 仅拦截特定路由 const reqPath: string = req.path || req.url || ''; if (!this.protectedPaths.some((p) => reqPath.endsWith(p))) { return next.handle(); } const accountSequence = req.user?.accountSequence; if (!accountSequence) { return next.handle(); } try { const eligibility = await this.client.getEligibility(accountSequence); // 无预种记录 → 纯认种用户,直接放行 if (!eligibility.hasPrePlanting) { return next.handle(); } // 有预种但未满足条件 → 拦截 if (!eligibility.canApplyAuthorization) { throw new ForbiddenException( '须累积购买5份预种计划合并成树后方可申请授权', ); } } catch (error) { if (error instanceof ForbiddenException) throw error; // planting-service 不可达,默认放行 this.logger.warn( `[PRE-PLANTING] Failed to check eligibility for ${accountSequence}, allowing through`, ); } return next.handle(); } }