【问题标题】:Inactive/browser close - change status to 0 and back to 1 when user returns非活动/浏览器关闭 - 当用户返回时将状态更改为 0 并返回 1
【发布时间】:2025-11-28 10:30:01
【问题描述】:

我正在尝试让我的网站在在线用户处于非活动状态或关闭浏览器时将其状态设置为 0。 如果他们重新打开网站或从空闲状态恢复(超时后),我希望状态恢复为 1(除非他们由于长时间不在网站而完全注销)

这是我目前尝试过的:

Inactive.php

include 'db.php';
mysql_query("UPDATE users SET status = 0 WHERE user_id = ".$_SESSION['user_id']."");

检查浏览器是否关闭

window.onbeforeunload = function() {
        $.ajax({
            url: 'inactive.php',
            type: 'GET',
            async: false,
            timeout: 4000
        });
    };

检查空闲超时

var IDLE_TIMEOUT = 60; //seconds
var _idleSecondsCounter = 0;
document.onclick = function() {
    _idleSecondsCounter = 0;
};
document.onmousemove = function() {
    _idleSecondsCounter = 0;
};
document.onkeypress = function() {
    _idleSecondsCounter = 0;
};
window.setInterval(CheckIdleTime, 1000);

function CheckIdleTime() {
    _idleSecondsCounter++;
    var oPanel = document.getElementById("SecondsUntilExpire");
    if (oPanel)
        oPanel.innerHTML = (IDLE_TIMEOUT - _idleSecondsCounter) + "";
    if (_idleSecondsCounter >= IDLE_TIMEOUT) {
        alert("Time expired!");
        document.location.href = "inactive.php";
    }
}

我的查询似乎不起作用。如何告诉它每 x 秒检查哪个用户?

【问题讨论】:

  • 真的需要阅读 SQL 注入和预处理语句。
  • 我读到了它们,我现在只是在测试。稍后我将重点关注安全性。

标签: javascript php ajax


【解决方案1】:

window.onbeforeunload 将产生竞争条件并且不会非常可靠。你也想改用window.addEventListener('beforeunload', function () {...});

CheckIdleTime 中的alert 将停止javascript 的执行,因此用户必须进行交互(单击OK)才能注销。否则,这种方法似乎很好。

现在,当用户离开页面时,通常您会将 cookie 设置为在离开网站时过期,但您似乎希望在您的网站中拥有活跃用户的运行记录。

为此,您可能需要一个两步方法,主动为“最后一次活动”设置标志和时间戳。如果您有一段时间没有看到用户的任何活动,还会运行一个垃圾收集脚本,将用户设置为“非活动”。

如果您需要真正实时的活动用户日志,您可能需要查看Node.js,尤其是Socket.IO,它可以更好地处理实时客户端-服务器 IO。

运行一个更新用户说他们实际上是活跃的查询可能要容易得多

 <script>
 setInterval(function () {
      $.post('active.php');
 }, 
 15000 // every 15 seconds
 );
 </script>

在active.php中(假设你添加了一个新的last_activeDATETIME,而user_id是一个int:

 mysql_query("UPDATE users SET status = 1, `last_active` = NOW() WHERE user_id = ". (int)$_SESSION['user_id']."");
 mysql_query("UPDATE users SET status = 0 WHERE `status` = 1 AND `last_active` < DATE_SUB(NOW(), INTERVAL 15 SECOND)"); // set all in active who have  not pinged in the last 15 seconds

这可能是架构的外观

 CREATE TABLE `users`
    `id` IN NOT NULL,
    `status` INT(1) DEFAULT 0,
    `last_active` DATETIME
 );

您可能想在“非活动”间隔中玩一下,并考虑创建一个索引。

【讨论】:

  • 你能告诉我如何在浏览器关闭时执行代码吗?我只是发出警报进行测试,但它必须以某种方式运行查询以将状态设置为 0。对于空闲,我确实为网站上的每一个动作设置了时间戳,但不确定如何将其设置为非活动状态(或状态到 0) 使用 ajax
  • @Gadgetster 我认为这是主要的收获,你不想证明某人是“不活跃的”你想证明他们是“活跃的”如果你解决的问题是这样它的错误就会少得多,而且准确得多。
  • 所以你是说默认情况下每个人都设置为 0,但如果他们处于活动状态,则设置为 1。好吧,我如何使用 ajax 运行检查,每 15 秒更新一次查询?
  • 我有一个 last_active int 和一个 user_id int。我完全按照你说的做了,我不知道它是否有效。每次我尝试刷新本地主机以查看状态是否更改时,它都会像使用网站一样使用它并更新时间。我尝试不触摸任何东西以查看用户是否下线,但它没有做任何事情。
  • last_active 应该是您手动更新的 TIMESTAMPDATETIME,而不是 int。
