From eb8511ee7a3a71d15043eaf90c867f59c6026d54 Mon Sep 17 00:00:00 2001 From: klkim1913 <49425719+klkim1913@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:06:03 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20SSE=20=EC=9C=A0=EC=A0=80=EC=9A=A9=20tes?= =?UTF-8?q?t=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix: 농산물 정시 시작 및 옥션 디테일시에도 완료 여부 검사 --- .../controller/NotificationController.java | 37 +++++++++++++ .../controller/ProduceController.java | 1 + .../repository/EmitterRepository.java | 29 +++++++++++ .../service/NotificationService.java | 52 +++++++++++++++++++ .../anywayclear/util/AuctionScheduler.java | 2 +- 5 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/anywayclear/controller/NotificationController.java create mode 100644 src/main/java/com/anywayclear/repository/EmitterRepository.java create mode 100644 src/main/java/com/anywayclear/service/NotificationService.java diff --git a/src/main/java/com/anywayclear/controller/NotificationController.java b/src/main/java/com/anywayclear/controller/NotificationController.java new file mode 100644 index 0000000..873fb39 --- /dev/null +++ b/src/main/java/com/anywayclear/controller/NotificationController.java @@ -0,0 +1,37 @@ +package com.anywayclear.controller; + +import com.anywayclear.service.NotificationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@RestController +@RequestMapping("/api/notifications") +@Slf4j +public class NotificationController { + private final NotificationService notificationService; + + public NotificationController(NotificationService notificationService) { + this.notificationService = notificationService; + } + + @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter subscribe(@AuthenticationPrincipal OAuth2User oAuth2User) { + String userId = (String) oAuth2User.getAttributes().get("userId"); + log.debug(userId); + return notificationService.subscribe(userId); + } + + @PostMapping("/send-data") + public void sendData(@AuthenticationPrincipal OAuth2User oAuth2User) { + String userId = (String) oAuth2User.getAttributes().get("userId"); + notificationService.notify(userId, "data"); +// notificationService.notify(id, ProduceResponse.toResponse(new Produce())); + } +} diff --git a/src/main/java/com/anywayclear/controller/ProduceController.java b/src/main/java/com/anywayclear/controller/ProduceController.java index 75b0970..e5f07b4 100644 --- a/src/main/java/com/anywayclear/controller/ProduceController.java +++ b/src/main/java/com/anywayclear/controller/ProduceController.java @@ -62,6 +62,7 @@ public ResponseEntity> getProduceList( @GetMapping("/{id}/auctions") public ResponseEntity getAuctionList(@PathVariable long id) { + produceService.updateProduceStatus(); return ResponseEntity.ok(auctionService.getAuctionList(id)); } } diff --git a/src/main/java/com/anywayclear/repository/EmitterRepository.java b/src/main/java/com/anywayclear/repository/EmitterRepository.java new file mode 100644 index 0000000..43ae0bb --- /dev/null +++ b/src/main/java/com/anywayclear/repository/EmitterRepository.java @@ -0,0 +1,29 @@ +package com.anywayclear.repository; + +import org.springframework.stereotype.Repository; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +public class EmitterRepository { + private final Map emitters = new ConcurrentHashMap<>(); + + /** + * + * @param id - 사용자 아이디 + * @param emitter - 이벤트 Emitter + */ + public void save(String id, SseEmitter emitter) { + emitters.put(id,emitter); + } + + public void deleteById(String id) { + emitters.remove(id); + } + + public SseEmitter get(String id) { + return emitters.get(id); + } +} diff --git a/src/main/java/com/anywayclear/service/NotificationService.java b/src/main/java/com/anywayclear/service/NotificationService.java new file mode 100644 index 0000000..d58888b --- /dev/null +++ b/src/main/java/com/anywayclear/service/NotificationService.java @@ -0,0 +1,52 @@ +package com.anywayclear.service; + +import com.anywayclear.repository.EmitterRepository; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; + +@Service +public class NotificationService { + // 기본 타임아웃 설정 + private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60; + private final EmitterRepository emitterRepository; + + public NotificationService(EmitterRepository emitterRepository) { + this.emitterRepository = emitterRepository; + } + + public SseEmitter subscribe(String userId) { + SseEmitter emitter = createEmitter(userId); + + sendToClient(userId, "EventStream Created. [userId=" + userId + "]"); + return emitter; + } + + public void notify(String userId, Object event) { + sendToClient(userId, event); + } + + private void sendToClient(String id, Object data) { + SseEmitter emitter = emitterRepository.get(id); + if (emitter != null) { + try { + emitter.send(SseEmitter.event().id(String.valueOf(id)).name("sse").data(data)); + } catch (IOException e) { + emitterRepository.deleteById(id); + emitter.completeWithError(e); + } + } + } + + private SseEmitter createEmitter(String id) { + SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); + emitterRepository.save(id, emitter); + // Emitter가 완료될 때(모든 데이터가 성공적으로 전송된 상태) Emitter를 삭제한다. + emitter.onCompletion(() -> emitterRepository.deleteById(id)); + // Emitter가 타임아웃 되었을 때(지정된 시간동안 어떠한 이벤트도 전송되지 않았을 때) Emitter를 삭제한다. + emitter.onTimeout(() -> emitterRepository.deleteById(id)); + + return emitter; + } +} diff --git a/src/main/java/com/anywayclear/util/AuctionScheduler.java b/src/main/java/com/anywayclear/util/AuctionScheduler.java index bc9a16f..72ec950 100644 --- a/src/main/java/com/anywayclear/util/AuctionScheduler.java +++ b/src/main/java/com/anywayclear/util/AuctionScheduler.java @@ -23,7 +23,7 @@ public AuctionScheduler(ProduceRepository produceRepository) { public void updateAuctionStatus() { log.debug("스케줄링 시작"); for (Produce produce : produceRepository.findAll()) { - if (produce.getStatus() == 0 && LocalDateTime.now().isAfter(produce.getStartDate())) { + if (produce.getStatus() == 0 && LocalDateTime.now().isAfter(produce.getStartDate().minusMinutes(1))) { produce.setStatus(1); } }