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

Laravel API中如何结合Service与Policy管理拒签按钮权限?

解决方案

要同时满足前端按钮权限控制和业务逻辑安全性的需求,你可以在Policy中添加报价已拒签的检查,同时保留Service中的校验逻辑,两者各司其职:

1. 更新QuotePolicy,加入已拒签检查

在Policy的reject方法中,首先判断报价是否已拒签,若已拒签直接返回拒绝,这样前端通过权限判断就能知道是否要显示「拒签」按钮:

public function reject(User $user, Quote $quote, AppTypeEnum $app): Response
{
    // 优先检查报价状态:已拒签则直接拒绝操作权限
    if ($quote->rejected_at) {
        return Response::deny('报价已完成拒签');
    }

    // 原有权限与应用类型校验逻辑
    if (!$user->can('reject quotes')) {
        return Response::deny();
    }

    if ($app === AppTypeEnum::CONSUMER) {
        return Response::allow();
    }

    return Response::deny();
}

2. 保留Service中的校验逻辑

Service里的reject方法不要改动,继续保留已拒签的检查。这是因为Policy的权限控制可能被绕过(比如后台内部直接调用Service、并发操作导致状态在Policy检查后变更),Service作为业务逻辑的核心层,必须自己校验状态,确保数据一致性,避免重复操作:

public function reject(Quote $quote): bool
{
    if ($quote->rejected_at) {
        throw new QuoteAlreadyRejectedException();
    }

    return $quote->update(['rejected_at' => Carbon::now()]);
}

3. 调整Controller的异常捕获(可选)

因为现在Policy会提前拦截已拒签的请求,会抛出AuthorizationException,所以需要在Controller中新增该异常的捕获,统一返回错误响应:

public function reject(Quote $quote): JsonResponse
{
    try {
        $this->authorize('reject', [$quote, AppTypeEnum::CONSUMER]);
        $success = $this->quoteService->reject($quote);
    } catch (QuoteAlreadyRejectedException|AuthorizationException $e) {
        return $this->errorResponse($e->getMessage(), $e->getCode());
    }

    return $this->successResponse($success);
}

逻辑说明

  • Policy的作用是对外提供权限判断依据,给前端返回是否显示按钮的状态,所以必须包含业务状态(已拒签)的检查;
  • Service的作用是保证业务逻辑的正确性,即使权限校验通过,也要再次校验状态,避免并发或绕过权限的操作导致数据错误。

这样既解决了前端按钮显示的问题,又确保了业务逻辑的安全性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:53:09