【解决方案2】:

修改代码

此代码将引用我在此处编写的示例 >> jquery-idle with jquery-ui Dialog

使用的库:

嵌入式库示例:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="http://thorst.github.io/jquery-idletimer/prod//src/idle-timer.js"></script>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css" />
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>

没有 jQuery 对话框:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="http://thorst.github.io/jquery-idletimer/prod//src/idle-timer.js"></script>

请记住,您可以使用您喜欢的任何对话方法来切换对话代码。我在对话框中包含了 jquery-ui,以使事情尽可能简单。这也不处理beforeunload 事件,因为您的代码中已经涵盖了该事件,但是我建议您在此处进一步阅读>> beforeunload * article

说明

HTML


这行代码用于存储倒数计时器的占位符。为简化起见,我也在计时器到期时使用它来显示“Session Expired”

<div id="sessionSecondsRemaining" style="font-size: 22px; font-weight: bolder;"></div>

这是一个使用 jQuery UI 的非常简单的模态对话框。您可以随意扩展或替换它。

<div id="dialog-confirm" title="Logout" style="display: none;">
<p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;">Your session has expired.</span></p>
</div>

没有 jQuery 对话框

<div id="sessionSecondsRemaining" style="display:none;"></div>

CSS


这只是一个小技巧,因为灰色背景中的错误无法正确显示 jQuery UI 模式对话框(为什么尚未修复 - facepalm

/* override jquery ui overlay style */
.ui-widget-overlay {
   background-image: none !important; /* FF fix */
   background: #000 url(images/new-ui-overlay.png) 50% 50% repeat-x;
}

没有 jQuery 对话框

  • 无需 CSS

Javascript


您可以在此部分配置 jquery-idletimer 的参数。

        var
            session = {
                //Logout Settings
                inactiveTimeout: 10000,     //(ms) The time until we display a warning message
                warningTimeout: 10000,      //(ms) The time until we log them out
                minWarning: 5000,           //(ms) If they come back to page (on mobile), The minumum amount, before we just log them out
                warningStart: null,         //Date time the warning was started
                warningTimer: null,         //Timer running every second to countdown to logout
                logout: function () {       //Logout function once warningTimeout has expired
                    //window.location = settings.autologout.logouturl;
                },

                //Keepalive Settings
                keepaliveTimer: null,
                keepaliveUrl: "",   // set the Keep Alive URL here (aka keepalive.php)
                keepaliveInterval: 5000,     //(ms) the interval to call said url
                keepAlive: function () {
                    $.ajax({ url: session.keepaliveUrl });
                }
            }
        ;

要添加“keepalive.php”支持,只需设置 keepalive.php 所在位置的完整 URL(以及您希望传递的任何参数,因为您正在使用会话,所以您不需要任何参数)。

                keepaliveUrl: "http://example.com/keepalive.php",   // set the Keep Alive URL here (aka keepalive.php)

这一行,初始化并设置 #sessionSecondsRemaining div 中的值。

                $('#sessionSecondsRemaining').html(Math.round((session.warningTimeout - diff) / 1000));

您可以在此部分放置控制对话框的代码,警告用户会话到期倒计时(通常 #sessionSecondsRemaining 会在此对话框中)

                $( "#dialog-confirm" ).dialog({
                    resizable: false,
                    height:140,
                    modal: true,
                    buttons: {
                        "Extend": function() {
                            clearTimeout(session.warningTimer);
                            $( this ).dialog( "close" );
                        },
                        Cancel: function() {
                            session.logout();
                            $( this ).dialog( "close" );
                        }
                    }
                });

没有 jQuery 对话框

  • 删除最后一个块

如果您注意到“扩展”,则终止警告计时器,并且 Cancel 会调用注销功能(也可在上面配置)

最后,这个块对于计时器倒计时到零的情况以及控制内部倒计时的显示非常重要#sessionSecondsRemaining

                    if (remaining >= 0) {
                        $('#sessionSecondsRemaining').html(remaining);
                    } else {
                        $( '#dialog-confirm' ).dialog( "close" );
                        clearInterval(session.warningTimer);
                        $( '#sessionSecondsRemaining' ).html('Session Expired');
                        session.logout();
                    }

else 下,可能是您在上面的块中真正需要修改的唯一位置。在那里,我调用了session.logout() 函数(应该是清理对话框后的最后一行,但这只是一个演示)。这是您关闭对话框和/或将用户重定向到会话过期页面或显示消息的位置。如果停留在同一页面上,请确保clearInterval(session.warningTimer);。如果不是,那这条线就没有关系了。

没有 jQuery 对话框

                    if (remaining >= 0) {
                        $('#sessionSecondsRemaining').html(remaining);
                    } else {
                        clearInterval(session.warningTimer);
                        session.logout();
                    }

keepalive.php

if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); }
include 'db.php';
$maxtimeout = 15; // Seconds for max timeout before forcing session reset on other users.
mysql_query("UPDATE users SET status = 1 WHERE user_id = ".$_SESSION['user_id']."");
mysql_query("UPDATE users SET status = 0 WHERE user_id <> ".$_SESSION['user_id']." AND (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(`timestamp_field`)) > " . $maxtimeout . "";

此任务应设置为在服务器端运行以清除任何杂散的数据库(如果您有大量活动,则不需要此脚本)

cron.php

include 'db.php';
// Set this for a longer timeout than in keepalive.php
$maxtimeout = 90; // Seconds for max timeout before forcing session reset on other users.
mysql_query("UPDATE users SET status = 0 WHERE (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(`timestamp_field`)) > " . $maxtimeout . "";

【讨论】:

  • 我在上面的某个地方有 session_start 只是没有在这里包含它。此代码是否适用于网站上的空闲和浏览器关闭?对于任何一种情况,我都需要用户为 0,在网站上活跃时为 1
  • 上面的 javascript 向服务器发送一个“ping”以保持会话处于活动状态。关闭窗口的问题在于,某些浏览器不允许该事件发生,除非它们正在浏览不同的站点。例如,如果您关闭整个浏览器,您将不会触发事件 --- 但是...如果您将时间戳字段添加到数据库,并将其设置为 ON_UPDATE = NOW(),那么您可以拥有另一个脚本(通过 cron),检查是否有用户在 XX 时间内没有 ping 服务器,并将其设置为 0。
  • @Gadgetster - 如果你愿意,我可以写一个例子。这是保持会话活动和数据库最新的异步方法。
  • @Gadgetster - 实际上,我刚刚注意到“胜利”的答案正是如此。只需确保在执行 $_SESSION var 内容之前在每个脚本上启动会话即可。唯一的问题是他的代码依赖于任何用户在一段时间内 ping 服务器,因此根据用户群,准确性可能不是最好的。
  • 是的,胜利的代码在浏览器关闭时确实有效。我想弄清楚如何将您的空闲检查与他的浏览器关闭检查代码结合起来
【解决方案3】:

有一个 javascript 库可能会有所帮助。
这是Ifvisible.js

它将允许您检测用户何时不再活动。

例如,您可以执行以下操作:

//Handle tab switch or browser minimize states    
ifvisible.on("blur", function(){
    //ajax call for inactive.php -> SET status = 0
});

ifvisible.on("focus", function(){
    //ajax call for active.php -> SET status = 1
});

//Detection of inactivity for xx seconds
ifvisible.setIdleDuration(120); // Page will become idle after 120 seconds

ifvisible.on("idle", function(){
    //ajax call for inactive.php -> SET status = 0
});

ifvisible.on("wakeup", function(){
    //ajax call for active.php -> SET status = 1
});

当然,您可以使用不同的参数调用相同的 inactive.php 程序,以了解您要将状态设置为 1 还是 0。
例如,使用您的 ajax 调用:

// if inactive : SET status = 0
    $.ajax({
        url: 'inactive.php?status=0',
        type: 'GET',
        async: false,
        timeout: 4000
    });
// if active : SET status = 1
    $.ajax({
        url: 'inactive.php?status=1',
        type: 'GET',
        async: false,
        timeout: 4000
    });

在你的 Inactive.php 中:

if ($_GET["status"] == "1") // status = 1 -> active
    $tatus = "1";
else // status = 0 -> inactive
    $tatus = "0";

mysql_query("UPDATE users SET status = " . $tatus . " WHERE user_id = ".$_SESSION['user_id']."");

访问网站了解有关 Ifvisible.js 的更多信息。

我希望它会帮助你。 :)

【讨论】:

  • ifevery.js 相当不错。试试 jquery-idletimer.js 的酷替代品——该项目有多活跃?
  • @SanuelJackson:您好,恭喜您获得赏金。 :)。对不起,我不明白你的问题:how active is that project ?.
  • 我的意思是 -- ifvisible.js 脚本被维护 -- 更新的频率如何,或者它只是最近 --- 或者它看起来会被放弃由原始开发者提供。
  • @SanuelJackson:好的! :),我很抱歉我的回答,但我不知道,我在博客上找到了,我发现它真的很好。 ;) 在 Github 上,有两个月的最后一次提交,所以它看起来并没有被放弃。但它不是很活跃。可能是它没有错误。 :D
  • 它看起来确实相当稳定。我肯定会在空闲检测部分对其进行一些测试以确保完整性。它似乎没有 onresume 的回调 - 最接近的是 onEvery(),但是该活动仅在页面再次可见时发生。感谢您提供的信息,将坚持使用 jquery-idletimer(至少在我写恢复功能之前,如果它允许的话)^.^
