PHP过期会员怎么处理

wen PHP项目 4

本文目录导读:

PHP过期会员怎么处理

  1. 基础数据模型设计(假设)
  2. 实时校验(核心拦截器)
  3. 定时任务(Cron)批量处理
  4. 过渡期与宽限期(Grace Period)
  5. 立即失效与“永久”处理
  6. 前端表现与体验
  7. 防止日期篡改(安全)
  8. 完整处理流程图(伪代码)
  9. 如果使用第三方支付(如 Stripe/支付宝)
  10. 总结建议

处理 PHP 中会员过期(Subscription Expiry)的逻辑,不仅仅是写一个 if 判断当前时间是否大于到期时间,良好的设计需要考虑数据模型定时任务权限校验以及用户体验

以下是分层次的完整处理方案:

基础数据模型设计(假设)

在数据库用户表中,通常需要存储以下日期字段:

  • plan_expiry:当前套餐的到期时间戳(或 DateTime)。
  • plan_type:套餐类型(如 monthly, yearly, admin)。

核心校验规则(伪代码):

if ($user->plan_expiry > now()) {
    // 有效期内,放行
} else {
    // 已过期,拒访 / 降级为游客
}

实时校验(核心拦截器)

在用户每次请求“受保护”接口或页面时,执行实时检查

中间件/基类控制器示例:

public function checkMembership(User $user) {
    // 判断是否过期
    $isExpired = $user->plan_expiry->isPast();
    if ($isExpired) {
        // 方案A:直接拒绝访问
        abort(403, '您的会员已过期,请续费');
        // 方案B:自动降级为普通用户(修改角色ID)
        // $user->update(['role' => 'guest']);
    }
}

注意: 即使有定时任务,实时校验也不能省略,因为定时任务可能延迟或失败。


定时任务(Cron)批量处理

使用 Laravel Task Scheduling 或 Linux Crontab,每天凌晨运行脚本清理过期用户。

定时任务操作逻辑(Laravel 示例):

// app/Console/Commands/ExpireMembers.php
$expiredUsers = User::where('plan_type', '!=', 'free')
                ->where('plan_expiry', '<', now())
                ->get();
foreach ($expiredUsers as $user) {
    // 1. 修改用户角色为“游客”
    $user->role = 'guest';
    // 2. 清理登录缓存/Token权限
    // $user->tokens()->delete(); // 如果用了 Sanctum/Passport
    // 3. 发送过期邮件通知(可选)
    Mail::to($user)->send(new MembershipExpired($user));
    $user->save();
}

Cron 配置(Laravel):

* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1

过渡期与宽限期(Grace Period)

为了避免用户“秒变”无权访问,可以设置宽限期(过期后 3 天内)

$graceEnd = $user->plan_expiry->addDays(3);
if (now()->between($user->plan_expiry, $graceEnd)) {
    // 宽限期内:允许看部分资源,但弹窗提示续费
} elseif (now()->greaterThan($graceEnd)) {
    // 完全锁定
}

立即失效与“永久”处理

(1)立即失效: 如果是 VIP 账号,建议在用户点击“取消订阅”或支付失败时,立即调用 update 方法将 plan_expiry 设置为当前时间,而不是等它自然过期。

(2)永久会员(Admin/终身): 设定一个超大值或使用 is_lifetime 布尔字段。

if ($user->is_lifetime || $user->plan_expiry == null) {
    // 放行
}

前端表现与体验

不要只显示“403 错误”,要让用户知道如何解决。

  1. API 响应:

    return response()->json([
        'status' => 'expired',
        'message' => '会员已过期',
        'renew_url' => '/subscription/renew'
    ], 403);
  2. 页面重定向: 如果已过期,重定向到“续费页面”,并带上当前用户ID,方便支付后直接更新到期时间。


防止日期篡改(安全)

  • 避免前端传参决定权限:权限判断必须是服务端根据数据库记录,不能依赖 request 传来的时间。
  • 加密签名:如果会员在微信/APP端,注意防止用户修改 plan_expiry 参数。
  • 使用 JWT Token 时,在 Token 中携带过期时间,中间件校验 Token 数据库时间,以数据库为准。

完整处理流程图(伪代码)

graph TD
    A[用户请求] --> B{Is Lifetime?}
    B -- Yes --> C[允许访问]
    B -- No --> D{plan_expiry > now?}
    D -- Yes --> C
    D -- No --> E{在宽限期?}
    E -- Yes --> F[允许访问 + 提示续费]
    E -- No --> G[强制登出/降级为游客]
    G --> H[跳转续费页面]

如果使用第三方支付(如 Stripe/支付宝)

订阅场景:建议使用 Webhook(回调) 来处理续费/到期,而不是完全依赖定时任务。

  • invoice.payment_succeeded -> 延长到期时间。
  • customer.subscription.deleted -> 立即将 plan_expiry 置为当前时间。

总结建议

最推荐的组合是:“数据库时间戳 + 中间件实时校验 + 定时任务清理(重体力操作)+ 优雅的降级提示”,不要为了减少数据库查询而将所有过期逻辑依赖定时任务,因为实时校验才是“最后一道防线”。

抱歉,评论功能暂时关闭!