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

Laravel中如何用Auth Policies控制日历预约JSON输出的属性可见性?

Laravel日历预约数据可见性控制方案

针对你提到的四种日历共享读取权限,结合Laravel的生态工具,推荐采用「Policies 授权判断 + 查询范围(Scope)过滤数据集 + 模型资源(Resource)控制属性可见性」的组合方案,既符合Laravel的设计理念,又能安全高效地处理权限逻辑。

核心思路

  1. Policies:负责判断用户对目标日历的权限等级(1-4),替代布尔型判断,直接返回权限标识。
  2. 查询范围(Scope):提前过滤用户无权访问的预约数据(比如规则1仅保留自己的预约,规则2排除他人的私密预约),减少后续属性处理的冗余。
  3. 模型资源(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 15:35:17