PHP 用户在线状态维护

wen PHP项目 3

本文目录导读:

PHP 用户在线状态维护

  1. 基于Session的方案(最简单)
  2. 数据库方案(推荐)
  3. Redis方案(高性能)
  4. 实时推送方案(WebSocket)
  5. 性能优化建议
  6. 完整示例:综合方案
  7. 最佳实践建议

我来详细介绍PHP用户在线状态维护的几种方案:

基于Session的方案(最简单)

实现方式

<?php
// 使用session最后活动时间
session_start();
$_SESSION['last_activity'] = time();
// 判断用户是否在线(5分钟内活跃)
function isUserOnline($lastActivity) {
    return (time() - $lastActivity) < 300;
}
// 更新在线状态
function updateOnlineStatus($userId) {
    $_SESSION['user_id'] = $userId;
    $_SESSION['last_activity'] = time();
}
?>

优点: 简单易实现
缺点: 无法跨设备、无法持久化、Session过期即失效

数据库方案(推荐)

数据库表结构

CREATE TABLE user_online (
    user_id INT PRIMARY KEY,
    last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    ip_address VARCHAR(45),
    session_id VARCHAR(100),
    user_agent VARCHAR(255),
    INDEX idx_last_activity (last_activity)
);

核心实现代码

<?php
class UserOnlineManager {
    private $pdo;
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    // 记录用户活动
    public function trackActivity($userId) {
        $sessionId = session_id();
        $ip = $_SERVER['REMOTE_ADDR'];
        $userAgent = $_SERVER['HTTP_USER_AGENT'];
        $sql = "INSERT INTO user_online (user_id, last_activity, ip_address, session_id, user_agent) 
                VALUES (?, NOW(), ?, ?, ?)
                ON DUPLICATE KEY UPDATE 
                last_activity = NOW(),
                ip_address = VALUES(ip_address),
                session_id = VALUES(session_id)";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$userId, $ip, $sessionId, $userAgent]);
    }
    // 检查用户是否在线
    public function isOnline($userId, $timeout = 300) {
        $sql = "SELECT COUNT(*) FROM user_online 
                WHERE user_id = ? 
                AND last_activity > DATE_SUB(NOW(), INTERVAL ? SECOND)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId, $timeout]);
        return $stmt->fetchColumn() > 0;
    }
    // 获取在线用户列表
    public function getOnlineUsers($timeout = 300) {
        $sql = "SELECT user_id, last_activity FROM user_online 
                WHERE last_activity > DATE_SUB(NOW(), INTERVAL ? SECOND)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$timeout]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    // 清理过期记录(定期执行)
    public function cleanupExpired($timeout = 300) {
        $sql = "DELETE FROM user_online 
                WHERE last_activity < DATE_SUB(NOW(), INTERVAL ? SECOND)";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$timeout]);
    }
    // 用户登出时主动删除
    public function userLogout($userId) {
        $sql = "DELETE FROM user_online WHERE user_id = ?";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$userId]);
    }
}
?>

在应用中集成

<?php
// 在页面加载时调用
require_once 'UserOnlineManager.php';
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$onlineManager = new UserOnlineManager($pdo);
// 已登录用户每次请求都更新状态
if ($_SESSION['user_id']) {
    $onlineManager->trackActivity($_SESSION['user_id']);
}
// 在需要的地方检查在线状态
if ($onlineManager->isOnline(123)) {
    echo "用户在线";
} else {
    echo "用户离线";
}
?>

Redis方案(高性能)

使用Redis实现

<?php
class RedisUserOnline {
    private $redis;
    private $prefix = 'user:online:';
    public function __construct($redis) {
        $this->redis = $redis;
    }
    // 记录用户在线
    public function markOnline($userId) {
        $key = $this->prefix . $userId;
        return $this->redis->setex($key, 300, time());
    }
    // 检查用户是否在线
    public function isOnline($userId) {
        $key = $this->prefix . $userId;
        return $this->redis->exists($key);
    }
    // 批量获取在线状态
    public function getBulkOnline($userIds) {
        $keys = array_map(function($id) {
            return $this->prefix . $id;
        }, $userIds);
        return $this->redis->mget($keys);
    }
    // 获取所有在线用户(需要维护一个集合)
    public function markOnlineWithTracking($userId) {
        $key = $this->prefix . $userId;
        $this->redis->setex($key, 300, time());
        // 记录在线集合
        $this->redis->sAdd('online:users', $userId);
    }
    // 获取在线用户数
    public function getOnlineCount() {
        return $this->redis->scard('online:users');
    }
}
?>

