feat: sse

This commit is contained in:
2026-06-30 08:52:23 +08:00
parent aa9a0732d6
commit d92f7bb581
6 changed files with 90 additions and 14 deletions

View File

@@ -1,17 +1,23 @@
package com.webgame.webgamebackend.sse;
import com.webgame.webgamebackend.common.auth.AuthenticationProvider;
import com.webgame.webgamebackend.common.dto.ApiResponse;
import com.webgame.webgamebackend.common.event.SseEventPublisher;
import com.webgame.webgamebackend.common.event.SseEventType;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
/**
* SSE 订阅端点
@@ -30,6 +36,7 @@ public class SseController {
private final SseConnectionManager connectionManager;
private final AuthenticationProvider authProvider;
private final SseEventPublisher eventPublisher;
@GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribe(
@@ -73,6 +80,49 @@ public class SseController {
return emitter;
}
/**
* 测试用消息发布接口
*
* 支持广播(不传 userId和定向推送传 userId两种模式。
* 可传入任意 JSON 数据作为消息体,事件类型固定为 CUSTOM。
*
* 请求体示例:
* <pre>
* // 广播
* { "data": { "msg": "hello" } }
*
* // 定向推送
* { "userId": "123", "data": { "msg": "hello" } }
* </pre>
*
* @param body 请求体,包含可选的 userId 和必填的 data
* @return 统一 API 响应
*/
@PostMapping("/publish")
public ApiResponse<Map<String, Object>> publish(@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) body.get("data");
if (data == null || data.isEmpty()) {
return ApiResponse.fail(400, "data 字段不能为空");
}
String userId = body.get("userId") != null ? body.get("userId").toString() : null;
if (userId != null && !userId.isBlank()) {
eventPublisher.sendTo(userId, SseEventType.CUSTOM, data);
log.info("[SSE] 测试定向推送, userId={}, data={}", userId, data);
} else {
eventPublisher.broadcast(SseEventType.CUSTOM, data);
log.info("[SSE] 测试广播推送, data={}", data);
}
return ApiResponse.ok(Map.of(
"mode", userId != null && !userId.isBlank() ? "targeted" : "broadcast",
"userId", userId != null ? userId : "all",
"data", data
));
}
/**
* 向 SSE 客户端返回认证错误
*