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
相关产品推荐
相关产品推荐

