個人周總結丨2019-10-13 用戶最大登錄數的實現(同一賬戶,是否允許多處登錄在線)

概述

“網關管理系統”的開發總算結束了,發佈到公司的實驗室後讓測試測下改改bug就可以告一段落了。接下來就是“醫療保險系統”新版本的迭代了。在這個版本上,我們要對“醫療保險系統”的數據庫按照醫院進行分庫分表,這個過程中會使用到數據庫中間件mycat。在正式開始開始這個工作前,我打算對“網關管理系統”的“只允許同一用戶在一處進行登錄”做個回顧。

需求說明

一個用戶只能在一處進行登錄,後一次的登錄會將前一次未退出的登錄註銷掉。

功能分析

在這裏插入圖片描述
實現的流程大概如上圖所示。當用戶發起請求時,判斷這個請求是否是登錄操作,如果不是登錄操作,則對用戶進行登錄狀態進行檢查,進行相應的處理。如果是登錄操作,判斷用戶登錄信息是否正確,然後檢查緩存,該用戶是否有在別處登錄,如果有就將之前的session進行銷燬,最後將最新登錄的session放到緩存中。
說到這,可能有的人會有些疑問,那些沒有正常退出用戶怎麼辦?這個問題在此之前我也想過,最後發現這個情景對應功能的實現沒有任務影響。分析如下:已知用戶沒有退出或非正常退出,1.假設用戶在session沒有失效前在別處進行登陸,根據我們的流程圖來看,之前沒有失效的session會在此次登陸被銷燬。2.假設用戶在此之後沒有做任何操作,則30分鐘後,session會自動被銷燬掉。

實現

過濾器的實現(相對於流程圖中session銷燬和更新)

public class KickoutSessionControlFilter extends AccessControlFilter {

    private String kickoutUrl; //踢出後到的地址
    private boolean kickoutAfter = false; //踢出之前登錄的/之後登錄的用戶 默認踢出之前登錄的用戶
    private int maxSession = 1; //同一個帳號最大會話數 默認1

    private SessionManager sessionManager;
    //encache 本地緩存 有效時間爲30分鐘
    private Cache<String, Deque<Serializable>> cache;//在線用戶

    @Autowired
    private UserMapper userMapper;

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setCacheManager(CacheManager cacheManager) {
        this.cache = cacheManager.getCache("shiro-kickout-session");
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            //如果沒有登錄,直接進行之後的流程
            return true;
        }

        Session session = subject.getSession();
        User principal = (User) subject.getPrincipal();
        String userId = principal.getId();
        Serializable sessionId = session.getId();

        //判斷用戶是否被禁用或者已刪除
        User user = userMapper.selectById(userId);
        boolean kickout = false;
        if (user == null || user.getStatus() == Constant.USER_STATUS_FORBIDDEN) {
            kickout = true;
        }
        //在操作期間,用戶被系統用戶禁用或刪除
        if(kickout){
            try {
                Deque<Serializable> remove = cache.remove(userId);
                if (remove != null) {
                    Serializable kickoutSessionId = remove.getFirst();
                    sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)).stop();
                }
            } catch (CacheException e) {
                //to do nothing
            } catch (SessionException e) {
                //to do nothing
            }
            return false;
        }
		//獲取該用戶的session列表(目前就一個,但如果允許多用戶登錄就可以不改了)
        Deque<Serializable> deque = cache.get(userId);
        if (deque == null) {
            deque = new LinkedList<Serializable>();
            cache.put(userId, deque);
        }

        //如果隊列裏沒有此sessionId,放入隊列
        if (deque.size() == 0) {
            deque.push(sessionId);
        } 
        //如果有,判斷是否是同一個sessionId
        else {
        	//取出sessionId
            Serializable kickoutSessionId = deque.removeFirst();
            //不是同一個,即別處存在登錄,銷燬
            if (!kickoutSessionId.equals(sessionId)) {
                try {
                    Session kickOutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                    if (kickOutSession != null) {
                        kickOutSession.stop();
                    }
                } catch (SessionException e) {
                    //to do nothing
                }
            }
            //添加最新的session
            deque.add(sessionId);
        }
        return true;
    }
}

項目中使用了shiro,故需要實現AccessControlFilter ,但本質是相同的。另外該代碼參考了書籍《跟我學shiro》。

總結

如果要對用戶的請求做某些與義務不相關的操作,我們可以通過實現Filter在定義一個可重用組件。如果你使用的shiro,請實現AccessControlFilter。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章