From 61a10cb86a82b43aae8d7009ce78274d95770b60 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Tue, 5 Mar 2024 19:58:41 +0900 Subject: [PATCH 01/11] =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/redis/RedisService.java | 27 +++++++++++-------- .../domain/item/service/ItemRedisService.java | 21 ++++++++++++--- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/api/farmingsoon/common/redis/RedisService.java b/src/main/java/com/api/farmingsoon/common/redis/RedisService.java index c2bb808..ffe324f 100644 --- a/src/main/java/com/api/farmingsoon/common/redis/RedisService.java +++ b/src/main/java/com/api/farmingsoon/common/redis/RedisService.java @@ -42,21 +42,26 @@ public Set getKeySet(String domain) { return redisTemplate.keys(domain); } - public boolean isExistsKey(String key){ - return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + public boolean isNotExistsKey(String key){ + return Boolean.FALSE.equals(redisTemplate.hasKey(key)); } - public void addToSet(String key, Long itemId){ - if(!isExistsKey(key)) {// 키가 없다면(set이 없다면) - redisTemplate.opsForSet().add(key, String.valueOf(itemId)); // set생성 - redisTemplate.expire(key, TimeUtils.getRemainingTimeUntilMidnight(), TimeUnit.SECONDS); // 만료기간 설정 - } - else // 기존 키 값으로 된 set에 추가 - redisTemplate.opsForSet().add(key,String.valueOf(itemId)); + public void addToSet(String key, String value){ + redisTemplate.opsForSet().add(key,value); + } + public void setExpireTime(String key, Long ttl){ + redisTemplate.expire(key, ttl , TimeUnit.SECONDS); // 만료기간 설정 + } + public void createSet(String key, String value){ + redisTemplate.opsForSet().add(key, value); // set생성 + } + public void addToSet(String key,Long ttl, String value){ + redisTemplate.opsForSet().add(key,value); } - public boolean isExistInSet(String key, Long itemId){ - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, String.valueOf(itemId))); + + public boolean isNotExistInSet(String key, Long itemId){ + return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(key, String.valueOf(itemId))); } } diff --git a/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java b/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java index d1beb92..57ffb9c 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java +++ b/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java @@ -1,6 +1,7 @@ package com.api.farmingsoon.domain.item.service; import com.api.farmingsoon.common.redis.RedisService; +import com.api.farmingsoon.common.util.TimeUtils; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -17,13 +18,27 @@ public void setBidEndTime(Long itemId, Integer expire){ } - // @Description 중복된 접근이 아니라면 조회수를 증가시키고 접근 처리 + /** + * @Description + * 1. 중복된 접근이 아니라면 조회수를 증가시키고 접근 처리 + * 2. set이 없다면 만들고 만료기간 자정으로 설정 + * 3. 있다면 추가 + */ + @Async("testExecutor") public void handleViewCount(String cookieValueOfViewer, Long itemId) { - if (!redisService.isExistInSet(cookieValueOfViewer, itemId)) + if (redisService.isNotExistInSet(cookieValueOfViewer, itemId)) { redisService.increaseData("viewCount_item_" + itemId); - redisService.addToSet(cookieValueOfViewer, itemId); + if(redisService.isNotExistsKey(cookieValueOfViewer)) + { + redisService.createSet(cookieValueOfViewer, String.valueOf(itemId)); + redisService.setExpireTime(cookieValueOfViewer, TimeUtils.getRemainingTimeUntilMidnight()); + } + else + { + redisService.addToSet(cookieValueOfViewer, String.valueOf(itemId)); + } } } } From f56d7fc9415a8d6f68d8c9b76ffe9f6cb7191a1b Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Tue, 5 Mar 2024 20:14:57 +0900 Subject: [PATCH 02/11] =?UTF-8?q?STOMP=20Command=20CONNECT=EC=8B=9C=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=9E=85=EC=9E=A5=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/interceptor/StompInterceptor.java | 16 +++++++++++----- .../farmingsoon/common/redis/RedisService.java | 4 ++-- .../chatroom/event/ChatRoomConnectEvent.java | 4 +++- .../chatroom/listener/ChatRoomEventListener.java | 6 +++++- .../chatroom/service/ChatRoomRedisService.java | 15 +++++++++++++++ .../domain/item/service/ItemRedisService.java | 2 +- 6 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java diff --git a/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java b/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java index 94b5fa1..dbd3f7f 100644 --- a/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java +++ b/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java @@ -28,17 +28,23 @@ public class StompInterceptor implements ChannelInterceptor { @Override public Message preSend(Message message, MessageChannel channel) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); - log.info("command : " + accessor.getCommand()); - - - if (StompCommand.CONNECT.equals(accessor.getCommand())) + StompCommand command = accessor.getCommand(); + log.info("command : " + command); + /** + * @Description + * 1. 모든 메시지 읽음 처리 + * 2. Redis에 채팅방 참여 정보 저장 + */ + if (StompCommand.CONNECT.equals(command)) eventPublisher.publishEvent(ChatRoomConnectEvent.builder() .memberId(Long.valueOf(accessor.getFirstNativeHeader("memberId"))) .chatRoomId(Long.valueOf(accessor.getFirstNativeHeader("chatRoomId"))) + .sessionId(accessor.getSessionId()) .build() ); + else if (StompCommand.DISCONNECT.equals(command)) { - + } return message; } diff --git a/src/main/java/com/api/farmingsoon/common/redis/RedisService.java b/src/main/java/com/api/farmingsoon/common/redis/RedisService.java index ffe324f..b1aba84 100644 --- a/src/main/java/com/api/farmingsoon/common/redis/RedisService.java +++ b/src/main/java/com/api/farmingsoon/common/redis/RedisService.java @@ -60,8 +60,8 @@ public void addToSet(String key,Long ttl, String value){ } - public boolean isNotExistInSet(String key, Long itemId){ - return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(key, String.valueOf(itemId))); + public boolean isNotExistInSet(String key, String value){ + return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(key, value)); } } diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java b/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java index 6f189f7..5b761e2 100644 --- a/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java @@ -10,10 +10,12 @@ public class ChatRoomConnectEvent { private Long memberId; private Long chatRoomId; + private String sessionId; @Builder - private ChatRoomConnectEvent(Long memberId, Long chatRoomId) { + private ChatRoomConnectEvent(Long memberId, Long chatRoomId, String sessionId) { this.memberId = memberId; this.chatRoomId = chatRoomId; + this.sessionId = sessionId; } } diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java b/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java index dcac40a..6e1e064 100644 --- a/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java @@ -1,7 +1,9 @@ package com.api.farmingsoon.domain.chatroom.listener; +import com.api.farmingsoon.common.redis.RedisService; import com.api.farmingsoon.domain.chat.service.ChatService; import com.api.farmingsoon.domain.chatroom.event.ChatRoomConnectEvent; +import com.api.farmingsoon.domain.chatroom.service.ChatRoomRedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; @@ -11,9 +13,11 @@ @Component public class ChatRoomEventListener { private final ChatService chatService; + private final ChatRoomRedisService chatRoomRedisService; @EventListener - public void readAllChatMessage(ChatRoomConnectEvent event){ + public void readAllChatAndSaveConnectMember(ChatRoomConnectEvent event){ + chatRoomRedisService.ConnectChatRoom(event.getChatRoomId(), event.getSessionId()); chatService.readAllMyNotReadChatList(event.getChatRoomId(), event.getMemberId()); } diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java b/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java new file mode 100644 index 0000000..05146be --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java @@ -0,0 +1,15 @@ +package com.api.farmingsoon.domain.chatroom.service; + +import com.api.farmingsoon.common.redis.RedisService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChatRoomRedisService { + private final RedisService redisService; + + public void ConnectChatRoom(Long chatRoomId, String sessionId) { + redisService.addToSet("chatRoom_" + chatRoomId, sessionId); + } +} diff --git a/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java b/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java index 57ffb9c..c6e7c30 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java +++ b/src/main/java/com/api/farmingsoon/domain/item/service/ItemRedisService.java @@ -27,7 +27,7 @@ public void setBidEndTime(Long itemId, Integer expire){ @Async("testExecutor") public void handleViewCount(String cookieValueOfViewer, Long itemId) { - if (redisService.isNotExistInSet(cookieValueOfViewer, itemId)) + if (redisService.isNotExistInSet(cookieValueOfViewer, String.valueOf(itemId))) { redisService.increaseData("viewCount_item_" + itemId); if(redisService.isNotExistsKey(cookieValueOfViewer)) From 6d4b8c3293e1713e48ef09d2a0b66e9f9dd21168 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Tue, 5 Mar 2024 20:56:28 +0900 Subject: [PATCH 03/11] =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=EC=97=90=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=EB=B0=A9=EC=9D=B4=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=EC=A7=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/interceptor/StompInterceptor.java | 14 +++++++++----- .../common/redis/RedisService.java | 9 +++++---- .../domain/chat/service/ChatService.java | 6 +++++- .../event/ChatRoomDisConnectEvent.java | 19 +++++++++++++++++++ .../listener/ChatRoomEventListener.java | 12 +++++++++--- .../service/ChatRoomRedisService.java | 15 ++++++++++++++- 6 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomDisConnectEvent.java diff --git a/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java b/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java index dbd3f7f..dca4a73 100644 --- a/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java +++ b/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java @@ -6,6 +6,7 @@ import com.api.farmingsoon.common.util.CookieUtils; import com.api.farmingsoon.common.util.JwtUtils; import com.api.farmingsoon.domain.chatroom.event.ChatRoomConnectEvent; +import com.api.farmingsoon.domain.chatroom.event.ChatRoomDisConnectEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; @@ -37,13 +38,16 @@ public Message preSend(Message message, MessageChannel channel) { */ if (StompCommand.CONNECT.equals(command)) eventPublisher.publishEvent(ChatRoomConnectEvent.builder() - .memberId(Long.valueOf(accessor.getFirstNativeHeader("memberId"))) - .chatRoomId(Long.valueOf(accessor.getFirstNativeHeader("chatRoomId"))) - .sessionId(accessor.getSessionId()) - .build() + .memberId(Long.valueOf(accessor.getFirstNativeHeader("memberId"))) + .chatRoomId(Long.valueOf(accessor.getFirstNativeHeader("chatRoomId"))) + .sessionId(accessor.getSessionId()) + .build() ); else if (StompCommand.DISCONNECT.equals(command)) { - + eventPublisher.publishEvent(ChatRoomDisConnectEvent.builder() + .chatRoomId(Long.valueOf(accessor.getFirstNativeHeader("chatRoomId"))) + .sessionId(accessor.getSessionId()) + .build()); } return message; diff --git a/src/main/java/com/api/farmingsoon/common/redis/RedisService.java b/src/main/java/com/api/farmingsoon/common/redis/RedisService.java index b1aba84..2de2fd0 100644 --- a/src/main/java/com/api/farmingsoon/common/redis/RedisService.java +++ b/src/main/java/com/api/farmingsoon/common/redis/RedisService.java @@ -55,13 +55,14 @@ public void setExpireTime(String key, Long ttl){ public void createSet(String key, String value){ redisTemplate.opsForSet().add(key, value); // set생성 } - public void addToSet(String key,Long ttl, String value){ - redisTemplate.opsForSet().add(key,value); - + public void deleteToSet(String key, String value){ + redisTemplate.opsForSet().remove(key, value); + } + public Long getSetSize(String key){ + return redisTemplate.opsForSet().size(key); } public boolean isNotExistInSet(String key, String value){ return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(key, value)); } - } diff --git a/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java b/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java index f1c5dc5..beacbba 100644 --- a/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java +++ b/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java @@ -10,6 +10,7 @@ import com.api.farmingsoon.domain.chat.model.Chat; import com.api.farmingsoon.domain.chat.repository.ChatRepository; import com.api.farmingsoon.domain.chatroom.model.ChatRoom; +import com.api.farmingsoon.domain.chatroom.service.ChatRoomRedisService; import com.api.farmingsoon.domain.chatroom.service.ChatRoomService; import com.api.farmingsoon.domain.member.model.Member; import com.api.farmingsoon.domain.member.service.MemberService; @@ -31,16 +32,19 @@ public class ChatService { private final ChatRoomService chatRoomService; private final MemberService memberService; private final ApplicationEventPublisher eventPublisher; + private final ChatRoomRedisService chatRoomRedisService; @Transactional public void create(ChatMessageRequest chatMessageRequest) { + Long connectMemberSize = chatRoomRedisService.getConnectMemberSize("chatRoom_" + chatMessageRequest.getChatRoomId()); + ChatRoom chatRoom = chatRoomService.getChatRoom(chatMessageRequest.getChatRoomId()); Member sender = memberService.getMemberById(chatMessageRequest.getSenderId()); Chat chat = chatRepository.save( Chat.builder(). sender(sender) .message(chatMessageRequest.getMessage()) - .isRead(false) + .isRead(connectMemberSize == 2) // 채팅방에 둘 모두 존재한다면 읽음으로 처리 .chatRoom(chatRoom).build() ); diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomDisConnectEvent.java b/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomDisConnectEvent.java new file mode 100644 index 0000000..8bd20dc --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomDisConnectEvent.java @@ -0,0 +1,19 @@ +package com.api.farmingsoon.domain.chatroom.event; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +public class ChatRoomDisConnectEvent { + + private Long chatRoomId; + private String sessionId; + + @Builder + private ChatRoomDisConnectEvent(Long chatRoomId, String sessionId) { + this.chatRoomId = chatRoomId; + this.sessionId = sessionId; + } +} diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java b/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java index 6e1e064..34722fd 100644 --- a/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java @@ -1,11 +1,10 @@ package com.api.farmingsoon.domain.chatroom.listener; -import com.api.farmingsoon.common.redis.RedisService; +import com.api.farmingsoon.common.sse.SseService; import com.api.farmingsoon.domain.chat.service.ChatService; import com.api.farmingsoon.domain.chatroom.event.ChatRoomConnectEvent; import com.api.farmingsoon.domain.chatroom.service.ChatRoomRedisService; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @@ -14,11 +13,18 @@ public class ChatRoomEventListener { private final ChatService chatService; private final ChatRoomRedisService chatRoomRedisService; + private final SseService sseService; @EventListener public void readAllChatAndSaveConnectMember(ChatRoomConnectEvent event){ - chatRoomRedisService.ConnectChatRoom(event.getChatRoomId(), event.getSessionId()); + chatRoomRedisService.connectChatRoom(event.getChatRoomId(), event.getSessionId()); chatService.readAllMyNotReadChatList(event.getChatRoomId(), event.getMemberId()); + // @Todo 웹소켓으로 채팅방에 상대방이 들어왔음을 알림 + } + + @EventListener + public void deleteConnectMember(ChatRoomConnectEvent event){ + chatRoomRedisService.disConnectChatRoom(event.getChatRoomId(), event.getSessionId()); } } diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java b/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java index 05146be..d36156d 100644 --- a/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/service/ChatRoomRedisService.java @@ -9,7 +9,20 @@ public class ChatRoomRedisService { private final RedisService redisService; - public void ConnectChatRoom(Long chatRoomId, String sessionId) { + public void connectChatRoom(Long chatRoomId, String sessionId) { redisService.addToSet("chatRoom_" + chatRoomId, sessionId); } + + /** + * 채팅방에 남은 사람이 한명이라면 나갈 때 키 삭제 + */ + public void disConnectChatRoom(Long chatRoomId, String sessionId) { + if(redisService.getSetSize("chatRoom_" + chatRoomId) == 1){ + redisService.deleteData("chatRoom_" + chatRoomId); + } + redisService.deleteToSet("chatRoom_" + chatRoomId, sessionId); + } + public Long getConnectMemberSize(String key){ + return redisService.getSetSize(key); + } } From 0d9371492b1a0087666e7308eee95a097ab3cec1 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Tue, 5 Mar 2024 21:12:30 +0900 Subject: [PATCH 04/11] =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=EC=97=90=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=EB=B0=A9=EC=9D=B4=20=EC=97=86=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=EB=A7=8C=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/listener/KeyExpirationListener.java | 2 +- .../domain/chat/listener/ChatEventListener.java | 4 ++-- .../domain/chat/service/ChatService.java | 8 +++++++- .../notification/event/NotReadChatEvent.java | 15 +++++++++++++++ .../listener/NotificationEventListener.java | 12 +++++++++--- 5 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/api/farmingsoon/domain/notification/event/NotReadChatEvent.java diff --git a/src/main/java/com/api/farmingsoon/common/redis/listener/KeyExpirationListener.java b/src/main/java/com/api/farmingsoon/common/redis/listener/KeyExpirationListener.java index 4268754..1d09c06 100644 --- a/src/main/java/com/api/farmingsoon/common/redis/listener/KeyExpirationListener.java +++ b/src/main/java/com/api/farmingsoon/common/redis/listener/KeyExpirationListener.java @@ -26,7 +26,7 @@ public void onMessage(Message key, byte[] pattern) { String[] expiredKey = key.toString().split("_"); if (expiredKey[0].equals("bidEnd")) { applicationEventPublisher.publishEvent(new BidEndKeyExpiredEvent((Long.valueOf(expiredKey[1])))); ; // itemId - } else if (expiredKey[0].equals("chatting")) { // memberId + } else if (expiredKey[0].equals("debouncing")) { // memberId applicationEventPublisher.publishEvent(new ChatNotificationDebounceKeyExpiredEvent(Long.valueOf(expiredKey[1]))); // receiverId } } diff --git a/src/main/java/com/api/farmingsoon/domain/chat/listener/ChatEventListener.java b/src/main/java/com/api/farmingsoon/domain/chat/listener/ChatEventListener.java index fe57d87..f6f0e84 100644 --- a/src/main/java/com/api/farmingsoon/domain/chat/listener/ChatEventListener.java +++ b/src/main/java/com/api/farmingsoon/domain/chat/listener/ChatEventListener.java @@ -25,8 +25,8 @@ public class ChatEventListener { public void sendChatAndDebounceNotification(ChatSaveEvent event) throws InterruptedException { messagingTemplate.convertAndSend("/sub/chat-room/" + event.getChatRoomId(), event.getChatResponse()); - if(!redisService.isExistsKey("chatting_" + event.getReceiverId())) // 알림 디바운싱 - redisService.setData("chatting_" + event.getReceiverId(),"", 2L,TimeUnit.SECONDS); + if(redisService.isNotExistsKey("debouncing_" + event.getReceiverId())) // 알림 디바운싱 + redisService.setData("debouncing_" + event.getReceiverId(),"", 2L,TimeUnit.SECONDS); } } \ No newline at end of file diff --git a/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java b/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java index beacbba..fe8d655 100644 --- a/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java +++ b/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java @@ -14,6 +14,7 @@ import com.api.farmingsoon.domain.chatroom.service.ChatRoomService; import com.api.farmingsoon.domain.member.model.Member; import com.api.farmingsoon.domain.member.service.MemberService; +import com.api.farmingsoon.domain.notification.event.NotReadChatEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; @@ -48,10 +49,15 @@ public void create(ChatMessageRequest chatMessageRequest) { .chatRoom(chatRoom).build() ); + Member receiver = ChatRoom.resolveToReceiver(chatRoom, sender.getEmail()); + + if(connectMemberSize == 1) // 채팅방에 상대방이 없다면 알림을 전송 + eventPublisher.publishEvent(new NotReadChatEvent(receiver.getId())); + eventPublisher.publishEvent( ChatSaveEvent.builder() .chatRoomId(chatMessageRequest.getChatRoomId()) - .receiverId(ChatRoom.resolveToReceiver(chatRoom, sender.getEmail()).getId()) + .receiverId(receiver.getId()) .chatResponse(ChatResponse.of(chat)) .build()); diff --git a/src/main/java/com/api/farmingsoon/domain/notification/event/NotReadChatEvent.java b/src/main/java/com/api/farmingsoon/domain/notification/event/NotReadChatEvent.java new file mode 100644 index 0000000..11afcc2 --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/notification/event/NotReadChatEvent.java @@ -0,0 +1,15 @@ +package com.api.farmingsoon.domain.notification.event; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +public class NotReadChatEvent { + + private Long receiverId; + + public NotReadChatEvent(Long receiverId) { + this.receiverId = receiverId; + } +} diff --git a/src/main/java/com/api/farmingsoon/domain/notification/listener/NotificationEventListener.java b/src/main/java/com/api/farmingsoon/domain/notification/listener/NotificationEventListener.java index ae483a7..0c3b429 100644 --- a/src/main/java/com/api/farmingsoon/domain/notification/listener/NotificationEventListener.java +++ b/src/main/java/com/api/farmingsoon/domain/notification/listener/NotificationEventListener.java @@ -2,6 +2,7 @@ import com.api.farmingsoon.common.sse.SseService; import com.api.farmingsoon.domain.notification.event.ChatNotificationDebounceKeyExpiredEvent; +import com.api.farmingsoon.domain.notification.event.NotReadChatEvent; import com.api.farmingsoon.domain.notification.event.NotificationSaveEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,8 +27,13 @@ public void sendNotification(NotificationSaveEvent event) throws InterruptedExce } @EventListener - public void sendChatNotification(ChatNotificationDebounceKeyExpiredEvent event) throws InterruptedException { - sseService.sendToClient("CHATTING", event.getReceiverId(), "새로운 채팅 메시지가 있습니다."); - log.info("채팅 알림 전송"); + public void sendChatRoomUpdateNotification(ChatNotificationDebounceKeyExpiredEvent event) throws InterruptedException { + sseService.sendToClient("CHATROOM_UPDATE", event.getReceiverId(), "채팅방 목록을 업데이트 해주세요."); + log.info("채팅방 업데이트 알림 전송"); + } + @EventListener + public void sendNewChatNotification(NotReadChatEvent event) throws InterruptedException { + sseService.sendToClient("NEW_CHAT", event.getReceiverId(), "새로운 채팅 메시지가 있습니다."); + log.info("새로운 채팅 알림 전송"); } } From 61185db0cb7053a186aa26934f4eea354ac5a9a3 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Wed, 6 Mar 2024 00:24:36 +0900 Subject: [PATCH 05/11] =?UTF-8?q?=EC=83=81=EB=8C=80=EB=B0=A9=EC=9D=B4=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=EC=97=90=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EB=90=90=EC=9D=8C=EC=9D=84=20=EC=95=8C=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/dto/ChatResponse.java | 1 + .../domain/chat/dto/ChattingConnectResponse.java | 16 ++++++++++++++++ .../chatroom/event/ChatRoomConnectEvent.java | 6 +++--- .../chatroom/listener/ChatRoomEventListener.java | 16 +++++++++++++--- 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/api/farmingsoon/domain/chat/dto/ChattingConnectResponse.java diff --git a/src/main/java/com/api/farmingsoon/domain/chat/dto/ChatResponse.java b/src/main/java/com/api/farmingsoon/domain/chat/dto/ChatResponse.java index 54a0912..2b73307 100644 --- a/src/main/java/com/api/farmingsoon/domain/chat/dto/ChatResponse.java +++ b/src/main/java/com/api/farmingsoon/domain/chat/dto/ChatResponse.java @@ -17,6 +17,7 @@ public class ChatResponse { private Long senderId; private Boolean isRead; private LocalDateTime createAt; + private final String type = "SEND"; @Builder private ChatResponse(Long senderId, String message, Boolean isRead, Long chatId, LocalDateTime createAt) { diff --git a/src/main/java/com/api/farmingsoon/domain/chat/dto/ChattingConnectResponse.java b/src/main/java/com/api/farmingsoon/domain/chat/dto/ChattingConnectResponse.java new file mode 100644 index 0000000..ca4bcf7 --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/chat/dto/ChattingConnectResponse.java @@ -0,0 +1,16 @@ +package com.api.farmingsoon.domain.chat.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +public class ChattingConnectResponse { + + private Long connectMemberId; + private final String type = "SEND"; + + public ChattingConnectResponse(Long connectMemberId) { + this.connectMemberId = connectMemberId; + } +} diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java b/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java index 5b761e2..6062472 100644 --- a/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/event/ChatRoomConnectEvent.java @@ -8,13 +8,13 @@ @Getter public class ChatRoomConnectEvent { - private Long memberId; + private Long connectMemberId; private Long chatRoomId; private String sessionId; @Builder - private ChatRoomConnectEvent(Long memberId, Long chatRoomId, String sessionId) { - this.memberId = memberId; + private ChatRoomConnectEvent(Long connectMemberId, Long chatRoomId, String sessionId) { + this.connectMemberId = connectMemberId; this.chatRoomId = chatRoomId; this.sessionId = sessionId; } diff --git a/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java b/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java index 34722fd..1903fc7 100644 --- a/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java +++ b/src/main/java/com/api/farmingsoon/domain/chatroom/listener/ChatRoomEventListener.java @@ -1,11 +1,13 @@ package com.api.farmingsoon.domain.chatroom.listener; import com.api.farmingsoon.common.sse.SseService; +import com.api.farmingsoon.domain.chat.dto.ChattingConnectResponse; import com.api.farmingsoon.domain.chat.service.ChatService; import com.api.farmingsoon.domain.chatroom.event.ChatRoomConnectEvent; import com.api.farmingsoon.domain.chatroom.service.ChatRoomRedisService; import lombok.RequiredArgsConstructor; import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Component; @RequiredArgsConstructor @@ -13,13 +15,21 @@ public class ChatRoomEventListener { private final ChatService chatService; private final ChatRoomRedisService chatRoomRedisService; - private final SseService sseService; + private final SimpMessagingTemplate messagingTemplate; + + /** + * @Description + * 1. 채팅방에 연결됐음을 저장 + * 2. 연결된 사람의 채팅방에 안읽었던 메시지를 모두 읽음 처리 + * 3. 채팅방에 상대방이 연결되었음을 알리는 알림 + */ @EventListener public void readAllChatAndSaveConnectMember(ChatRoomConnectEvent event){ chatRoomRedisService.connectChatRoom(event.getChatRoomId(), event.getSessionId()); - chatService.readAllMyNotReadChatList(event.getChatRoomId(), event.getMemberId()); - // @Todo 웹소켓으로 채팅방에 상대방이 들어왔음을 알림 + chatService.readAllMyNotReadChatList(event.getChatRoomId(), event.getConnectMemberId()); + messagingTemplate.convertAndSend("/sub/chat-room/" + event.getChatRoomId(), new ChattingConnectResponse(event.getConnectMemberId())); + } @EventListener From ac95cbeba06e1954ce0e2e23466949ea9bbf1285 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Wed, 6 Mar 2024 00:28:52 +0900 Subject: [PATCH 06/11] =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EC=9B=B9=EC=86=8C?= =?UTF-8?q?=EC=BC=93=EC=9D=84=20=ED=86=B5=ED=95=B4=20=EC=9D=BD=EC=9D=8C?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=98=EB=8D=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../farmingsoon/common/interceptor/StompInterceptor.java | 2 +- .../farmingsoon/domain/chat/controller/ChatController.java | 5 ----- .../api/farmingsoon/domain/chat/service/ChatService.java | 7 ------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java b/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java index dca4a73..dd056c2 100644 --- a/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java +++ b/src/main/java/com/api/farmingsoon/common/interceptor/StompInterceptor.java @@ -38,7 +38,7 @@ public Message preSend(Message message, MessageChannel channel) { */ if (StompCommand.CONNECT.equals(command)) eventPublisher.publishEvent(ChatRoomConnectEvent.builder() - .memberId(Long.valueOf(accessor.getFirstNativeHeader("memberId"))) + .connectMemberId(Long.valueOf(accessor.getFirstNativeHeader("memberId"))) .chatRoomId(Long.valueOf(accessor.getFirstNativeHeader("chatRoomId"))) .sessionId(accessor.getSessionId()) .build() diff --git a/src/main/java/com/api/farmingsoon/domain/chat/controller/ChatController.java b/src/main/java/com/api/farmingsoon/domain/chat/controller/ChatController.java index 580a9c0..33d5825 100644 --- a/src/main/java/com/api/farmingsoon/domain/chat/controller/ChatController.java +++ b/src/main/java/com/api/farmingsoon/domain/chat/controller/ChatController.java @@ -25,10 +25,5 @@ public class ChatController { public void sendMessage(ChatMessageRequest chatMessageRequest) { chatService.create(chatMessageRequest); } - @MessageMapping("/chat/read") - public void readMessage(ReadMessageRequest readMessageRequest) { - chatService.read(readMessageRequest); - } - } diff --git a/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java b/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java index fe8d655..10523d2 100644 --- a/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java +++ b/src/main/java/com/api/farmingsoon/domain/chat/service/ChatService.java @@ -68,13 +68,6 @@ public ChatListResponse getChats(Long chatRoomId, Pageable pageable) { return ChatListResponse.of(chatRepository.findByChatRoomOrderByIdAsc(chatRoom, pageable)); } - @Transactional - public void read(ReadMessageRequest readMessageRequest) { - Chat chat = chatRepository.findById(readMessageRequest.getChatId()).orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_CHAT)); - chat.read(); - log.info(chat.getId() + " "+ chat.getIsRead()); - } - @Transactional public void readAllMyNotReadChatList(Long chatRoomId, Long memberId) { ChatRoom chatRoom = chatRoomService.getChatRoom(chatRoomId); From 8dfad59ea8974342b3532c3938f6c8c8c0703ecf Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Thu, 7 Mar 2024 15:53:03 +0900 Subject: [PATCH 07/11] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=82=AC=EC=A0=84=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/controller/ItemController.java | 12 +++++ .../farmingsoon/domain/item/domain/Item.java | 3 ++ .../item/dto/ItemListResponseBySubQuery.java | 20 ++++++++ .../item/dto/ItemResponseBySubQuery.java | 50 +++++++++++++++++++ .../item/repository/ItemRepositoryCustom.java | 3 ++ .../repository/ItemRepositoryCustomImpl.java | 45 +++++++++++++++-- .../domain/item/service/ItemService.java | 15 ++++-- src/main/resources/application-test.yml | 4 +- src/main/resources/application.yml | 5 ++ .../domain/item/ItemIntegrationTest.java | 14 +++++- 10 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java create mode 100644 src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java diff --git a/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java b/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java index 3a4d406..f3ac849 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java +++ b/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java @@ -60,6 +60,18 @@ public Response getItemList( ItemListResponse items = itemService.getItemList(category, keyword, pageable, sortcode); return Response.success(HttpStatus.OK, "상품 목록 조회 성공!", items); } + @GetMapping("/test") + public Response getItemListBySubQuery( + @PageableDefault(size = 12) Pageable pageable, + @RequestParam(value = "sortcode", defaultValue = "recent") String sortcode, + @RequestParam(value = "category", required = false) String category, + @RequestParam(value = "keyword", required = false) String keyword) { + + ItemListResponseBySubQuery itemListBySubQuery = itemService.getItemListBySubQuery(category, keyword, pageable, sortcode); + return Response.success(HttpStatus.OK, "상품 목록 조회 성공!", itemListBySubQuery); + } + + @GetMapping("/me") public Response getMyItemList( @PageableDefault(size = 12, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { diff --git a/src/main/java/com/api/farmingsoon/domain/item/domain/Item.java b/src/main/java/com/api/farmingsoon/domain/item/domain/Item.java index c538968..bf34479 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/domain/Item.java +++ b/src/main/java/com/api/farmingsoon/domain/item/domain/Item.java @@ -7,6 +7,7 @@ import com.api.farmingsoon.domain.member.model.Member; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; @@ -57,8 +58,10 @@ public class Item extends BaseTimeEntity { // *Todo 양방향 안쓰는 쪽으로 고려해보기 @OneToMany(mappedBy = "item") + @BatchSize(size = 12) private List bidList; + @BatchSize(size = 12) @OneToMany(mappedBy = "item") private List likeableItemList; diff --git a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java new file mode 100644 index 0000000..89c3bac --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java @@ -0,0 +1,20 @@ +package com.api.farmingsoon.domain.item.dto; + +import com.api.farmingsoon.common.pagenation.Pagination; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +public class ItemListResponseBySubQuery { + + private Pagination pagination; // 페이지 관련 데이터 + private List itemResponseBySubQueryList; // 페이지 관련 데이터 + + public ItemListResponseBySubQuery(Pagination pagination, List itemResponseBySubQueryList) { + this.pagination = pagination; + this.itemResponseBySubQueryList = itemResponseBySubQueryList; + } +} diff --git a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java new file mode 100644 index 0000000..0f47560 --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java @@ -0,0 +1,50 @@ +package com.api.farmingsoon.domain.item.dto; + +import com.api.farmingsoon.domain.item.domain.ItemStatus; +import com.querydsl.core.annotations.QueryProjection; +import com.querydsl.core.types.dsl.EnumPath; +import com.querydsl.core.types.dsl.NumberExpression; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + + +@NoArgsConstructor +@Getter +public class ItemResponseBySubQuery { + private Long itemId; // 상품 접근 + private String title; + private String description; + private LocalDateTime expiredAt; + private Integer highestPrice; + private Integer hopePrice; + private Integer lowestPrice; + private String itemStatus; + private Integer bidCount; + private Integer likeCount; + private Integer viewCount; + private String thumbnailImgUrl; + private Boolean likeStatus; + + @QueryProjection + public ItemResponseBySubQuery(Long itemId, String title, String description, LocalDateTime expiredAt, Integer highestPrice, Integer hopePrice, Integer lowestPrice, String itemStatus, Integer bidCount, Integer likeCount, Integer viewCount, String thumbnailImgUrl, Boolean likeStatus) { + this.itemId = itemId; + this.title = title; + this.description = description; + this.expiredAt = expiredAt; + this.highestPrice = highestPrice; + this.hopePrice = hopePrice; + this.lowestPrice = lowestPrice; + this.itemStatus = itemStatus; + this.bidCount = bidCount; + this.likeCount = likeCount; + this.viewCount = viewCount; + this.thumbnailImgUrl = thumbnailImgUrl; + this.likeStatus = likeStatus; + } + + +} diff --git a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java index b330546..7818d6d 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java +++ b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java @@ -1,6 +1,7 @@ package com.api.farmingsoon.domain.item.repository; import com.api.farmingsoon.domain.item.domain.Item; +import com.api.farmingsoon.domain.item.dto.ItemResponseBySubQuery; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -13,4 +14,6 @@ public interface ItemRepositoryCustom { List findNotEndBidItemList(); List findBiddingItemList(); + + Page findItemResponseList(String category, String keyword, Pageable pageable, String sortcode); } diff --git a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java index 5689d28..f69f52e 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java +++ b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java @@ -2,16 +2,18 @@ import com.api.farmingsoon.domain.item.domain.Item; import com.api.farmingsoon.domain.item.domain.ItemStatus; +import com.api.farmingsoon.domain.item.dto.ItemResponseBySubQuery; +import com.api.farmingsoon.domain.item.dto.QItemResponseBySubQuery; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import java.time.LocalDateTime; import java.util.ArrayList; @@ -19,7 +21,6 @@ import static com.api.farmingsoon.domain.bid.model.QBid.bid; import static com.api.farmingsoon.domain.item.domain.QItem.item; -import static com.api.farmingsoon.domain.member.model.QMember.member; @Slf4j @RequiredArgsConstructor @@ -27,11 +28,11 @@ public class ItemRepositoryCustomImpl implements ItemRepositoryCustom { private final JPAQueryFactory queryFactory; + @Override public Page findItemList(String category, String keyword, Pageable pageable, String sortcode) { List content = queryFactory .selectFrom(item) - .innerJoin(item.member, member).fetchJoin() .leftJoin(item.bidList, bid) .where(eqCategory(category), containsKeyword(keyword)) .groupBy(item.id) @@ -43,14 +44,48 @@ public Page findItemList(String category, String keyword, Pageable pageabl Long total = queryFactory .select(item.count()) .from(item) - .innerJoin(item.member, member) .where(eqCategory(category), containsKeyword(keyword)) .fetchOne(); return new PageImpl<>(content, pageable, total); } + @Override + public Page findItemResponseList(String category, String keyword, Pageable pageable, String sortcode) { + List list = queryFactory.select( + new QItemResponseBySubQuery( + item.id, + item.title, + item.description, + item.expiredAt, + //queryFactory.select(item.bidList.any().price.max()).from(bid), + queryFactory.select(bid.price.max()).from(bid).where(bid.item.eq(item)), + item.hopePrice, + queryFactory.select(item.bidList.any().price.min()).from(bid), + item.itemStatus.stringValue(), + item.bidList.size(), + item.likeableItemList.size(), + item.viewCount, + item.thumbnailImageUrl, + Expressions.FALSE + ) + ).from(item) + .where(eqCategory(category), containsKeyword(keyword)) + .groupBy(item.id) + .orderBy(getAllOrderSpecifiers(sortcode)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long total = queryFactory + .select(item.count()) + .from(item) + .where(eqCategory(category), containsKeyword(keyword)) + .fetchOne(); + + return new PageImpl<>(list, pageable, total); + } @Override public List findNotEndBidItemList() { return queryFactory.selectFrom(item) @@ -65,6 +100,8 @@ public List findBiddingItemList() { .fetch(); } + + private BooleanExpression eqCategory(String category) { log.debug("카테고리: {}", category); return category != null ? item.category.eq(category) : null; diff --git a/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java b/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java index 0a7397a..c326c9a 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java +++ b/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java @@ -1,5 +1,6 @@ package com.api.farmingsoon.domain.item.service; +import com.api.farmingsoon.common.pagenation.Pagination; import com.api.farmingsoon.common.redis.RedisService; import com.api.farmingsoon.common.util.Transaction; import com.api.farmingsoon.domain.item.dto.*; @@ -24,13 +25,9 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.time.Duration; -import java.time.LocalDateTime; import java.util.*; -import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor @@ -81,7 +78,9 @@ public Long saveItemAndImage(Item item, List imageUrls) { @Transactional(readOnly = true) public ItemListResponse getItemList(String category, String keyword, Pageable pageable, String sortcode) { Optional viewer = authenticationUtils.getOptionalMember(); - return ItemListResponse.of(itemRepository.findItemList(category, keyword, pageable, sortcode), viewer); + Page itemList = itemRepository.findItemList(category, keyword, pageable, sortcode); + + return ItemListResponse.of(itemList, viewer); } @Transactional(readOnly = true) public ItemDetailResponse getItemDetail(Long itemId) { @@ -175,4 +174,10 @@ public void bidEndByScheduler(){ public List findBiddingItemList(){ return itemRepository.findBiddingItemList(); } + + public ItemListResponseBySubQuery getItemListBySubQuery(String category, String keyword, Pageable pageable, String sortcode) { + Page itemResponseList = itemRepository.findItemResponseList(category, keyword, pageable, sortcode); + return new ItemListResponseBySubQuery(Pagination.of(itemResponseList), itemResponseList.getContent()); + + } } diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 4a87d3d..6fd5379 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,4 +5,6 @@ logging: level: org: hibernate: - SQL: debug + type: + descriptor: + sql: trace diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index fd29d2e..f2d0ef0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,6 +12,11 @@ spring: console: enabled: true jpa: + show-sql: true + properties: + hibernate: + format_sql: true + highlight_sql: true hibernate: ddl-auto: create # defer-datasource-initialization: true diff --git a/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java b/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java index fd232e7..df2698c 100644 --- a/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java +++ b/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java @@ -100,7 +100,7 @@ void beforeEach(){ UserDetails principal = new User("user1@naver.com", "", authorities); SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principal, "", authorities)); - for(int i = 1; i <= 20; i++){ + for(int i = 1; i <= 100; i++){ Item item = Item.builder() .title("title" + i) .description("description" + i) @@ -124,8 +124,20 @@ void beforeEach(){ .build(); } + @DisplayName("상품 등록 후 상세 조회 성공") + @WithUserDetails(value = "user1@naver.com", setupBefore = TestExecutionEvent.TEST_EXECUTION) + @Test + void getItems() throws Exception { + + MvcResult mvcResult2 = mockMvc.perform(get("/api/items/test")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + } + @DisplayName("상품 등록 성공") @WithUserDetails(value = "user1@naver.com", setupBefore = TestExecutionEvent.TEST_EXECUTION) @Test From 1a437d8317d7386e46f074f095d6efbb636ffeac Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Fri, 8 Mar 2024 18:17:08 +0900 Subject: [PATCH 08/11] =?UTF-8?q?yml=20=EC=88=98=EC=A0=95(=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f2d0ef0..62b7937 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,12 +11,12 @@ spring: h2: console: enabled: true - jpa: - show-sql: true - properties: - hibernate: - format_sql: true - highlight_sql: true +# jpa: +# show-sql: true +# properties: +# hibernate: +# format_sql: true +# highlight_sql: true hibernate: ddl-auto: create # defer-datasource-initialization: true From 168f4e8b6a4e74d95368b9aefe905a27e4e8c894 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Fri, 8 Mar 2024 18:21:16 +0900 Subject: [PATCH 09/11] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/controller/ItemController.java | 25 ++++++++----------- ...Query.java => ItemBySubQueryResponse.java} | 9 ++----- .../item/dto/ItemListBySubQueryResponse.java | 20 +++++++++++++++ .../item/dto/ItemListResponseBySubQuery.java | 20 --------------- .../item/repository/ItemRepositoryCustom.java | 4 +-- .../repository/ItemRepositoryCustomImpl.java | 11 ++++---- .../domain/item/service/ItemService.java | 10 +++++--- 7 files changed, 46 insertions(+), 53 deletions(-) rename src/main/java/com/api/farmingsoon/domain/item/dto/{ItemResponseBySubQuery.java => ItemBySubQueryResponse.java} (79%) create mode 100644 src/main/java/com/api/farmingsoon/domain/item/dto/ItemListBySubQueryResponse.java delete mode 100644 src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java diff --git a/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java b/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java index f3ac849..794dc58 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java +++ b/src/main/java/com/api/farmingsoon/domain/item/controller/ItemController.java @@ -6,7 +6,6 @@ import com.api.farmingsoon.common.util.CookieUtils; import com.api.farmingsoon.domain.item.dto.*; import com.api.farmingsoon.domain.item.service.ItemService; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; @@ -18,8 +17,6 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import java.util.Optional; - @RestController @Slf4j @RequestMapping("/api/items") @@ -60,17 +57,6 @@ public Response getItemList( ItemListResponse items = itemService.getItemList(category, keyword, pageable, sortcode); return Response.success(HttpStatus.OK, "상품 목록 조회 성공!", items); } - @GetMapping("/test") - public Response getItemListBySubQuery( - @PageableDefault(size = 12) Pageable pageable, - @RequestParam(value = "sortcode", defaultValue = "recent") String sortcode, - @RequestParam(value = "category", required = false) String category, - @RequestParam(value = "keyword", required = false) String keyword) { - - ItemListResponseBySubQuery itemListBySubQuery = itemService.getItemListBySubQuery(category, keyword, pageable, sortcode); - return Response.success(HttpStatus.OK, "상품 목록 조회 성공!", itemListBySubQuery); - } - @GetMapping("/me") public Response getMyItemList( @@ -99,5 +85,16 @@ public Response soldOut(@PathVariable(name = "itemId") Long itemId, @Reque itemService.soldOut(itemId, buyerId); return Response.success(HttpStatus.OK, "상품 판매 완료!"); } +/* + @GetMapping("/test") + public Response getItemListBySubQuery( + @PageableDefault(size = 12) Pageable pageable, + @RequestParam(value = "sortcode", defaultValue = "recent") String sortcode, + @RequestParam(value = "category", required = false) String category, + @RequestParam(value = "keyword", required = false) String keyword) { + ItemListBySubQueryResponse itemListBySubQuery = itemService.getItemListBySubQuery(category, keyword, pageable, sortcode); + return Response.success(HttpStatus.OK, "상품 목록 조회 성공!", itemListBySubQuery); + } + */ } diff --git a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemBySubQueryResponse.java similarity index 79% rename from src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java rename to src/main/java/com/api/farmingsoon/domain/item/dto/ItemBySubQueryResponse.java index 0f47560..57efa91 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemResponseBySubQuery.java +++ b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemBySubQueryResponse.java @@ -1,11 +1,6 @@ package com.api.farmingsoon.domain.item.dto; -import com.api.farmingsoon.domain.item.domain.ItemStatus; import com.querydsl.core.annotations.QueryProjection; -import com.querydsl.core.types.dsl.EnumPath; -import com.querydsl.core.types.dsl.NumberExpression; -import com.querydsl.core.types.dsl.NumberPath; -import com.querydsl.core.types.dsl.StringPath; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,7 +9,7 @@ @NoArgsConstructor @Getter -public class ItemResponseBySubQuery { +public class ItemBySubQueryResponse { private Long itemId; // 상품 접근 private String title; private String description; @@ -30,7 +25,7 @@ public class ItemResponseBySubQuery { private Boolean likeStatus; @QueryProjection - public ItemResponseBySubQuery(Long itemId, String title, String description, LocalDateTime expiredAt, Integer highestPrice, Integer hopePrice, Integer lowestPrice, String itemStatus, Integer bidCount, Integer likeCount, Integer viewCount, String thumbnailImgUrl, Boolean likeStatus) { + public ItemBySubQueryResponse(Long itemId, String title, String description, LocalDateTime expiredAt, Integer highestPrice, Integer hopePrice, Integer lowestPrice, String itemStatus, Integer bidCount, Integer likeCount, Integer viewCount, String thumbnailImgUrl, Boolean likeStatus) { this.itemId = itemId; this.title = title; this.description = description; diff --git a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListBySubQueryResponse.java b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListBySubQueryResponse.java new file mode 100644 index 0000000..ac9883c --- /dev/null +++ b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListBySubQueryResponse.java @@ -0,0 +1,20 @@ +package com.api.farmingsoon.domain.item.dto; + +import com.api.farmingsoon.common.pagenation.Pagination; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +public class ItemListBySubQueryResponse { + + private Pagination pagination; // 페이지 관련 데이터 + private List itemBySubQueryResponseList; // 페이지 관련 데이터 + + public ItemListBySubQueryResponse(Pagination pagination, List itemBySubQueryResponseList) { + this.pagination = pagination; + this.itemBySubQueryResponseList = itemBySubQueryResponseList; + } +} diff --git a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java b/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java deleted file mode 100644 index 89c3bac..0000000 --- a/src/main/java/com/api/farmingsoon/domain/item/dto/ItemListResponseBySubQuery.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.api.farmingsoon.domain.item.dto; - -import com.api.farmingsoon.common.pagenation.Pagination; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; - -@Getter -@NoArgsConstructor -public class ItemListResponseBySubQuery { - - private Pagination pagination; // 페이지 관련 데이터 - private List itemResponseBySubQueryList; // 페이지 관련 데이터 - - public ItemListResponseBySubQuery(Pagination pagination, List itemResponseBySubQueryList) { - this.pagination = pagination; - this.itemResponseBySubQueryList = itemResponseBySubQueryList; - } -} diff --git a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java index 7818d6d..eb0ede2 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java +++ b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustom.java @@ -1,7 +1,7 @@ package com.api.farmingsoon.domain.item.repository; import com.api.farmingsoon.domain.item.domain.Item; -import com.api.farmingsoon.domain.item.dto.ItemResponseBySubQuery; +import com.api.farmingsoon.domain.item.dto.ItemBySubQueryResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -15,5 +15,5 @@ public interface ItemRepositoryCustom { List findBiddingItemList(); - Page findItemResponseList(String category, String keyword, Pageable pageable, String sortcode); + //Page findItemListBySubQuery(String category, String keyword, Pageable pageable, String sortcode); } diff --git a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java index f69f52e..31fbc8c 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java +++ b/src/main/java/com/api/farmingsoon/domain/item/repository/ItemRepositoryCustomImpl.java @@ -2,8 +2,7 @@ import com.api.farmingsoon.domain.item.domain.Item; import com.api.farmingsoon.domain.item.domain.ItemStatus; -import com.api.farmingsoon.domain.item.dto.ItemResponseBySubQuery; -import com.api.farmingsoon.domain.item.dto.QItemResponseBySubQuery; +import com.api.farmingsoon.domain.item.dto.ItemBySubQueryResponse; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; @@ -50,9 +49,9 @@ public Page findItemList(String category, String keyword, Pageable pageabl return new PageImpl<>(content, pageable, total); } - @Override - public Page findItemResponseList(String category, String keyword, Pageable pageable, String sortcode) { - List list = queryFactory.select( +/* @Override + public Page findItemResponseList(String category, String keyword, Pageable pageable, String sortcode) { + List list = queryFactory.select( new QItemResponseBySubQuery( item.id, item.title, @@ -85,7 +84,7 @@ public Page findItemResponseList(String category, String return new PageImpl<>(list, pageable, total); - } + }*/ @Override public List findNotEndBidItemList() { return queryFactory.selectFrom(item) diff --git a/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java b/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java index c326c9a..e6bd621 100644 --- a/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java +++ b/src/main/java/com/api/farmingsoon/domain/item/service/ItemService.java @@ -174,10 +174,12 @@ public void bidEndByScheduler(){ public List findBiddingItemList(){ return itemRepository.findBiddingItemList(); } - - public ItemListResponseBySubQuery getItemListBySubQuery(String category, String keyword, Pageable pageable, String sortcode) { - Page itemResponseList = itemRepository.findItemResponseList(category, keyword, pageable, sortcode); - return new ItemListResponseBySubQuery(Pagination.of(itemResponseList), itemResponseList.getContent()); +/* + @Description 서브쿼리 테스트 전용 + public ItemListBySubQueryResponse getItemListBySubQuery(String category, String keyword, Pageable pageable, String sortcode) { + Page itemResponseList = itemRepository.findItemListBySubQuery(category, keyword, pageable, sortcode); + return new ItemListBySubQueryResponse(Pagination.of(itemResponseList), itemResponseList.getContent()); } +*/ } From 2cda859a28a8d22eacce070e45370996fa4d8400 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Fri, 8 Mar 2024 18:21:53 +0900 Subject: [PATCH 10/11] =?UTF-8?q?test=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/api/farmingsoon/domain/item/ItemIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java b/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java index df2698c..f8f3e9f 100644 --- a/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java +++ b/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java @@ -100,7 +100,7 @@ void beforeEach(){ UserDetails principal = new User("user1@naver.com", "", authorities); SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principal, "", authorities)); - for(int i = 1; i <= 100; i++){ + for(int i = 1; i <= 20; i++){ Item item = Item.builder() .title("title" + i) .description("description" + i) From 12bc416a505ec7c8bdd9d28545e252ee2e681e51 Mon Sep 17 00:00:00 2001 From: gkfktkrh153 Date: Fri, 8 Mar 2024 18:22:36 +0900 Subject: [PATCH 11/11] =?UTF-8?q?test=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/api/farmingsoon/domain/item/ItemIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java b/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java index f8f3e9f..6a34dbb 100644 --- a/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java +++ b/src/test/java/com/api/farmingsoon/domain/item/ItemIntegrationTest.java @@ -129,7 +129,7 @@ void beforeEach(){ @Test void getItems() throws Exception { - MvcResult mvcResult2 = mockMvc.perform(get("/api/items/test")) + MvcResult mvcResult2 = mockMvc.perform(get("/api/items")) .andDo(print()) .andExpect(status().isOk()) .andReturn();