From cc9e17989f9c3d5bae43f0d7b2fa0a6778b63f5d Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:27:33 +0900 Subject: [PATCH 01/28] =?UTF-8?q?[feat]=20#5=20socket=20test=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20cors=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/gachon/checkmate/global/config/CorsConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/gachon/checkmate/global/config/CorsConfig.java b/src/main/java/org/gachon/checkmate/global/config/CorsConfig.java index 4672fd3..9936a15 100644 --- a/src/main/java/org/gachon/checkmate/global/config/CorsConfig.java +++ b/src/main/java/org/gachon/checkmate/global/config/CorsConfig.java @@ -16,6 +16,7 @@ public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:3000"); + config.addAllowedOrigin("https://jxy.me"); //stomp 테스트를 위한 사이트 cors 등록 config.addAllowedHeader("*"); config.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); source.registerCorsConfiguration("/**", config); From bf414e387d03668067e8fff2ba02fd86efb14e69 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:28:52 +0900 Subject: [PATCH 02/28] =?UTF-8?q?[feat]=20#5=20socket=20config=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/WebSocketConfig.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/config/WebSocketConfig.java diff --git a/src/main/java/org/gachon/checkmate/global/config/WebSocketConfig.java b/src/main/java/org/gachon/checkmate/global/config/WebSocketConfig.java new file mode 100644 index 0000000..92df108 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/config/WebSocketConfig.java @@ -0,0 +1,39 @@ +package org.gachon.checkmate.global.config; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.global.socket.handler.StompErrorHandler; +import org.gachon.checkmate.global.socket.interceptor.StompInterceptor; +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; + +@RequiredArgsConstructor +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + private final StompInterceptor stompInterceptor; + private final StompErrorHandler stompErrorHandler; + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.setApplicationDestinationPrefixes("/app"); // 발행자가 "/app"의 경로로 메시지를 주면 가공을 해서 구독자들에게 전달 + registry.enableSimpleBroker("/queue", "/topic"); //관습적으로 "/queue"의 경우 1-1 연결, "/topic"은 1-N의 경우 사용한다함 + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws").setAllowedOrigins("*"); + registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS(); + + registry.setErrorHandler(stompErrorHandler); + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(stompInterceptor); + } +} From fcdb365f3b54a8da278cd5318f80483e03493e6e Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:29:08 +0900 Subject: [PATCH 03/28] =?UTF-8?q?[feat]=20#5=20socket=20interceptor=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../socket/interceptor/StompInterceptor.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java diff --git a/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java b/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java new file mode 100644 index 0000000..ae42802 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java @@ -0,0 +1,128 @@ +package org.gachon.checkmate.global.socket.interceptor; + +import io.jsonwebtoken.Claims; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.gachon.checkmate.global.socket.SocketJwtProvider; +import org.gachon.checkmate.global.socket.error.SocketException; +import org.gachon.checkmate.global.socket.error.SocketUnauthorizedException; +import org.gachon.checkmate.global.socket.error.SocketErrorCode; +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 java.util.Map; +import java.util.Objects; +import java.util.Random; + +import static org.gachon.checkmate.global.socket.error.SocketErrorCode.SESSION_ATTRIBUTE_NOT_FOUND; +import static org.gachon.checkmate.global.socket.error.SocketErrorCode.SOCKET_SERVER_ERROR; + +@Slf4j +@Component +@RequiredArgsConstructor +public class StompInterceptor implements ChannelInterceptor { + + private static final String AUTHORIZATION = "Authorization"; + private static final String BEARER = "Bearer "; + public static final String DEFAULT_PATH = "/queue/chat/"; + private final SocketJwtProvider socketJwtProvider; + + // websocket을 통해 들어온 요청이 처리 되기전 실행된다. + @Override + public Message preSend(Message message, MessageChannel channel) { + Random random = new Random(); + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + StompCommand command = accessor.getCommand(); + + if (StompCommand.CONNECT.equals(command)) { // websocket 연결요청 -> JWT 인증 + + // JWT 인증 부분은 나중에 추가하도록 하겠습니다. +// User user = getUserByAuthorizationHeader( +// accessor.getFirstNativeHeader("Authorization")); + + // Exception 처리 테스트용 + getUserByAuthorizationHeader( + accessor.getFirstNativeHeader("Authorization")); + // 현재는 임의의 값으로 두었습니다. + Long userId = (long) random.nextInt(1000); + setValue(accessor, "userId", userId); + log.info("[CONNECT]:: userId : " + userId); +// setValue(accessor, "username", user.getNickname()); +// setValue(accessor, "profileImgUrl", user.getProfileImgUrl()); + + } else if (StompCommand.SUBSCRIBE.equals(command)) { // 채팅방 구독요청(진입) + Long userId = (Long)getValue(accessor, "userId"); + log.debug("userId : " + userId); + if(checkIsDestinationChatRoom(accessor)) { + Long roomId = parseRoomIdFromPath(accessor); + setValue(accessor, "roomId", roomId); + log.debug("roomId : " + roomId); + } + } else if (StompCommand.UNSUBSCRIBE.equals(command)) { + Long userId = (Long)getValue(accessor, "userId"); + log.info("UNSUBSCRIBE userId : {}", userId); + } else if (StompCommand.DISCONNECT == command) { // Websocket 연결 종료 + Long userId = (Long)getValue(accessor, "userId"); + log.info("DISCONNECTED userId : {}", userId); + } + return message; + } + + private void getUserByAuthorizationHeader(String authHeaderValue) { + String accessToken = getTokenByAuthorizationHeader(authHeaderValue); + + Claims claims = socketJwtProvider.getClaimsFormToken(accessToken); + Long userId = claims.get("userId", Long.class); + +// return userRepository.findById(userId) +// .orElseThrow(() -> new UserNotFoundException(userId)); + } + + private String getTokenByAuthorizationHeader(String authHeaderValue) { + if (Objects.isNull(authHeaderValue) || authHeaderValue.isBlank()) { + throw new SocketUnauthorizedException(SocketErrorCode.USER_NOT_AUTHORIZED); + } + String accessToken = SocketJwtProvider.extractToken(authHeaderValue); + socketJwtProvider.validateAccessToken(accessToken); + + return accessToken; + } + + private Boolean checkIsDestinationChatRoom(StompHeaderAccessor accessor) { + return accessor.getDestination().contains("/queue/chat/"); + } + + private Long parseRoomIdFromPath(StompHeaderAccessor accessor) { + String destination = accessor.getDestination(); + return Long.parseLong(destination.substring(DEFAULT_PATH.length())); + } + + private Object getValue(StompHeaderAccessor accessor, String key) { + Map sessionAttributes = getSessionAttributes(accessor); + Object value = sessionAttributes.get(key); + + if (Objects.isNull(value)) { + throw new SocketException(SOCKET_SERVER_ERROR); + } + return value; + } + + private void setValue(StompHeaderAccessor accessor, String key, Object value) { + Map sessionAttributes = getSessionAttributes(accessor); + sessionAttributes.put(key, value); + } + + private Map getSessionAttributes(StompHeaderAccessor accessor) { + Map sessionAttributes = accessor.getSessionAttributes(); + if (Objects.isNull(sessionAttributes)) { + throw new SocketException(SESSION_ATTRIBUTE_NOT_FOUND); + } + return sessionAttributes; + } + +} + From 7ce9923605ef437c9b8cf19dd9ce6d96b08a3728 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:29:25 +0900 Subject: [PATCH 04/28] =?UTF-8?q?[feat]=20#5=20socket=20error=20handler=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../socket/handler/StompErrorHandler.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/socket/handler/StompErrorHandler.java diff --git a/src/main/java/org/gachon/checkmate/global/socket/handler/StompErrorHandler.java b/src/main/java/org/gachon/checkmate/global/socket/handler/StompErrorHandler.java new file mode 100644 index 0000000..23bfd47 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/handler/StompErrorHandler.java @@ -0,0 +1,35 @@ +package org.gachon.checkmate.global.socket.handler; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.global.socket.error.SocketErrorCode; +import org.springframework.messaging.Message; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler; + +import java.nio.charset.StandardCharsets; + +@RequiredArgsConstructor +@Component +public class StompErrorHandler extends StompSubProtocolErrorHandler { + @Override + public Message handleClientMessageProcessingError(MessageclientMessage, Throwable ex) { + return super.handleClientMessageProcessingError(clientMessage, ex); + } + + // JWT 예외 + private Message errorMessage(SocketErrorCode socketErrorCode){ + String code = String.valueOf(socketErrorCode.getMessage()); + StompHeaderAccessor accessor = getStompHeaderAccessor(socketErrorCode); + return MessageBuilder.createMessage(code.getBytes(StandardCharsets.UTF_8), accessor.getMessageHeaders()); + } + + private StompHeaderAccessor getStompHeaderAccessor(SocketErrorCode socketErrorCode) { + StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR); + accessor.setMessage(socketErrorCode.getMessage()); + accessor.setLeaveMutable(true); + return accessor; + } +} From 8bcc33ca511349007788078d010fcb3708263273 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:29:37 +0900 Subject: [PATCH 05/28] =?UTF-8?q?[feat]=20#5=20socket=20exception=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/socket/error/SocketException.java | 14 ++++++++++++++ .../socket/error/SocketUnauthorizedException.java | 7 +++++++ 2 files changed, 21 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/socket/error/SocketException.java create mode 100644 src/main/java/org/gachon/checkmate/global/socket/error/SocketUnauthorizedException.java diff --git a/src/main/java/org/gachon/checkmate/global/socket/error/SocketException.java b/src/main/java/org/gachon/checkmate/global/socket/error/SocketException.java new file mode 100644 index 0000000..d8b6043 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/error/SocketException.java @@ -0,0 +1,14 @@ +package org.gachon.checkmate.global.socket.error; + +import lombok.Getter; +import org.springframework.messaging.MessageDeliveryException; + +@Getter +public class SocketException extends MessageDeliveryException { + private final SocketErrorCode socketErrorCode; + + public SocketException(SocketErrorCode socketErrorCode) { + super(socketErrorCode.getMessage()); + this.socketErrorCode = socketErrorCode; + } +} diff --git a/src/main/java/org/gachon/checkmate/global/socket/error/SocketUnauthorizedException.java b/src/main/java/org/gachon/checkmate/global/socket/error/SocketUnauthorizedException.java new file mode 100644 index 0000000..030d85e --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/error/SocketUnauthorizedException.java @@ -0,0 +1,7 @@ +package org.gachon.checkmate.global.socket.error; + +public class SocketUnauthorizedException extends SocketException { + public SocketUnauthorizedException(SocketErrorCode socketErrorCode) { + super(socketErrorCode); + } +} From 277ccb7b11d83f32c4f660d3096811af33f90796 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:29:43 +0900 Subject: [PATCH 06/28] =?UTF-8?q?[feat]=20#5=20socket=20code=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/socket/error/SocketErrorCode.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java diff --git a/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java b/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java new file mode 100644 index 0000000..445ea40 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java @@ -0,0 +1,33 @@ +package org.gachon.checkmate.global.socket.error; + + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum SocketErrorCode { + + /** + * 1401 UnAuthorized + */ + USER_NOT_AUTHORIZED(1401, "접근할 수 있는 권한이 없습니다."), + EXPIRED_ACCESS_TOKEN(1401, "액세스 토큰이 만료 되었습니다."), + INVALID_ACCESS_TOKEN_VALUE(1401, "액세스 토큰의 값이 올바르지 않습니다."), + + + /** + * 1404 Not found + */ + SESSION_ATTRIBUTE_NOT_FOUND(1404, "SessionAttributes를 찾을 수 없습니다."), + SESSION_ATTRIBUTE_NULL_VALUE(1404, "세션 속성값이 null입니다."), + + /** + * 1500 Socket Server + */ + SOCKET_SERVER_ERROR(1500, "소켓 서버 오류입니다."); + + private final int code; + private final String message; +} From 2ee34f05ab1e57ecf0cb6d2c0966c00764fe0049 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Mon, 1 Jan 2024 17:29:51 +0900 Subject: [PATCH 07/28] =?UTF-8?q?[feat]=20#5=20socket=20jwt=20provider=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/socket/SocketJwtProvider.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java diff --git a/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java b/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java new file mode 100644 index 0000000..1049860 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java @@ -0,0 +1,91 @@ +package org.gachon.checkmate.global.socket; + + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import lombok.Getter; +import org.gachon.checkmate.global.error.ErrorCode; +import org.gachon.checkmate.global.error.exception.UnauthorizedException; +import org.gachon.checkmate.global.socket.error.SocketException; +import org.gachon.checkmate.global.socket.error.SocketUnauthorizedException; +import org.gachon.checkmate.global.socket.error.SocketErrorCode; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Base64; +import java.util.Date; + +@Getter +@Component +public class SocketJwtProvider { + @Value("${jwt.secret}") + private String secretKey; + @Value("${jwt.access-token-expire-time}") + private long ACCESS_TOKEN_EXPIRE_TIME; + @Value("${jwt.refresh-token-expire-time}") + private long REFRESH_TOKEN_EXPIRE_TIME; + + private static final String BEARER_TYPE = "Bearer"; + + public String getIssueToken(Long userId, boolean isAccessToken) { + if (isAccessToken) return generateToken(userId, ACCESS_TOKEN_EXPIRE_TIME); + else return generateToken(userId, REFRESH_TOKEN_EXPIRE_TIME); + } + + public void validateAccessToken(String accessToken) { + try { + getJwtParser().parseClaimsJws(accessToken); + } catch (ExpiredJwtException e) { + throw new SocketUnauthorizedException(SocketErrorCode.EXPIRED_ACCESS_TOKEN); + } catch (Exception e) { + throw new SocketUnauthorizedException(SocketErrorCode.INVALID_ACCESS_TOKEN_VALUE); + } + } + + public static String extractToken(String authHeaderValue) { + if (authHeaderValue.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) { + return authHeaderValue.substring(BEARER_TYPE.length()).trim(); + } + return null; + } + + public Claims getClaimsFormToken(String token) { + try { + Claims claims = Jwts.parser().setSigningKey(secretKey.getBytes()) + .parseClaimsJws(token).getBody(); + return claims; + } catch (Exception e){ + throw new SocketException(SocketErrorCode.INVALID_ACCESS_TOKEN_VALUE); + } + } + + public Long getSubject(String token) { + return Long.valueOf(getJwtParser().parseClaimsJws(token) + .getBody() + .getSubject()); + } + + private String generateToken(Long userId, long tokenTime) { + final Date now = new Date(); + final Date expiration = new Date(now.getTime() + tokenTime); + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setSubject(String.valueOf(userId)) + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + private JwtParser getJwtParser() { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build(); + } + + private Key getSigningKey() { + String encoded = Base64.getEncoder().encodeToString(secretKey.getBytes()); + return Keys.hmacShaKeyFor(encoded.getBytes()); + } +} From 490ce6f0b5c45ae81cd621edd1168ce69d751d51 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:13:27 +0900 Subject: [PATCH 08/28] =?UTF-8?q?[feat]=20#5=20gradle=EC=97=90=20socket=20?= =?UTF-8?q?&=20message=20&=20mongodb=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 05093d5..a0b7664 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,12 @@ dependencies { annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드 annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드 + // websocket + implementation 'org.springframework.boot:spring-boot-starter-websocket' + + // Messaging + implementation 'org.springframework:spring-messaging:6.1.0' + // 이메일 SMTP implementation 'org.springframework.boot:spring-boot-starter-mail' @@ -60,9 +66,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' + // mongoDB + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + // redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' - } tasks.named('test') { @@ -85,4 +93,4 @@ sourceSets { // gradle clean 시에 QClass 디렉토리 삭제 clean { delete file(generated) -} \ No newline at end of file +} From 1714e6920ba251c35ece2959955af5276afb3199 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:13:44 +0900 Subject: [PATCH 09/28] =?UTF-8?q?[feat]=20#5=20chat=20entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checkmate/domain/chat/entity/Chat.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/entity/Chat.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/entity/Chat.java b/src/main/java/org/gachon/checkmate/domain/chat/entity/Chat.java new file mode 100644 index 0000000..edaa1c9 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/entity/Chat.java @@ -0,0 +1,52 @@ +package org.gachon.checkmate.domain.chat.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import lombok.*; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.LocalDateTime; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Builder +@Entity +@Document(collection = "chatting") +/** + * 채팅 정보를 담는 객체로 몽고db를 통해 관리됩니다. + * isRead라는 것을 통해 상대가 이 메시지를 읽었는지 판단합니다. + */ +public class Chat { + + @Id + @Field(name="_id") + private String id; + + @Field(name="chat_room_id") + private String chatRoomId; + + @Field(name="sender_id") + private Long senderId; + + @Field(name="content") + private String content; + + @Field(name="is_read") + private Boolean isRead; + + @LastModifiedDate + @Field(name="send_time") + private LocalDateTime sendTime; + + public static Chat createChat(String chatRoomId, Long senderId, String content, Boolean isRead) { + return Chat.builder() + .chatRoomId(chatRoomId) + .senderId(senderId) + .content(content) + .isRead(isRead) + .build(); + } +} From 402ca9d12948d9232e884f6f07b5c4818b3e989a Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:14:12 +0900 Subject: [PATCH 10/28] =?UTF-8?q?[feat]=20#5=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20entity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/entity/ChatRoom.java | 43 +++++++++++++++++++ .../domain/chat/entity/LiveChatRoom.java | 38 ++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/entity/ChatRoom.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/entity/LiveChatRoom.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/entity/ChatRoom.java b/src/main/java/org/gachon/checkmate/domain/chat/entity/ChatRoom.java new file mode 100644 index 0000000..2e0bb43 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/entity/ChatRoom.java @@ -0,0 +1,43 @@ +package org.gachon.checkmate.domain.chat.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.*; +import org.gachon.checkmate.global.common.BaseTimeEntity; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +@Entity +@Getter +@Table(name = "chat_room") +/** + * 채팅방에 포함된 두명의 사용자를 판단하기 위해 만들었습니다. + * 이때 채팅방의 id는 autoIncrement 되는것이 아닌 두 사용자의 id를 사용해 만들어지게 됩니다. + * ex) user1 id = 1, user2 id = 2라면 채팅방 id는 "1+2" 이 되고 항상 작은 숫자가 앞으로 가게 됩니다. + * ex) user1 id = 10, user2 id = 3 , 채팅방 id = "3+10" + */ +// ChatRoom의 경우 DB에 저장해서 해당 채팅방 id를 통해 채팅방 속 두명의 사용자를 판단하기 위해 만들었습니다. +public class ChatRoom extends BaseTimeEntity { + + @Id + @Column(name = "chat_room_id", length = 100) + private String id; + + @Column(name = "first_member_id") + private Long firstMemberId; + + @Column(name = "second_member_id") + private Long secondMemberId; + + public static ChatRoom createChatRoom(String id, Long firstMemberId, Long secondMemberId) { + return ChatRoom.builder() + .id(id) + .firstMemberId(firstMemberId) + .secondMemberId(secondMemberId) + .build(); + } + +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/entity/LiveChatRoom.java b/src/main/java/org/gachon/checkmate/domain/chat/entity/LiveChatRoom.java new file mode 100644 index 0000000..7e53757 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/entity/LiveChatRoom.java @@ -0,0 +1,38 @@ +package org.gachon.checkmate.domain.chat.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +@RedisHash(value = "chatRoom") +/** + * 채팅을 보내고 읽음/안읽음 표시를 하기 위해 유저의 채팅방 입장 상황을 redis로 받기 위한 객체입니다. + * .채팅방에 입장할 때 LiveChatRoom에 유저의 저장을 하고 Unsubscribe하거나 disconnect됐을 때 삭제합니다 + */ +public class LiveChatRoom { + + @Id + private String id; + + @Indexed + private String chatRoomId; + + @Indexed + private Long userId; + + + public static LiveChatRoom createLiveChatRoom(String chatRoomId, Long userId) { + return LiveChatRoom.builder() + .chatRoomId(chatRoomId) + .userId(userId) + .build(); + } +} From aaa7e2aa8d43cbca86a0038887cee7ba2e8303ad Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:15:08 +0900 Subject: [PATCH 11/28] =?UTF-8?q?[fix]=20#5=20jpa=20config=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/gachon/checkmate/global/config/JpaConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/gachon/checkmate/global/config/JpaConfig.java b/src/main/java/org/gachon/checkmate/global/config/JpaConfig.java index 1492bf5..1d2ad06 100644 --- a/src/main/java/org/gachon/checkmate/global/config/JpaConfig.java +++ b/src/main/java/org/gachon/checkmate/global/config/JpaConfig.java @@ -2,9 +2,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@EnableJpaAuditing @Configuration +@EnableJpaAuditing +@EnableJpaRepositories(basePackages = "org.gachon.checkmate.domain.*.repository") public class JpaConfig { } From 0d0882895d89d1f31abc2d912da4ff56b62f99b2 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:15:23 +0900 Subject: [PATCH 12/28] =?UTF-8?q?[feat]=20#5=20mongodb=20config=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/MongoDbConfig.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/config/MongoDbConfig.java diff --git a/src/main/java/org/gachon/checkmate/global/config/MongoDbConfig.java b/src/main/java/org/gachon/checkmate/global/config/MongoDbConfig.java new file mode 100644 index 0000000..d22ab19 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/config/MongoDbConfig.java @@ -0,0 +1,33 @@ +package org.gachon.checkmate.global.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.config.EnableMongoAuditing; +import org.springframework.data.mongodb.core.convert.DbRefResolver; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +@Configuration +@RequiredArgsConstructor +@EnableMongoAuditing +@EnableMongoRepositories(basePackages = "org.gachon.checkmate.domain.chat.mongorepository") +public class MongoDbConfig { + + private final MongoMappingContext mongoMappingContext; + + // 데이터 저장시 "_class" 필드가 자동 추가되는 현상 방지를 위한 설정 + @Bean + public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory mongoDatabaseFactory, + MongoMappingContext mongoMappingContext) { + DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory); + MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext); + converter.setTypeMapper(new DefaultMongoTypeMapper(null)); + return converter; + } + +} From 0479805d5c3950b4f0cdf7a5a90d88e8d001aacd Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:16:01 +0900 Subject: [PATCH 13/28] =?UTF-8?q?[feat]=20#5=20StompInterceptor=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../socket/interceptor/StompInterceptor.java | 98 +++++++++++++------ 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java b/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java index ae42802..b2d7100 100644 --- a/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java +++ b/src/main/java/org/gachon/checkmate/global/socket/interceptor/StompInterceptor.java @@ -1,12 +1,15 @@ package org.gachon.checkmate.global.socket.interceptor; -import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.gachon.checkmate.domain.chat.entity.LiveChatRoom; +import org.gachon.checkmate.domain.chat.repository.LiveChatRoomRepository; +import org.gachon.checkmate.domain.member.repository.UserRepository; import org.gachon.checkmate.global.socket.SocketJwtProvider; +import org.gachon.checkmate.global.socket.error.SocketErrorCode; import org.gachon.checkmate.global.socket.error.SocketException; +import org.gachon.checkmate.global.socket.error.SocketNotFoundException; import org.gachon.checkmate.global.socket.error.SocketUnauthorizedException; -import org.gachon.checkmate.global.socket.error.SocketErrorCode; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; @@ -14,12 +17,11 @@ import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Random; -import static org.gachon.checkmate.global.socket.error.SocketErrorCode.SESSION_ATTRIBUTE_NOT_FOUND; -import static org.gachon.checkmate.global.socket.error.SocketErrorCode.SOCKET_SERVER_ERROR; +import static org.gachon.checkmate.global.socket.error.SocketErrorCode.*; @Slf4j @Component @@ -30,56 +32,69 @@ public class StompInterceptor implements ChannelInterceptor { private static final String BEARER = "Bearer "; public static final String DEFAULT_PATH = "/queue/chat/"; private final SocketJwtProvider socketJwtProvider; + private final UserRepository userRepository; + private final LiveChatRoomRepository liveChatRoomRepository; // websocket을 통해 들어온 요청이 처리 되기전 실행된다. @Override public Message preSend(Message message, MessageChannel channel) { - Random random = new Random(); StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); StompCommand command = accessor.getCommand(); if (StompCommand.CONNECT.equals(command)) { // websocket 연결요청 -> JWT 인증 + // JWT 인증 부분 + Long userId = getUserByAuthorizationHeader( + accessor.getFirstNativeHeader("Authorization") + ); - // JWT 인증 부분은 나중에 추가하도록 하겠습니다. -// User user = getUserByAuthorizationHeader( -// accessor.getFirstNativeHeader("Authorization")); - - // Exception 처리 테스트용 - getUserByAuthorizationHeader( - accessor.getFirstNativeHeader("Authorization")); - // 현재는 임의의 값으로 두었습니다. - Long userId = (long) random.nextInt(1000); setValue(accessor, "userId", userId); log.info("[CONNECT]:: userId : " + userId); -// setValue(accessor, "username", user.getNickname()); -// setValue(accessor, "profileImgUrl", user.getProfileImgUrl()); + } else if (StompCommand.SEND.equals(command)) { + // 매 요청마다 accessToken 검증 + validateToken(accessor); } else if (StompCommand.SUBSCRIBE.equals(command)) { // 채팅방 구독요청(진입) Long userId = (Long)getValue(accessor, "userId"); - log.debug("userId : " + userId); if(checkIsDestinationChatRoom(accessor)) { - Long roomId = parseRoomIdFromPath(accessor); - setValue(accessor, "roomId", roomId); - log.debug("roomId : " + roomId); + String enterRoomId = parseRoomIdFromPath(accessor); + setValue(accessor, "roomId", enterRoomId); } + log.debug("userId : " + userId); } else if (StompCommand.UNSUBSCRIBE.equals(command)) { Long userId = (Long)getValue(accessor, "userId"); + if(checkIsDestinationChatRoom(accessor)) { + deleteLiveChatRoom(accessor); + deleteValue(accessor, "roomId"); + } log.info("UNSUBSCRIBE userId : {}", userId); } else if (StompCommand.DISCONNECT == command) { // Websocket 연결 종료 Long userId = (Long)getValue(accessor, "userId"); + deleteLiveChatRoom(accessor); log.info("DISCONNECTED userId : {}", userId); } return message; } - private void getUserByAuthorizationHeader(String authHeaderValue) { + private Long getUserByAuthorizationHeader(String authHeaderValue) { String accessToken = getTokenByAuthorizationHeader(authHeaderValue); + Long userId = socketJwtProvider.getSubject(accessToken); + validateUserExist(userId); + return userId; + } - Claims claims = socketJwtProvider.getClaimsFormToken(accessToken); - Long userId = claims.get("userId", Long.class); + private void validateUserExist(Long userId) { + if(!userRepository.existsById(userId)) { + throw new SocketNotFoundException(USER_NOT_FOUND); + } + } -// return userRepository.findById(userId) -// .orElseThrow(() -> new UserNotFoundException(userId)); + private void deleteLiveChatRoom(StompHeaderAccessor accessor) { + if(isChatRoomAttributeExist(accessor)) { + String roomId = (String) getValue(accessor, "roomId"); + Long userId = (Long) getValue(accessor, "userId"); + List liveChatRooms = liveChatRoomRepository.findAllByUserId(userId); + liveChatRoomRepository.deleteAll(liveChatRooms); + } } private String getTokenByAuthorizationHeader(String authHeaderValue) { @@ -92,13 +107,27 @@ private String getTokenByAuthorizationHeader(String authHeaderValue) { return accessToken; } + private void validateToken(StompHeaderAccessor accessor) { + String authHeaderValue = accessor.getFirstNativeHeader("Authorization"); + if (Objects.isNull(authHeaderValue) || authHeaderValue.isBlank()) { + throw new SocketUnauthorizedException(SocketErrorCode.USER_NOT_AUTHORIZED); + } + String accessToken = SocketJwtProvider.extractToken(authHeaderValue); + socketJwtProvider.validateAccessToken(accessToken); + Long userId = socketJwtProvider.getSubject(accessToken); + Long originalUserId = (Long) getValue(accessor, "userId"); + if(!originalUserId.equals(userId)) { + throw new SocketUnauthorizedException(SocketErrorCode.NOT_ORIGINAL_USER); + } + } + private Boolean checkIsDestinationChatRoom(StompHeaderAccessor accessor) { - return accessor.getDestination().contains("/queue/chat/"); + return accessor.getDestination().contains(DEFAULT_PATH); } - private Long parseRoomIdFromPath(StompHeaderAccessor accessor) { + private String parseRoomIdFromPath(StompHeaderAccessor accessor) { String destination = accessor.getDestination(); - return Long.parseLong(destination.substring(DEFAULT_PATH.length())); + return destination.substring(DEFAULT_PATH.length()); } private Object getValue(StompHeaderAccessor accessor, String key) { @@ -111,11 +140,22 @@ private Object getValue(StompHeaderAccessor accessor, String key) { return value; } + private Boolean isChatRoomAttributeExist(StompHeaderAccessor accessor) { + Map sessionAttributes = getSessionAttributes(accessor); + Object value = sessionAttributes.get("roomId"); + return !Objects.isNull(value); + } + private void setValue(StompHeaderAccessor accessor, String key, Object value) { Map sessionAttributes = getSessionAttributes(accessor); sessionAttributes.put(key, value); } + private void deleteValue(StompHeaderAccessor accessor, String key) { + Map sessionAttributes = getSessionAttributes(accessor); + sessionAttributes.remove(key); + } + private Map getSessionAttributes(StompHeaderAccessor accessor) { Map sessionAttributes = accessor.getSessionAttributes(); if (Objects.isNull(sessionAttributes)) { From 5ebdaab26b2be230fb92ce860369d0710fac2f6a Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:16:48 +0900 Subject: [PATCH 14/28] =?UTF-8?q?[feat]=20#5=20socket=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checkmate/global/socket/error/SocketErrorCode.java | 3 +++ .../global/socket/error/SocketNotFoundException.java | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/global/socket/error/SocketNotFoundException.java diff --git a/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java b/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java index 445ea40..827dfe2 100644 --- a/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java +++ b/src/main/java/org/gachon/checkmate/global/socket/error/SocketErrorCode.java @@ -15,6 +15,7 @@ public enum SocketErrorCode { USER_NOT_AUTHORIZED(1401, "접근할 수 있는 권한이 없습니다."), EXPIRED_ACCESS_TOKEN(1401, "액세스 토큰이 만료 되었습니다."), INVALID_ACCESS_TOKEN_VALUE(1401, "액세스 토큰의 값이 올바르지 않습니다."), + NOT_ORIGINAL_USER(1401, "처음 접속한 유저와 다른 유저의 토큰입니다."), /** @@ -22,6 +23,8 @@ public enum SocketErrorCode { */ SESSION_ATTRIBUTE_NOT_FOUND(1404, "SessionAttributes를 찾을 수 없습니다."), SESSION_ATTRIBUTE_NULL_VALUE(1404, "세션 속성값이 null입니다."), + USER_NOT_FOUND(1404, "유저를 찾을 수 없습니다."), + CHATROOM_NOT_FOUND(1404, "해당 채팅방을 찾을 수 없습니다."), /** * 1500 Socket Server diff --git a/src/main/java/org/gachon/checkmate/global/socket/error/SocketNotFoundException.java b/src/main/java/org/gachon/checkmate/global/socket/error/SocketNotFoundException.java new file mode 100644 index 0000000..a08fe10 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/socket/error/SocketNotFoundException.java @@ -0,0 +1,7 @@ +package org.gachon.checkmate.global.socket.error; + +public class SocketNotFoundException extends SocketException { + public SocketNotFoundException(SocketErrorCode socketErrorCode) { + super(socketErrorCode); + } +} From 9bd6b2aa0293f309e30fcbfb4b46988412e59864 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:16:58 +0900 Subject: [PATCH 15/28] =?UTF-8?q?[feat]=20#5=20socket=20jwt=20provider=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/socket/SocketJwtProvider.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java b/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java index 1049860..d7ca9d5 100644 --- a/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java +++ b/src/main/java/org/gachon/checkmate/global/socket/SocketJwtProvider.java @@ -4,11 +4,8 @@ import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.Getter; -import org.gachon.checkmate.global.error.ErrorCode; -import org.gachon.checkmate.global.error.exception.UnauthorizedException; -import org.gachon.checkmate.global.socket.error.SocketException; -import org.gachon.checkmate.global.socket.error.SocketUnauthorizedException; import org.gachon.checkmate.global.socket.error.SocketErrorCode; +import org.gachon.checkmate.global.socket.error.SocketUnauthorizedException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -50,16 +47,6 @@ public static String extractToken(String authHeaderValue) { return null; } - public Claims getClaimsFormToken(String token) { - try { - Claims claims = Jwts.parser().setSigningKey(secretKey.getBytes()) - .parseClaimsJws(token).getBody(); - return claims; - } catch (Exception e){ - throw new SocketException(SocketErrorCode.INVALID_ACCESS_TOKEN_VALUE); - } - } - public Long getSubject(String token) { return Long.valueOf(getJwtParser().parseClaimsJws(token) .getBody() From 853529759c183e2f94a424832d6ee71e6cc6aae8 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:17:10 +0900 Subject: [PATCH 16/28] =?UTF-8?q?[feat]=20#5=20socket=20jwt=20provider=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gachon/checkmate/global/config/auth/jwt/JwtProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gachon/checkmate/global/config/auth/jwt/JwtProvider.java b/src/main/java/org/gachon/checkmate/global/config/auth/jwt/JwtProvider.java index b27e46e..aabe4ef 100644 --- a/src/main/java/org/gachon/checkmate/global/config/auth/jwt/JwtProvider.java +++ b/src/main/java/org/gachon/checkmate/global/config/auth/jwt/JwtProvider.java @@ -94,4 +94,4 @@ private Key getSigningKey() { String encoded = Base64.getEncoder().encodeToString(secretKey.getBytes()); return Keys.hmacShaKeyFor(encoded.getBytes()); } -} \ No newline at end of file +} From fc7669503467114edc3f066ace45db64b937b4a6 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:19:18 +0900 Subject: [PATCH 17/28] =?UTF-8?q?[feat]=20#5=20socket=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=82=B4=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/dto/ChatLastMessageDto.java | 25 +++++++++++ .../domain/chat/dto/ChatMessageDto.java | 24 ++++++++++ .../domain/chat/dto/ChatRoomListDto.java | 19 ++++++++ .../chat/dto/ChatRoomListUserInfoDto.java | 25 +++++++++++ .../domain/chat/dto/ChatUserInfoDto.java | 24 ++++++++++ .../chat/dto/request/ChatListRequestDto.java | 13 ++++++ .../chat/dto/request/ChatRequestDto.java | 6 +++ .../dto/response/ChatListResponseDto.java | 44 +++++++++++++++++++ .../chat/dto/response/ChatResponseDto.java | 23 ++++++++++ .../response/ChatRoomEnterResponseDto.java | 16 +++++++ .../dto/response/ChatRoomListResponseDto.java | 18 ++++++++ .../chat/dto/response/NewChatResponseDto.java | 23 ++++++++++ 12 files changed, 260 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/ChatLastMessageDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/ChatMessageDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListUserInfoDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/ChatUserInfoDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatListRequestDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatRequestDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatListResponseDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatResponseDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomEnterResponseDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomListResponseDto.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/response/NewChatResponseDto.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatLastMessageDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatLastMessageDto.java new file mode 100644 index 0000000..2a901ff --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatLastMessageDto.java @@ -0,0 +1,25 @@ +package org.gachon.checkmate.domain.chat.dto; + +import lombok.Builder; +import org.gachon.checkmate.domain.chat.entity.Chat; + +import java.time.LocalDateTime; + +@Builder +public record ChatLastMessageDto ( + String content, + LocalDateTime sendTime +) { + public static ChatLastMessageDto of(Chat chat) { + return ChatLastMessageDto.builder() + .content(chat.getContent()) + .sendTime(chat.getSendTime()) + .build(); + } + public static ChatLastMessageDto createEmptyChat() { + return ChatLastMessageDto.builder() + .content(null) + .sendTime(null) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatMessageDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatMessageDto.java new file mode 100644 index 0000000..5fdfb49 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatMessageDto.java @@ -0,0 +1,24 @@ +package org.gachon.checkmate.domain.chat.dto; + + +import lombok.Builder; +import org.gachon.checkmate.domain.chat.entity.Chat; + +import java.time.LocalDateTime; + +@Builder +public record ChatMessageDto ( + Long userId, + String content, + Boolean isRead, + LocalDateTime sendTime +) { + public static ChatMessageDto of(Chat chat) { + return ChatMessageDto.builder() + .userId(chat.getSenderId()) + .content(chat.getContent()) + .isRead(chat.getIsRead()) + .sendTime(chat.getSendTime()) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListDto.java new file mode 100644 index 0000000..e664cec --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListDto.java @@ -0,0 +1,19 @@ +package org.gachon.checkmate.domain.chat.dto; + +import lombok.Builder; + + +@Builder +public record ChatRoomListDto( + ChatLastMessageDto lastChatInfo, + Long notReadCount, + ChatRoomListUserInfoDto userInfo +) { + public static ChatRoomListDto of(ChatLastMessageDto lastChatInfo, Long notReadCount, ChatRoomListUserInfoDto chatRoomListUserInfoDto) { + return ChatRoomListDto.builder() + .lastChatInfo(lastChatInfo) + .notReadCount(notReadCount) + .userInfo(chatRoomListUserInfoDto) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListUserInfoDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListUserInfoDto.java new file mode 100644 index 0000000..9a7ec18 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatRoomListUserInfoDto.java @@ -0,0 +1,25 @@ +package org.gachon.checkmate.domain.chat.dto; + +import com.querydsl.core.annotations.QueryProjection; +import org.gachon.checkmate.domain.member.entity.GenderType; + +import java.time.LocalDate; + +public record ChatRoomListUserInfoDto( + Long userId, + String name, + String profile, + String major, + GenderType gender, + LocalDate endDate +) { + @QueryProjection + public ChatRoomListUserInfoDto(Long userId, String name, String profile, String major, GenderType gender, LocalDate endDate) { + this.userId = userId; + this.name = name; + this.profile = profile; + this.major = major; + this.gender = gender; + this.endDate = endDate; + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatUserInfoDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatUserInfoDto.java new file mode 100644 index 0000000..94aa551 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/ChatUserInfoDto.java @@ -0,0 +1,24 @@ +package org.gachon.checkmate.domain.chat.dto; + +import com.querydsl.core.annotations.QueryProjection; + +import java.time.LocalDate; + +public record ChatUserInfoDto( + Long userId, + String name, + String profile, + Long postId, + String title, + LocalDate endDate +) { + @QueryProjection + public ChatUserInfoDto(Long userId, String name, String profile, Long postId, String title, LocalDate endDate) { + this.userId = userId; + this.name = name; + this.profile = profile; + this.postId = postId; + this.title = title; + this.endDate = endDate; + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatListRequestDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatListRequestDto.java new file mode 100644 index 0000000..7187777 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatListRequestDto.java @@ -0,0 +1,13 @@ +package org.gachon.checkmate.domain.chat.dto.request; + +public record ChatListRequestDto( + Long otherUserId, + Integer pageNumber, + Integer pageSize +) { + public ChatListRequestDto(Long otherUserId, Integer pageNumber, Integer pageSize) { + this.otherUserId = otherUserId; + this.pageNumber = (pageNumber != null) ? pageNumber : 0; + this.pageSize = pageSize != null ? pageSize : 20; + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatRequestDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatRequestDto.java new file mode 100644 index 0000000..9dfc0ef --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/request/ChatRequestDto.java @@ -0,0 +1,6 @@ +package org.gachon.checkmate.domain.chat.dto.request; + +public record ChatRequestDto( + String content +) { +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatListResponseDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatListResponseDto.java new file mode 100644 index 0000000..2835388 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatListResponseDto.java @@ -0,0 +1,44 @@ +package org.gachon.checkmate.domain.chat.dto.response; + +import lombok.Builder; +import lombok.Getter; +import org.gachon.checkmate.domain.chat.dto.ChatMessageDto; +import org.gachon.checkmate.domain.chat.dto.ChatUserInfoDto; +import org.gachon.checkmate.domain.chat.entity.Chat; + +import java.util.ArrayList; +import java.util.List; + +@Builder +@Getter +public class ChatListResponseDto{ + + private String chatRoomId; + + private ChatUserInfoDto chatUserInfoDto; + + @Builder.Default + private List chatMessageList = new ArrayList<>(); + + @Builder.Default + private Boolean hasNextPage = null; + + @Builder.Default + private Integer pageNumber = null; + + public static ChatListResponseDto of(String chatRoomId, ChatUserInfoDto chatUserInfoDto) { + return ChatListResponseDto.builder() + .chatRoomId(chatRoomId) + .chatUserInfoDto(chatUserInfoDto) + .build(); + } + + public void addChatMessage(Chat chat) { + this.chatMessageList.add(ChatMessageDto.of(chat)); + } + + public void updatePageInfo(Boolean hasNextPage, Integer pageNumber) { + this.hasNextPage = hasNextPage; + this.pageNumber = pageNumber; + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatResponseDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatResponseDto.java new file mode 100644 index 0000000..6a357e7 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatResponseDto.java @@ -0,0 +1,23 @@ +package org.gachon.checkmate.domain.chat.dto.response; + +import lombok.Builder; +import org.gachon.checkmate.domain.chat.entity.Chat; + +import java.time.LocalDateTime; + +@Builder +public record ChatResponseDto ( + Long senderId, + String content, + Boolean isRead, + LocalDateTime sendTime +) { + public static ChatResponseDto of(Chat chat) { + return ChatResponseDto.builder() + .senderId(chat.getSenderId()) + .content(chat.getContent()) + .isRead(chat.getIsRead()) + .sendTime(chat.getSendTime()) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomEnterResponseDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomEnterResponseDto.java new file mode 100644 index 0000000..df3a9cb --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomEnterResponseDto.java @@ -0,0 +1,16 @@ +package org.gachon.checkmate.domain.chat.dto.response; + +import lombok.Builder; + +@Builder +public record ChatRoomEnterResponseDto ( + Long userId, + String chatRoomId +) { + public static ChatRoomEnterResponseDto of(Long userId, String chatRoomId) { + return ChatRoomEnterResponseDto.builder() + .userId(userId) + .chatRoomId(chatRoomId) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomListResponseDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomListResponseDto.java new file mode 100644 index 0000000..33b78c8 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/ChatRoomListResponseDto.java @@ -0,0 +1,18 @@ +package org.gachon.checkmate.domain.chat.dto.response; + +import lombok.Builder; +import org.gachon.checkmate.domain.chat.dto.ChatRoomListDto; + +import java.util.List; + + +@Builder +public record ChatRoomListResponseDto( + List chatRoomList +) { + public static ChatRoomListResponseDto of(List chatRoomListDto) { + return ChatRoomListResponseDto.builder() + .chatRoomList(chatRoomListDto) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/response/NewChatResponseDto.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/NewChatResponseDto.java new file mode 100644 index 0000000..314a6fd --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/NewChatResponseDto.java @@ -0,0 +1,23 @@ +package org.gachon.checkmate.domain.chat.dto.response; + +import lombok.Builder; +import org.gachon.checkmate.domain.chat.entity.Chat; + +import java.time.LocalDateTime; + +@Builder +public record NewChatResponseDto( + String chatRoomId, + Long senderId, + String content, + LocalDateTime sendTime +) { + public static NewChatResponseDto of(Chat chat) { + return NewChatResponseDto.builder() + .chatRoomId(chat.getChatRoomId()) + .senderId(chat.getSenderId()) + .content(chat.getContent()) + .sendTime(chat.getSendTime()) + .build(); + } +} From cd8acb4ab435eb7aacd2796c8af8b450c8977303 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:19:36 +0900 Subject: [PATCH 18/28] =?UTF-8?q?[feat]=20#5=20SocketBaseResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/dto/response/SocketBaseResponse.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/response/SocketBaseResponse.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/response/SocketBaseResponse.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/SocketBaseResponse.java new file mode 100644 index 0000000..a7a50c1 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/response/SocketBaseResponse.java @@ -0,0 +1,22 @@ +package org.gachon.checkmate.domain.chat.dto.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.gachon.checkmate.domain.chat.dto.MessageType; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Getter +public class SocketBaseResponse { + private MessageType messageType; + private T data; + + public static SocketBaseResponse of(MessageType messageType, T data) { + return SocketBaseResponse.builder() + .messageType(messageType) + .data(data) + .build(); + } +} From 02ed988b295ae4e997d78f2729ae6be4aee5a5da Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:19:53 +0900 Subject: [PATCH 19/28] =?UTF-8?q?[feat]=20#5=20=EC=9D=91=EB=8B=B5=20Messag?= =?UTF-8?q?eType=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checkmate/domain/chat/dto/MessageType.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/dto/MessageType.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/dto/MessageType.java b/src/main/java/org/gachon/checkmate/domain/chat/dto/MessageType.java new file mode 100644 index 0000000..880446f --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/dto/MessageType.java @@ -0,0 +1,17 @@ +package org.gachon.checkmate.domain.chat.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum MessageType { + CHAT("CHAT"), + NEW_CHAT_NOTIFICATION("NEW_CHAT_NOTIFICATION"), + ROOM_ENTER("ROOM_ENTER"), + ROOM_LIST("ROOM_LIST"), + CHAT_LIST("CHAT_LIST"); + + private final String desc; +} From 682dcac436b55a4cd8e5cda3858dca4c43995ef9 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:20:17 +0900 Subject: [PATCH 20/28] =?UTF-8?q?[feat]=20#5=20Chat=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20repository=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mongorepository/ChatCustomRepository.java | 17 ++++ .../ChatCustomRepositoryImpl.java | 92 +++++++++++++++++++ .../chat/mongorepository/ChatRepository.java | 9 ++ 3 files changed, 118 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepository.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepositoryImpl.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatRepository.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepository.java b/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepository.java new file mode 100644 index 0000000..1d706f4 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepository.java @@ -0,0 +1,17 @@ +package org.gachon.checkmate.domain.chat.mongorepository; + +import org.gachon.checkmate.domain.chat.dto.ChatLastMessageDto; +import org.gachon.checkmate.domain.chat.entity.Chat; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +public interface ChatCustomRepository { + + Slice findBeforeChatList(final String chatRoomId, final Pageable pageable); + + void updateChatRead(final String chatRoomId, final Long userId); + + ChatLastMessageDto findLastChatRoomContent(final String chatRoomId); + + Long findUserNotReadCount(final String chatRoomId, final Long userId); +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepositoryImpl.java b/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepositoryImpl.java new file mode 100644 index 0000000..f0cdcb8 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatCustomRepositoryImpl.java @@ -0,0 +1,92 @@ +package org.gachon.checkmate.domain.chat.mongorepository; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.chat.dto.ChatLastMessageDto; +import org.gachon.checkmate.domain.chat.entity.Chat; +import org.springframework.data.domain.*; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import java.util.List; + +import static org.gachon.checkmate.domain.chat.dto.ChatLastMessageDto.createEmptyChat; + +@RequiredArgsConstructor +public class ChatCustomRepositoryImpl implements ChatCustomRepository { + private final MongoTemplate mongoTemplate; + + /** + * 채팅방의 이전 채팅들을 확인하는 메소드 + */ + @Override + public Slice findBeforeChatList(final String chatRoomId, final Pageable pageable) { + Query query = new Query() + .with(pageable) + .skip(pageable.getPageSize() * pageable.getPageNumber()) // offset + .limit(pageable.getPageSize()+1); + query.with(Sort.by(Sort.Order.desc("sendTime"))); + + query.addCriteria(Criteria.where("chatRoomId").is(chatRoomId)); + List chats = mongoTemplate.find(query, Chat.class, "chatting"); + + return new SliceImpl<>(chats, pageable, hasNextPage(chats, pageable.getPageSize())); + } + + /** + * 채팅방에 입장하고 안읽은 메시지를 읽음 처리해주는 메소드 + */ + @Override + public void updateChatRead(final String chatRoomId, final Long userId) { + Query query = new Query(); + Update update = new Update(); + + query.addCriteria(Criteria.where("chatRoomId").is(chatRoomId) + .and("sender").ne(userId)); + + update.set("isRead", true); + mongoTemplate.updateMulti(query, update, Chat.class); + } + + /** + * 채팅방에 남겨진 마지막 채팅 메시지를 가져오는 메소드 + */ + @Override + public ChatLastMessageDto findLastChatRoomContent(final String chatRoomId) { + + Pageable pageable = PageRequest.of(0, 1); + + Query query = new Query() + .with(pageable) + .skip(pageable.getPageSize() * pageable.getPageNumber()) // offset + .limit(pageable.getPageSize()); + query.with(Sort.by(Sort.Order.desc("sendTime"))); + query.addCriteria(Criteria.where("chatRoomId").is(chatRoomId)); + + List chats = mongoTemplate.find(query, Chat.class, "chatting"); + return chats.isEmpty() ? createEmptyChat() : ChatLastMessageDto.of(chats.get(0)); + } + + /** + * 채팅방에 유저가 읽지않은 메시지의 수를 가져오는 메소드 + */ + @Override + public Long findUserNotReadCount(final String chatRoomId, final Long userId) { + Query query = new Query(); + + query.addCriteria(Criteria.where("chatRoomId").is(chatRoomId) + .and("senderId").ne(userId) + .and("isRead").is(false)); + + return mongoTemplate.count(query, Chat.class); + } + + private boolean hasNextPage(List chats, int pageSize) { + if (chats.size() > pageSize) { + chats.remove(pageSize); + return true; + } + return false; + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatRepository.java b/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatRepository.java new file mode 100644 index 0000000..642de06 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/mongorepository/ChatRepository.java @@ -0,0 +1,9 @@ +package org.gachon.checkmate.domain.chat.mongorepository; + + +import org.gachon.checkmate.domain.chat.entity.Chat; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface ChatRepository extends MongoRepository, ChatCustomRepository { + +} From fa9cc8991b377a404bc08dc2a5de076c49e23328 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:20:35 +0900 Subject: [PATCH 21/28] =?UTF-8?q?[feat]=20#5=20ChatRoom=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20repository=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/repository/ChatRoomRepository.java | 10 ++++++++++ .../chat/repository/LiveChatRoomRepository.java | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/repository/ChatRoomRepository.java create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/repository/LiveChatRoomRepository.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/repository/ChatRoomRepository.java b/src/main/java/org/gachon/checkmate/domain/chat/repository/ChatRoomRepository.java new file mode 100644 index 0000000..2da602f --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/repository/ChatRoomRepository.java @@ -0,0 +1,10 @@ +package org.gachon.checkmate.domain.chat.repository; + +import org.gachon.checkmate.domain.chat.entity.ChatRoom; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ChatRoomRepository extends JpaRepository { + List findAllByFirstMemberIdOrSecondMemberId(Long firstMemberId, Long secondMemberId); +} diff --git a/src/main/java/org/gachon/checkmate/domain/chat/repository/LiveChatRoomRepository.java b/src/main/java/org/gachon/checkmate/domain/chat/repository/LiveChatRoomRepository.java new file mode 100644 index 0000000..59e9e85 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/repository/LiveChatRoomRepository.java @@ -0,0 +1,14 @@ +package org.gachon.checkmate.domain.chat.repository; + +import org.gachon.checkmate.domain.chat.entity.LiveChatRoom; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface LiveChatRoomRepository extends CrudRepository { + + List findAllByUserId(Long userId); + + Boolean existsLiveChatRoomByChatRoomIdAndUserId(String chatRoomId, Long userId); + +} From 1212d6729c26cf7587278a0e224844d37a570aa8 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:21:02 +0900 Subject: [PATCH 22/28] =?UTF-8?q?[feat]=20#5=20ChatRoomListDto=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/util/ListComparatorChatSendTime.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/util/ListComparatorChatSendTime.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/util/ListComparatorChatSendTime.java b/src/main/java/org/gachon/checkmate/domain/chat/util/ListComparatorChatSendTime.java new file mode 100644 index 0000000..d10c6a5 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/util/ListComparatorChatSendTime.java @@ -0,0 +1,15 @@ +package org.gachon.checkmate.domain.chat.util; + +import org.gachon.checkmate.domain.chat.dto.ChatRoomListDto; + +import java.util.Comparator; + +public class ListComparatorChatSendTime implements Comparator { + @Override + public int compare(ChatRoomListDto o1, ChatRoomListDto o2) { + if(o1.lastChatInfo().sendTime() == null || o2.lastChatInfo().sendTime() == null) { + return 0; + } + return o2.lastChatInfo().sendTime().compareTo(o1.lastChatInfo().sendTime()); + } +} From 7043c79c5fd30579c25d8449a9c156e4612cc944 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:21:15 +0900 Subject: [PATCH 23/28] =?UTF-8?q?[feat]=20#5=20ChatController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/controller/ChatController.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/controller/ChatController.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/controller/ChatController.java b/src/main/java/org/gachon/checkmate/domain/chat/controller/ChatController.java new file mode 100644 index 0000000..fabf3d0 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/controller/ChatController.java @@ -0,0 +1,55 @@ +package org.gachon.checkmate.domain.chat.controller; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.chat.dto.MessageType; +import org.gachon.checkmate.domain.chat.dto.request.ChatListRequestDto; +import org.gachon.checkmate.domain.chat.dto.request.ChatRequestDto; +import org.gachon.checkmate.domain.chat.dto.response.*; +import org.gachon.checkmate.domain.chat.service.ChatService; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RequiredArgsConstructor +@RestController +public class ChatController { + + private final ChatService chatService; + + private final SimpMessageSendingOperations sendingOperations; + + // 채팅 전송 + @MessageMapping("/chat") + public void sendChat(@Header("simpSessionAttributes") Map simpSessionAttributes, + @Payload final ChatRequestDto request) { + ChatResponseDto response = chatService.sendChat(simpSessionAttributes, request); + sendingOperations.convertAndSend("/queue/chat/"+simpSessionAttributes.get("roomId"), SocketBaseResponse.of(MessageType.CHAT, response)); + } + + // 채팅방 정보 조회 + @MessageMapping("/room-list") + public void getChatRoomList(@Header("simpSessionAttributes") Map simpSessionAttributes) { + ChatRoomListResponseDto response = chatService.getChatRoomList(simpSessionAttributes); + sendingOperations.convertAndSend("/queue/user/"+simpSessionAttributes.get("userId"), SocketBaseResponse.of(MessageType.ROOM_LIST, response)); + } + + // 이전 채팅 불러오기 + @MessageMapping("/chat-list") + public void getChatList(@Header("simpSessionAttributes") Map simpSessionAttributes, + @Payload final ChatListRequestDto request) { + final ChatListResponseDto response = chatService.getChatList(simpSessionAttributes, request); + sendingOperations.convertAndSend("/queue/user/" + simpSessionAttributes.get("userId"), SocketBaseResponse.of(MessageType.CHAT_LIST, response)); + } + + // 채팅방 입장하기 + @MessageMapping("/room-enter") + public void enterRoom(@Header("simpSessionAttributes") Map simpSessionAttributes, + @Payload final ChatListRequestDto request) { + ChatRoomEnterResponseDto response = chatService.enterChatRoom(simpSessionAttributes, request); + sendingOperations.convertAndSend("/queue/chat/" + response.chatRoomId(), SocketBaseResponse.of(MessageType.ROOM_ENTER, response)); + } +} From bc4cd24e363f8fe892842af6982ef93d0128b81c Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:21:26 +0900 Subject: [PATCH 24/28] =?UTF-8?q?[feat]=20#5=20ChatService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/service/ChatService.java | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java diff --git a/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java b/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java new file mode 100644 index 0000000..d043538 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java @@ -0,0 +1,214 @@ +package org.gachon.checkmate.domain.chat.service; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.chat.dto.*; +import org.gachon.checkmate.domain.chat.dto.request.ChatListRequestDto; +import org.gachon.checkmate.domain.chat.dto.request.ChatRequestDto; +import org.gachon.checkmate.domain.chat.dto.response.*; +import org.gachon.checkmate.domain.chat.entity.Chat; +import org.gachon.checkmate.domain.chat.entity.ChatRoom; +import org.gachon.checkmate.domain.chat.entity.LiveChatRoom; +import org.gachon.checkmate.domain.chat.mongorepository.ChatRepository; +import org.gachon.checkmate.domain.chat.repository.ChatRoomRepository; +import org.gachon.checkmate.domain.chat.repository.LiveChatRoomRepository; +import org.gachon.checkmate.domain.chat.util.ListComparatorChatSendTime; +import org.gachon.checkmate.domain.member.repository.UserQuerydslRepository; +import org.gachon.checkmate.global.socket.error.SocketErrorCode; +import org.gachon.checkmate.global.socket.error.SocketNotFoundException; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.gachon.checkmate.domain.chat.entity.ChatRoom.createChatRoom; +import static org.gachon.checkmate.domain.chat.entity.LiveChatRoom.createLiveChatRoom; + +@Service +@Transactional +@RequiredArgsConstructor +public class ChatService { + + private final SimpMessageSendingOperations sendingOperations; + private final ChatRepository chatRepository; + private final ChatRoomRepository chatRoomRepository; + private final LiveChatRoomRepository liveChatRoomRepository; + private final UserQuerydslRepository userQuerydslRepository; + + /** + * 채팅을 전송하는 코드입니다. + * 1. 채팅상대가 채팅방에 들어와있는지 확인하고 앍맞은 isRead값을 넣은 채팅을 MongoDB에 저장합니다. + * 2. 만약 다른 유저가 채팅에 들어와있지 않다면 해당 유저의 /queue/user/{다른 유저 번호}로 알림 메세지를 보냅니다. + * 3. 채팅을 return해주고 controller에서 /queue/chat/{채팅방Id}로 메세지를 보냅니다. + */ + public ChatResponseDto sendChat(Map simpSessionAttributes, + ChatRequestDto request) { + String roomId = getRoomIdInAttributes(simpSessionAttributes); + Long userId = getUserIdInAttributes(simpSessionAttributes); + ChatRoom chatRoom = getChatRoomById(roomId); + Long otherUserId = getOtherUserIdInChatRoom(chatRoom, userId); + // 채팅상대가 이 채팅방에 들어와있는지 확인함 + Boolean isOtherUserInChatRoom = isUserInChatRoom(roomId, otherUserId); + Chat chat = Chat.createChat(roomId, userId, request.content(), isOtherUserInChatRoom); + Chat savedChat = chatRepository.save(chat); + + // 만약 채팅방에 다른 유저가 안들어와있을 때 알림가도록 메세지 전송해줌 + if(!isOtherUserInChatRoom) { + NewChatResponseDto notificationChat = NewChatResponseDto.of(savedChat); + sendingOperations.convertAndSend("/queue/user/" + otherUserId, SocketBaseResponse.of(MessageType.NEW_CHAT_NOTIFICATION, notificationChat)); + } + + return ChatResponseDto.of(savedChat); + } + + + /** + * 유저의 채팅방들을 불러오는 코드입니다. + * 1. 유저가 속한 채팅방을 가져옵니다. + * 2. 해당 채팅방에서 각각 채팅 상대 유저의 정보, 채팅방의 마지막 채팅내용, 읽지않은 채팅 수를 나타냅니다. + * 3. 채팅방의 마지막 채팅 최신순으로 정렬해줍니다. + */ + public ChatRoomListResponseDto getChatRoomList(Map simpSessionAttributes) { + Long userId = getUserIdInAttributes(simpSessionAttributes); + List chatRooms = getUserChatRoomsByUserId(userId); + List response = new ArrayList<>( + chatRooms.stream() + .map(chatRoom -> getChatRoomListResponse(chatRoom, userId)) + .toList() + ); + // 최신 메시지순으로 정렬 + sortChatRoomListDtoByLastSendTime(response); + return ChatRoomListResponseDto.of(response); + } + + /** + * 채팅방에 들어가 이전 채팅목록을 불러오는 코드입니다. + * 1. 다른 유저의 유저 정보를 받아옵니다. + * 2. 다른 유저가 작성한 post의 정보를 받아옵니다. + * 3. 해당 채팅방이 처음 만들어지는 건지 아니면 이미 있는 채팅방인지 확인합니다. + * 3-1. 만약 채팅방 있다면 mongoDB의 chatting에서 해당 채팅방의 채팅 내역을 20개씩 끊어 받아옵니다. + * 3-2. 만약 채팅방이 없다면(처음 생성된것이라면) 새로운 채팅방을 만들어 저장해줍니다. (이때 이전 채팅들은 null로 들어갑니다.) + */ + public ChatListResponseDto getChatList(Map simpSessionAttributes, + ChatListRequestDto request) { + Long userId = getUserIdInAttributes(simpSessionAttributes); + String chatRoomId = getChatRoomId(request.otherUserId(), userId); + + ChatUserInfoDto chatUserInfoDto = getUserChatUserInfoByUserId(request); + ChatListResponseDto response = ChatListResponseDto.of(chatRoomId, chatUserInfoDto); + + PageRequest pageRequest = PageRequest.of(request.pageNumber(), request.pageSize()); + Slice chatMessages = chatRepository.findBeforeChatList(chatRoomId, pageRequest); + chatMessages.getContent().forEach(response::addChatMessage); + response.updatePageInfo(chatMessages.hasNext(), chatMessages.getNumber()); + + return response; + } + + /** + * 채팅방을 들어올 때 실행합니다. + * 1. redis에 해당 채팅방에 유저가 들어왔다는 것을 저장합니다. + * 2. 이전에 유저가 읽지 않은 채팅을 모두 읽음처리 해줍니다. + */ + public ChatRoomEnterResponseDto enterChatRoom(Map simpSessionAttributes, + ChatListRequestDto request) { + Long userId = getUserIdInAttributes(simpSessionAttributes); + String chatRoomId = getChatRoomId(request.otherUserId(), userId); + + // 해당 채팅방에 유저 접속상태 확인 위해 redis에 저장 + saveUserInLiveChatRoom(chatRoomId, userId); + + // 채팅방이 있는지 확인하고 없다면 하나 새로 만들어줌 (채팅방 id : 유저 두명의 아이디인데 작은 id가 앞으로감 + // ex) 유저 아이디가 10, 3 인 채팅방이면 3:10이 있는지 확인 + if(validateChatRoomExist(chatRoomId)) { + // 안읽은 메세지들 읽음 처리 해줌 + chatRepository.updateChatRead(chatRoomId, userId); + } + else { + ChatRoom chatRoom = createChatRoom(chatRoomId, userId, request.otherUserId()); + chatRoomRepository.save(chatRoom); + } + return ChatRoomEnterResponseDto.of(userId, chatRoomId); + } + + private void sortChatRoomListDtoByLastSendTime(List chatRoomListDtos) { + if(!chatRoomListDtos.isEmpty()) { + chatRoomListDtos.sort(new ListComparatorChatSendTime()); + } + } + + private ChatRoomListDto getChatRoomListResponse(ChatRoom chatRoom, Long userId) { + Long otherUserId = getChatRoomOtherUserId(chatRoom, userId); + // 채팅방 목록에 표시돼야하는 상대 유저의 정보, 게시물 마감일을 가져옴 + ChatRoomListUserInfoDto otherUserInfo = getUserChatRoomListInfoByUserId(otherUserId); + // 채팅방에 있는 마지막 채팅 내용 가져옴 + ChatLastMessageDto chatLastMessageDto = getLastChatRoomContent(chatRoom); + // 채팅방 유저가 안읽은 메세지 수 가져옴 + Long userNotReadCount = getUserNotReadCount(chatRoom, userId); + + return ChatRoomListDto.of(chatLastMessageDto,userNotReadCount, otherUserInfo); + } + + private ChatRoomListUserInfoDto getUserChatRoomListInfoByUserId(Long userId) { + return userQuerydslRepository.findUserChatRoomListInfo(userId); + } + + private Long getUserNotReadCount(ChatRoom chatRoom, Long userId) { + return chatRepository.findUserNotReadCount(chatRoom.getId(), userId); + } + + private ChatLastMessageDto getLastChatRoomContent(ChatRoom chatRoom) { + return chatRepository.findLastChatRoomContent(chatRoom.getId()); + } + + private Long getChatRoomOtherUserId(ChatRoom chatRoom, Long userId) { + return chatRoom.getFirstMemberId().equals(userId) ? chatRoom.getSecondMemberId() : chatRoom.getFirstMemberId(); + } + + private List getUserChatRoomsByUserId(Long userId) { + return chatRoomRepository.findAllByFirstMemberIdOrSecondMemberId(userId, userId); + } + + private ChatUserInfoDto getUserChatUserInfoByUserId(ChatListRequestDto request) { + return userQuerydslRepository.findUserChatUserInfo(request.otherUserId()); + } + + private void saveUserInLiveChatRoom(String chatRoomId, Long userId) { + LiveChatRoom liveChatRoom = createLiveChatRoom(chatRoomId, userId); + liveChatRoomRepository.save(liveChatRoom); + } + + private Boolean isUserInChatRoom(String roomId, Long userId) { + return liveChatRoomRepository.existsLiveChatRoomByChatRoomIdAndUserId(roomId, userId); + } + + private ChatRoom getChatRoomById(String roomId) { + return chatRoomRepository.findById(roomId) + .orElseThrow(() -> new SocketNotFoundException(SocketErrorCode.CHATROOM_NOT_FOUND)); + } + + private Long getOtherUserIdInChatRoom(ChatRoom chatRoom, Long myUserId) { + return chatRoom.getFirstMemberId().equals(myUserId) ? chatRoom.getSecondMemberId() : chatRoom.getFirstMemberId(); + } + + private boolean validateChatRoomExist(String chatRoomId) { + return chatRoomRepository.existsById(chatRoomId); + } + + private String getChatRoomId(Long otherUserId, Long userId) { + return userId < otherUserId ? userId + "+" + otherUserId : otherUserId + "+" + userId; + } + + private Long getUserIdInAttributes(Map simpSessionAttributes) { + return (Long)simpSessionAttributes.get("userId"); + } + + private String getRoomIdInAttributes(Map simpSessionAttributes) { + return (String)simpSessionAttributes.get("roomId"); + } + +} From 9684a0291f5d0aa86b9cf565ca099fc70df00a06 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:21:53 +0900 Subject: [PATCH 25/28] =?UTF-8?q?[feat]=20#5=20whitelist=20/ws=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gachon/checkmate/global/config/auth/SecurityConfig.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gachon/checkmate/global/config/auth/SecurityConfig.java b/src/main/java/org/gachon/checkmate/global/config/auth/SecurityConfig.java index 6f133ba..acc0f8b 100644 --- a/src/main/java/org/gachon/checkmate/global/config/auth/SecurityConfig.java +++ b/src/main/java/org/gachon/checkmate/global/config/auth/SecurityConfig.java @@ -29,7 +29,9 @@ public class SecurityConfig { "api/member/email", "api/member/signup", "api/member/signin", - "api/member/reissue" + "api/member/reissue", + "/ws/*", + "/ws/**" }; @Bean @@ -59,4 +61,4 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} \ No newline at end of file +} From 646d6edeb60e385f9f03bf8b2ade9a14440f86b2 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Fri, 5 Jan 2024 19:21:57 +0900 Subject: [PATCH 26/28] =?UTF-8?q?[feat]=20#5=20UserQuerydslRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/UserQuerydslRepository.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/java/org/gachon/checkmate/domain/member/repository/UserQuerydslRepository.java diff --git a/src/main/java/org/gachon/checkmate/domain/member/repository/UserQuerydslRepository.java b/src/main/java/org/gachon/checkmate/domain/member/repository/UserQuerydslRepository.java new file mode 100644 index 0000000..d9824ae --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/member/repository/UserQuerydslRepository.java @@ -0,0 +1,68 @@ +package org.gachon.checkmate.domain.member.repository; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.chat.dto.ChatRoomListUserInfoDto; +import org.gachon.checkmate.domain.chat.dto.ChatUserInfoDto; +import org.gachon.checkmate.domain.chat.dto.QChatRoomListUserInfoDto; +import org.gachon.checkmate.domain.chat.dto.QChatUserInfoDto; +import org.springframework.stereotype.Repository; + +import static org.gachon.checkmate.domain.member.entity.QUser.user; +import static org.gachon.checkmate.domain.post.entity.QPost.post; + + +@RequiredArgsConstructor +@Repository +public class UserQuerydslRepository { + private final JPAQueryFactory queryFactory; + + public ChatRoomListUserInfoDto findUserChatRoomListInfo(Long userId) { + return queryFactory + .select(new QChatRoomListUserInfoDto( + user.id, + user.name, + user.profile, + user.major, + user.gender, + post.endDate + )) + .from(user) + .leftJoin(user.postList, post) + .where( + eqUserId(userId) + ) + .orderBy(postEndDateDesc()) + .limit(1) + .fetchOne(); + } + public ChatUserInfoDto findUserChatUserInfo(Long userId) { + return queryFactory + .select(new QChatUserInfoDto( + user.id, + user.name, + user.profile, + post.id, + post.title, + post.endDate + )) + .from(user) + .leftJoin(user.postList, post) + .where( + eqUserId(userId) + ) + .orderBy(post.endDate.desc()) + .limit(1) + .fetchOne(); + } + + private static OrderSpecifier postEndDateDesc() { + return post.endDate != null ? post.endDate.desc() : null; + } + + private BooleanExpression eqUserId(Long userId) { + return user.id.eq(userId); + } +} From f08adad362bb06df1c8e296594737a223f2d6389 Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Sat, 6 Jan 2024 16:20:44 +0900 Subject: [PATCH 27/28] =?UTF-8?q?[fix]=20#5=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=95=8C=EB=A6=BC=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checkmate/domain/chat/service/ChatService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java b/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java index d043538..d2db615 100644 --- a/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java +++ b/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java @@ -57,15 +57,11 @@ public ChatResponseDto sendChat(Map simpSessionAttributes, Chat savedChat = chatRepository.save(chat); // 만약 채팅방에 다른 유저가 안들어와있을 때 알림가도록 메세지 전송해줌 - if(!isOtherUserInChatRoom) { - NewChatResponseDto notificationChat = NewChatResponseDto.of(savedChat); - sendingOperations.convertAndSend("/queue/user/" + otherUserId, SocketBaseResponse.of(MessageType.NEW_CHAT_NOTIFICATION, notificationChat)); - } + sendNotificationToOtherUser(isOtherUserInChatRoom, savedChat, otherUserId); return ChatResponseDto.of(savedChat); } - /** * 유저의 채팅방들을 불러오는 코드입니다. * 1. 유저가 속한 채팅방을 가져옵니다. @@ -135,6 +131,13 @@ public ChatRoomEnterResponseDto enterChatRoom(Map simpSessionAtt return ChatRoomEnterResponseDto.of(userId, chatRoomId); } + private void sendNotificationToOtherUser(Boolean isOtherUserInChatRoom, Chat savedChat, Long otherUserId) { + if(!isOtherUserInChatRoom) { + NewChatResponseDto notificationChat = NewChatResponseDto.of(savedChat); + sendingOperations.convertAndSend("/queue/user/" + otherUserId, SocketBaseResponse.of(MessageType.NEW_CHAT_NOTIFICATION, notificationChat)); + } + } + private void sortChatRoomListDtoByLastSendTime(List chatRoomListDtos) { if(!chatRoomListDtos.isEmpty()) { chatRoomListDtos.sort(new ListComparatorChatSendTime()); From 1ed0ae0ef29d9203c16649dd7180e4243ffe496f Mon Sep 17 00:00:00 2001 From: OJOJIN Date: Sat, 6 Jan 2024 16:21:46 +0900 Subject: [PATCH 28/28] =?UTF-8?q?[fix]=20#5=20collect(Collectors.toList())?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checkmate/domain/chat/service/ChatService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java b/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java index d2db615..d3ee534 100644 --- a/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java +++ b/src/main/java/org/gachon/checkmate/domain/chat/service/ChatService.java @@ -21,9 +21,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.gachon.checkmate.domain.chat.entity.ChatRoom.createChatRoom; import static org.gachon.checkmate.domain.chat.entity.LiveChatRoom.createLiveChatRoom; @@ -71,11 +71,10 @@ public ChatResponseDto sendChat(Map simpSessionAttributes, public ChatRoomListResponseDto getChatRoomList(Map simpSessionAttributes) { Long userId = getUserIdInAttributes(simpSessionAttributes); List chatRooms = getUserChatRoomsByUserId(userId); - List response = new ArrayList<>( - chatRooms.stream() + List response = chatRooms.stream() .map(chatRoom -> getChatRoomListResponse(chatRoom, userId)) - .toList() - ); + .collect(Collectors.toList()); + // 최신 메시지순으로 정렬 sortChatRoomListDtoByLastSendTime(response); return ChatRoomListResponseDto.of(response);