Laravel中如何用Auth Policies控制日历预约JSON输出的属性可见性?
Laravel日历预约数据可见性控制方案
针对你提到的四种日历共享读取权限,结合Laravel的生态工具,推荐采用「Policies 授权判断 + 查询范围(Scope)过滤数据集 + 模型资源(Resource)控制属性可见性」的组合方案,既符合Laravel的设计理念,又能安全高效地处理权限逻辑。
核心思路
- Policies:负责判断用户对目标日历的权限等级(1-4),替代布尔型判断,直接返回权限标识。
- 查询范围(Scope):提前过滤用户无权访问的预约数据(比如规则1仅保留自己的预约,规则2排除他人的私密预约),减少后续属性处理的冗余。
- 模型资源(Resource):根据权限等级和预约的私密状态,精准控制返回的属性字段,避免手动赋值null的风险。
具体实现步骤
1. 定义权限等级存储
在日历共享关联表(如calendar_shares)中添加permission_level字段,用数字对应四种规则:
- 1:仅查看自己添加的预约
- 2:查看自己的预约+非私密预约
- 3:查看自己的预约+非私密预约+私密预约的起止时间
- 4:无限制查看全部内容
2. 更新Appointment Policy
在AppointmentPolicy中新增方法,返回用户对目标日历的权限等级:
namespace App\Policies; use App\Models\User; use App\Models\Calendar; class AppointmentPolicy { /** * 获取用户对日历的权限等级 */ public function getPermissionLevel(User $user, Calendar $calendar): int { // 日历所有者默认拥有最高权限 if ($calendar->owner_id === $user->id) { return 4; } // 从共享表获取权限等级,无权限返回0 return $calendar->shares() ->where('user_id', $user->id) ->value('permission_level') ?? 0; } /** * 基础授权:判断用户是否有权访问该日历的预约 */ public function view(User $user, Calendar $calendar): bool { return $this->getPermissionLevel($user, $calendar) > 0; } }
3. 添加模型查询范围
在Appointment模型中定义查询范围,提前过滤用户能访问的预约集合:
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use App\Models\User; class Appointment extends Model { protected $fillable = [ 'calendar_id', 'user_id', 'start', 'end', 'title', 'description', 'location', 'is_private' ]; /** * 过滤用户有权访问的预约 */ public function scopeForUser(Builder $query, User $user): Builder { return $query->whereHas('calendar', function (Builder $q) use ($user) { $q->where(function (Builder $q) use ($user) { // 所有者能查看全部预约 $q->where('owner_id', $user->id); })->orWhere(function (Builder $q) use ($user) { // 共享用户根据权限过滤 $q->whereHas('shares', function (Builder $q) use ($user) { $q->where('user_id', $user->id) ->whereIn('permission_level', [1,2,3,4]); }); }); })->when(true, function (Builder $query) use ($user) { $query->where(function (Builder $q) use ($user) { $calendar = $this->calendar; $permissionLevel = app(AppointmentPolicy::class)->getPermissionLevel($user, $calendar); // 权限1:仅保留自己创建的预约 if ($permissionLevel === 1) { $q->where('user_id', $user->id); } // 权限2:排除他人的私密预约 if ($permissionLevel === 2) { $q->where(function (Builder $subQ) use ($user) { $subQ->where('user_id', $user->id) ->orWhere('is_private', false); }); } }); }); } // 关联日历模型 public function calendar() { return $this->belongsTo(Calendar::class); } }
4. 创建模型资源控制属性可见性
生成AppointmentResource,根据权限等级和预约状态过滤返回字段:
namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class AppointmentResource extends JsonResource { public function toArray($request) { $user = $request->user(); $permissionLevel = app(\App\Policies\AppointmentPolicy::class)->getPermissionLevel($user, $this->calendar); // 自己创建的预约或最高权限,返回全部属性 if ($this->user_id === $user->id || $permissionLevel === 4) { return [ 'id' => $this->id, 'start' => $this->start, 'end' => $this->end, 'title' => $this->title, 'description' => $this->description, 'location' => $this->location, 'is_private' => $this->is_private, ]; } // 非私密预约,权限2及以上返回全部属性 if (!$this->is_private) { return [ 'id' => $this->id, 'start' => $this->start, 'end' => $this->end, 'title' => $this->title, 'description' => $this->description, 'location' => $this->location, 'is_private' => $this->is_private, ]; } // 权限3:仅返回私密预约的核心字段 if ($permissionLevel === 3) { return [ 'id' => $this->id, 'start' => $this->start, 'end' => $this->end, 'is_private' => true, ]; } // 兜底:无权限返回空数组 return []; } }
5. 控制器中整合逻辑
在控制器中先授权,再获取过滤后的数据集,最后返回资源:
namespace App\Http\Controllers; use App\Models\Calendar; use App\Models\Appointment; use App\Http\Resources\AppointmentResource; class AppointmentController extends Controller { public function index(Calendar $calendar) { // 先验证用户是否有权访问该日历 $this->authorize('view', $calendar); // 获取用户有权查看的预约集合 $appointments = Appointment::forUser(auth()->user()) ->where('calendar_id', $calendar->id) ->get(); // 返回资源集合 return AppointmentResource::collection($appointments); } }
额外优化建议
- 将权限等级的判断逻辑封装成Trait,在模型和Policy中复用,减少代码冗余。
- 若需要在前端展示不同的UI状态,可在资源中添加
permission_level字段,方便前端判断。 - 对于敏感属性(如
description),在权限不足时直接不返回字段,比设置为null更安全,避免前端误展示。
内容的提问来源于stack exchange,提问作者user2190492
相关产品推荐
相关产品推荐

