透过用户的要求理解用户的关切

先介绍一下项目背景:公司生产了一些电视机和照相机,同时公开了一些接口,允许开发者基于此开发一些第三方应用。有些开发者可能没有电视机和照相机真机,或者团队中真机不足,为了能够是他们顺利地进行开发,所以我们开发一款软件,让开发者可以申请虚拟设备。该软件中实现了 SSDP 协议让第三方应用发现虚拟设备。

前些天,Tony 作为用户又作为项目的负责人提出一个要求:不能让一个账号同时在两个电脑(或者浏览器)上登录。这下可难倒了我们外包团队。他们的开发人员 W 找到我,说这个该怎么做呢?W 也提出一种方案,就是使用 WebSocket,对于不支持 WebSocket 的浏览器使用轮询来作为 fallback 方案。这个方案可以使用现有的 socket.io 库来轻松实现。

首先讲一下,不让一个账号同时两处登录,这样做的站点一般有聊天室等,有了这样的限制以后,一个用户在某个浏览器上登录以后,再不退出的情况下直接关闭浏览器,用户就不能在其他浏览器上登录了,直到 Session 过期。Session 自动过期时间可长可短,一般是半个小时。用户体验非常不好,更好的策略是,后登录的把先登录的踢掉。

以前我在 J2EE 企业级应用框架时实现了这样的功能,虽然实现了,但是还没有在任何一个项目中实际使用了。实现了,更多是为了功能的完备性。还有一点,在一个实现了完整的认证授权系统中,实现这样的功能是非常简单的。实现的方法如下:

  1. 首次成功登录以后,会再数据库的 Session 表中添加一条记录。字段一般包括 Session ID,用户 ID,Cookie,登录时间,最后操作时间(初始化为登录时间)。

  2. 以后用户没操作一次,都会修改 Session 表中的最后登录时间。这个在 J2EE 中一般通过 filter 实现。并且服务器端会定期执行一个任务,清除 Session 表中过期 Session。一个 Session 何时过期,一般判断条件是当前距离最后操作时间大于半个小时(可配置)。

  3. 当用户再次登录时,首先判断用户名和密码是否正确,如果正确,然后用户是否已经在别处登录,即 Session 表是否有该用户的未过期 Session,如果是,则给用户一个错误提示,否则才判定位登录成功。

在我们当前的应用中,服务器端语言使用的 Node,框架是 Express。Session 管理我们可以使用的是 Express 自带的 Session 中间件。代码如下:

1
2
3
var express = require('express');
var app = new express();
app.use(express.session());

Express 的 Session 中间件没有 API 可以得到用户是否已经在别处登录。另外一个问题就是即便我们自己实现 Session 控制,那么也是不可取的,因为项目特殊性,用户在登录以后就加在几乎所有的资源,也只有在登录,申请设备和获取已申请设备清单时才会发送一个服务器端的请求。之后用户只需要按下启动按钮,之后虚拟设备就运行在 Web App 中了,第三方应用就可以通过 SSDP 协议发现设备并与该设备进行 HTTP 通信了。以后只有部分静态资源文件会从 Node Server 上获取之后,就没有其他的请求了。所以自从获取已申请设备清单以后,就没有会修改最后操作时间的请求了。

W 给出的一个方案:使用 socket.io,保持与服务器端通信。可行倒是可行,但是我们要为了一个需求大动干戈吗?这个要划一个问号。然后我跟 W 说,我去跟 Tony 商量一下,看看他的目的是什么,再决定怎么做。

在跟 Tony 的交流中,发现他有过这样的经历:开发阶段两个人使用了同一个账号同时登录,这两个浏览器中自然有着同样的设备列表,然后都启动了某个设备,这时候在局域网中就有了两个有着同样 UUID 的虚拟设备,他们 IP 不同,设别描述文件地址也不同,设备状态也不同。有些 SSDP 客户端会因此而崩溃掉。所以他想让一个账号不能同时在两处登录就能解决该问题了。

我告诉 Tony,既然你是为了解决这样的问题,那么我们能否换个思路,让每个浏览器上的设备 UUID 都不相同,在生成 UUID 时,不仅加入设备 ID,还加入浏览器所在电脑的 IP,时间戳,随机值等。这样一来,团队成员可以共享一个账号,在同一个局域网中也可以独立使用,互不影响。然后 Tony 认同了这一做法。到此,这个需求就这么解决了,仅仅修改一下 UUID 的生成算法。

在上面的案例中,可以发现,用户提出的要求可能只是他们所关切的问题的一个表象,或者是他们认为的这个问题的一个解决方案。我们不能仅仅局限于此,而要透过他们的要求去探索他们真正关切的问题。