关于近期开发中遇到的同一账户多人登录造成数据库数据不一致的思考和解决(避开了数据库存状态的常用处理手段)
1.问题:近期开发了工单系统,开发结束项目上线测试过程中,发现同一账户多人同时登录如果不进行限制,该用户的操作就不是唯一的,导致数据库存放的数据出现了问题,项目整个就出问题了,经过本人再三思考,网上好多方案都是通过数据库存一个登录状态做处理,这样的话又得对数据库进行更改,很难受,那还有什么其他的处理方案吗?经思考,想出了以下处理方案:
1.采用servelet的application对象存放所有登录用户的信息,存放方式为sessionid-username存储
2.在用户登录时从application对象中取出所有的键,对应得值——即username与登录用户名进行匹配,匹配成功说明已经登录,不让该用户登录,否则返给前台登录成功的状态。
3.前台拿到数据后根据是否登录决定是否保存sessionid及是否进行成功跳转
4.用户下线时从appication中移除该sessionid的键
2.具体代码:
2-1.index.jsp登录页面:
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> name:<input type=text id='name'/> <button>login in</button> <script type="text/javascript" src='js/jquery-1.8.3.js'></script> <script type="text/javascript" src='js/cookie.js'></script> <script type="text/javascript"> $('button').on('click',function(){ $.post('<%=path%>/loginservlet',{name:$('#name').val()},function(data){ console.dir(data); var json = eval("("+data+")"); if (!json.islogin&&json.flag==1){ console.dir('login is permited!'); setCookie("sessionId",json.sessionId,1); console.dir('set cookie success!it is'+json.sessionId); alert(json.sessionId); window.location.href='success.jsp'; }else{ alert('the account you are using is logined'); } }) }); </script> </body> </html>
2-2.homepage.jsp登录成功页面:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>login success</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="sso login success page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> <a href="javascript:void(0);" onclick='logout()'>logout</a><br/> ${name } ,welcome!!!<br> <script type="text/javascript" src='js/jquery-1.8.3.js'></script> <script type="text/javascript" src='js/cookie.js'></script> <script type="text/javascript"> function logout(){ var sessionId = getCookie("sessionId"); console.dir(sessionId); $.post('<%=path%>/logout',{sessionId:sessionId},function(data){ var json = eval("("+data+")"); console.dir(json); if (json.logout){ alert('logout success!'); window.location.href = "index.jsp"; }else{ alert('logout fail!'); } }); } </script> </body> </html>
2-3.loginservlet.java后台登录逻辑处理:
package com.java.gwb.servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONObject; public class loginservlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String username = request.getParameter("name"); ServletContext application = request.getSession().getServletContext(); Enumeration<String> en = application.getAttributeNames(); boolean islogin = false; String sessionIds = request.getSession().getId(); JSONObject json = new JSONObject(); if (username.equals("gwb")||username.equals("ls")){ request.getSession().setAttribute("name", username); json.accumulate("flag", 1); while(en.hasMoreElements()){ String sessionId = en.nextElement(); if (application.getAttribute(sessionId).equals(username)){ islogin = true; } } json.accumulate("islogin", islogin); if (islogin){//如果已经登录,就不再存 json.accumulate("sessionId", ""); }else{ application.setAttribute(sessionIds, username); json.accumulate("sessionId", sessionIds); } }else{ json.accumulate("flag", 0); json.accumulate("sessionId", ""); } out.write(json.toString()); out.flush(); out.close(); } }
2-4.logoutservlet.java后台退出逻辑处理:
package com.java.gwb.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONObject; public class logoutservlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String sessionid = request.getParameter("sessionId"); ServletContext application = request.getSession().getServletContext(); boolean logout = false; JSONObject json = new JSONObject(); try{ application.removeAttribute(sessionid); logout = true; }catch (Exception e) { System.out.println("下线失败"); } json.accumulate("logout", logout); out.write(json.toString()); out.flush(); out.close(); } }
2-5.web.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name></display-name> <servlet> <description>This is the description of my J2EE component</description> <display-name>This is the display name of my J2EE component</display-name> <servlet-name>loginservlet</servlet-name> <servlet-class>com.java.gwb.servlet.loginservlet</servlet-class> </servlet> <servlet> <description>This is the description of my J2EE component</description> <display-name>This is the display name of my J2EE component</display-name> <servlet-name>logoutservlet</servlet-name> <servlet-class>com.java.gwb.servlet.logoutservlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginservlet</servlet-name> <url-pattern>/loginservlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>logoutservlet</servlet-name> <url-pattern>/logout</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
3.效果测试:使用不同的浏览器模拟多个用户登录同一账户:
3-1.a浏览器登录成功后去b浏览器登录测试:
a登录成功图
b登录被拒绝登录页面
3-2.a浏览器登录用户下线,b浏览器登录图:
a浏览器登录用户成功退出登录图
b浏览器用户在a退出后可以成功登录图
4.存在的问题分析:
4-1.如果在访问量比较大的情况下服务器存了好多数据,每次登录要频繁的进行服务器操作,服务器压力是有些大
4-2.对于异常下线如直接关闭窗口或者直接关机,如果服务器未重启,用户账号就会一直登录不了,所以,这个问题有待解决。
5.优点:
5-1.虽然服务器压力大了,但是数据库却解放出来了,我们把数据库的痛苦嫁接到了服务器上了,哈哈。
5-2.可以在不改变数据库表结构的情况下对用户登录实现单点登陆的控制。
6.补充实现:
思路:1.使用redis缓存数据库来实现,我们在登录时给每个登录账户一个id,数据一般是从数据库查询出来的,所以直接使用该用户的id即可,将其使用redis的setbit指定一个变量叫allonlineuserid_state userid 1
2.用户在登录时使用getbit allonlineuserid_state userid 查询该用户是否登录过,如果返回1说明已登录,否则允许登录
3.用户下线后使用setbit allonlineuserid_state userid 0使该用户下线,那么下一个账号就能登录进去了。这里直接使用数据库截图展示吧:
6.1.用户id为1的用户上线,将变量allonlineuserid_state的位置1处的二进制位置设置为1表示id为1的用户上线
6.2.用户使用账号密码登录后去数据库查询出用户id为1值,再在缓存数据库查询该用户在线状态。
6.3.在java中做逻辑判断,说明用户已经在线,踢出前一个登录账号或告诉现登录用户无法登陆。
6.4.id为1的用户下线时,重新设置allonlienuserid_state位置1处的值为0.