PHP 怎么实现防沉迷

wen PHP项目 2

本文目录导读:

PHP 怎么实现防沉迷

  1. 基础实名认证 + 时间控制
  2. 核心防沉迷逻辑
  3. 调用示例
  4. API接口设计
  5. 高级功能补充
  6. 前端配合
  7. 注意事项

我来详细介绍PHP实现防沉迷系统的几种方案:

基础实名认证 + 时间控制

数据库设计

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    id_card VARCHAR(18) NOT NULL,  -- 身份证号
    real_name VARCHAR(50) NOT NULL,
    birthday DATE NOT NULL,        -- 出生日期
    status TINYINT DEFAULT 1,      -- 1:正常 0:冻结
    created_at TIMESTAMP
);
CREATE TABLE play_records (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    play_date DATE NOT NULL,
    start_time DATETIME,
    end_time DATETIME,
    duration INT DEFAULT 0,        -- 累计时长(分钟)
    FOREIGN KEY (user_id) REFERENCES users(id)
);

核心防沉迷逻辑

年龄验证类

<?php
class AntiAddiction {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 根据身份证号计算年龄
     */
    public function calculateAge($idCard) {
        // 获取出生日期 (身份证第7-14位)
        $birthday = substr($idCard, 6, 8);
        $birthYear = substr($birthday, 0, 4);
        $birthMonth = substr($birthday, 4, 2);
        $birthDay = substr($birthday, 6, 2);
        $birthDate = new DateTime("$birthYear-$birthMonth-$birthDay");
        $now = new DateTime();
        $age = $now->diff($birthDate)->y;
        return $age;
    }
    /**
     * 获取防沉迷策略
     */
    public function getPolicy($age) {
        if ($age < 8) {
            return [
                'allowed' => false,
                'daily_limit' => 0,           // 不允许游戏
                'night_ban' => null,           // 完全禁止
                'message' => '未满8周岁用户无法使用游戏服务'
            ];
        } elseif ($age < 16) {
            return [
                'allowed' => true,
                'daily_limit' => 60,           // 每天1小时
                'night_ban' => ['22:00', '08:00'], // 晚上10点至早8点禁止
                'message' => '未成年人需合理安排游戏时间'
            ];
        } elseif ($age < 18) {
            return [
                'allowed' => true,
                'daily_limit' => 120,          // 每天2小时
                'night_ban' => ['22:00', '08:00'],
                'message' => '未成年人需合理安排游戏时间'
            ];
        } elseif ($age >= 18) {
            return [
                'allowed' => true,
                'daily_limit' => null,          // 成年人不限制
                'night_ban' => null,
                'message' => '成年人请适度游戏'
            ];
        }
    }
    /**
     * 检查游戏状态
     */
    public function checkPlayStatus($userId) {
        // 获取用户信息
        $user = $this->getUser($userId);
        if (!$user) {
            return ['code' => 404, 'msg' => '用户不存在'];
        }
        // 计算用户年龄
        $age = $this->calculateAge($user['id_card']);
        $policy = $this->getPolicy($age);
        // 未满8岁
        if (!$policy['allowed']) {
            return [
                'code' => 403,
                'can_play' => false,
                'msg' => $policy['message']
            ];
        }
        // 检查夜间限制
        $now = date('H:i');
        if ($policy['night_ban']) {
            list($start, $end) = $policy['night_ban'];
            if ($this->isNightTime($now, $start, $end)) {
                return [
                    'code' => 403,
                    'can_play' => false,
                    'msg' => "当前时间($now)限制登录"
                ];
            }
        }
        // 检查今日累计时长
        $todayDuration = $this->getTodayDuration($userId);
        if ($policy['daily_limit'] && $todayDuration >= $policy['daily_limit']) {
            return [
                'code' => 403,
                'can_play' => false,
                'msg' => '今日游戏时间已达上限'
            ];
        }
        return [
            'code' => 200,
            'can_play' => true,
            'remaining_time' => $policy['daily_limit'] - $todayDuration,
            'policy' => $policy
        ];
    }
    /**
     * 记录游戏开始
     */
    public function startPlay($userId) {
        $status = $this->checkPlayStatus($userId);
        if (!$status['can_play']) {
            return $status;
        }
        $today = date('Y-m-d');
        $record = $this->db->query(
            "SELECT * FROM play_records WHERE user_id=$userId AND play_date='$today'"
        )->fetch_assoc();
        if ($record) {
            // 继续昨天的记录或者创建新记录
            $this->db->query(
                "UPDATE play_records SET start_time=NOW() WHERE id={$record['id']}"
            );
        } else {
            $this->db->query(
                "INSERT INTO play_records (user_id, play_date, start_time) 
                 VALUES ($userId, '$today', NOW())"
            );
        }
        return ['code' => 200, 'msg' => '开始游戏'];
    }
    /**
     * 记录游戏结束
     */
    public function endPlay($userId) {
        $today = date('Y-m-d');
        $record = $this->db->query(
            "SELECT * FROM play_records 
             WHERE user_id=$userId AND play_date='$today' AND end_time IS NULL
             ORDER BY id DESC LIMIT 1"
        )->fetch_assoc();
        if ($record) {
            $endTime = time();
            $startTime = strtotime($record['start_time']);
            $duration = ($endTime - $startTime) / 60; // 分钟
            $this->db->query(
                "UPDATE play_records 
                 SET end_time=NOW(), 
                     duration=duration + $duration 
                 WHERE id={$record['id']}"
            );
        }
        return ['code' => 200, 'msg' => '游戏结束'];
    }
    /**
     * 获取今日累计时长
     */
    private function getTodayDuration($userId) {
        $today = date('Y-m-d');
        $result = $this->db->query(
            "SELECT SUM(duration) as total 
             FROM play_records 
             WHERE user_id=$userId AND play_date='$today'"
        )->fetch_assoc();
        return $result['total'] ?? 0;
    }
    /**
     * 判断是否处于夜间禁止时段
     */
    private function isNightTime($now, $start, $end) {
        $now = strtotime($now);
        $start = strtotime($start);
        $end = strtotime($end);
        // 如果结束时间小于开始时间,说明跨天
        if ($end < $start) {
            return ($now >= $start || $now <= $end);
        }
        return ($now >= $start && $now <= $end);
    }
}

