jfinal+H5的websocket 实现同一账户在不同地点不同电脑只能登陆一个(互相踢下线)

公司项目需求,因为项目是开账户卖钱的,为了避免有的用户开一个账户N个人用,所以要求A账户只能在一个地点登录,别人如果使用A账户在别的电脑或者地点登录后就会吧上一个人给踢下线,当然也可以让后一个登录的人登录不了,这都是看你逻辑怎么控制的。


效果类似是qq登录的效果,先来张实现后的图



具体实现: 分两步

第一步使用 HttpSessionAttributeListener  监听session的属性的变化,原理是同一个账户不同地点登录,用户名肯定是相同的,session不同而已,以用户名为key存到map里,value为session,用户每次登录的时候,触发监听的attributeAdded方法,判断一下map里是否包含相同的用户名,如果没有则新加入进去,如果包含代表

相同的账号前边已经有人登录了,此时要判断一下当前用户的session和上一个用户的session的id是否一致,不一致才干掉上一个,不然会出现这种情况(用户a登录后,map里存了一份,但是a直接把浏览器关闭了,map里一致存着一份呢,下次a在打开浏览器登录的时候,就会发现登录不了了,因为没有判断的话,有可能会把自己的session干掉。所以要判断一下)

第二步:把上一个登录的账户干掉以后还不行,还需要跟人家通知一下,告诉一声,说“哎,你的账户在另一地点登录被踢下线了”,一开始想用ajax轮询解决来的,发现弊端太多js定时器的话,所以采用H5的websocekt通知到前台


第一步实现:

web.xml 配置监听

 <listener>
   <display-name>recordSession</display-name>
   <listener-class>com.wupao.sessionListener.SessionListener</listener-class>
  </listener>

package com.wupao.sessionListener;

import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import com.wupao.controller.WebSocketController;
import com.wupao.model.Usera;

public class SessionListener implements HttpSessionAttributeListener {

	private int count = 0;
    public static Map<String, HttpSession> sessions;

    static {
        if (sessions == null) {
            sessions = Collections
                    .synchronizedMap(new HashMap<String, HttpSession>());
        }
    }

    private void getCount(HttpSessionEvent arg0) {
        synchronized (this) {
            HttpSession session = arg0.getSession();
            ServletContext sctx = session.getServletContext();
            sctx.setAttribute("count", count);
        }
    }

    public void attributeAdded(HttpSessionBindingEvent arg0) {
        if (arg0.getName().equals("agencyUser")) {//过滤只需要指定的session名
            synchronized (this) {
                HttpSession session = arg0.getSession();
                Usera dailishi = (Usera) session.getAttribute("agencyUser");
                String username=dailishi.getUserName();
              //    System.out.println("username="+username);
              //   System.out.println(session.getId());
                 
                if (!sessions.containsKey(username)) {
                    sessions.put(username, session);
                } else {
                	System.out.println(sessions.get(username).getId());
                	//这里要删除,如果当前登录的跟上一个用户登录的sessionID不一致才干掉
                	if(!sessions.get(username).getId().equals(session.getId())){
                		
                		//通过H5的webSocket像前台发送消息,提示你已经被踢掉了亲
                		WebSocketController.getHashMap().get(sessions.get(username)).broadcast("该账户在另一地点登录,点击确定,请重新登录");
                		WebSocketController.getHashMap().remove(sessions.get(username));//销毁sessionId和xx的对应关系
                		
                		sessions.get(username).removeAttribute("agencyUser");//干掉以前的,放入现在登录的
                		sessions.put(username, session);
                	}
                	
                }
                count = sessions.size();
                System.out.println("现在登录的代理有="+count+"个");
            }
        }
        getCount(arg0);
    }

    public void attributeRemoved(HttpSessionBindingEvent arg0) {}

    public void attributeReplaced(HttpSessionBindingEvent arg0) {

    }

}

当然程序里用户退出逻辑也需要加点东西

