9a0e802b2297

SSE.jpeg

1.应用场景

当我们做异步请求处理的时候,客户端向服务端的请求不是立即返回的,而是在服务端有数据的时候才发送给客户端的。本例演示是服务端数据是另外的事件触发并推送给客户端的。

当使用微信或者支付宝扫码支付的时候,在客户支付成功之后,后台得到微信(支付宝)回调需要通知我们的客户端支付已经完成,客户端得到通知后可以做一些后续的操作。

这涉及到后台需要将支付成功的消息主动推送给客户端,让客户端记性后续的操作。

客户端向后台(SseController->push)发送异步请求,客户端处于监听等待状态;

微信(支付宝)支付成功后回调后台(SseController->payCallback模拟);

payCallback方法通过applicationContext.publishEvent向系统内部发送支付完成事件;

push方法通过payCompletedListener监听事件并通过SseEmitter发送给客户端。

名词解释:SSE–server send event是一种服务端推送的技术,本例使用SseEmitter来实现。

2. 上代码

web api 接口类SseController

@RestController

public class SseController {

@Autowired

ApplicationContext applicationContext;

@Autowired

PayCompletedListener payCompletedListener;

@GetMapping("/push")

public SseEmitter push(@RequestParam Long payRecordId){

final SseEmitter emitter = new SseEmitter();

try {

payCompletedListener.addSseEmitters(payRecordId,emitter);

}catch (Exception e){

emitter.completeWithError(e);

}

return emitter;

}

@GetMapping("/pay-callback")

public String payCallback(@RequestParam Long payRecordId){

applicationContext.publishEvent(new PayCompletedEvent(this,payRecordId));

return "请到监听处查看消息";

}

}

支付完成事件 PayCompletedEvent

@Getter

@Setter

public class PayCompletedEvent extends ApplicationEvent {

private Long payRecordId;

public PayCompletedEvent(Object source, Long payRecordId) {

super(source);

this.payRecordId = payRecordId;

}

}

支付完成事件监听器PayCompletedListener

@Component

public class PayCompletedListener {

private static Map sseEmitters = new Hashtable<>();

public void addSseEmitters(Long payRecordId, SseEmitter sseEmitter) {

sseEmitters.put(payRecordId, sseEmitter);

}

@EventListener

public void deployEventHandler(PayCompletedEvent payCompletedEvent) throws IOException {

Long payRecordId = payCompletedEvent.getPayRecordId();

SseEmitter sseEmitter = sseEmitters.get(payRecordId);

sseEmitter.send("支付成功");

sseEmitter.complete();

}

}

3. 效果演示

先访问http://localhost:8080/push?payRecordId=123,此时客户端处于等待状态.

9a0e802b2297

sse1.png

再访问http://localhost:8080/pay-callback?payRecordId=123,模拟回调通知客户端支付成功,这是会通知上一步并推送到客户端.

9a0e802b2297

sse2.png

4. 另外一个极简的例子

服务端模拟servlet的一个get请求

import java.io.IOException;

import java.util.concurrent.TimeUnit;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* Servlet implementation class SSE

*/

@WebServlet("/SSE")

public class SSE extends HttpServlet {

private static final long serialVersionUID = 1L;

/**

* @see HttpServlet#HttpServlet()

*/

public SSE() {

super();

// TODO Auto-generated constructor stub

}

/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse

* response)

*/

protected void doGet(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

response.setContentType("text/event-stream");

response.setCharacterEncoding("utf-8");

for (int i = 0; i < 5; i++) {

// 指定事件标识

response.getWriter().write("event:me\n");

// 格式: data: + 数据 + 2个回车

response.getWriter().write("data:" + i + "\n\n");

response.getWriter().flush();

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

}

}

}

/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse

* response)

*/

protected void doPost(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

// TODO Auto-generated method stub

doGet(request, response);

}

}

模拟客户端的代码

Insert title here

// 初始化, 参数为url

// 依赖H5

var sse = new EventSource("SSE");

sse.onmessage = function(e) {

console.log("message", e.data, e);

}

// 监听指定事件, (就不会进入onmessage了)

sse.addEventListener("me", function(e) {

console.log("me event", e.data);

// 如果不关闭,会自动重连

if (e.data == 3) {

sse.close();

}

});

效果

9a0e802b2297

没有加特殊事件监听之前

9a0e802b2297

只监听me事件

5.鸣谢

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