目录
概述:本项目整合websocket协议,实现一个简单web聊天室功能;
1、用户在登录页面点击登录后,跳转到后端的 “ loginvalidate ” 路径下进行,如果用户名密码正确,则跳转到聊天室页面。
2、 聊天室页面首先会先建立一个websocket连接,并广播上线消息提醒,默认连接的时localhost:8080服务器,后续可根据需要自行进行修改。
4、消息的即时发送,针对选定的在线用户进行消息的发送。编辑
5、连接关闭:当关闭浏览器或退出页面自动关闭websocket连接
概述:本项目整合websocket协议,实现一个简单web聊天室功能;
这个聊天室主要包含一下几个功能:
1、聊天室的登录、退出;登陆时,浏览器自动向服务器发起websocket连接,退出时自动切断。
2、登陆后能够查看当前在线的用户列表。
3、登录的用户可以点击一个在线的其他用户,并给他发送消息,消息先提交到服务器,在通过服务器转发给另一端用用户,支持消息群发功能,群发消息展示给所有用户。
4、支持好友上、下线提醒功能。
执行步骤:
1、用户在登录页面点击登录后,跳转到后端的 “ loginvalidate ” 路径下进行,如果用户名密码正确,则跳转到聊天室页面。
2、 聊天室页面首先会先建立一个websocket连接,并广播上线消息提醒,默认连接的时localhost:8080服务器,后续可根据需要自行进行修改。
3、展示在线用户
4、消息的即时发送,针对选定的在线用户进行消息的发送。
5、群发消息,给所有在线用户发送消息提醒。