【解决方案4】:

这是我在我的系统中应用的,以保持跟踪用户仍然在线。我让客户端站点每分钟 ping 一次,告诉数据库他仍然在线。

关于检测浏览器关闭,您可以查看Close/kill the session when the browser or tab is closed by Daniel Melo

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var timeLeft = Number(60);

setInterval(function(){
    timeLeft--;
    if(timeLeft<0){
        expiredLogout();
    }
    $("#timer").html(timeLeft);
    //If timer less than 30, show message to user will logout soon
    if(timeLeft<30){
        $("#logoutMsg").show( "slow" );
    }else{
        $("#logoutMsg").hide( "slow" );
    }
    $("#logoutMsg").html('You will logout soon, in '+timeLeft+' sec.');
},1000);
//Initial Function
$(function() {
    //Mouse move top up the time left value;
    $(document).mousemove(function(event) {
        timeLeft = Number(60);
    });
    stillAlive();
    //Every 1 minute ping the active.php
    setInterval(function(){ stillAlive();},60000);
});
//Redirect to other page if time out 
function expiredLogout(){
    window.location = "inactive.php";
};
function stillAlive(){
    //Update userID 1 still active
    var postVal = {userID:1};
    $.post("active.php", postVal, null, "json")
    .success(function(data) {})
    .error(function() {})
    .complete(function() {});
};
</script>
<label id="timer"></label>
<div id="logoutMsg"></div>
</body>
</html>

Demo

【讨论】:

  • 您的代码的一些问题是,无论其他事件如何,stillAlive() 都会继续触发。没有设置 clearInterval()。此外,在用户再次变为活动状态后,不会重置服务器 ping 间隔。此外,没有处理 keydown/keyup 或点击事件。
  • 谢谢@SanuelJackson,你说得对,我的代码不是完整的事件处理,我只是提供了我如何处理用户保持活动的部分想法。
【解决方案5】:

第一个问题是你如何定义“空闲”。什么终止了“空闲”期?将焦点带到浏览器窗口?滚动页面?点击页面?点击一个按钮?点击链接? - 后者似乎是大多数网站使用的方法,这可以单独在服务器端完成:如果用户没有在您的网站上打开另一个页面,例如 5 分钟,那么他被认为是空闲的。 15 分钟后,他被系统登出。

【讨论】:

  • 几乎没有任何动静,就像他去喝咖啡并离开网站一小时我希望他的状态更改为 0(离线)。你能告诉我怎么做吗?
  • 如果您查看他在问题中发布的代码,您会注意到他已分配给 click()、mousemove()、keypress() 和 onbeforeunload()。
最近更新 更多