引用
SSE(Server-Sent Events)是 HTML5 遵循 W3C 标准提出的客户端和服务端之间进行实时通信的协议。
SSE 客户端可以接收来自服务器的“流”数据,而不需要进行轮询
。由于**不浪费请求
**,因此 SSE 对于减轻服务器的压力非常有用。
SSE 使用**纯 JavaScript
** 实现简单,不需要额外的插件或库来处理消息(简单方便
)。
SSE 天生具有自适应性,由于 SSE 是基于 HTTP 响应使用 EventStream 传递消息,因此它利用了 HTTP 的开销和互联网上的结构。
SSE 可以与任何服务器语言和平台一起使用,因为 SSE 是一种规定了消息传递方式的技术,不依赖于具体的服务器语言和平台。
SSE 是**单向通信**,只能从服务器推送到客户端。如果应用程序需要双向通信,就需要使用 Websocket。
SSE无法发送二进制数据,只能发送 UTF-8 编码的文本。如果应用程序需要发送二进制数据,就需要使用 Websocket。
SSE 不是所有浏览器都支持。虽然 SSE 是 HTML5 的一部分,但具体的浏览器支持性可能会有差异。
养生店PC端操作单端通信告知房间小屏数据发生变更做出页面刷新。
当前台PC端 安排了 技师到某个房间,或者上钟,确认项目,设置空房等等. 进行消息通知 .房间小屏进行数据的相应变更
前后端通信断网情况处理。断开连接后,每隔一定睡眠时间,再次自调用确保重建连接。
let source = null
//查看
if (!!window.EventSource) {
createSource()
} else {
console.log("不支持");
}
window.onbeforeunload = function () {
close();
};
// 关闭 为避免占用服务器资源. 调用后台提供出来的清除通信链接API
function close() {
source.close();
const httpRequest = new XMLHttpRequest();
httpRequest.open('GET', 'http://192.168.10.83:8080/api/sse/communication/closePadClient?shopId=1&roomId=1', true);
httpRequest.send();
console.log("close");
}
function createSource(){
source = new EventSource('http://192.168.10.83:8080/api/sse/communication/createPadClient?shopId=1&roomId=1');
//建立连接
source.onopen = function (event) {
console.log("建立连接" + event);
}
//接收数据
source.onmessage = function (event) {
console.log(event.data);
}
//错误监听
source.onerror = async function (event) {
if (event.currentTarget.readyState === EventSource.CLOSED) {
console.log("连接关闭");
await sleep(3000)
close()
createSource()
} else {
console.log(event);
}
}
}
//js 睡眠解决方案
const sleep = async (count) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, count)
})
}
Spring MVC代码
@Controller
@RequestMapping("/api/sse/communication")
public class SSECommunicationServiceApi {
@RequestMapping("/createPadClient")
public SseEmitter createPadClient(HttpServletRequest request) {
String shopId = request.getParameter("shopId");
String roomId = request.getParameter("roomId");
if (!StringUtils.hasText(shopId)) {
throw new BusinessException("连接错误!店铺id为空");
}
if (!StringUtils.hasText(roomId)) {
throw new BusinessException("连接错误!房间id为空");
}
// 设置超时时间,0表示不过期。默认30000毫秒
//可以在客户端一直断网、直接关闭页面但未提醒后端的情况下,服务端在一定时间等待后自动关闭网络连接
SseEmitter sseEmitter = new SseEmitter(0L);
String roomKey = shopId+":"+roomId;
SSECommunicationUtil.putSSEEmitter(roomKey,sseEmitter);
System.out.println("sse连接,当前客户端:"+roomKey);
return sseEmitter;
}
@ResponseBody
@RequestMapping("/closePadClient")
public BaseRespVO closePadClient(HttpServletRequest request) {
String shopId = request.getParameter("shopId");
String roomId = request.getParameter("roomId");
if (!StringUtils.hasText(shopId)) {
throw new BusinessException("关闭连接错误!店铺id为空");
}
if (!StringUtils.hasText(roomId)) {
throw new BusinessException("关闭连接错误!房间id为空");
}
String roomKey = shopId+":"+roomId;
SSECommunicationUtil.removeSSEEmitter(roomKey);
System.out.println("sse连接,当前客户端:"+roomKey);
return new BaseRespVO("关闭成功!");
}
@ResponseBody
@RequestMapping( "/testNotify")
public BaseRespVO testNotify(HttpServletRequest request) {
String shopId = request.getParameter("shopId");
String roomId = request.getParameter("roomId");
SSECommunicationUtil.updateRegNotificationPad(shopId,roomId, RegChangeNotificationEnum.ARRANGE_DOCTOR);
return new BaseRespVO("发送完成!");
}
}
SSE管理连接工具类。
public class SSECommunicationUtil {
private static Map<String, SseEmitter> cache = new ConcurrentHashMap<>();
public static void updateRegNotificationPad(String shopId, String roomId, RegChangeNotificationEnum notify) {
try {
SseEmitter sseEmitter = cache.get(shopId+":"+roomId);
Random random = new Random();
int sseId = random.nextInt(100000);
sseEmitter.send(
SseEmitter
.event()
.data(notify)
.id("" + sseId)
.reconnectTime(3000)
);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static void putSSEEmitter(String key,SseEmitter emitter) {
SSECommunicationUtil.cache.put(key,emitter);
}
public static void removeSSEEmitter(String key) {
SSECommunicationUtil.cache.remove(key);
}
}
通知消息枚举类
public enum RegChangeNotificationEnum {
ARRANGE_DOCTOR("ARRANGE_DOCTOR","对已预约记录安排医生"),
CLOCK_UP("CLOCK_UP","医生已进行调理(已上钟)"),
CLOCK_DOWN("CLOCK_DOWN","医生已结束调理(已下钟)"),
CONFIM_ITEM("CONFIM_ITEM","已确认调理项目"),
VACATED_ROOM("VACATED_ROOM","已打扫,置空房"),
;
private String code;
private String title;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
RegChangeNotificationEnum(String code, String title) {
this.code = code;
this.title = title;
}
}