Springboot 集成webscoket 实现系统给用户发信息, 用户界面右上角有个小铃铛
一. 引入pom.xml ,启动类记得添加注解:@EnableWebSocket
<!-- websocket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
二. 创建配置类:WebSocketConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* websocket 配置
*
* @author yzz
*/
@Configuration
public class WebSocketConfig
{
@Bean
public ServerEndpointExporter serverEndpointExporter()
{
return new ServerEndpointExporter();
}
}
三. 信号量相关处理类:SemaphoreUtils
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 信号量相关处理
*
* @author xx
*/
public class SemaphoreUtils
{
/**
* SemaphoreUtils 日志控制器
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);
/**
* 获取信号量
*
* @param semaphore
* @return
*/
public static boolean tryAcquire(Semaphore semaphore)
{
boolean flag = false;
try
{
flag = semaphore.tryAcquire();
}
catch (Exception e)
{
LOGGER.error("获取信号量异常", e);
}
return flag;
}
/**
* 释放信号量
*
* @param semaphore
*/
public static void release(Semaphore semaphore)
{
try
{
semaphore.release();
}
catch (Exception e)
{
LOGGER.error("释放信号量异常", e);
}
}
}
四. 消息处理类:MyWebSocket
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* MyWebSocket 消息处理
*
* @author xx
*/
@Component
@ServerEndpoint("/websocket/message/{userId}")
public class MyWebSocket
{
/*** WebSocketServer 日志控制器*/
private static final Logger LOGGER = LoggerFactory.getLogger(MyWebSocket.class);
/*** 默认最多允许同时在线人数100*/
public static int socketMaxOnlineCount = 100;
private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
private static int onlineCount = 0;
/** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
在外部可以获取此连接的所有websocket对象,并能对其触发消息发送功能,我们的定时发送核心功能的实现在与此变量 */
private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
// 接收userId
private String userId = "";
public static Map<String,String> body;
static
{
body = new HashMap<String, String>();
body.put("-1", "-1");
}
/**
* 连接建立成功调用的方法
* 注意这里的userid,前端请求webscoket要这样:
* ws://127.0.0.1:8080/websocket/message/2
*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) throws Exception
{
this.session = session;
boolean semaphoreFlag = false;
// 尝试获取信号量
semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);
if (!semaphoreFlag)
{
// 未获取到信号量
LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);
sendMessage("当前在线人数超过限制数:" + socketMaxOnlineCount);
session.close();
}else{
this.userId = userId;
for (MyWebSocket webSocket:webSocketSet){
if (webSocket.getUserId().equals(userId)){
webSocketSet.remove(webSocket);
subOnlineCount();
body.remove(userId);
System.out.println("用户"+userId+"连接关闭当前人数为"+getOnlineCount());
}
}
try {
body.put(userId,null);
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
// sendMessage("连接已建立成功.");
} catch (Exception e) {
System.out.println("IO异常");
}
}
}
/**
* 连接关闭时处理
*/
@OnClose
public void onClose(Session session)
{
LOGGER.info("\n 关闭连接 - {}", session);
MyWebSocket webSocket = this;
boolean b=false;
for (MyWebSocket socket:webSocketSet){
if (socket.equals(webSocket)){
b=true;
}
}
if (b) {
//连接关闭后,将此websocket从set中删除
webSocketSet.remove(this);
subOnlineCount(); //在线数减1
body.remove(userId);
// 获取到信号量则需释放
SemaphoreUtils.release(socketSemaphore);
}
LOGGER.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 抛出异常时处理
*/
@OnError
public void onError(Session session, Throwable exception) throws Exception
{
if (session.isOpen())
{
// 关闭连接
session.close();
}
String sessionId = session.getId();
LOGGER.info("\n 连接异常 - {}", sessionId);
LOGGER.info("\n 异常信息 - {}", exception);
// 获取到信号量则需释放
SemaphoreUtils.release(socketSemaphore);
}
/**
* 服务器接收到客户端消息时调用的方法
*/
@OnMessage
public void onMessage(String message, Session session)
{
String msg = message.replace(" ", "");
LOGGER.info("\n 来自客户端的消息: {}", msg);
}
/**
* 发送消息,在定时任务中会调用此方法*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
MyWebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
MyWebSocket.onlineCount--;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public static CopyOnWriteArraySet<MyWebSocket> getWebSocketSet() {
return webSocketSet;
}
public static void setWebSocketSet(CopyOnWriteArraySet<MyWebSocket> webSocketSet) {
MyWebSocket.webSocketSet = webSocketSet;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
五. 系统给用户发送信息。
注:使用了定时任务。后管点击发送信息信息保存到数据库通知管理表,然后通过定时任务去获取然后发送给指定的客户,代码如下:
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
@Scheduled(cron = "0/1 * * * * ?") //每秒执行一次
public void test(){
//1.获取所有在线连接
CopyOnWriteArraySet<MyWebSocket> webSocketSet =MyWebSocket.getWebSocketSet();
webSocketSet.forEach(c->{
try {
//客户id
String userId = c.getUserId();
//查询通知管理表
LambdaQueryWrapper<BusiNotice> wrapper=new LambdaQueryWrapper();
wrapper.eq(BusiNotice::getToUser, Integer.parseInt(userId));//接收人
wrapper.eq(BusiNotice::getLanguageType,"cn");//语言
wrapper.eq(BusiNotice::getStatus,0);//0未读,1已读
wrapper.in(BusiNotice::getType,0,2);//0系统通知,1首页通知,2邮件站内信
wrapper.orderByDesc(BusiNotice::getCreateTime);
List<BusiNotice> list = noticeService.list(wrapper);
//将信息处理成json字符串
Gson gson = new Gson();
String listToJsonString = gson.toJson(list);
//查询缓存里面是否有之前发送过的信息,
//是null ,直接发送,并放入缓存中
//不是null,判断处理的json和缓存里的是否一样,不同:发送并放入缓存中|| 相同:不发送
String s = MyWebSocket.body.get(userId);
if (!ObjectUtils.isEmpty(s)) {
if (listToJsonString.equals(s)) {
return;
} else {
c.sendMessage(listToJsonString);
MyWebSocket.body.put(userId,listToJsonString);
logger.info("用户"+userId+"发送成功");
}
}else {
c.sendMessage(listToJsonString);
MyWebSocket.body.put(userId,listToJsonString);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
五. 2)nginx 配置
server {
listen 443;
server_name XXXXX.com.cn;
ssl_certificate /web/ssl/cn1.pem;
ssl_certificate_key /web/ssl/cn1.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
root /web/cn/index;
try_files $uri $uri/ /index.html =404;
}
location /api/ {
proxy_pass http://127.0.0.1:9999/;
proxy_buffering off;
proxy_connect_timeout 15s;
proxy_send_timeout 15s;
proxy_read_timeout 15s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /wss/ {
proxy_pass http://127.0.0.1:9999/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
六. 1)前端页面: webscoket.html
**注意:如果是要通过 nginx 那么链接要改成:wss://XXXXX.com.cn/wss/websocket/message/1
XXXXX.com.cn表示域名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>测试界面</title>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" ></script>
</head>
<body>
<div>
<input type="text" style="width: 20%" value="ws://127.0.0.1:8080/websocket/message/2" id="url">
<button id="btn_join">连接</button>
<button id="btn_exit">断开</button>
</div>
<br/>
<textarea id="message" cols="100" rows="9"></textarea> <button id="btn_send">发送消息</button>
<br/>
<br/>
<textarea id="text_content" readonly="readonly" cols="100" rows="9"></textarea>返回内容
<br/>
<br/>
<script type="text/javascript">
$(document).ready(function(){
var ws = null;
// 连接
$('#btn_join').click(function() {
var url = $("#url").val();
ws = new WebSocket(url);
ws.onopen = function(event) {
$('#text_content').append('已经打开连接!' + '\n');
}
ws.onmessage = function(event) {
$('#text_content').append(event.data + '\n');
}
ws.onclose = function(event) {
$('#text_content').append('已经关闭连接!' + '\n');
}
});
// 发送消息
$('#btn_send').click(function() {
var message = $('#message').val();
if (ws) {
ws.send(message);
} else {
alert("未连接到服务器");
}
});
//断开
$('#btn_exit').click(function() {
if (ws) {
ws.close();
ws = null;
}
});
})
</script>
</body>
</html>
六. 2)前端页面: webscoket.vue
<template>
<div>
<el-input v-model="url" type="text" style="width: 20%" />
<el-button @click="join" type="primary">连接</el-button>
<el-button @click="exit" type="danger">断开</el-button>
<br />
<el-input type="textarea" v-model="message" :rows="9" />
<el-button type="info" @click="send">发送消息</el-button>
<br />
<br />
<el-input type="textarea" v-model="text_content" :rows="9" /> 返回内容
<br />
<br />
</div>
</template>
<script>
export default {
data() {
return {
url: "ws://127.0.0.1:8080/websocket/message/2",
message: "",
text_content: "",
ws: null,
};
},
methods: {
join() {
const wsuri = this.url;
this.ws = new WebSocket(wsuri);
const self = this;
this.ws.onopen = function (event) {
self.text_content = self.text_content + "已经打开连接!" + "\n";
};
this.ws.onmessage = function (event) {
self.text_content = event.data + "\n";
};
this.ws.onclose = function (event) {
self.text_content = self.text_content + "已经关闭连接!" + "\n";
};
},
exit() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
},
send() {
if (this.ws) {
this.ws.send(this.message);
} else {
alert("未连接到服务器");
}
},
},
};
</script>