来看一下实现的界面效果
pom.xml的maven依赖
<!-- 引入 socket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 引入 Fastjson ,实现序列化使用 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置类
@Configuration
public class WebSocketConfiguration {
/**
* 给 spring 容器注入这个 ServerEndpointExporter对象
* <p>
* 这个bean会检测所有带有 @ServerEndpoint 注解的 bean 并注册他们。
* ps:
* 如果使用的是外置的 Tomcat 容器,则不需要自己提供 ServerEndpointExporter,因为它将由 Tomcat 容器自己提供和管理。
*
* @return ServerEndpointExporter
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
消息接口
public interface Message {
}
WebSocket 会话上下文工具
@Slf4j
public class WebSocketContext {
/**
* Session 与用户的映射
*/
private static final Map<Session, String> SESSION_USER_MAP = new ConcurrentHashMap<>();
/**
* 用户与 Session 的映射
*/
private static final Map<String, Session> USER_SESSION_MAP = new ConcurrentHashMap<>();
/**
* 添加 Session 在这个方法中,会绑定用户和 Session 之间的映射
*
* @param session Session
* @param user 用户
*/
public static void add(Session session, String user) {
// 更新 USER_SESSION_MAP , 这里的 user 正常来讲应该是具体的用户(id),而不是单纯的 session.getId()
USER_SESSION_MAP.put(user, session);
// 更新 SESSION_USER_MAP
SESSION_USER_MAP.put(session, user);
}
/**
* 移除 Session
*
* @param session Session
*/
public static void remove(Session session) {
// 从 SESSION_USER_MAP 中移除
String user = SESSION_USER_MAP.remove(session);
// 从 USER_SESSION_MAP 中移除
if (user != null && user.length() > 0) {
USER_SESSION_MAP.remove(user);
}
}
/**
* 广播发送消息给所有在线用户
*
* @param type 消息类型
* @param message 消息体
* @param <T> 消息类型
* @param me 当前消息的发送者,不会将消息发送给自己
*/
public static <T extends Message> void broadcast(String type, T message, Session me) {
// 创建消息
String messageText = buildTextMessage(type, message);
// 遍历 SESSION_USER_MAP ,进行逐个发送
for (Session session : SESSION_USER_MAP.keySet()) {
if (!session.equals(me)) {
sendTextMessage(session, messageText);
}
}
}
/**
* 发送消息给单个用户的 Session
*
* @param session Session
* @param type 消息类型
* @param message 消息体
* @param <T> 消息类型
*/
public static <T extends Message> void send(Session session, String type, T message) {
// 创建消息
String messageText = buildTextMessage(type, message);
// 遍历给单个 Session ,进行逐个发送
sendTextMessage(session, messageText);
}
/**
* 发送消息给指定用户
*
* @param user 指定用户
* @param type 消息类型
* @param message 消息体
* @param <T> 消息类型
* @return 发送是否成功
*/
public static <T extends Message> boolean send(String user, String type, T message) {
// 获得用户对应的 Session
Session session = USER_SESSION_MAP.get(user);
if (session == null) {
log.error("==> user({}) 不存在对应的 session", user);
return false;
}
// 发送消息
send(session, type, message);
return true;
}
/**
* 构建完整的消息
*
* @param type 消息类型
* @param message 消息体
* @param <T> 消息类型
* @return 消息
*/
private static <T extends Message> String buildTextMessage(String type, T message) {
JSONObject messageObject = new JSONObject();
messageObject.put("type", type);
messageObject.put("body", message);
return messageObject.toString();
}
/**
* 真正发送消息
*
* @param session Session
* @param messageText 消息
*/
private static void sendTextMessage(Session session, String messageText) {
if (session == null) {
log.error("===> session 为 null");
return;
}
RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic == null) {
log.error("===> session.basic 为 null");
return;
}
try {
basic.sendText(messageText);
} catch (IOException e) {
log.error("===> session: {} 发送消息: {} 发生异常", session, messageText, e);
}
}
/**
* 在线人数通知
*/
public static void countNotice() {
Integer count = SESSION_USER_MAP.size();
ChatCountMessage message = new ChatCountMessage();
message.setCount(count);
broadcast(MsgTypeEnum.CHAT_COUNT.getCode(), message, null);
}
}
消息类型枚举
@Getter
@AllArgsConstructor
public enum MsgTypeEnum {
/**
* 同于标识 当前消息是 聊天消息
*/
CHAT_MSG("1", "聊天消息"),
/**
* 用于标识 当前消息是 人数消息
*/
CHAT_COUNT("2", "聊天室人数");
private final String code;
private final String desc;
}
配置接入点
ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
@Component
@ServerEndpoint("/chat")
@Slf4j
public class WebSocketServer {
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
log.info("===> onOpen:{}", session.getId());
// 上线,并且通知到其他人
WebSocketContext.add(session, session.getId());
WebSocketContext.countNotice();
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
log.info("===> onClose:{}", session.getId());
// 下线,并且通知到其他人
WebSocketContext.remove(session);
WebSocketContext.countNotice();
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("===> onMessage:{},message:{}", session.getId(), message);
// 进行消息的转发,同步到其他的客户端上
ChatMsgMessage msg = JSON.parseObject(message, ChatMsgMessage.class);
WebSocketContext.broadcast(MsgTypeEnum.CHAT_MSG.getCode(), msg, session);
}
/**
* 监听错误
*
* @param session session
* @param error 错误
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("SessionId:{},出现异常:{}", session.getId(), error.getMessage());
error.printStackTrace();
}
}
在线人数消息实体
@Data
@Accessors(chain = true)
public class ChatCountMessage implements Message {
public static final String TYPE = MsgTypeEnum.CHAT_COUNT.getCode();
/**
* 消息编号
*/
private String msgId;
/**
* 内容
*/
private Integer count;
}
消息发送实体
@Data
@Accessors(chain = true)
public class ChatMsgMessage implements Message {
public static final String TYPE = MsgTypeEnum.CHAT_MSG.getCode();
/**
* 消息编号
*/
private String msgId;
/**
* 内容
*/
private String msg;
}
在resources下新建static静态文件夹
index.css文件
@font-face {
font-family: "pix";
src: url("../DottedSongtiSquareRegular.otf");
}
html, body, pre, code, kbd, samp {
font-family: "pix", serif;
font-weight: bold;
font-size: 35px;
}
.all-div {
display: flex;
flex-direction: column;
width: 800px;
margin: 20px auto;
overflow-scrolling: auto;
}
/*.message {*/
/* overflow: auto;*/
/* width: 800px;*/
/* height: 400px;*/
/* margin-top: 20px;*/
/*}*/
.send-btns {
display: flex;
}
/*自己发送聊天的样式*/
.message-me {
color: red;
text-align: right
}
.message-list {
display: flex;
flex-direction: column;
}
.message-left {
display: flex;
margin-top: 2rem;
align-self: flex-start;
}
.message-right {
display: flex;
margin-top: 2rem;
align-self: flex-end;
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在线聊天</title>
<link href="NES.css" rel="stylesheet"/>
<link href="/css/index.css" rel="stylesheet">
</head>
<style>
</style>
<body>
<div class="all-div">
<!-- 头部区域 -->
<div style="margin-left: 10px">
Spring Boot 集成 WebSocket 示例;
<span class="nes-text is-primary">在线人数:</span>
<span class="nes-text is-error" id="count">0</span>
</div>
<!-- 内容显示区域 -->
<div class="nes-container is-rounded is-dark message-list" id="message">
</div>
<!-- 操作区域 -->
<div class="nes-field is-inline">
<br/>
<input id="text" type="text" class="nes-input" style="padding: .2rem 1rem !important;"/>
<button onclick="send()" class="nes-btn is-success">发送</button>
<button onclick="closeWebSocket()" class="nes-btn is-error">关闭WebSocket连接</button>
</div>
</div>
</body>
<script type="text/javascript">
let websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
//改成你的地址
websocket = new WebSocket("ws://127.0.0.1:8080/chat");
} else {
alert('当前浏览器不支持 websocket')
throw "当前浏览器不支持 websocket"
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误" + " ");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功" + " ");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
let jsonData = event.data;
let data = JSON.parse(jsonData);
console.log("收到消息==", event);
if (data.type === "1") {
let msg = otherPersonShowMsg(data.body.msg)
setMessageInnerHTML(msg);
}
if (data.type === "2") {
setChatCountInnerHTML(data.body.count)
}
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭" + " ");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML;
}
//将消息显示在网页上
function setChatCountInnerHTML(innerHTML) {
document.getElementById('count').innerHTML = innerHTML;
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
setChatCountInnerHTML(0)
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send('{"msg":"' + message + '"}');
document.getElementById('text').value = '';
message = this.meShowMsg(message);
setMessageInnerHTML(message);
}
// 显示别人发送的消息
function otherPersonShowMsg(str) {
return ` <section class="message-left">
<i class="nes-bcrikko"></i>
<div class="nes-balloon from-left is-dark" style="padding: .2rem 1rem !important;">
<p>${str}</p>
</div>
</section>`
}
// 显示自己发送的消息
function meShowMsg(str) {
return ` <section class="message-right">
<div class="nes-balloon from-right is-dark" style="padding: .2rem 1rem !important;">
<p>${str}</p>
</div>
<i class="nes-bcrikko"></i>
</section>`
}
</script>
</html>
以上的是Spring Boot 整合 socket 实现简单聊天 若需完整代码 可识别二维码后 给您发代码。