实时推送方案(WebSocket)

结合Node.js + Socket.io

// client端JavaScript
const socket = io('wss://your-server.com');
// 记录用户活动
setInterval(() => {
    socket.emit('heartbeat', { userId: userId });
}, 60000); // 每分钟发送一次心跳
// 实时获取在线用户
socket.on('onlineUsers', (users) => {
    console.log('在线用户:', users);
});

PHP服务端处理心跳

<?php
// 通过API接口接收心跳
public function handleHeartbeat($userId) {
    // 更新数据库或Redis状态
    $this->trackActivity($userId);
    // 广播在线状态变化(通过消息队列或WebSocket)
    $broadcastData = [
        'user_id' => $userId,
        'status' => 'online'
    ];
}
?>

性能优化建议

批量更新

<?php
// 使用延迟批量更新
class BatchOnlineManager {
    private $pendingUpdates = [];
    public function queueActivity($userId) {
        $this->pendingUpdates[$userId] = time();
    }
    public function flushUpdates() {
        if (empty($this->pendingUpdates)) return;
        $sql = "INSERT INTO user_online (user_id, last_activity) VALUES ";
        $values = [];
        $params = [];
        foreach ($this->pendingUpdates as $userId => $time) {
            $values[] = "(?, ?)";
            $params[] = $userId;
            $params[] = date('Y-m-d H:i:s', $time);
        }
        $sql .= implode(',', $values);
        $sql .= " ON DUPLICATE KEY UPDATE last_activity = VALUES(last_activity)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        $this->pendingUpdates = [];
    }
    public function __destruct() {
        $this->flushUpdates();
    }
}
?>

完整示例:综合方案

<?php
class OnlineUserService {
    private $db;
    private $redis;
    private $timeout = 300; // 5分钟超时
    public function __construct($db, $redis = null) {
        $this->db = $db;
        $this->redis = $redis;
    }
    // 核心方法:更新在线状态
    public function updateStatus($userId) {
        // 方案1:更新数据库
        $this->updateDatabase($userId);
        // 方案2:更新Redis(如果可用)
        if ($this->redis) {
            $this->updateRedis($userId);
        }
    }
    private function updateDatabase($userId) {
        $sql = "UPDATE users SET last_active = NOW(), is_online = 1 
                WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId]);
    }
    private function updateRedis($userId) {
        $key = "user:online:{$userId}";
        $this->redis->setex($key, $this->timeout, 1);
    }
    // 检查在线状态
    public function checkOnline($userId) {
        // 优先从Redis读取
        if ($this->redis) {
            $result = $this->redis->get("user:online:{$userId}");
            if ($result !== null) {
                return $result == 1;
            }
        }
        // 回退到数据库查询
        $sql = "SELECT is_online, last_active FROM users WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId]);
        $user = $stmt->fetch();
        if ($user && $user['is_online']) {
            return (time() - strtotime($user['last_active'])) <= $this->timeout;
        }
        return false;
    }
    // 获取在线用户数量
    public function getOnlineCount() {
        if ($this->redis) {
            // Redis方式
            $keys = $this->redis->keys('user:online:*');
            return count($keys);
        } else {
            // 数据库方式
            $sql = "SELECT COUNT(*) FROM users 
                    WHERE is_online = 1 
                    AND last_active > DATE_SUB(NOW(), INTERVAL 5 MINUTE)";
            return $this->db->query($sql)->fetchColumn();
        }
    }
}
?>

最佳实践建议

  1. 超时设置:根据业务需求设置合理的超时时间(通常3-5分钟)
  2. 清理机制:定期清理过期记录,防止数据膨胀
  3. 性能考虑:高并发场景使用Redis,低并发使用数据库
  4. 用户感知:在页面加载时更新状态,离开时发送离线信号
  5. 安全考虑:防止恶意刷在线状态,验证用户身份后再更新
  6. 扩展性:考虑支持多设备登录,每个设备独立维护状态

根据实际需求选择合适方案,小型应用用Session或数据库即可,大型应用建议使用Redis或WebSocket方案。

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