挤兑登录到底是如何实现的?

情景描述

您的帐号于2019-07-03 09:20:30在另一地点(ip:127.0.0.1)登录,您已被迫下线。这样的情况在生活中也见到很多了吧,最常见的像QQ、微信不同电脑端的登录,前面登录的账号会被挤兑下线。

其实这些互相挤兑登录的情景,都可以总结为同一个账号在不同设备上的互相挤兑,很明显满足挤兑需要两个条件:
1.同一个账号 2.不同设备上登录

第一次接手这个需求的时候,也是有些懵,怎么才算挤兑?如何挤兑? 仔细想想,挤兑登录挤兑登录,首先要登录啊,后台某个用户登录凭证是啥呢?很明显,session中保存的用户信息。那我们的挤兑需求貌似有了大体思路,第一次用户登录后,后台会在session中记录用户登录信息,第二次在另一设备上登录时,判断当前账号是否在其他设备上有session登录信息,有的话把之前的登录信息标记为被挤兑,然后保存新设备的登录信息。

代码实战

先来看看我们的一些涉及的实体,其中get()/set()方法已省略
SessionInfo:记录保存在session中的登录信息

public class SessionInfo {
	//用户id
	private Long userId;
	//用户数手机号
	private String phone;
	//登录平台:ios/android/web
	private String platform;
	//会话有效期(秒), 默认31天
	protected Integer validSeconds = 2678400;
}

ExtrusionSessionInfo:继承SessionInfo,标识被挤兑的SessionInfo

public class ExtrusionSessionInfo extends SessionInfo {
	//挤兑时间
    private Date extrusionTime;
    //挤兑IP
    private String extrusionIp;
}

LoginLogEntity:用来记录登录信息的数据表实体

public class LoginLogEntity extends BaseEntity {

    @Id
    @GeneratedValue(generator = RedisIdGenerator.NAME)
    private Long loginLogId;
    //登录用户
    @NotNull
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private UserEntity user;
    //登录用户ip
    @Length(max = 16)
    @Column(length = 16)
    private String loginIp;
    //登录用户sid
    @Length(max = 191)
    @Column(length = 191)
    private String httpHeaderSid;
    //登录状态 LOGINED:已登入/EXTRUSION:被挤出/LOGOUT:主动登出
    private LoginState state;
    //挤兑用户ip
    @Length(max = 16)
    @Column(length = 16)
    private String extrusionIp;
    //挤兑时间
    private Date extrusionTime;
    //登录时间
    @CreatedDate
    private Date loginTime;
    //登出时间
    private Date logoutTime;
    //登录平台
    private String platform;
}

新设备账号登录流程

  • 新设备调用登录接口登录
  • 查询LoginLogEntity表,是否存在当前用户登录信息,且状态是已登录state=LOGINED,存在,说明在其他设备上登录了该账号
  • 将之前保存在LoginLogEntity中登录信息的登录状态变更为被挤兑state=EXTRUSION,同时根据LoginLogEntity保存的sid清除原session登录信息,清除原session这一步很重要,涉及到getSessionInfo()能否获取出挤兑信息
  • 记录当前账号在新设备上的登录信息,同时session中保存登录信息
  • 新设备登录完成

以上流程走过之后,我们把原登录信息在数据库中标记出了被挤兑,剩下就是老设备要提示“您的帐号于2019-07-03 09:20:30在另一地点(ip:127.0.0.1)登录,您已被迫下线”的信息。这个提示可以是后台主动推送,也可以是前端调用接口提示,我们这边的方案是前端调用接口方式提示,后台主动提示实时性好,处理方案可以对接一些第三方的移动推送,这里就不多说了。

旧设备账号提示被挤兑流程

emm…码了好多字了,先来一段代码解乏:

public class SessionInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
        String reqIp = RequestContextHolderEx.getRealIpAddress();
        SessionInfo sessionInfo = sessionContext.getSessionInfo();

        //账号登录挤兑
        if (sessionInfo instanceof ExtrusionSessionInfo) {

            if (logger.isDebugEnabled()) {
                String sid = sessionContext.getSid();
                logger.debug("账号登录挤兑 " + reqIp + " " + path + " sid=>" + sid);
            }

            ExtrusionSessionInfo extrusionSessionInfo = (ExtrusionSessionInfo) sessionInfo;
            String msg = String.format(
                    "您的帐号于%s在另一地点(ip:%s)登录,您已被迫下线",
                    extrusionSessionInfo.getExtrusionTimeString(),
                    extrusionSessionInfo.getExtrusionIp()
            );

            throw new BusinessException("403", msg);

        } else if (sessionInfo == null) {
            throw new BusinessException("403", "请先登录云实养车");
        }

        return true;
    }
}