public void loginOut() {
		String roleType = getPara("roleType");
		if(roleType.equals("0")){
			getSession().removeAttribute("user");
		}else if(roleType.equals("1")){
			  Usera dailishi = (Usera) getSessionAttr("agencyUser");
			
			//用户退出的时候要删除掉,hashmap中,sessionId跟自己一致的,退出的时候只删除自己的
			SessionListener sessionListener =new SessionListener();
			String userName=dailishi.getUserName();
			if(sessionListener.sessions.get(userName).getId().equals(getSession().getId())){
				sessionListener.sessions.remove(userName);
			}
			getSession().removeAttribute("agencyUser");
			
		}
		
		render("login.jsp");
		return;
	}

同账号登录只能登录一个的问题解决了,但是还要给被踢下来的人一个提醒啊


这个实现比较麻烦,参考了一下网上的技术文章,因为我前台程序用的是Jfinal所以一开始需要Jfinal整合一下websocket。

WEBConfig中加入过滤器,专门来接受websocket

/**
	 * 功能扩展
	 */
	public void configHandler(Handlers me) {
		//静态文件过滤器
		me.add(new StaticHandler("/static"));
		//	me.add(new UrlSkipHandler("/static", false));
		 me.add(new WebSocketHandler("^/websocket"));
		}


package com.wupao.handler;

import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jfinal.handler.Handler;
import com.jfinal.kit.StrKit;

public class WebSocketHandler extends Handler {
	private Pattern filterUrlRegxPattern;

	public WebSocketHandler(String filterUrlRegx) {
		if (StrKit.isBlank(filterUrlRegx))
			throw new IllegalArgumentException(
					"The para filterUrlRegx can not be blank.");
		filterUrlRegxPattern = Pattern.compile(filterUrlRegx);
	}

	@Override
	public void handle(String target, HttpServletRequest request,
			HttpServletResponse response, boolean[] isHandled) {
		if (filterUrlRegxPattern.matcher(target).find())
			return;
		else
			next.handle(target, request, response, isHandled);

	}
}

package com.wupao.controller;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.wupao.handler.GetHttpSessionConfigurator;




@ServerEndpoint(value="/websocket",configurator = GetHttpSessionConfigurator.class)
public class WebSocketController {
	 private static Session session;
	 private HttpSession httpSession;
	private static  HashMap<String, WebSocketController> hashMap =new HashMap<>();
	
	
	
	
	public static HashMap<String, WebSocketController> getHashMap() {
		return hashMap;
	}

	public static void setHashMap(HashMap<String, WebSocketController> hashMap) {
		WebSocketController.hashMap = hashMap;
	}

	@OnOpen
	public void onOpen(Session session,EndpointConfig config) throws IOException {
		 this.session = session;  
	        httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());  
	        System.out.println("存入hashMap 中的httpSessionId="+httpSession.getId());
	        hashMap.put(httpSession.getId(), this);
//	        connections.add(this);  
	      //  String message = String.format("* %s %s", nickname, "has joined.");  
	       // broadcast(message);  
	      //  session.getBasicRemote().sendText("lalal");  
	}

	@OnClose
	public void onClose(Session session) {
		hashMap.remove(httpSession.getId());
	}

	@OnMessage
	public void onMessage(String requestJson, Session session) throws IOException {
		
		
		
			session.getBasicRemote().sendText(requestJson);
	}
	
	
	
	
	public static void broadcast(String msg) {  
                synchronized (WebSocketController.class) {  
                   try {
					session.getBasicRemote().sendText(msg);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}  
             
        }  
    }  
}


因为httpsession和websocket.Session不是同一个,为了在OnOpen方法中获取传统的httpsession,