5、连接关闭:当关闭浏览器或退出页面自动关闭websocket连接
源码:
login页面
<!DOCTYPE>
<html>
<head>
<title>login</title>
<script src="./js/jquery-1.12.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style type="text/css">
.vertical-center{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div class="container vertical-center">
<div class="col-md-6 col-md-offset-3">
<form action="loginvalidate" method="post">
<h2 >登录聊天室</h2>
<label for="inputEmail" class="sr-only">userid</label>
<input type="text" name="username" id="inputEmail" class="form-control" placeholder="userid" required autofocus/>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required/>
<label for="inputEmail" class="sr-only">userid</label>
<button class="btn btn-lg btn-primary btn-block" type="submit">login</button>
</form>
</div>
</div> <!-- /container -->
</body>
</html>
聊天室页面:chatroom.html
<!DOCTYPE>
<html>
<head>
<title>聊天室</title>
<script src="./js/jquery-1.12.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"/>
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style>
body{
margin-top:5px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">当前登录用户</h3>
</div>
<div class="panel-body">
<div class="list-group">
<a href="#" class="list-group-item">你好,<span id="user"></span></a>
<a href="logout" class="list-group-item">退出</a>
</div>
</div>
</div>
<div class="panel panel-primary" id="online">
<div class="panel-heading">
<h3 class="panel-title">当前在线的其他用户</h3>
</div>
<div class="panel-body">
<div class="list-group" id="users">
</div>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">群发系统广播</h3>
</div>
<div class="panel-body">
<input type="text" class="form-control" id="msg" /><br>
<button id="broadcast" type="button" class="btn btn-primary">发送</button>
</div>
</div>
</div>
<div class="col-md-9">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" id="talktitle"></h3>
</div>
<div class="panel-body">
<div class="well" id="log-container" style="height:400px;overflow-y:scroll">
</div>
<input type="text" id="myinfo" class="form-control col-md-12" /> <br>
<button id="send" type="button" class="btn btn-primary">发送</button>
</div>
</div>
</div>
</div>
</div>
<script>
Date.prototype.format = function(fmt) {
var o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
if(/(y+)/.test(fmt)) {
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
}
for(var k in o) {
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
}
$(document).ready(function() {
var user;
var uid;
// 指定websocket路径
var websocket;
$.get("/currentuser",function(data){
user = data.name;
uid = data.uid;
$("#user").html(user);
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/webSocket/"+user);
console.log("连接成功")
}
websocket.onmessage = function(event) {
var data=JSON.parse(event.data);
if(data.to==0){//上线消息
if(data.text!=user)
{
$("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">'+data.text+'</a>');
alert(data.text + "上线了");
}
}else if(data.to==-2){//下线消息
if(data.text!=user)
{
$("#users > a").remove(":contains('"+data.text+"')");
alert(data.text + "下线了");
}
}else {
// 普通消息
// 接收服务端的实时消息并添加到HTML页面中
$("#log-container").append("<div class='bg-info'><label class='text-danger'>"+data.from+" "+data.date+"</label><div class='text-success'>"+data.text+"</div></div><br>");
// 滚动条滚动到最低部
scrollToBottom();
}
};
$.post("/onlineusers?currentuser="+user,function(data){
for(var i=0;i<data.length;i++)
$("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">'+data[i]+'</a>');
});
});
$("#broadcast").click(function(){
var data = {};
data["from"] = "系统消息";
data["to"] = -1;
data["text"] = $("#msg").val();
websocket.send(JSON.stringify(data));
$("#msg").val("");
});
$("#send").click(function() {
if ($("body").data("to")==undefined) {
alert("请选择聊天对象");
return false;
}
var data = {};
data["from"] = user;
data["to"] = $("body").data("to");
data["text"] = $("#myinfo").val();
websocket.send(JSON.stringify(data));
$("#log-container").append("<div class='bg-success'><label class='text-info'>我 " + new Date().format("yyyy-MM-dd hh:mm:ss") + "</label><div class='text-info'>" + $("#myinfo").val() + "</div></div><br>");
scrollToBottom();
$("#myinfo").val("");
});
});
function talk(a){
$("#talktitle").text("与"+a.innerHTML+"的聊天");
$("body").data("to",a.innerHTML);
}
function scrollToBottom(){
var div = document.getElementById('log-container');
div.scrollTop = div.scrollHeight;
}
</script>
</body>
</html>
Login
package com.project.websocketdemo.controller;
import com.alibaba.druid.util.StringUtils;
import com.project.websocketdemo.entity.User;
import com.project.websocketdemo.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.sql.Struct;
/**
* @Author:yaojunjie
* @Package:com.project.websocketdemo.controller
* @Date:2024/5/2 21:02
*/
@Controller
public class Login {
@Autowired
private LoginService loginService;
@RequestMapping("/loginvalidate")
public String loginvalidate(@RequestParam("username") String username, @RequestParam("password") String pwd, HttpSession httpSession){
if (StringUtils.isEmpty(username)){
return "login";
}
String realPwd = loginService.getpwdbyname(username);
if (!StringUtils.isEmpty(realPwd) && StringUtils.equals(pwd,realPwd)){
Long uid = loginService.getUidbyname(username);
httpSession.setAttribute("uid",uid);
return "chatroom";
}else {
return "fail";
}
}
@RequestMapping("/login")
public String login() {
return "login";
}
@RequestMapping("/logout")
public String logout(HttpSession httpSession) {
return "login";
}
@RequestMapping(value = "/currentuser", method = RequestMethod.GET)
@ResponseBody
public User currentuser(HttpSession httpSession) {
Long uid = (Long) httpSession.getAttribute("uid");
String name = loginService.getnamebyid(uid);
return new User(uid, name);
}
}
ChatController
package com.project.websocketdemo.controller;
import com.project.websocketdemo.service.LoginService;
import com.project.websocketdemo.service.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.websocket.Session;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author:yaojunjie
* @Package:com.project.websocketdemo.controller
* @Date:2024/5/2 23:11
*/
@Controller
public class ChatController {
@Autowired
private LoginService loginService;
@RequestMapping("/onlineusers")
@ResponseBody
public Set<String> onlineusers(@RequestParam("currentuser") String currentuser){
ConcurrentHashMap<String, Session> map = WebSocketServer.getSessionPools();
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
Set<String> nameset = new HashSet<>();
while (it.hasNext()){
String entry = it.next();
if (!entry.equals(currentuser)){
nameset.add(entry);
}
}
return nameset;
}
}
WebSocket
package com.project.websocketdemo.service;
import com.alibaba.fastjson.JSON;
import com.project.websocketdemo.entity.Message;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author:yaojunjie
* @Package:com.project.websocketdemo.service
* @Date:2024/5/2 22:20
*/
@ServerEndpoint("/webSocket/{username}")
@Component
public class WebSocketServer {
// 静态变量,用来记录当前在线连接数。
private static AtomicInteger onlineNum = new AtomicInteger();
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象
private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
/**
* 连接成功时调用
* @param session
* @param userName
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "username") String userName) {
sessionPools.put(userName, session);
addOnlineCount();
System.out.println("加入webSocket!当前人数为" + onlineNum);
// 广播上线消息
Message message = new Message();
message.setDate(new Date());
message.setTo("0");
message.setText(userName);
broadcast(JSON.toJSONString(message,true));
}
// 收到客户端消息后,根据接收人的username把消息推下去或者群发
// to = -1 是群发消息
@OnMessage
public void onMessage(String message){
System.out.println("Server get "+ message);
Message msg = JSON.parseObject(message, Message.class);
msg.setDate(new Date());
if ("-1".equals(msg.getTo())){
broadcast(JSON.toJSONString(msg,true));
}else {
sendInfo(msg.getTo(),JSON.toJSONString(msg,true));
}
}
/**
* 用户退出时关闭了页面或浏览器,这导致了 WebSocket 连接的断开。
* 在 WebSocket 连接断开时,浏览器会触发 WebSocket 的 onclose 事件
* @param userName
*/
@OnClose
public void onClose(@PathParam(value = "username") String userName){
sessionPools.remove(userName);
subOnlineCount();
System.out.println(userName + "断开webSocket连接!当前人数为" + onlineNum);
// 广播下线消息
Message msg = new Message();
msg.setDate(new Date());
msg.setTo("-2");
msg.setText(userName);
broadcast(JSON.toJSONString(msg,true));
}
//错误时调用
@OnError
public void onError(Session session, Throwable throwable){
System.out.println("发生错误");
throwable.printStackTrace();
}
private void sendInfo(String username, String jsonString) {
Session session = sessionPools.get(username);
try {
sendMessage(session,jsonString);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 群发消息
private void broadcast(String jsonString) {
for (Session session : sessionPools.values()) {
try {
sendMessage(session,jsonString);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 发送消息
public void sendMessage(Session session,String message) throws IOException {
if (session!=null){
synchronized (session){
System.out.println("发送数据:" + message);
session.getBasicRemote().sendText(message);
}
}
}
public static void addOnlineCount() {
onlineNum.incrementAndGet();
}
public static void subOnlineCount() {
onlineNum.decrementAndGet();
}
public static AtomicInteger getOnlineNumber() {
return onlineNum;
}
public static ConcurrentHashMap<String, Session> getSessionPools() {
return sessionPools;
}
}