From edc7711351d382560efb4a1eecb8bb8d79e541fa Mon Sep 17 00:00:00 2001 From: HyunwooYi <83715335+HyunwooYi@users.noreply.github.com> Date: Sat, 22 Jun 2024 18:51:37 +0900 Subject: [PATCH] =?UTF-8?q?fea:=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20test1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 72 ++++++++++--------- .../controller/SseController.java | 43 +++++++++++ .../notification/entity/SseEmitters.java | 49 +++++++++++++ .../repository/EmitterRepository.java | 27 +++++++ 4 files changed, 156 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/umc/teamC/domain/notification/controller/SseController.java create mode 100644 src/main/java/com/umc/teamC/domain/notification/entity/SseEmitters.java create mode 100644 src/main/java/com/umc/teamC/domain/notification/repository/EmitterRepository.java diff --git a/build.gradle b/build.gradle index 12d2daa..060c8dd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,65 +1,67 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.0' - id 'io.spring.dependency-management' version '1.1.5' + id 'java' + id 'org.springframework.boot' version '3.3.0' + id 'io.spring.dependency-management' version '1.1.5' } group = 'com.umc' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' + sourceCompatibility = '17' } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - // Websocket - implementation 'org.springframework.boot:spring-boot-starter-websocket' + // Websocket + implementation 'org.springframework.boot:spring-boot-starter-websocket' - // Swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.0.4' + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.0.4' - // Redis - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '3.2.0' - implementation 'org.springframework.session:spring-session-data-redis:3.1.1' + // Redis + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '3.2.0' + implementation 'org.springframework.session:spring-session-data-redis:3.1.1' - // Mail - implementation 'org.springframework.boot:spring-boot-starter-mail' + // Mail + implementation 'org.springframework.boot:spring-boot-starter-mail' - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'org.springframework.security:spring-security-test' + // Security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' - // JWT 필수 의존성 주입 - implementation 'io.jsonwebtoken:jjwt-api:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + // JWT 필수 의존성 주입 + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + + implementation 'org.springframework.boot:spring-boot-starter-web-services' } jar { - enabled = false + enabled = false } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/com/umc/teamC/domain/notification/controller/SseController.java b/src/main/java/com/umc/teamC/domain/notification/controller/SseController.java new file mode 100644 index 0000000..2cd3daf --- /dev/null +++ b/src/main/java/com/umc/teamC/domain/notification/controller/SseController.java @@ -0,0 +1,43 @@ +package com.umc.teamC.domain.notification.controller; + +import com.umc.teamC.domain.notification.entity.SseEmitters; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; + +@RestController +@RequiredArgsConstructor +public class SseController { + + private final SseEmitters sseEmitters; + + @GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public ResponseEntity connect() { + SseEmitter emitter = new SseEmitter(); + sseEmitters.add(emitter); + try { + emitter.send(SseEmitter.event() + .name("connect") + .data("connected!")); + } catch (IOException e) { + throw new RuntimeException(e); + } + return ResponseEntity.ok(emitter); + } + + @PostMapping("/count") + public ResponseEntity count() { + sseEmitters.count(); + return ResponseEntity.ok().build(); + } + + + + +} diff --git a/src/main/java/com/umc/teamC/domain/notification/entity/SseEmitters.java b/src/main/java/com/umc/teamC/domain/notification/entity/SseEmitters.java new file mode 100644 index 0000000..b7d5049 --- /dev/null +++ b/src/main/java/com/umc/teamC/domain/notification/entity/SseEmitters.java @@ -0,0 +1,49 @@ +package com.umc.teamC.domain.notification.entity; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; + +@Component +@Slf4j +public class SseEmitters { + + private static final AtomicLong counter = new AtomicLong(); + private final List emitters = new CopyOnWriteArrayList<>(); + + public SseEmitter add(SseEmitter emitter) { + this.emitters.add(emitter); + log.info("new emitter added: {}", emitter); + log.info("emitter list size: {}", emitters.size()); + emitter.onCompletion(() -> { + log.info("onCompletion callback"); + this.emitters.remove(emitter); // 만료되면 리스트에서 삭제 + }); + emitter.onTimeout(() -> { + log.info("onTimeout callback"); + emitter.complete(); + }); + + return emitter; + } + + public void count() { + long count = counter.incrementAndGet(); + emitters.forEach(emitter -> { + try { + emitter.send(SseEmitter.event() + .name("count") + .data(count)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } +} + + diff --git a/src/main/java/com/umc/teamC/domain/notification/repository/EmitterRepository.java b/src/main/java/com/umc/teamC/domain/notification/repository/EmitterRepository.java new file mode 100644 index 0000000..721b1ff --- /dev/null +++ b/src/main/java/com/umc/teamC/domain/notification/repository/EmitterRepository.java @@ -0,0 +1,27 @@ +package com.umc.teamC.domain.notification.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@RequiredArgsConstructor +public class EmitterRepository { + + private final Map emitters = new ConcurrentHashMap<>(); + + public void save(Long id, SseEmitter emitter) { + emitters.put(id, emitter); + } + + public void deleteById(Long userId) { + emitters.remove(userId); + } + + public SseEmitter get(Long userId) { + return emitters.get(userId); + } +}