【任务10】注册与登录:不允许多个设备同时在线

1、用文字简单描述一下你的处理逻辑

2、思考题:如果我想实现电脑/手机可以同时登录,类似于微信可以手机,电脑同时在线,但是无法两个手机/电脑同时在线,这种可以怎么做呢?

本问题对应的项目链接:从0设计一个web项目
PS:每次在最后,都要给出你的GitHub链接哦,方便我打开

共以下 6 个回答

  • 星河揽我入梦 永久会员 2024年7月8日 上午10:21

    我的实现逻辑是用redis存储随机数,key为用户id,只保留最后一个登录的用户生成的token,在用户登录时进行判断,如果redis里面存了该用户的token,我们就将其删除,然后设置一个新的token值,将其挤下线;
    https://github.com/CurryisCoat/community/tree/dev

    0 赞同 0 条回复
  • 豆子彩虹 普通 2022年10月27日 上午11:27

    我的实现逻辑和前面的Seconds大佬很像,同样是用redis存储随机数-用户名和用户名-随机数。不同的点在于,我的随机数-用户名是多对一的关系。也就是说,记录多个session随机数的值,当你在其他地方的登陆下线后,你此前正常登陆的session随机数还是可以使用的。如果是一对一的关系的话,在其他地方登陆不管下不下线,此前保留一天的session都不能够使用了。用户名-随机数这一对表示的当前登陆的session随机数,设置5分钟过期,也就是5分钟后自动下线,发送请求时会刷新过期时间。
    同样我的代码也存在key值可能为中文的问题,后面可能考虑换成用户id来改进。
    github地址:https://github.com/lei342345795/FromZerotoExpert

    0 赞同 1 条回复
    • shuaidi 永久会员 @ 豆子彩虹 2022年10月27日 下午12:09

      你的意思是说,你保存了多个随机数,是为了让「最新登录的人下线之后」,其他地方登录的人的session还能正常使用?

  • lucca 普通 2022年10月13日 下午10:53

    登录态逻辑

    v1.0(Cookie + Session)

    1. 用户登录,获取用户信息,存到 Session,返回请求自动携带 SessionId
    2. 添加拦截器,获取 Session 中的用户信息,获取到了就处于登录态,反之无权访问,需要先登录

    v2.0(Redis + ThreadLocal)

    使用 Redis 解决 Session 无法共享的问题,使用 ThreadLocal 模拟本地 Session 存储

    1. 用户登录,获取用户信息,生成 token,作 user 的 key,存到 redis,并把 token 返回给前端
    2. 缓存刷新拦截器:拦截 没有token 或 token过期 的信息,对拥有合法token的请求,获取用户信息存到ThreadLocal(模拟session),同时刷新过期时间,放行
    3. 登录拦截器:拦截 ThreadLocal 没有 user 的请求(说明 token 是无效的),反之放行

    至此,还不具备对多设备同时在线进行限制的能力

    解决方案

    维护 用户id:token 的键值对,其中,token是唯一的,只保存最后一个登录的用户生成的 token,称为在线 token

    用户登录时,会生成token,如果存在旧的在线 token(之前有人在线),就根据用户 id 获取该 token,并删除该 token 存储的用户信息(下线)

    这样下一次前面的用户校验登录态时就获取不到用户信息了,被视为已下线。

    如何实现多类型设备登录,而限制多台同一类型设备登录?

    修改在线token的结构,改为 hash 结构,维护多种类型设备在线token,每种设备仍保持唯一

    • key:用户id
    • value:
      • 手机 token
      • PC token
      • Pad token

    需要打造能鉴别设备类型的能力。

    github地址(点击即可)

    0 赞同 0 条回复
  • 小肸 永久会员 2022年10月9日 下午5:04
    • 之前判断用户登录态时,我好像是实现了不让多个设备同时在线的功能吧。我是使用redis的字符串数据类型,所以一个accountId只能对应一个sessionId,当另一台主机登录时,这个accountId对应的sessionId被覆盖成新主机的sessionId,所以旧主机在发送请求判断是否位于登录态时,因为新的sessionId和它自己的sessionId不匹配,所以旧主机需要重新登录。
    • 允许不同型号设备在线,可以利用redis的哈希表数据类型,比如键为accountId,值为”笔记本” “sessionId1” “手机” “sessionId2” “平板” “sessionId3” 存入数据库,然后根据请求设备的型号,匹配对应的sessionId,实现不同种类的设备只运行一台设备在线。

    GitHub地址:https://github.com/shuaishuaixi03/FromZerotoExpert/tree/dev

    0 赞同 0 条回复
  • 秊 永久会员 2022年9月30日 下午12:56
    1. 我的思路是: 将用户登录时的时间戳作为唯一登录凭证,一份作为 Cookie 【loginCert = 时间戳】放在用户的浏览器中,一份记录在redis中:【lc = 时间戳 】(loginCertifate)。

      每次用户登录时都会更新这个唯一登录凭证,拦截器中拦截本地的登录凭证和 redis 记录的凭证不同的请求。如果同时有两用户登录,则后面用户的唯一登录凭证是最新的,前一个人只需在下一次操作时用拦截器中拦截即可。

      遇到的问题:

    • 在创建拦截器时,因为拦截器是在Spring Context 初始化之前执行,不能直接注入 Service 对象,但我的拦截判断需要它。虽然通过一些途径能达到我的需求,但这样是不是”走歪路“?
    • 在拦截器判断中我需要用到保存的Cookie,就会导致每次用户在请求时都需要重新查一遍所有Cookie直至找到我要用的Cookie,效率不高?

    这些问题等国庆放假我再好好想想。

    1. 需要用到 request 中 key 为 user-agent 的值。思路:扩展”唯一登录用户凭证“,根据当前登录的电脑/手机设备,生成“唯一设备登录凭证”,在本地设置名为 ”PcLoginCert” 或 “MbLoginCert” 的 Cookie,同时也在 redis 用户哈希表中用 “plc” 或 “mlc” 保存对应凭证。拦截器拦截时,根据当前电脑/手机,检测 redis 中相应的凭证是否一致。

    服务器链接:http://124.223.184.187:8088/FromZerotoExpert
    Github仓库链接:https://github.com/jiangyklala/FromZerotoExpert/tree/dev

    0 赞同 0 条回复
  • Second(S) 普通 2022年9月28日 下午6:10

    一、仅后者在线的处理逻辑
    1.前置实现(怎样设置的登录态):
    当用户第一次登录成功时,我们随机生成一个sessionId(雪花算法生成的唯一字符),用session进行存储,键为”user”,值为刚刚生成的sessionId,然后在reids数据库中存入该用户的登录信息,在这里我们存储两个键值对,一个是键为username(真实的用户名)值为sessionId的键值对,另一个是键为sessionId值为username(真实的用户名)的键值对。这样我们就可以将用户的信息存储到redis数据库中,方便调用和查询。
    2.仅后者在线
    当用户第二次登录时,首先服务器会查询redis数据库是否含有该用户名username-sessionId的键值对(若是第一次登录或者登录已过期,则不会查到,直接跳过此判断),若查询到确实存在此键值对,则说明不是第一次登录,于是把已经存储在redis数据库中的uername-sessionId和sessionId-username键值对设置过期,并生成一个新的sessionId存储在session中,在session中存储的键依然是”user”,存储的值是新生成的sessionId,这样就能把之前session中存储的sessionId覆盖掉了。并在redis数据库中保存新生成的sessionId-username和username-sessionId两个键值对。这样的话对于登录的前者,在session中存储的sessionId已经被覆盖了,在redis数据库存储的之前用户信息也被删除了,所以前者的登录会在拦截器部分被当作未登录处理。于是就简单的实现了仅后者在线的逻辑。
    二、思考题(手机/PC端可以同时登录)
    以我目前的实现方式,具体的设计思路如下:
    1.登录态的设置
    和上述的过程的实现方式类似,只不过当用户第一次登录时,首先需要获取user-agent进行用户登录方式的判断,通过利用request.getHeader(user-agent)的方法判断该用户的登录方式,若登录的是手机端,则在session中存储的内容为user:phone-sessionIdPhone,若为PC端,则在session中存储的内容为user:pc-sessionIdPc,并在redis数据库中存储对应的信息,比如当用户是手机端时,redis数据库以该用户名username+:phone拼接而成的字符串和sessionIdPhone互为键值对进行存储;当用户是PC端时,redis数据库以该用户名username+:pc拼接而成的字符串和sessionIdPc互为键值对进行存储。
    2.仅允许一个手机/电脑同时在线
    当用户第二次登录时,先进行request.getHeader(user-agent)的方法判断该用户是手机端还是PC端,若为手机端登录则在redis数据库中查找是否存在username:phone-sessionId的键值对存在,若存在则证明之前已经用手机端登陆过了,所以重新生成sessionIdPhone,并将之前redis数据库存储的username:phone-sessionIdPhone和sessionIdPhone-username:phone进行更新,同样地,若是PC端也是如此。总之就是比较折腾和麻烦,可能换一种数据结构会比较好一点,比如利用redis中的key-hash的结构进行设计可能扩展性会强一点,不过这边还没细想,因为不知道自己这样的做法是不是符合规范,只是先想了这么多,只能说是能简单完成了基本需求吧。
    gitHub地址:https://github.com/Second6s9/FromZerotoExpert/tree/dev
    gitHub防覆盖地址:https://github.com/Second6s9/FromZerotoExpert/tree/day10
    服务器地址:http://123.57.142.33:8088/FromZerotoExpert/

    0 赞同 5 条回复
    • shuaidi 永久会员 @ Second(S) 2022年9月28日 下午10:25

      关于一人登录的,这样实现好像也还行,不过就是有个需要注意的点,就是用户名很多是中文名,中文的话,不是特别适合作为 key。

    • Second(S) 普通 @ shuaidi 2022年9月28日 下午10:59

      是的帅地哥,这里我是做过一些中文用户名的测试案例的,功能上其实没太大问题,不过存中文key会发生中文字符转义的情况,我其实也发现了中文key确实不大合适,所以这里后面我会进行修改的。因为我之前没怎么接触过限制登录个数的这种需求的,所以我就按照自己想的方式,自己设计了这一套处理的逻辑,写着顺手就直接敲出来了,可能看上去弊端会很多吧。这里我后面会查一些相关的项目或者博客,然后重新设计一套比较标准的处理逻辑的。

    • shuaidi 永久会员 @ Second(S) 2022年9月28日 下午11:07

      是滴,先自己想,有弊端也问题不大=》之后看看别人和自己的区别,很棒

    • 秊 永久会员 @ Second(S) 2022年9月29日 下午1:35

      可以使用redis中的哈希类型,一个key可以保存多个键值对,方便存储用户信息,其中每个field设置成英文,value设置成要存储的信息。具体可参考《redis开发与运维》的2.3.3章节

    • Second(S) 普通 @ 秊 2022年9月29日 下午3:24

      好的好的,感谢大佬的思路和指导

# 回答此问题

后才能回答