1. 摘要
軟件設計中其障,經常存在這樣的場景掐暮,為了防止計費等沖突限制,實現同一個用戶不允許同一個用戶多個設備同時登錄愿卒,只允許唯一登錄缚去。本文介紹實現方法。
2.設計場景
1)同一時刻不允許某個用戶多地登錄琼开。
2)用戶已在A處登錄易结,現在從B處登錄是允許的,但會把A處擠掉(考慮到用戶在A處登錄后因某些情況跑到了B處柜候,但還想繼續(xù)之前的工作搞动,所以需要登錄系統(tǒng))。
3)B處擠掉A后改橘,A再做其它操作的時候系統(tǒng)會給出提示滋尉,該用戶在別處登錄,如不是本人操作可能密碼泄漏飞主,請修改密碼狮惜。
3. 業(yè)務流程圖
每個用戶登錄的時候,通常我們會將用戶信息存入session碌识,以便用戶進行操作的時候系統(tǒng)方便得到用戶的基本信息碾篡。但這個session具有私有性,只對當前用戶可見(如果同意用戶在不同瀏覽器登錄會得到不同的session筏餐,這也是為什么可以多用戶登錄的根源所在)开泽。那么接著問題就來了,某個用戶登錄的時候如何能知道自己是否在線魁瞪,相信聰明的你已經想到穆律,這還不好半惠呼,把在線的用戶信息存儲在一個公共的地方問題不就迎刃而解了么,網上一查峦耘,解決方案無出其右剔蹋,大致為以下兩種:
1)數據庫中標識在線用戶
2)存儲到application中
經過重重考慮,我們會發(fā)現方案一需要解決許多棘手的問題(用戶異常退出未來得及修改狀態(tài)辅髓,頻繁訪問數據庫影響性能等)泣崩,這對于一個要求完美的你來說顯然是不合時宜的,于是我們采用了方案二洛口,將在線用戶信息保存到application中矫付,具體設計如下。
3.1 登錄流程圖 -B處登錄
3.1被擠掉后操作流程圖 -A處已登錄
3. 代碼實現
1)登錄方法
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(String userName, String password, RedirectAttributes redirectAttributes, HttpServletRequest request) {
//判斷用戶是否已經在線及處理(已在線則剔除)
String loginLimite = limiteLogin.loginLimite(request, userName);
//判斷用戶名第焰、密碼是否正確
String result = userService.login(userName, password);
if (result.equals("success")) {
request.getSession().setAttribute("now_user", userService.findByUserName(userName)); //用戶掉線买优,登錄后重定向到保存的鏈接
Object url = request.getSession().getAttribute("redirect_link");
if (url != null) {
request.getSession().removeAttribute("redirect_link");
return "redirect:" + url.toString();
}
return "index";
}
redirectAttributes.addFlashAttribute("message", result);
return "redirect:/other/toLogin";
}
2)登錄判斷是否已經在線
@Service
@Transactional
public class LimiteLogin {
private static Logger log = Logger.getLogger(SessionListener.class);
private static Map<String, String> loginUserMap = new HashMap<>();//存儲在線用戶
private static Map<String, String> loginOutTime = new HashMap<>();//存儲剔除用戶時間
@Autowired
private UserService userService;
public String loginLimite(HttpServletRequest request, String userName) {
User user = userService.findByUserName(userName);
String sessionId = request.getSession().getId();
for (String key : loginUserMap.keySet()) {
//用戶已在另一處登錄
if (key.equals(user.getUserName()) && !loginUserMap.containsValue(sessionId)) {
log.info("用戶:" + user.getUserName() + ",于" + DateUtil.dateFormat(new Date(), "yyyy-MM-dd HH:mm:ss") + "被剔除樟遣!");
loginOutTime.put(user.getUserName(), DateUtil.dateFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
loginUserMap.remove(user.getUserName());
break;
}
}
loginUserMap.put(user.getUserName(), sessionId);
request.getSession().getServletContext().setAttribute("loginUserMap", loginUserMap);
request.getSession().getServletContext().setAttribute("loginOutTime", loginOutTime);
return "success";
}
}
3)登錄攔截器(未登錄跳轉登錄頁)
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("now_user");
if (session.getAttribute("now_user") == null) {
response.sendRedirect(request.getContextPath() + "/other/toLogin");
return false;
}
//多用戶登錄限制判斷,并給出提示信息
boolean isLogin = false;
if (user != null) {
Map<String, String> loginUserMap = (Map<String, String>) session.getServletContext().getAttribute("loginUserMap");
String sessionId = session.getId();
for (String key : loginUserMap.keySet()) {
//用戶已在另一處登錄
if (key.equals(user.getUserName()) && !loginUserMap.containsValue(sessionId)) {
isLogin = true;
break;
}
}
}
if (isLogin) {
Map<String, String> loginOutTime = (Map<String, String>) session.getServletContext().getAttribute("loginOutTime");
session.setAttribute("mess", "用戶:" + user.getUserName() + "而叼,于 " + loginOutTime.get(user.getUserName()) + " 已在別處登錄!");
loginOutTime.remove(user.getUserName());
session.getServletContext().setAttribute("loginUserMap", loginOutTime);
response.sendRedirect(request.getContextPath() + "/other/toLogin");
return false;
}
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
super.afterCompletion(request, response, handler, ex);
}
}
4)在session銷毀的時候,把loginUserMap中保存的鍵值對清除
public class SessionListener implements HttpSessionListener {
private static Logger log = Logger.getLogger(SessionListener.class);
@Override
public void sessionCreated(HttpSessionEvent event) {
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
String sessionId = session.getId();
//在session銷毀的時候,把loginUserMap中保存的鍵值對清除
User user = (User) session.getAttribute("now_user");
if (user != null) {
Map<String, String> loginUserMap = (Map<String, String>) event.getSession().getServletContext().getAttribute("loginUserMap");
if(loginUserMap.get(user.getUserName()).equals(sessionId)){
log.info("clean user from application : " + user.getUserName());
loginUserMap.remove(user.getUserName());
event.getSession().getServletContext().setAttribute("loginUserMap", loginUserMap);
}
}
}
}
5)web.xml
<!-- session listener 多用戶登錄限制,退出清除session信息的同時清除application中存放用戶登錄信息-->
<listener>
<listener-class>com.service.limitelogin.SessionListener</listener-class>
</listener>
6)頁面代碼
(用于給出提示的同時,清除被擠掉用戶的session信息豹悬,否則提示信息會一直顯示)
<script type="text/javascript">
$(document).ready(function () {
var message='${mess}';
if (message != "") {
$.ajax({
type: 'GET',
async: false,
cache: false,
url: '/other/clearUserSession',
dataType: '',
data: {},
success: function (data) {
}
});
$('#mess').html(message);
}
});
</script>
7)清除擠掉用戶session代碼
/**
* 多用戶登錄限制,清除session信息(登錄信息葵陵、提示信息)
*
* @param request
* @return
*/
@ResponseBody
@RequestMapping(value = "/clearUserSession")
public String clearUserSession(HttpServletRequest request) {
HttpSession httpSession = request.getSession();
//httpSession.invalidate();
httpSession.removeAttribute("now_user");
httpSession.removeAttribute("mess");
return "success";
}
到此開發(fā)工作完成。