在我们以往的软件或者网站使用中,都有遇到过这种情况,莫名的弹出广告或者通知!而在我们的业务系统中,有的时候也需要群发通知公告的方式去告知网站用户一些信息,那么这种功能是怎么实现的呢,本文将使用springboot+webSocket来实现这类功能,当然也有其他方式来实现 长连接/websocket/SSE等主流服务器推送技术比较
使用Intellij IDEA 快速创建一个springboot + webSocket项目
复制代码
`<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>`
@ServerEndpoint
这个注解。这个注解是Javaee标准里的注解,tomcat7以上已经对其进行了实现,如果是用传统方法使用tomcat发布的项目,只要在pom文件中引入javaee标准即可使用。复制代码
`<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>`
首先要注入ServerEndpointExporter类,这个bean会自动注册使用了@ServerEndpoint
注解声明的Websocket endpoint。要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为 它(ServerEndpointExporter) 将由容器自己提供和管理。
WebSocketConfig.java
复制代码
`import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}`
接下来就是写websocket的具体实现类,很简单,直接上代码:
BulletinWebSocket.java
复制代码
`package com.example.websocket.controller;
import com.example.websocket.service.BulletinService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArraySet;
/**
*/
@ServerEndpoint(value = "/webSocket/bulletin")
@Component
public class BulletinWebSocket {
private static final Logger LOGGER = LoggerFactory.getLogger(BulletinWebSocket.class);
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext context) {
applicationContext = context;
}
public BulletinWebSocket() {
LOGGER.info("BulletinWebSocket init ");
}
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<BulletinWebSocket> BULLETIN_WEBSOCKETS = new CopyOnWriteArraySet<BulletinWebSocket>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
@OnOpen
public void onOpen(Session session) throws IOException {
this.session = session;
// 加入set中
BULLETIN_WEBSOCKETS.add(this);
// 新登录用户广播通知
this.session.getBasicRemote().sendText(applicationContext.getBean(BulletinService.class).getBulletin()+"-"+new Date());
LOGGER.info("有新连接加入{}!当前在线人数为{}", session, getOnlineCount());
}
@OnClose
public void onClose() {
BULLETIN_WEBSOCKETS.remove(this);
LOGGER.info("有一连接关闭!当前在线人数为{}", getOnlineCount());
}
/**
*
*/
@OnMessage
public void onMessage(String message, Session session) {
LOGGER.info("来自客户端的信息:{}", message);
}
@OnError
public void onError(Session session, Throwable error) {
LOGGER.error("发生错误:{}", session.toString());
error.printStackTrace();
}
/**
*/
@Scheduled(cron = "0/2 ?")
public void sendMessage() throws IOException {
// 所有在线用户广播通知
BULLETIN_WEBSOCKETS.forEach(socket -> {
try {
socket.session.getBasicRemote().sendText("定时:"+applicationContext.getBean(BulletinService.class).getBulletin()+"-"+new Date());
} catch (IOException e) {
e.printStackTrace();
}
});
}
public static synchronized int getOnlineCount() {
return BULLETIN_WEBSOCKETS.size();
}
}`
使用springboot的唯一区别是要添加@Component
注解,而使用独立容器不用,是因为容器自己管理websocket的,但在springboot中连容器都是spring管理的。
虽然@Component
默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来private static CopyOnWriteArraySet<BulletinWebSocket> BULLETIN_WEBSOCKETS = new CopyOnWriteArraySet<BulletinWebSocket>();
。
复制代码
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>static</h1>
<div id="msg" class="panel-body">
</div>
<input id="text" type="text"/>
<button onclick="send()">发送</button>
</body>
<script src="https://cdn.bootcss.com/web-socket-js/1.0.0/web_socket.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://127.0.0.1:8080/webSocket/bulletin");
}
else {
alert("对不起!你的浏览器不支持webSocket")
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
setMessageInnerHTML("加入连接");
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
};
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("断开连接");
};
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,
// 防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
var is = confirm("确定关闭窗口?");
if (is) {
websocket.close();
}
};
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
$("#msg").append(innerHTML + "<br/>")
};
//关闭连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = $("#text").val();
websocket.send(message);
$("#text").val("");
}
</script>
</html>`
GITHUB源码地址《===
复制代码
`CREATE TABLE
bulletin` (
id
int(11) NOT NULL AUTO_INCREMENT COMMENT '编号id',
title
varchar(50) COLLATE utf8_bin NOT NULL COMMENT '标题',
content
varchar(1000) COLLATE utf8_bin NOT NULL COMMENT '内容',
user_type
tinyint(1) NOT NULL COMMENT '通告对象类型 1:单个用户 2:多个用户 3:全部用户',
user_roles
varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '通告对象角色',
user_depts
varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '通告对象部门',
type
tinyint(1) DEFAULT NULL COMMENT '通告类型 1:系统升级',
publish_time
datetime DEFAULT NULL COMMENT '发布时间',
status
tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 0:待发布 1:已发布 2:撤销 ',
created_at
datetime NOT NULL COMMENT '创建时间',
updated_at
timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
created_by
int(11) NOT NULL COMMENT '创建人',
updated_by
int(11) NOT NULL COMMENT '修改人',
PRIMARY KEY (id
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='通告表';``
复制代码
`CREATE TABLE
bulletin_user` (
bulletin_id
int(11) NOT NULL COMMENT '通告编号id',
user_id
int(11) NOT NULL COMMENT '用户id',
is_read
tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否阅读 0否 1是',
created_at
datetime NOT NULL COMMENT '创建时间',
updated_at
timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (bulletin_id
,user_id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户通告标记表';``
以上的功能实现居然可以参考上面 BulletinWebSocket.java 中的这几块代码
复制代码
`/**
@OnOpen
public void onOpen(Session session) throws IOException {
this.session = session;
// 加入set中
BULLETIN_WEBSOCKETS.add(this);
// 新登录用户广播通知
this.session.getBasicRemote().sendText(applicationContext.getBean(BulletinService.class).getBulletin()+"-"+new Date());
LOGGER.info("有新连接加入{}!当前在线人数为{}", session, getOnlineCount());
}`
复制代码
`public void sendMessage() throws IOException {
// 所有在线用户广播通知
BULLETIN_WEBSOCKETS.forEach(socket -> {
try {
socket.session.getBasicRemote().sendText("定时:"+applicationContext.getBean(BulletinService.class).getBulletin()+"-"+new Date());
} catch (IOException e) {
e.printStackTrace();
}
});
}`
SpringBoot 部署与Spring部署都有一些差别,但现在用Srpingboot的公司多,SpringBoot创建项目快,所以使用该方式来讲解,有一个问题就是开发WebSocket时发现无法通过@Autowired注入bean,一直为空。怎么解决呢?
其实不是不能注入,是已经注入了,但是客户端每建立一个链接就会创建一个对象,这个对象没有任何的bean注入操作,下面贴下实践
接下来
解决办法就是springboot的启动类注入一个static的对象
最后在WebSocket endpoint类添加相应的静态对象,并添加set方法
接着如果那里要使用Spring管理在Bean的话,就可以使用这种方式使用 applicationContext.getBean(BulletinService.class)
原网址: 访问
创建于: 2023-10-08 15:36:44
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论