检测非活动用户

Avatar of Mateusz Rybczonek
Mateusz Rybczonek

DigitalOcean 为您旅程的每个阶段提供云产品。从 200 美元的免费信用额度 开始!

大多数情况下,您并不真正关心用户是否积极参与或在您的应用程序上暂时处于非活动状态。非活动状态,意味着,也许他们起身去喝水,或者更有可能,切换标签去做其他事情。但是,在某些情况下,跟踪用户活动和检测非活动状态可能很方便。

让我们思考一下您可能需要该功能的几个示例

  • 跟踪文章阅读时间
  • 自动保存表单或文档
  • 自动暂停游戏
  • 隐藏视频播放器控件
  • 出于安全原因自动注销用户

我最近遇到了一个涉及最后一个示例的功能,出于安全原因自动注销非活动用户。

为什么我们要关心自动注销?

许多应用程序允许用户访问一定数量的个人数据。根据应用程序的目的,数据的数量和价值可能有所不同。它可能只是用户的姓名,但也可能是更敏感的数据,例如医疗记录、财务记录等。

一些用户可能会忘记注销并保持会话打开。这种情况发生在你身上多少次了?也许你的手机突然响了,或者你需要立即离开,让浏览器保持开启状态。保持用户会话打开状态很危险,因为其他人可能会使用该会话提取敏感数据。

解决此问题的一种方法是跟踪用户是否在特定时间段内与应用程序交互,如果超过该时间则触发注销。您可能希望显示一个弹出窗口,或者一个警告用户即将注销的计时器。或者,您可以在检测到非活动用户时立即注销。

更进一步,我们要做的是计算从用户上次交互开始经过的时间。如果该时间段长于我们的阈值,我们希望触发我们的非活动处理程序。如果用户在阈值被突破之前执行操作,我们重置计数器并重新开始计数。

本文将展示我们如何基于 此示例 实现此类活动跟踪逻辑。

步骤 1:实现跟踪逻辑

让我们实现两个函数。第一个负责在用户每次与应用程序交互时重置我们的计时器,第二个负责处理用户变得非活动的情况

  • resetUserActivityTimeout – 这将是我们的方法,负责在用户每次与应用程序交互时清除现有的超时并启动一个新的超时。
  • inactiveUserAction – 这将是在用户活动超时运行时触发的我们的方法。
let userActivityTimeout = null;

function resetUserActivityTimeout() {
  clearTimeout(userActivityTimeout);
  userActivityTimeout = setTimeout(() => {
    inactiveUserAction();
  }, INACTIVE_USER_TIME_THRESHOLD);
}

function inactiveUserAction() {
  // logout logic
}

好的,所以我们有负责跟踪活动的方法,但我们还没有在任何地方使用它们。

步骤 2:跟踪激活

现在我们需要实现负责激活跟踪的方法。在这些方法中,我们 添加事件监听器,当检测到事件时,这些监听器将调用我们的 resetUserActivityTimeout 方法。您可以监听任意数量的事件,但为简单起见,我们将该列表限制为一些最常见的事件。

function activateActivityTracker() {
  window.addEventListener("mousemove", resetUserActivityTimeout);
  window.addEventListener("scroll", resetUserActivityTimeout);
  window.addEventListener("keydown", resetUserActivityTimeout);
  window.addEventListener("resize", resetUserActivityTimeout);
}

就是这样。我们的用户跟踪已准备就绪。我们唯一需要做的是在页面加载时调用 activateActivityTracker

我们可以这样保留它,但如果你仔细观察,我们刚刚提交的代码存在一个严重的性能问题。用户每次与应用程序交互时,整个逻辑都会运行。这很好,但再仔细看看。某些类型的事件在用户与页面交互时会触发大量次,即使这对我们的跟踪来说不是必需的。让我们看看 mousemove 事件。即使您只稍微移动一下鼠标,mousemove 事件也会触发数十次。这是一个真正的性能杀手。我们可以通过引入一个节流器来解决这个问题,该节流器将只允许用户活动逻辑在指定的时间段内触发一次。

让我们现在就来做。

步骤 3:提高性能

首先,我们需要添加一个变量,该变量将保留对我们的节流器超时的引用。

let userActivityThrottlerTimeout = null

然后,我们创建一个方法来创建我们的节流器。在该方法中,我们检查节流器超时是否已存在,如果不存在,我们创建一个超时,该超时将在特定时间段后触发 resetUserActivityTimeout。这是所有用户活动在该时间段内不会再次触发跟踪逻辑的时间段。在此时间之后,节流器超时将被清除,允许下一次交互重置活动跟踪器。

userActivityThrottler() {
  if (!userActivityThrottlerTimeout) {
    userActivityThrottlerTimeout = setTimeout(() => {
      resetUserActivityTimeout();

      clearTimeout(userActivityThrottlerTimeout);
      userActivityThrottlerTimeout = null;
    }, USER_ACTIVITY_THROTTLER_TIME);
  }
}

我们刚刚创建了一个应该在用户交互时触发的新方法,因此我们需要记住在我们的激活逻辑中将事件处理程序从 resetUserActivityTimeout 更改为 userActivityThrottler

activateActivityTracker() {
  window.addEventListener("mousemove", userActivityThrottler);
  // ...
}

奖励:让我们用 Vue 重写它!

现在我们已经实现了我们的活动跟踪逻辑,让我们看看如何将该逻辑移动到使用 Vue 构建的应用程序中。我们将根据 此示例 进行解释。

首先,我们需要将所有变量移动到组件的 data 中,这是所有反应式属性所在的位置。

export default {
  data() {
    return {
      isInactive: false,
      userActivityThrottlerTimeout: null,
      userActivityTimeout: null
    };
  },
// ...

然后我们将所有函数移动到 methods

// ...
  methods: {
    activateActivityTracker() {...},
    resetUserActivityTimeout() {...},
    userActivityThrottler() {...},
    inactiveUserAction() {...}
  },
// ...

由于我们正在使用 Vue 及其反应式系统,我们可以放弃所有直接的 DOM 操作(即 document.getElementById("app").innerHTML)并依赖我们的 isInactive 数据属性。我们可以在组件的模板中直接访问数据属性,如下所示。

<template>
  <div id="app">
    <p>User is inactive = {{ isInactive }}</p>
  </div>
</template>

我们需要做的最后一件事是找到一个合适的位置来激活跟踪逻辑。Vue 带有组件 生命周期钩子,这正是我们需要的——特别是 beforeMount 钩子。所以让我们把它放在那里。

// ...
  beforeMount() {
    this.activateActivityTracker();
  },
// ...

我们还可以做一件事。由于我们正在使用超时并在窗口上注册事件监听器,因此始终建议在完成后进行一些清理。我们可以在另一个生命周期钩子 beforeDestroy 中执行此操作。当组件的生命周期结束时,让我们删除我们注册的所有监听器并清除所有超时。

// ...
  beforeDestroy() {
    window.removeEventListener("mousemove", this.userActivityThrottler);
    window.removeEventListener("scroll", this.userActivityThrottler);
    window.removeEventListener("keydown", this.userActivityThrottler);
    window.removeEventListener("resize", this.userActivityThrottler);
  
    clearTimeout(this.userActivityTimeout);
    clearTimeout(this.userActivityThrottlerTimeout);
  }
// ...

总结!

此示例完全专注于检测用户与应用程序的交互,对其做出反应并在特定时间段内未检测到交互时触发方法。我希望此示例尽可能通用,因此这就是为什么我将检测到非活动用户时应发生的事情的实现留给您。

希望您能在您的项目中发现此解决方案有用!