啊啊啊,开心,终于看到“您的帐号于%s在另一地点(ip:%s)登录…”这几个字了。这段代码很简单,就是一个拦截器,里面的逻辑就是如果SessionInfo是标识了挤兑即ExtrusionSessionInfo,直接抛出自定义异常提示账号被挤兑,重要的是sessionContext.getSessionInfo()这个方法返回的东西,咱们瞅瞅去。

public SessionInfo getSessionInfo() {
	HttpSession session = request.getSession(false);
    if (null == session) {
        String sid = getSid();
        if (null != sid) {
            //sid不为空,session为空,说明账号被挤了
            try {
                extrusionSessionInfoCache.get(sid, () -> {

                    Optional<Map<String, Object>> row = jdbcTemplate.queryForList(
                            "SELECT log.extrusion_time AS extrusionTime,log.extrusion_ip AS ip " +
                                    "FROM sm_login_log log " +
                                    "WHERE log.http_header_sid=? AND log.state=?", sid, LoginState.EXTRUSION.ordinal())
                            .stream()
                            .findFirst();

                    //如果存在被挤兑记录,返回ExtrusionSessionInfo
                    if (row.isPresent()) {
                        return Optional.of(new ExtrusionSessionInfo((java.sql.Timestamp) row.get().get("extrusionTime"), (String) row.get().get("ip")));
                    }
                    return Optional.empty();
                });
            } catch (ExecutionException e) {
                //ignore
            }
        }
        return null;
    }
}

这里首先因为前面新设备登录的时候清除了老设备的session信息,所以他满足session==null条件,里面的逻辑就是查询登录日志表,根据当前用户sid和登录状态是挤兑状态查询,如果存在的话说明就是被挤兑了,后面直接封装返回ExtrusionSessionInfo,拦截器那边判断出是ExtrusionSessionInfo类型,则直接提示客户端该账号被挤兑了。

总结

整个挤兑流程就这样,其实整个操作下来不复杂,无非就是记录登录信息、记录挤兑信息、清除session信息、拦截器判断是否被挤兑...当然,这些代码省略了很多优化的细节,这里就不展开了,要不然又是天花乱坠扯一堆。

此篇到此结束,感谢大家浏览,如有描述不当之处烦请及时指出!如还有不明之处,不妨试着多看几遍.

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
《 Big bang 》是著名的美剧。在剧中 Sheldon 可以说是一个极品,真不知 Leonard 是如何忍受这位极品室友成天的唠叨。 你知道么? Sheldon 有一个神秘的小本本,记录了所有他从小开始讨厌的人名。 Stuart 这位漫画店老板就是小本本的一员哦,谁叫他常常毫不客气地挤兑 Sheldon ,曾多次赌赢过 Sheldon 呢。 Penny 是一个漂亮的女孩,好奇心也很强。为了满足她的好奇心,我当回编剧让她意外知道了 Sheldon 的那个小本本放在了哪里。于是她几乎每天都去看,看看上面有哪些人。但是那个小本本上的人名实在太多。要知道她可是没上过大学在饭店里面当服务员啊。请聪明的你帮帮她处理处理那个小本本吧。Sheldon 每天都会在小本本里记录些人名,当然有时也会与他们和好就会从小本本中将这个人名删除。我们假设 Sheldon 会在一个空的小本本上插入、删除、查询某个人。 要帮助 Penny ,你需要知道一个链表是怎么初始化、插入、删除以及查找的。 输入格式: 输入数据只有一组,有很多行。每行的格式可能是下列一种: insert a name delete name show search name 其中 a 是一个整数,代表在第 a 个名字前插入名字。 name 是一个姓名,只包含英文字母的大小写,每个名字不超过30个字符。 输入保证不会插入列表中已经存在的姓名,不会删除列表中不存在的姓名,不会搜索列表中不存在的姓名,也不会要求在非法的位置插入列表。
05-25

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发飙的蜗牛咻咻咻~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值