调用示例

// 使用示例
$db = new mysqli('localhost', 'username', 'password', 'database');
$anti = new AntiAddiction($db);
// 检查游戏状态
$result = $anti->checkPlayStatus(1);
if ($result['can_play']) {
    echo "可以开始游戏,剩余时间:" . $result['remaining_time'] . "分钟";
    $anti->startPlay(1);
} else {
    echo $result['msg'];
    exit;
}
// 游戏结束
$anti->endPlay(1);

API接口设计

<?php
// api.php
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
$userId = (int)($_POST['user_id'] ?? 0);
$anti = new AntiAddiction($db);
switch ($action) {
    case 'check':
        $result = $anti->checkPlayStatus($userId);
        break;
    case 'start':
        $result = $anti->startPlay($userId);
        break;
    case 'end':
        $result = $anti->endPlay($userId);
        break;
    default:
        $result = ['code' => 400, 'msg' => 'Invalid action'];
}
echo json_encode($result);

高级功能补充

跨平台时长累计

// 使用session或redis存储跨端数据
class CrossPlatformCounter {
    private $redis;
    public function addTime($userId, $platform, $duration) {
        $key = "user:{$userId}:playtime:".date('Y-m-d');
        $this->redis->incrBy($key, $duration);
        $this->redis->expire($key, 24*3600);
    }
    public function getTotalTime($userId) {
        // 从各个平台汇总
        return $this->redis->get("user:{$userId}:playtime:".date('Y-m-d'));
    }
}

定时任务清理

// cron.php - 每天凌晨执行
$sql = "DELETE FROM play_records WHERE play_date < DATE_SUB(NOW(), INTERVAL 7 DAY)";
$db->query($sql);

前端配合

// 前端定时检查
setInterval(async () => {
    const response = await fetch('/api.php?action=check', {
        method: 'POST',
        body: new URLSearchParams({ user_id: userId })
    });
    const data = await response.json();
    if (!data.can_play) {
        alert(data.msg);
        // 强制退出游戏
        location.href = '/logout.php';
    }
}, 60000); // 每分钟检查一次

注意事项

  1. 安全验证:身份证号需加密存储,使用AES加密
  2. 防绕过:服务端强制校验,不仅依赖前端
  3. 实名制:接入第三方实名认证接口
  4. 日志记录:记录所有登录和登出行为
  5. 异常处理:处理时区问题、跨天计算
  6. 性能优化:使用Redis缓存高频查询

这个方案涵盖了基本的防沉迷需求,实际生产环境还需根据具体业务场景进行调整优化。

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