From e8383b1e0f54ef393d4578bcffdf61d86d1821c6 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Wed, 23 Jul 2025 23:03:09 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20Stomp=20Config=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20-=20build.gradle=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20ErrorCode=20=EC=B6=94=EA=B0=80=20-=20config=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../chat/config/StompEventListener.java | 34 ++++++++ .../chat/config/StompHandler.java | 78 +++++++++++++++++++ .../chat/config/StompWebSocketConfig.java | 46 +++++++++++ .../chat/config/WebSocketConfig.java | 32 ++++++++ .../chat/config/WebSocketHandler.java | 41 ++++++++++ .../common/exception/ErrorCode.java | 3 + 7 files changed, 236 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompEventListener.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompHandler.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java diff --git a/build.gradle b/build.gradle index 6f6a01f61..b4f3985bc 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,8 @@ dependencies { // Etc implementation 'org.hibernate.validator:hibernate-validator' implementation 'com.amazonaws:aws-java-sdk-s3:1.12.782' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.webjars:stomp-websocket' } tasks.named('test', Test) { diff --git a/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java new file mode 100644 index 000000000..ec9e618e4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java @@ -0,0 +1,34 @@ +package com.example.solidconnection.chat.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionConnectEvent; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Slf4j +public class StompEventListener { + + private final Set sessions = ConcurrentHashMap.newKeySet(); + + @EventListener + public void connectHandle(SessionConnectEvent event){ + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); + sessions.add(accessor.getSessionId()); + log.info("connect session ID: {}", accessor.getSessionId()); + log.info("total session count: {}", sessions.size()); + } + + @EventListener + public void disconnectHandle(SessionDisconnectEvent event){ + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); + sessions.remove(accessor.getSessionId()); + log.info("disconnect session ID: {}", accessor.getSessionId()); + log.info("total session count: {}", sessions.size()); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java new file mode 100644 index 000000000..be68524c0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java @@ -0,0 +1,78 @@ +package com.example.solidconnection.chat.config; + +import com.example.solidconnection.auth.token.JwtTokenProvider; +import com.example.solidconnection.common.exception.CustomException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_SUBSCRIBE; + +@Component +@RequiredArgsConstructor +@Slf4j +public class StompHandler implements ChannelInterceptor { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + public Message preSend(Message message, MessageChannel channel) { + final StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + + if(StompCommand.CONNECT == accessor.getCommand()){ + log.info("connect요청시 토큰 유효성 검증"); + + String bearerToken = accessor.getFirstNativeHeader("Authorization"); + + if(bearerToken == null || !bearerToken.startsWith("Bearer ")) { + throw new IllegalArgumentException("Authorization header missing or invalid format"); + } + + String token = bearerToken.substring(7); + + Claims claims = jwtTokenProvider.parseClaims(token); + + log.info("토큰 검증 성공, 유저 인증 완료 - 사용자: {}", claims.getSubject()); + } + + if(StompCommand.SUBSCRIBE == accessor.getCommand()){ + log.info("subscribe 검증"); + + try { + String bearerToken = accessor.getFirstNativeHeader("Authorization"); + + if(bearerToken == null || !bearerToken.startsWith("Bearer ")) { + throw new IllegalArgumentException("Authorization header missing or invalid format"); + } + + String token = bearerToken.substring(7); + + Claims claims = jwtTokenProvider.parseClaims(token); + + String email = claims.getSubject(); + String destination = accessor.getDestination(); + + if(destination != null && destination.contains("/topic/")) { + String roomId = destination.split("/")[2]; + log.info("사용자 {} 가 룸 {} 구독 시도", email, roomId); + + // todo: room 검증로직 구현 + } + + } catch (Exception e) { + log.error("구독 검증 실패: {}", e.getMessage()); + throw new CustomException(UNAUTHORIZED_SUBSCRIBE); + } + } + + return message; + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java new file mode 100644 index 000000000..ab03d7558 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -0,0 +1,46 @@ +package com.example.solidconnection.chat.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +@RequiredArgsConstructor +public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { + + private final StompHandler stompHandler; + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/connect") + .setAllowedOriginPatterns("*"); // todo: 사용하는 도메인으로 한정 + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.setApplicationDestinationPrefixes("/publish"); + registry.enableSimpleBroker("/topic") + .setHeartbeatValue(new long[]{15000, 15000}); + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(stompHandler) + .taskExecutor() + .corePoolSize(6) + .maxPoolSize(12) + .queueCapacity(1000); + } + + @Override + public void configureClientOutboundChannel(ChannelRegistration registration) { + registration.taskExecutor() + .corePoolSize(6) + .maxPoolSize(12); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java new file mode 100644 index 000000000..c06ad51ed --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.chat.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +@Configuration +@EnableWebSocket +@RequiredArgsConstructor +public class WebSocketConfig implements WebSocketConfigurer { + + private final WebSocketHandler webSocketHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(webSocketHandler, "/connect") + .setAllowedOriginPatterns("*"); // todo: 사용하는 도메인으로 한정 + } + + @Bean + public ServletServerContainerFactoryBean createWebSocketContainer() { + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); + container.setMaxTextMessageBufferSize(10 * 1024 * 1024); + container.setMaxBinaryMessageBufferSize(10 * 1024 * 1024); + container.setMaxSessionIdleTimeout(300000L); + return container; + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java new file mode 100644 index 000000000..5111ac980 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java @@ -0,0 +1,41 @@ +package com.example.solidconnection.chat.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Slf4j +public class WebSocketHandler extends TextWebSocketHandler { + + private final Set sessions = ConcurrentHashMap.newKeySet(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + sessions.add(session); + log.info("Connected : {}", session.getId()); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + log.info("Received message : {}", payload); + for(WebSocketSession s : sessions) { + if(s.isOpen()) { + s.sendMessage(new TextMessage(payload)); + } + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + sessions.remove(session); + log.info("Disconnected : {}, status: {}", session.getId(), status); + } +} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 7b71469aa..ce4972915 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -111,6 +111,9 @@ public enum ErrorCode { UNAUTHORIZED_MENTORING(HttpStatus.FORBIDDEN.value(), "멘토링 권한이 없습니다."), MENTORING_ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토링입니다."), + // socket + UNAUTHORIZED_SUBSCRIBE(HttpStatus.FORBIDDEN.value(), "구독 권한이 없습니다."), + // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), From a874ae82db0ac80222fa80d3cab5baedf8e313c6 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Fri, 25 Jul 2025 04:24:58 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81=20-=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20-=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20-=20=EB=A7=A4?= =?UTF-8?q?=EC=A7=81=EB=84=98=EB=B2=84=20application-variable=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B4=80=EB=A6=AC=20-=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20-=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=BD=94=EB=93=9C=20=ED=95=A8=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 - .../chat/config/StompHandler.java | 76 +++++++++---------- .../chat/config/StompWebSocketConfig.java | 46 +++++++++-- .../chat/config/WebSocketConfig.java | 32 -------- .../chat/config/WebSocketHandler.java | 41 ---------- .../common/exception/ErrorCode.java | 1 + 6 files changed, 73 insertions(+), 124 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java delete mode 100644 src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java diff --git a/build.gradle b/build.gradle index b4f3985bc..86083f2f1 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,6 @@ dependencies { implementation 'org.hibernate.validator:hibernate-validator' implementation 'com.amazonaws:aws-java-sdk-s3:1.12.782' implementation 'org.springframework.boot:spring-boot-starter-websocket' - implementation 'org.webjars:stomp-websocket' } tasks.named('test', Test) { diff --git a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java index be68524c0..76f0d8404 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java @@ -1,12 +1,13 @@ package com.example.solidconnection.chat.config; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; + import com.example.solidconnection.auth.token.JwtTokenProvider; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; @@ -14,8 +15,6 @@ import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.stereotype.Component; -import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_SUBSCRIBE; - @Component @RequiredArgsConstructor @Slf4j @@ -27,52 +26,45 @@ public class StompHandler implements ChannelInterceptor { public Message preSend(Message message, MessageChannel channel) { final StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); - if(StompCommand.CONNECT == accessor.getCommand()){ - log.info("connect요청시 토큰 유효성 검증"); - - String bearerToken = accessor.getFirstNativeHeader("Authorization"); - - if(bearerToken == null || !bearerToken.startsWith("Bearer ")) { - throw new IllegalArgumentException("Authorization header missing or invalid format"); - } - - String token = bearerToken.substring(7); - - Claims claims = jwtTokenProvider.parseClaims(token); - - log.info("토큰 검증 성공, 유저 인증 완료 - 사용자: {}", claims.getSubject()); + if (StompCommand.CONNECT.equals(accessor.getCommand())) { + log.info("CONNECT 요청 - 토큰 검증 시작"); + Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); + log.info("토큰 검증 성공 - 사용자: {}", claims.getSubject()); } - if(StompCommand.SUBSCRIBE == accessor.getCommand()){ - log.info("subscribe 검증"); - - try { - String bearerToken = accessor.getFirstNativeHeader("Authorization"); - - if(bearerToken == null || !bearerToken.startsWith("Bearer ")) { - throw new IllegalArgumentException("Authorization header missing or invalid format"); - } + if (StompCommand.SUBSCRIBE.equals(accessor.getCommand())) { + log.info("SUBSCRIBE 요청 - 검증 시작"); + Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); - String token = bearerToken.substring(7); + String email = claims.getSubject(); + String destination = accessor.getDestination(); - Claims claims = jwtTokenProvider.parseClaims(token); + String roomId = extractRoomId(destination); + log.info("사용자 {}가 룸 {} 구독 시도", email, roomId); - String email = claims.getSubject(); - String destination = accessor.getDestination(); - - if(destination != null && destination.contains("/topic/")) { - String roomId = destination.split("/")[2]; - log.info("사용자 {} 가 룸 {} 구독 시도", email, roomId); + // todo: roomId 기반 실제 구독 권한 검사 로직 추가 + } - // todo: room 검증로직 구현 - } + return message; + } - } catch (Exception e) { - log.error("구독 검증 실패: {}", e.getMessage()); - throw new CustomException(UNAUTHORIZED_SUBSCRIBE); - } + private Claims validateAndExtractClaims(StompHeaderAccessor accessor, ErrorCode errorCode) { + String bearerToken = accessor.getFirstNativeHeader("Authorization"); + if (bearerToken == null || !bearerToken.startsWith("Bearer ")) { + throw new CustomException(errorCode); } + String token = bearerToken.substring(7); + return jwtTokenProvider.parseClaims(token); + } - return message; + private String extractRoomId(String destination) { + if (destination == null) { + throw new CustomException(ErrorCode.INVALID_ROOMID); + } + String[] parts = destination.split("/"); + if (parts.length < 3 || !parts[1].equals("topic")) { + throw new CustomException(ErrorCode.INVALID_ROOMID); + } + return parts[2]; } } diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java index ab03d7558..6bc707876 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -1,9 +1,11 @@ package com.example.solidconnection.chat.config; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @@ -15,32 +17,60 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { private final StompHandler stompHandler; + @Value("${websocket.thread-pool.inbound.core-pool-size}") + private int inboundCorePoolSize; + + @Value("${websocket.thread-pool.inbound.max-pool-size}") + private int inboundMaxPoolSize; + + @Value("${websocket.thread-pool.inbound.queue-capacity}") + private int inboundQueueCapacity; + + @Value("${websocket.thread-pool.outbound.core-pool-size}") + private int outboundCorePoolSize; + + @Value("${websocket.thread-pool.outbound.max-pool-size}") + private int outboundMaxPoolSize; + + @Value("${websocket.heartbeat.server-interval}") + private long heartbeatServerInterval; + + @Value("${websocket.heartbeat.client-interval}") + private long heartbeatClientInterval; + @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/connect") - .setAllowedOriginPatterns("*"); // todo: 사용하는 도메인으로 한정 + .setAllowedOriginPatterns("*") + .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(1); + scheduler.setThreadNamePrefix("wss-heartbeat-"); + scheduler.initialize(); + registry.setApplicationDestinationPrefixes("/publish"); registry.enableSimpleBroker("/topic") - .setHeartbeatValue(new long[]{15000, 15000}); + .setHeartbeatValue(new long[]{heartbeatServerInterval, heartbeatClientInterval}) + .setTaskScheduler(scheduler); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(stompHandler) - .taskExecutor() - .corePoolSize(6) - .maxPoolSize(12) - .queueCapacity(1000); + .taskExecutor() + .corePoolSize(inboundCorePoolSize) + .maxPoolSize(inboundMaxPoolSize) + .queueCapacity(inboundQueueCapacity); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { registration.taskExecutor() - .corePoolSize(6) - .maxPoolSize(12); + .corePoolSize(outboundCorePoolSize) + .maxPoolSize(outboundMaxPoolSize); } } diff --git a/src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java deleted file mode 100644 index c06ad51ed..000000000 --- a/src/main/java/com/example/solidconnection/chat/config/WebSocketConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.solidconnection.chat.config; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; -import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; - -@Configuration -@EnableWebSocket -@RequiredArgsConstructor -public class WebSocketConfig implements WebSocketConfigurer { - - private final WebSocketHandler webSocketHandler; - - @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(webSocketHandler, "/connect") - .setAllowedOriginPatterns("*"); // todo: 사용하는 도메인으로 한정 - } - - @Bean - public ServletServerContainerFactoryBean createWebSocketContainer() { - ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); - container.setMaxTextMessageBufferSize(10 * 1024 * 1024); - container.setMaxBinaryMessageBufferSize(10 * 1024 * 1024); - container.setMaxSessionIdleTimeout(300000L); - return container; - } -} diff --git a/src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java deleted file mode 100644 index 5111ac980..000000000 --- a/src/main/java/com/example/solidconnection/chat/config/WebSocketHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.example.solidconnection.chat.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.TextWebSocketHandler; - -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -@Component -@Slf4j -public class WebSocketHandler extends TextWebSocketHandler { - - private final Set sessions = ConcurrentHashMap.newKeySet(); - - @Override - public void afterConnectionEstablished(WebSocketSession session) throws Exception { - sessions.add(session); - log.info("Connected : {}", session.getId()); - } - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { - String payload = message.getPayload(); - log.info("Received message : {}", payload); - for(WebSocketSession s : sessions) { - if(s.isOpen()) { - s.sendMessage(new TextMessage(payload)); - } - } - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { - sessions.remove(session); - log.info("Disconnected : {}, status: {}", session.getId(), status); - } -} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index ce4972915..931901084 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -113,6 +113,7 @@ public enum ErrorCode { // socket UNAUTHORIZED_SUBSCRIBE(HttpStatus.FORBIDDEN.value(), "구독 권한이 없습니다."), + INVALID_ROOMID(HttpStatus.BAD_REQUEST.value(), "경로의 roomId가 잘못되었습니다."), // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), From b6af18f6ab6ac0a60bdda7af87e37cc332ec169b Mon Sep 17 00:00:00 2001 From: Yeonri Date: Fri, 25 Jul 2025 04:46:03 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/config/StompWebSocketConfig.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java index 6bc707876..2a2afcc2a 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -17,25 +17,25 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { private final StompHandler stompHandler; - @Value("${websocket.thread-pool.inbound.core-pool-size}") + @Value("${websocket.thread-pool.inbound.core-pool-size:6}") private int inboundCorePoolSize; - @Value("${websocket.thread-pool.inbound.max-pool-size}") + @Value("${websocket.thread-pool.inbound.max-pool-size:12}") private int inboundMaxPoolSize; - @Value("${websocket.thread-pool.inbound.queue-capacity}") + @Value("${websocket.thread-pool.inbound.queue-capacity:1000}") private int inboundQueueCapacity; - @Value("${websocket.thread-pool.outbound.core-pool-size}") + @Value("${websocket.thread-pool.outbound.core-pool-size:6}") private int outboundCorePoolSize; - @Value("${websocket.thread-pool.outbound.max-pool-size}") + @Value("${websocket.thread-pool.outbound.max-pool-size:12}") private int outboundMaxPoolSize; - @Value("${websocket.heartbeat.server-interval}") + @Value("${websocket.heartbeat.server-interval:15000}") private long heartbeatServerInterval; - @Value("${websocket.heartbeat.client-interval}") + @Value("${websocket.heartbeat.client-interval:15000}") private long heartbeatClientInterval; @Override From 772435e7ecb147a49820a7a8728026fa8baf3d34 Mon Sep 17 00:00:00 2001 From: lsy1307 Date: Sun, 27 Jul 2025 22:04:24 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81=20-=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20-=20@ConfigurationProperties=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20application.yml=EC=97=90=20websocket?= =?UTF-8?q?=20=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/config/StompEventListener.java | 13 ++-- .../chat/config/StompHandler.java | 4 -- .../chat/config/StompProperties.java | 32 +++++++++ .../chat/config/StompWebSocketConfig.java | 67 ++++++++----------- src/test/resources/application.yml | 12 ++++ 5 files changed, 75 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompProperties.java diff --git a/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java index ec9e618e4..e4c5bcd70 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java @@ -1,5 +1,7 @@ package com.example.solidconnection.chat.config; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; @@ -7,9 +9,6 @@ import org.springframework.web.socket.messaging.SessionConnectEvent; import org.springframework.web.socket.messaging.SessionDisconnectEvent; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - @Component @Slf4j public class StompEventListener { @@ -17,18 +16,14 @@ public class StompEventListener { private final Set sessions = ConcurrentHashMap.newKeySet(); @EventListener - public void connectHandle(SessionConnectEvent event){ + public void connectHandle(SessionConnectEvent event) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); sessions.add(accessor.getSessionId()); - log.info("connect session ID: {}", accessor.getSessionId()); - log.info("total session count: {}", sessions.size()); } @EventListener - public void disconnectHandle(SessionDisconnectEvent event){ + public void disconnectHandle(SessionDisconnectEvent event) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); sessions.remove(accessor.getSessionId()); - log.info("disconnect session ID: {}", accessor.getSessionId()); - log.info("total session count: {}", sessions.size()); } } diff --git a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java index 76f0d8404..67a02c9a9 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java @@ -27,20 +27,16 @@ public Message preSend(Message message, MessageChannel channel) { final StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); if (StompCommand.CONNECT.equals(accessor.getCommand())) { - log.info("CONNECT 요청 - 토큰 검증 시작"); Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); - log.info("토큰 검증 성공 - 사용자: {}", claims.getSubject()); } if (StompCommand.SUBSCRIBE.equals(accessor.getCommand())) { - log.info("SUBSCRIBE 요청 - 검증 시작"); Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); String email = claims.getSubject(); String destination = accessor.getDestination(); String roomId = extractRoomId(destination); - log.info("사용자 {}가 룸 {} 구독 시도", email, roomId); // todo: roomId 기반 실제 구독 권한 검사 로직 추가 } diff --git a/src/main/java/com/example/solidconnection/chat/config/StompProperties.java b/src/main/java/com/example/solidconnection/chat/config/StompProperties.java new file mode 100644 index 000000000..5568ed596 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompProperties.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.chat.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "websocket") +public record StompProperties( + ThreadPool threadPool, + HeartbeatProperties heartbeat +) { + + public record ThreadPool( + InboundProperties inbound, + OutboundProperties outbound + ) {} + + public record InboundProperties( + int corePoolSize, + int maxPoolSize, + int queueCapacity + ) {} + + public record OutboundProperties( + int corePoolSize, + int maxPoolSize + ) {} + + public record HeartbeatProperties( + long serverInterval, + long clientInterval + ) {} +} + diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java index 2a2afcc2a..77d339374 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -1,7 +1,8 @@ package com.example.solidconnection.chat.config; +import com.example.solidconnection.security.config.CorsProperties; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; @@ -16,35 +17,34 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { private final StompHandler stompHandler; - - @Value("${websocket.thread-pool.inbound.core-pool-size:6}") - private int inboundCorePoolSize; - - @Value("${websocket.thread-pool.inbound.max-pool-size:12}") - private int inboundMaxPoolSize; - - @Value("${websocket.thread-pool.inbound.queue-capacity:1000}") - private int inboundQueueCapacity; - - @Value("${websocket.thread-pool.outbound.core-pool-size:6}") - private int outboundCorePoolSize; - - @Value("${websocket.thread-pool.outbound.max-pool-size:12}") - private int outboundMaxPoolSize; - - @Value("${websocket.heartbeat.server-interval:15000}") - private long heartbeatServerInterval; - - @Value("${websocket.heartbeat.client-interval:15000}") - private long heartbeatClientInterval; + private final StompProperties stompProperties; + private final CorsProperties corsProperties; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { + List strings = corsProperties.allowedOrigins(); + String[] allowedOrigins = strings.toArray(String[]::new); registry.addEndpoint("/connect") - .setAllowedOriginPatterns("*") + .setAllowedOrigins(allowedOrigins) .withSockJS(); } + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(stompHandler) + .taskExecutor() + .corePoolSize(stompProperties.threadPool().inbound().corePoolSize()) + .maxPoolSize(stompProperties.threadPool().inbound().maxPoolSize()) + .queueCapacity(stompProperties.threadPool().inbound().queueCapacity()); + } + + @Override + public void configureClientOutboundChannel(ChannelRegistration registration) { + registration.taskExecutor() + .corePoolSize(stompProperties.threadPool().outbound().corePoolSize()) + .maxPoolSize(stompProperties.threadPool().outbound().maxPoolSize()); + } + @Override public void configureMessageBroker(MessageBrokerRegistry registry) { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); @@ -54,23 +54,10 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { registry.setApplicationDestinationPrefixes("/publish"); registry.enableSimpleBroker("/topic") - .setHeartbeatValue(new long[]{heartbeatServerInterval, heartbeatClientInterval}) + .setHeartbeatValue(new long[]{ + stompProperties.heartbeat().serverInterval(), + stompProperties.heartbeat().clientInterval() + }) .setTaskScheduler(scheduler); } - - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(stompHandler) - .taskExecutor() - .corePoolSize(inboundCorePoolSize) - .maxPoolSize(inboundMaxPoolSize) - .queueCapacity(inboundQueueCapacity); - } - - @Override - public void configureClientOutboundChannel(ChannelRegistration registration) { - registration.taskExecutor() - .corePoolSize(outboundCorePoolSize) - .maxPoolSize(outboundMaxPoolSize); - } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 83fe6e8cf..7abc6949f 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -42,6 +42,18 @@ view: count: scheduling: delay: 3000 +websocket: + thread-pool: + inbound: + core-pool-size: 8 + max-pool-size: 16 + queue-capacity: 1000 + outbound: + core-pool-size: 8 + max-pool-size: 16 + heartbeat: + server-interval: 15000 + client-interval: 15000 oauth: apple: token-url: "https://appleid.apple.com/auth/token" From f6f96729da7621553798f6f989bbb48333105a7e Mon Sep 17 00:00:00 2001 From: lsy1307 Date: Tue, 29 Jul 2025 13:47:03 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81=20-=20application-variable.yml?= =?UTF-8?q?=EC=97=90=20=EC=84=A4=EC=A0=95=EA=B0=92=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20code=20formatting,=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20@Sl4fj?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20-=20StompWebSocketConfig=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/config/StompEventListener.java | 2 - .../chat/config/StompHandler.java | 6 +-- .../chat/config/StompProperties.java | 43 ++++++++----------- .../chat/config/StompWebSocketConfig.java | 28 +++++------- .../common/exception/ErrorCode.java | 2 +- 5 files changed, 30 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java index e4c5bcd70..1064eef3a 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java @@ -2,7 +2,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.stereotype.Component; @@ -10,7 +9,6 @@ import org.springframework.web.socket.messaging.SessionDisconnectEvent; @Component -@Slf4j public class StompEventListener { private final Set sessions = ConcurrentHashMap.newKeySet(); diff --git a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java index 67a02c9a9..660f01f28 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java @@ -7,7 +7,6 @@ import com.example.solidconnection.common.exception.ErrorCode; import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; @@ -17,7 +16,6 @@ @Component @RequiredArgsConstructor -@Slf4j public class StompHandler implements ChannelInterceptor { private final JwtTokenProvider jwtTokenProvider; @@ -55,11 +53,11 @@ private Claims validateAndExtractClaims(StompHeaderAccessor accessor, ErrorCode private String extractRoomId(String destination) { if (destination == null) { - throw new CustomException(ErrorCode.INVALID_ROOMID); + throw new CustomException(ErrorCode.INVALID_ROOM_ID); } String[] parts = destination.split("/"); if (parts.length < 3 || !parts[1].equals("topic")) { - throw new CustomException(ErrorCode.INVALID_ROOMID); + throw new CustomException(ErrorCode.INVALID_ROOM_ID); } return parts[2]; } diff --git a/src/main/java/com/example/solidconnection/chat/config/StompProperties.java b/src/main/java/com/example/solidconnection/chat/config/StompProperties.java index 5568ed596..ce9663c72 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompProperties.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompProperties.java @@ -3,30 +3,21 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "websocket") -public record StompProperties( - ThreadPool threadPool, - HeartbeatProperties heartbeat -) { - - public record ThreadPool( - InboundProperties inbound, - OutboundProperties outbound - ) {} - - public record InboundProperties( - int corePoolSize, - int maxPoolSize, - int queueCapacity - ) {} - - public record OutboundProperties( - int corePoolSize, - int maxPoolSize - ) {} - - public record HeartbeatProperties( - long serverInterval, - long clientInterval - ) {} -} +public record StompProperties(ThreadPool threadPool, HeartbeatProperties heartbeat) { + public record ThreadPool(InboundProperties inbound, OutboundProperties outbound) { + + } + + public record InboundProperties(int corePoolSize, int maxPoolSize, int queueCapacity) { + + } + + public record OutboundProperties(int corePoolSize, int maxPoolSize) { + + } + + public record HeartbeatProperties(long serverInterval, long clientInterval) { + + } +} \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java index 77d339374..86b6eef5d 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -1,5 +1,8 @@ package com.example.solidconnection.chat.config; +import com.example.solidconnection.chat.config.StompProperties.HeartbeatProperties; +import com.example.solidconnection.chat.config.StompProperties.InboundProperties; +import com.example.solidconnection.chat.config.StompProperties.OutboundProperties; import com.example.solidconnection.security.config.CorsProperties; import java.util.List; import lombok.RequiredArgsConstructor; @@ -24,25 +27,19 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { public void registerStompEndpoints(StompEndpointRegistry registry) { List strings = corsProperties.allowedOrigins(); String[] allowedOrigins = strings.toArray(String[]::new); - registry.addEndpoint("/connect") - .setAllowedOrigins(allowedOrigins) - .withSockJS(); + registry.addEndpoint("/connect").setAllowedOrigins(allowedOrigins).withSockJS(); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(stompHandler) - .taskExecutor() - .corePoolSize(stompProperties.threadPool().inbound().corePoolSize()) - .maxPoolSize(stompProperties.threadPool().inbound().maxPoolSize()) - .queueCapacity(stompProperties.threadPool().inbound().queueCapacity()); + InboundProperties inboundProperties = stompProperties.threadPool().inbound(); + registration.interceptors(stompHandler).taskExecutor().corePoolSize(inboundProperties.corePoolSize()).maxPoolSize(inboundProperties.maxPoolSize()).queueCapacity(inboundProperties.queueCapacity()); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { - registration.taskExecutor() - .corePoolSize(stompProperties.threadPool().outbound().corePoolSize()) - .maxPoolSize(stompProperties.threadPool().outbound().maxPoolSize()); + OutboundProperties outboundProperties = stompProperties.threadPool().outbound(); + registration.taskExecutor().corePoolSize(outboundProperties.corePoolSize()).maxPoolSize(outboundProperties.maxPoolSize()); } @Override @@ -51,13 +48,8 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { scheduler.setPoolSize(1); scheduler.setThreadNamePrefix("wss-heartbeat-"); scheduler.initialize(); - + HeartbeatProperties heartbeatProperties = stompProperties.heartbeat(); registry.setApplicationDestinationPrefixes("/publish"); - registry.enableSimpleBroker("/topic") - .setHeartbeatValue(new long[]{ - stompProperties.heartbeat().serverInterval(), - stompProperties.heartbeat().clientInterval() - }) - .setTaskScheduler(scheduler); + registry.enableSimpleBroker("/topic").setHeartbeatValue(new long[]{heartbeatProperties.serverInterval(), heartbeatProperties.clientInterval()}).setTaskScheduler(scheduler); } } diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 5bd916da0..4659e61bf 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -114,7 +114,7 @@ public enum ErrorCode { // socket UNAUTHORIZED_SUBSCRIBE(HttpStatus.FORBIDDEN.value(), "구독 권한이 없습니다."), - INVALID_ROOMID(HttpStatus.BAD_REQUEST.value(), "경로의 roomId가 잘못되었습니다."), + INVALID_ROOM_ID(HttpStatus.BAD_REQUEST.value(), "경로의 roomId가 잘못되었습니다."), // report ALREADY_REPORTED_BY_CURRENT_USER(HttpStatus.BAD_REQUEST.value(), "이미 신고한 상태입니다."), From 6ebfec7a5961782b03712fa764f1c15a3ddc814e Mon Sep 17 00:00:00 2001 From: Yeonri Date: Wed, 30 Jul 2025 23:54:01 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20secret=20=ED=95=B4=EC=8B=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/secret b/src/main/resources/secret index be52e6ce9..e592f6d36 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit be52e6ce9ca3d2c6eb51442108328b00a539510b +Subproject commit e592f6d36f57185c8d92a7838c0e3039603b2c57