背景:当一个用户反馈给另一个用户的时候,被反馈的用户可以弹出消息框,在反馈框中会有红色小点点。这就意味着我后端要主动推给前端。之前做过webSocket,真的感觉不敢用,前端要是做轮询的话,性能都没有了,一种拣了芝麻丢了西瓜的感觉。嗝,百度看到了sse。
sse是后端向前端发信息的单向通道,客户端发送一个请求,服务端保持足够连接直到有新消息发送回客户端,仍然保持着连接,这样连接就可以消息再次发送,由服务器单向发送给客户端。
偷了别的图
package com.ccas.manage.config;
import com.ccas.manage.DTO.res.ResMess;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@Slf4j
public class SseEmitterServer {
private static final Logger logger = LoggerFactory.getLogger(SseEmitterServer.class);
/**
* 当前连接数
*/
private static AtomicInteger count = new AtomicInteger(0);
/**
* 使用map对象,便于根据userId来获取对应的SseEmitter,或者放redis里面
*/
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
/**
* 创建用户连接并返回 SseEmitter
*
* @param userId 用户ID
* @return SseEmitter
*/
public static SseEmitter connect(String userId) {
// 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException
SseEmitter sseEmitter = new SseEmitter(0L);
// 注册回调
sseEmitter.onCompletion(completionCallBack(userId));
sseEmitter.onError(errorCallBack(userId));
sseEmitter.onTimeout(timeoutCallBack(userId));
sseEmitterMap.put(userId, sseEmitter);
// 数量+1
count.getAndIncrement();
logger.info("创建新的sse连接,当前用户:{}", userId);
return sseEmitter;
}
/**
* 给指定用户发送信息
*/
public static void sendMessage(String userId, ResMess resMess) {
if (sseEmitterMap.containsKey(userId)) {
try {
// sseEmitterMap.get(userId).send(message, MediaType.APPLICATION_JSON);
sseEmitterMap.get(userId).send(resMess);
} catch (IOException e) {
logger.error("用户[{}]推送异常:{}", userId, e.getMessage());
removeUser(userId);
}
}
}
// /**
// * 群发消息
// */
// public static void batchSendMessage(String wsInfo, List<String> ids) {
// ids.forEach(userId -> sendMessage(wsInfo, userId));
// }
/**
* 群发所有人
*/
public static void batchSendMessage(String wsInfo) {
sseEmitterMap.forEach((k, v) -> {
try {
v.send(wsInfo, MediaType.APPLICATION_JSON);
} catch (IOException e) {
logger.error("用户[{}]推送异常:{}", k, e.getMessage());
removeUser(k);
}
});
}
/**
* 移除用户连接
*/
public static void removeUser(String userId) {
sseEmitterMap.remove(userId);
// 数量-1
count.getAndDecrement();
logger.info("移除用户:{}", userId);
}
/**
* 获取当前连接信息
*/
public static List<String> getIds() {
return new ArrayList<>(sseEmitterMap.keySet());
}
/**
* 获取当前连接数量
*/
public static int getUserCount() {
return count.intValue();
}
private static Runnable completionCallBack(String userId) {
return () -> {
logger.info("结束连接:{}", userId);
removeUser(userId);
};
}
private static Runnable timeoutCallBack(String userId) {
return () -> {
logger.info("连接超时:{}", userId);
removeUser(userId);
};
}
private static Consumer<Throwable> errorCallBack(String userId) {
return throwable -> {
logger.info("连接异常:{}", userId);
removeUser(userId);
};
}
}
配置一个SseEmitterServer,另外也不需要引入别的依赖了,因为sse是靠http的。
使用的时候也是很简单的。
SseEmitterServer.sendMessage(reqMessage.getSubId()+"",new ResMess(null,reqMessage.getMessage()));
直接这样使用就好了
ok 886