You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular:解析器获取数据后的路由守卫及异常处理方案咨询

Angular发票详情页路由权限处理最佳方案

针对你提出的/invoices/:invoiceId路由的双重保护需求,结合守卫与解析器的执行顺序限制,以下是两种可行的解决方案及优劣分析:

方案一:在解析器中直接处理错误重定向(推荐)

解析器不仅可以返回数据,还支持返回UrlTree触发路由重定向,无需组件介入即可在路由层面完成拦截,完全符合权限校验的职责划分。

修改后的invoice.resolver.ts:

import { inject } from '@angular/core';
import { ResolveFn, Router } from '@angular/router';
import { InvoiceApiService } from './invoice-api.service';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { ToastService } from './toast.service'; // 用于错误提示的服务

export const invoiceResolver: ResolveFn<any> = (route) => {
  const invoiceId = parseInt(route.paramMap.get('invoiceId') ?? '0', 10);
  const invoiceApiService = inject(InvoiceApiService);
  const router = inject(Router);
  const toastService = inject(ToastService);

  return invoiceApiService.invoice(invoiceId).pipe(
    catchError((error) => {
      // 根据错误类型针对性处理
      if ([403, 404].includes(error.status)) {
        toastService.showError('无权访问该发票');
      } else {
        toastService.showError('服务器错误,请稍后重试');
      }
      // 返回UrlTree触发首页重定向
      return of(router.createUrlTree(['/']));
    })
  );
};

优势:

  • 逻辑集中在路由层,组件无需处理权限校验逻辑
  • 组件不会被加载,避免页面闪烁等不良体验
  • 同时完成数据预加载与权限验证,符合Resolver的设计初衷

方案二:用CanActivate守卫加载并验证发票

若不想依赖Resolver,可新增一个CanActivate守卫,在守卫中完成发票加载、权限校验,并通过共享服务将数据传递给组件。

路由配置修改

{
  path: 'invoice/:invoiceId',
  component: InvoiceComponent,
  canActivate: [authGuard, invoiceOwnerGuard]
}

实现invoiceOwnerGuard.ts

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { InvoiceApiService } from './invoice-api.service';
import { catchError, map } from 'rxjs/operators';
import { of } from 'rxjs';
import { ToastService } from './toast.service';
import { InvoiceDataService } from './invoice-data.service'; // 共享数据服务
import { AuthenticationPolicy } from './authentication-policy';

export const invoiceOwnerGuard: CanActivateFn = (route) => {
  const invoiceId = parseInt(route.paramMap.get('invoiceId') ?? '0', 10);
  const invoiceApiService = inject(InvoiceApiService);
  const router = inject(Router);
  const toastService = inject(ToastService);
  const invoiceDataService = inject(InvoiceDataService);
  const authPolicy = inject(AuthenticationPolicy);

  return invoiceApiService.invoice(invoiceId).pipe(
    map(invoice => {
      // 校验发票归属当前登录用户
      const currentUserId = authPolicy.getCurrentUserId();
      if (invoice.userId === currentUserId) {
        invoiceDataService.setInvoice(invoice); // 存储数据供组件使用
        return true;
      } else {
        toastService.showError('无权访问该发票');
        return router.createUrlTree(['/']);
      }
    }),
    catchError(() => {
      toastService.showError('加载发票失败');
      return of(router.createUrlTree(['/']));
    })
  );
};

组件中获取数据

import { Component, OnInit } from '@angular/core';
import { InvoiceDataService } from './invoice-data.service';

@Component({
  selector: 'app-invoice',
  templateUrl: './invoice.component.html'
})
export class InvoiceComponent implements OnInit {
  invoice: Invoice | null = null;

  constructor(private invoiceDataService: InvoiceDataService) {}

  ngOnInit() {
    this.invoice = this.invoiceDataService.getInvoice();
  }
}

优势:将所有权限校验逻辑集中在守卫层,职责清晰;劣势:需要额外维护共享数据服务,不如Resolver预加载数据的方式简洁。

不推荐方案:组件中处理解析为null的情况

若Resolver返回null后在组件内判断重定向,会导致组件短暂初始化(可能出现页面闪烁),违反路由层负责权限拦截的单一职责原则,同时分散了逻辑,不利于维护。

内容的提问来源于stack exchange,提问作者schaenk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:50:15