公司有个需求,前端界面需要展示实时展示下单消息。
解决方案:
1.前端间隔一定时常,轮询向后端发送请求,查询下单数据
2.利用长链接,后端向前端主动推送下单消息
果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码
一、新增see对象
public class SeeResult {
/**
* 消息推送根据clientId。
*/
public String clientId;
public long timestamp;
public SseEmitter sseEmitter;
public SeeResult(String clientId, long timestamp, SseEmitter sseEmitter) {
this.clientId = clientId;
this.timestamp = timestamp;
this.sseEmitter = sseEmitter;
}
}
二、新增连接存储map
public class SseMap {
public static final Map sseEmitterMap = new ConcurrentHashMap<>();
}
三、新增controller
前端连接我采取的方案时 业务标识+时间戳;例如 1-1652685165648
@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/api/message/see")
public class SseEmitterController {
/**
* 返回SseEmitter对象
*
* @param clientId
* @return
*/
@GetMapping("/start")
public SseEmitter testSseEmitter(String clientId) throws IOException{
if (StringUtils.isBlank(clientId)){
throw new DeliveryException("参数不能为空");
}
// 默认30秒超时,设置为0L则永不超时
SseEmitter sseEmitter = new SseEmitter(0L);
SseMap.sseEmitterMap.put(clientId, new SeeResult(clientId, System.currentTimeMillis(), sseEmitter));
return sseEmitter;
}
/**
* 向SseEmitter对象发送数据
*
* @param clientId
* @return
*/
@GetMapping("/send")
public String setSseEmitter(String clientId) {
try {
SeeResult result = SseMap.sseEmitterMap.get(clientId);
if (result != null && result.sseEmitter != null) {
long timestamp = System.currentTimeMillis();
String re = "{\"success\":true,\"message\":\"操作成功!\",\"code\":200,\"result\":{\"id\":1018},\"timestamp\":1629254216358}";
result.sseEmitter.send(SseEmitter.event().name("msg").data(re));
}
} catch (IOException e) {
log.error("IOException!", e);
return "error";
}
return "Succeed!";
}
/**
* 将SseEmitter对象设置成完成
*
* @param clientId
* @return
*/
@GetMapping("/end")
public String completeSseEmitter(String clientId) {
SeeResult result = SseMap.sseEmitterMap.get(clientId);
if (result != null) {
SseMap.sseEmitterMap.remove(clientId);
result.sseEmitter.complete();
}
return "Succeed!";
}
四、前端(Vue)
因连接需要经过网关,token验证。 EventSource EventSource - Web API 接口参考 | MDN相关文档没找到有设置请求头参数的方式 github 上有组件event-source-polyfill,附上地址
GitHub - Yaffle/EventSource: a polyfill for http://www.w3.org/TR/eventsource/
五、部署至生产环境
nginx配置ssl 443反向代理,前端一直连接不上,但是http协议是正常的。
经过测试,需要nginx 中增加 proxy_buffering off参数
网上查看proxy_buffering 参数详解以及介绍,
proxy_buffering
语法: proxy_buffering on|off
默认值: proxy_buffering on
上下文: http, server, location
这个参数用来控制是否打开后端响应内容的缓冲区,如果这个设置为off,那么proxy_buffers和proxy_busy_buffers_size这两个指令将会失效。 但是无论proxy_buffering是否开启,对proxy_buffer_size都是生效的。
proxy_buffering开启的情况下,nignx会把后端返回的内容先放到缓冲区当中,然后再返回给客户端(边收边传,不是全部接收完再传给客户端)。 临时文件由proxy_max_temp_file_size和proxy_temp_file_write_size这两个指令决定的。如果响应内容无法放在内存里边,那么部分内容会被写到磁盘上。
如果proxy_buffering关闭,那么nginx会立即把从后端收到的响应内容传送给客户端,每次取的大小为proxy_buffer_size的大小,这样效率肯定会比较低。
nginx不尝试计算被代理服务器整个响应内容的大小,nginx能从服务器接受的最大数据,是由指令proxy_buffer_size指定的.
注:1、 proxy_buffering启用时,要提防使用的代理缓冲区太大。这可能会吃掉你的内存,限制代理能够支持的最大并发连接数。
2、对于基于长轮询(long-polling)的Comet 应用来说,关闭 proxy_buffering 是重要的,不然异步响应将被缓存导致Comet无法工作
但是不知道和https和http有什么关系?