package com.wupao.handler;

	import java.util.Map;  
	  
	import javax.servlet.http.HttpSession;  
	import javax.websocket.HandshakeResponse;  
	import javax.websocket.Session;  
	import javax.websocket.server.HandshakeRequest;  
	import javax.websocket.server.ServerEndpointConfig;  
	  
	  
	  
	//配置类  将http中的session传入websocket中  
	public class GetHttpSessionConfigurator extends  
	        ServerEndpointConfig.Configurator {  
	    @Override  
	    public void modifyHandshake(ServerEndpointConfig config,  
	            HandshakeRequest request, HandshakeResponse response) {  
	        // TODO Auto-generated method stub  
	        HttpSession httpSession = (HttpSession) request.getHttpSession();  
	        // ActionContext.getContext().getSession()  
	        config.getUserProperties().put(HttpSession.class.getName(), httpSession);  
	    }  
	}  
加在:
@ServerEndpoint(value="/websocket",configurator = GetHttpSessionConfigurator.class)

后台的OK了

需要配置前台js的websocket,用户登录以后传入接口地址,调用这个js创建websocket对象,此时已经握手了,只不过没传递消息而已,后台a用户被后登录的a踢掉以后,发消息提醒一下你被提掉了

	//通过H5的webSocket像前台发送消息,提示你已经被踢掉了亲
                		WebSocketController.getHashMap().get(sessions.get(username)).broadcast("该账户在另一地点登录,点击确定,请重新登录");
                		WebSocketController.getHashMap().remove(sessions.get(username));//销毁sessionId和xx的对应关系




  
function socket(url){
var websocket = null;
    //判断当前浏览器是否支持WebSocket
    if ('WebSocket' in window) {
       // websocket = new WebSocket("ws://192.168.1.136:8081/tengJinPlatform/websocket");
    	websocket = new WebSocket("ws"+url+'websocket');
    }
    else {
        alert('当前浏览器 Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket连接发生错误");
    };

    //连接成功建立的回调方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket连接成功");
    };

    //接收到消息的回调方法
    websocket.onmessage = function (event) {
    	alert(event.data);
        //setMessageInnerHTML(event.data);
    	 window.location.reload();
    };

    //连接关闭的回调方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket连接关闭");
    };

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        closeWebSocket();
    };

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
    	
        //document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
    

}


好的,下面我将为您提供一个简单的demo示例,使用jfinal+vue+el来实现一个用户管理系统。 1. 准备工作 首先,需要安装Java环境和Maven工具。然后,创建一个Maven项目,并添加以下依赖: ```xml <dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal</artifactId> <version>4.9.06</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> ``` 2. 创建数据库表 在MySQL中创建一个名为user的表,包含以下字段: - id - username - password - nickname - email 3. 创建后端代码 创建一个UserController类,用于处理用户相关的请求。 ```java public class UserController extends Controller { public void index() { render("index.html"); } public void list() { int pageNumber = getParaToInt("page"); int pageSize = getParaToInt("limit"); Page<User> userList = User.dao.paginate(pageNumber, pageSize, "select *", "from user"); renderJson(JSON.toJSONString(userList)); } public void save() { User user = getModel(User.class, ""); if (user.save()) { renderJson(JSON.toJSONString(Result.success())); } else { renderJson(JSON.toJSONString(Result.failure("保存失败"))); } } public void update() { User user = getModel(User.class, ""); if (user.update()) { renderJson(JSON.toJSONString(Result.success())); } else { renderJson(JSON.toJSONString(Result.failure("更新失败"))); } } public void delete() { Integer id = getParaToInt("id"); if (User.dao.deleteById(id)) { renderJson(JSON.toJSONString(Result.success())); } else { renderJson(JSON.toJSONString(Result.failure("删除失败"))); } } } ``` 创建一个User类,用于操作数据库。 ```java public class User extends Model<User> { public static final User dao = new User().dao(); public Integer getId() { return getInt("id"); } public void setId(Integer id) { set("id", id); } public String getUsername() { return getStr("username"); } public void setUsername(String username) { set("username", username); } public String getPassword() { return getStr("password"); } public void setPassword(String password) { set("password", password); } public String getNickname() { return getStr("nickname"); } public void setNickname(String nickname) { set("nickname", nickname); } public String getEmail() { return getStr("email"); } public void setEmail(String email) { set("email", email); } } ``` 4. 创建前端代码 创建一个index.html文件,用于展示用户列表和添加用户。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>User Management System</title> <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.1/theme-chalk/index.css"> </head> <body> <div id="app"> <el-container> <el-header> <h1 style="color: white">用户管理系统</h1> </el-header> <el-main> <el-table :data="userList" border style="width: 100%"> <el-table-column label="ID" prop="id"></el-table-column> <el-table-column label="用户名" prop="username"></el-table-column> <el-table-column label="昵称" prop="nickname"></el-table-column> <el-table-column label="邮箱" prop="email"></el-table-column> <el-table-column label="操作"> <template slot-scope="scope"> <el-button type="primary" @click="editUser(scope.row)">编辑</el-button> <el-button type="danger" @click="deleteUser(scope.row)">删除</el-button> </template> </el-table-column> </el-table> <el-pagination :total="total" :page-size="pageSize" :current-page.sync="currentPage" @current-change="getPage"></el-pagination> <el-form :model="user" ref="userForm" label-width="80px" style="margin-top: 20px;"> <el-form-item label="用户名" prop="username"> <el-input v-model="user.username" placeholder="请输入用户名"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input v-model="user.password" placeholder="请输入密码"></el-input> </el-form-item> <el-form-item label="昵称" prop="nickname"> <el-input v-model="user.nickname" placeholder="请输入昵称"></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="user.email" placeholder="请输入邮箱"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="saveUser">保存</el-button> <el-button @click="resetForm">重置</el-button> </el-form-item> </el-form> </el-main> </el-container> </div> <script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script> <script src="https://cdn.staticfile.org/element-ui/2.15.1/index.js"></script> <script src="https://cdn.staticfile.org/axios/0.21.1/axios.min.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data: { userList: [], total: 0, pageSize: 10, currentPage: 1, user: { username: '', password: '', nickname: '', email: '' } }, created: function () { this.getPage(1); }, methods: { getPage: function (page) { let _this = this; axios.get('/user/list', { params: { page: page, limit: _this.pageSize } }).then(function (response) { _this.userList = response.data.list; _this.total = response.data.total; }).catch(function (error) { console.log(error); }); }, editUser: function (row) { this.user = row; }, deleteUser: function (row) { let _this = this; let id = row.id; axios.post('/user/delete', { id: id }).then(function (response) { _this.getPage(_this.currentPage); }).catch(function (error) { console.log(error); }); }, saveUser: function () { let _this = this; this.$refs.userForm.validate(function (valid) { if (valid) { axios.post('/user/save', _this.user).then(function (response) { _this.getPage(_this.currentPage); _this.resetForm('userForm'); }).catch(function (error) { console.log(error); }); } else { return false; } }); }, resetForm: function (formName) { this.$refs[formName].resetFields(); this.user = { username: '', password: '', nickname: '', email: '' }; } } }); </script> </body> </html> ``` 5. 配置路由 在JFinalConfig类中配置路由。 ```java public class DemoConfig extends JFinalConfig { @Override public void configConstant(Constants me) { me.setDevMode(true); } @Override public void configRoute(Routes me) { me.add("/user", UserController.class); } @Override public void configPlugin(Plugins me) { DruidPlugin dp = new DruidPlugin("jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8", "root", "123456"); me.add(dp); ActiveRecordPlugin arp = new ActiveRecordPlugin(dp); me.add(arp); arp.addMapping("user", User.class); } @Override public void configInterceptor(Interceptors me) { } @Override public void configHandler(Handlers me) { } } ``` 6. 运行项目 运行项目,访问http://localhost:8080/user/index即可看到用户管理系统页面。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值