Skip to content

Commit

Permalink
Merge branch 'dev' into be/feat/#69-url-notification
Browse files Browse the repository at this point in the history
# Conflicts:
#	be/src/main/java/movlit/be/pub_sub/chatMessage/application/service/ChatMessageService.java
#	be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/GroupChatroomService.java
  • Loading branch information
devbattery committed Feb 3, 2025
2 parents 0488bc3 + d19c5a3 commit 63b4ddd
Show file tree
Hide file tree
Showing 18 changed files with 352 additions and 63 deletions.
4 changes: 3 additions & 1 deletion be/src/main/java/movlit/be/common/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package movlit.be.common.config;

import jakarta.servlet.http.HttpServletResponse;

import java.util.Arrays;
import java.util.List;

import lombok.RequiredArgsConstructor;
import movlit.be.auth.application.service.MyOAuth2MemberService;
import movlit.be.auth.application.service.OAuth2AuthenticationSuccessHandler;
Expand Down Expand Up @@ -98,7 +100,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CorsConfigurat
.requestMatchers(HttpMethod.GET, "/api/subscribe/*").authenticated()
.requestMatchers("/collect/indices/**", "/collect/movie/**", "/discover",
"/websocket/**", "/echo", "/api/members/login", "/img/**", "/js/**", "/css/**",
"/error/**", "api/books/**", "/ws-stomp/**")
"/error/**", "api/books/**", "/ws-stomp/**", "/notification/**")
.permitAll()
.requestMatchers(HttpMethod.POST, "/api/follows/*/follow").authenticated()
.requestMatchers(HttpMethod.DELETE, "/api/follows/*/follow").authenticated()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public int getFollowCount(
// 내가 팔로우하는 사람들(팔로우) 개수 조회
return (int) followRepository.countFollowsByLoginId(loginId);
}

@Transactional(readOnly = true)
public boolean isFollowing(MemberId loginId, MemberId otherMemberId){
return followRepository.existsByFollowerIdAndFolloweeIdWithoutException(loginId, otherMemberId);
}
private FollowResponse toFollowResponse(Follow follow){
MemberEntity follower = follow.getFollower(); // 나를 팔로우하는 사람(팔로워) 정보 가져오기

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
public interface FollowRepository {
boolean existsByFollowerIdAndFolloweeId(MemberId followerId, MemberId followeeId);

boolean existsByFollowerIdAndFolloweeIdWithoutException(MemberId followerId, MemberId followeeId);

Follow save(Follow follow);

Follow findByFollowerIdAndFolloweeId(MemberId followerId, MemberId followeeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public boolean existsByFollowerIdAndFolloweeId(MemberId followerId, MemberId fol
return false;
}

@Override
public boolean existsByFollowerIdAndFolloweeIdWithoutException(MemberId followerId, MemberId followeeId) {
return followJpaRepository.existsByFollowerIdAndFolloweeId(followerId, followeeId);
}

@Override
public Follow save(Follow follow) {
return followJpaRepository.save(follow);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package movlit.be.follow.presentation;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import movlit.be.auth.application.service.MyMemberDetails;
Expand Down Expand Up @@ -87,30 +89,41 @@ public ResponseEntity<List<FollowResponse>> getMyFollowingDetail(
}

// 나를 팔로우하는 사람들(팔로워) 개수 조회 - 내 팔로워 개수
@GetMapping("/my/followers/count")
@GetMapping("/{memberId}/followers/count")
public ResponseEntity<Integer> getMyFollowerCount(
@AuthenticationPrincipal MyMemberDetails details
@PathVariable MemberId memberId
){
MemberId loginId = null;
if (details != null){
loginId = details.getMemberId();
}
Integer followerCount = followReadService.getFollowerCount(loginId);
Integer followerCount = followReadService.getFollowerCount(memberId);

return ResponseEntity.status(HttpStatus.OK).body(followerCount);
}

// 내가 팔로우하는 사람들(팔로우) 개수 조회 - 내 팔로우 개수
@GetMapping("/my/follows/count")
@GetMapping("/{memberId}/follows/count")
public ResponseEntity<Integer> getMyFollowCount(
@AuthenticationPrincipal MyMemberDetails details
@PathVariable MemberId memberId
){
int followCount = followReadService.getFollowCount(memberId);

return ResponseEntity.status(HttpStatus.OK).body(followCount);
}

// 특정 사용자를 팔로우하고 있는지 여부 확인
// 로그인한 유저(MyMemberDetails)가, 특정 사용자(pathvariable)를 팔로우하는지 여부 체크 api
@GetMapping("/check/{otherMemberId}")
public ResponseEntity<Map<String, Boolean>> checkFollowing(
@AuthenticationPrincipal MyMemberDetails details,
@PathVariable MemberId otherMemberId
){
MemberId loginId = null;
if (details != null){
loginId = details.getMemberId();
}
int followCount = followReadService.getFollowCount(loginId);
boolean isFollowing = followReadService.isFollowing(loginId, otherMemberId);

return ResponseEntity.status(HttpStatus.OK).body(followCount);
Map<String, Boolean> response = new HashMap<>();
response.put("following", isFollowing);

return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public class ImageService {
private final ImageRepository imageRepository;
private final S3Service s3Service;
private final MemberRepository memberRepository;
private final GroupChatroomService groupChatroomService;
private final RedisMessagePublisher redisMessagePublisher;

private final ApplicationEventPublisher eventPublisher;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package movlit.be.pub_sub.chatMessage.application.service;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import movlit.be.common.exception.RedisStreamOperationReturnNull;
Expand All @@ -23,6 +25,7 @@
import movlit.be.pub_sub.notification.NotificationMessage;
import movlit.be.pub_sub.notification.NotificationType;
import org.springframework.beans.factory.annotation.Value;
import movlit.be.pub_sub.notification.NotificationService;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
Expand All @@ -36,8 +39,8 @@ public class ChatMessageService {
private final ChatMessageRepository chatMessageRepository;
private final RedisMessagePublisher messagePublisher;
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final RedisNotificationPublisher redisNotificationPublisher;
private final NotificationService notificationService;

private static final String MESSAGE_QUEUE = "chat_message_queue"; // 큐 이름 (채팅방마다 별도의 큐를 사용할 수 있음)
private final MemberReadService memberReadService;
Expand Down Expand Up @@ -94,6 +97,8 @@ public void sendMessageForGroup(ChatMessageDto chatMessageDto) {
produceChatMessage(chatMessageDto);

messagePublisher.sendMessage(chatMessageDto);

notificationService.groupChatroomMessageNotification(chatMessageDto); // 그룹 채팅방 메시지 전송 알림
}

// 해당 채팅방의 읽지 않은 메시지 갯수 return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package movlit.be.pub_sub.chatRoom.application.service;

import lombok.RequiredArgsConstructor;
import movlit.be.common.util.ids.MemberId;
import movlit.be.pub_sub.chatMessage.application.service.ChatMessageService;
import movlit.be.pub_sub.chatMessage.presentation.dto.response.ChatMessageDto;
import movlit.be.pub_sub.chatRoom.presentation.dto.GroupChatroomResponseDto;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;

@Service
@RequiredArgsConstructor
public class FetchGroupChatroomUseCase {

private final GroupChatroomService groupChatroomService;
private final ChatMessageService chatMessageService;

// 내가 가입한 그룹채팅 리스트 가져오기
public List<GroupChatroomResponseDto> execute(MemberId memberId) {
List<GroupChatroomResponseDto> chatroomList = groupChatroomService.fetchMyGroupChatroomList(memberId);

return chatroomList.stream()
.peek(chatroom -> {
ChatMessageDto recentMessage = chatMessageService.fetchRecentMessage(
chatroom.getGroupChatroomId().getValue());
if (Objects.nonNull(recentMessage)) {
chatroom.setRecentMessage(recentMessage);
}
})
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import movlit.be.book.application.service.BookDetailReadService;
Expand Down Expand Up @@ -40,8 +41,8 @@
import movlit.be.pub_sub.notification.NotificationDto;
import movlit.be.pub_sub.notification.NotificationMessage;
import movlit.be.pub_sub.notification.NotificationType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import movlit.be.pub_sub.notification.NotificationController;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -53,17 +54,14 @@ public class GroupChatroomService {

private final GroupChatRepository groupChatRepository;
private final MemberReadService memberReadService;
private final ChatMessageService chatMessageService;

private final BookHeartReadService bookHeartReadService;
private final BookDetailReadService bookDetailReadService;
private final MovieReadService movieReadService;
private final MovieHeartService movieHeartService;

private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper;
private final GroupChatroomCreationWorker worker;
private final ApplicationEventPublisher eventPublisher;
private final MovieReadService movieReadService;
private final MovieHeartService movieHeartService;
private final BookDetailReadService bookDetailReadService;
private final BookHeartReadService bookHeartReadService;

private final RedisNotificationPublisher redisNotificationPublisher;

Expand Down Expand Up @@ -117,7 +115,8 @@ public GroupChatroomResponse requestCreateGroupChatroom(GroupChatroomRequest req
/**
* 찜한 콘텐츠에 대해 새로운 채팅방 생성됨을 알림
*/
private void publishNewGroupChatroomNoti(String contentId, String roomName, GroupChatroomResponse createdChatroom) {
private void publishNewGroupChatroomNoti(String contentId, String roomName,
GroupChatroomResponse createdChatroom) {
log.info("::GroupChatroomService_publishNewGroupChatroomNoti::");

// ContentId : MV_pureContentId 또는 BK_pureContentId -> 책과 영화 구분 필요
Expand All @@ -137,14 +136,16 @@ private void publishNewGroupChatroomNoti(String contentId, String roomName, Grou
BookId bookId = new BookId(pureContentId);
String bookName = bookDetailReadService.fetchByBookId(bookId).getTitle();
contentName = bookName.substring(0, bookName.indexOf(" -"));
heartingMemberIds = bookHeartReadService.fetchHeartingMemberIdsByBookId(bookId);
heartingMemberIds =
bookHeartReadService.fetchHeartingMemberIdsByBookId(bookId);
}

// 멤버들에게 알림 발송
if (!heartingMemberIds.isEmpty()) {
for (MemberId heartigMemberId : heartingMemberIds) {
log.info(">> 알림발송할 멤버 " + heartigMemberId.getValue());
NotificationDto notification = new NotificationDto(heartigMemberId.getValue(),
NotificationDto notification = new NotificationDto(
heartigMemberId.getValue(),
NotificationMessage.generateNewGroupChatroomNotiMessage(contentType, contentName, roomName),
NotificationType.CONTENT_HEART_CHATROOM,
null); // TODO
Expand All @@ -153,6 +154,8 @@ private void publishNewGroupChatroomNoti(String contentId, String roomName, Grou
}
}



private Map<String, String> getPureResponse(Optional<Map<String, String>> responseOpt) {
if (responseOpt.isEmpty()) {
throw new GroupChatroomAlreadyExistsException();
Expand Down Expand Up @@ -264,15 +267,22 @@ private void validateAlreadyJoined(MemberId memberId, GroupChatroom existingGrou
}

// 내가 가입한 그룹채팅 리스트 가져오기
public List<GroupChatroomResponseDto> fetchMyGroupChatList(MemberId memberId) {
return groupChatRepository.fetchGroupChatroomByMemberId(memberId).stream().peek(chatRoom -> {
// TODO: repository단에서 ChatMessageDto 정보와 join하여 가져오기 -> 이러면 list 돌면서 결합 안 해줘도 됨
ChatMessageDto recentMessage = chatMessageService.fetchRecentMessage(
chatRoom.getGroupChatroomId().getValue());
if (Objects.nonNull(recentMessage)) {
chatRoom.setRecentMessage(recentMessage);
}
}).toList();
// public List<GroupChatroomResponseDto> fetchMyGroupChatList(MemberId memberId) {
// return groupChatRepository.fetchGroupChatroomByMemberId(memberId).stream()
// .peek(chatRoom -> {
// // TODO: repository단에서 ChatMessageDto 정보와 join하여 가져오기 -> 이러면 list 돌면서 결합 안 해줘도 됨
// ChatMessageDto recentMessage = chatMessageService.fetchRecentMessage(
// chatRoom.getGroupChatroomId().getValue());
// if (Objects.nonNull(recentMessage)) {
// chatRoom.setRecentMessage(recentMessage);
// }
// })
// .toList();
// }

// 내가 가입한 그룹채팅 리스트만 가져오기
public List<GroupChatroomResponseDto> fetchMyGroupChatroomList(MemberId memberId) {
return groupChatRepository.fetchGroupChatroomByMemberId(memberId);
}

// 특정 그룹채팅 안 멤버 정보 update (멤버 정보 redis 1차 캐시)
Expand Down Expand Up @@ -313,4 +323,7 @@ public List<GroupChatroomMemberResponse> fetchMembersInGroupChatroom(GroupChatro
}
}

public GroupChatroom fetchGroupChatroomById(GroupChatroomId groupChatroomId) {
return groupChatRepository.findByChatroomId(groupChatroomId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;
import java.util.concurrent.TimeUnit;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import movlit.be.common.util.ids.GroupChatroomId;
Expand All @@ -26,8 +28,8 @@
@RequiredArgsConstructor
@Slf4j
public class ProfileImageUpdatedEventListener {
private final GroupChatroomService groupChatroomService;
private final RedisMessagePublisher redisMessagePublisher;
private final FetchGroupChatroomUseCase fetchGroupChatroomUseCase;

private final RedisTemplate<String, Object> redisTemplate;
private final MemberReadService memberReadService;
Expand All @@ -36,20 +38,21 @@ public class ProfileImageUpdatedEventListener {
private static final String CHATROOM_MEMBERS_KEY_PREFIX = "chatroom:";
private static final String CHATROOM_MEMBERS_KEY_SUFFIX = ":members";
private static final long CHATROOM_MEMBERS_CACHE_TTL = 60 * 60; // 1시간

@TransactionalEventListener
public void handleProfileImageUpdatedEvent(ProfileImageUpdatedEvent event) throws JsonProcessingException {
MemberId memberId = event.getMemberId();

// 해당 멤버가 속한 모든 그룹채팅방 ID를 조회
List<GroupChatroomResponseDto> groupChatroomResponseDtoList = groupChatroomService.fetchMyGroupChatList(memberId);
List<GroupChatroomResponseDto> groupChatroomResponseDtoList = fetchGroupChatroomUseCase.execute(memberId);

// 업데이트된 멤버정보 조회
MemberEntity updatedMember = memberReadService.findEntityById(memberId);
log.info("RedisMessageSubscriber >>> 프로필업데이트된 멤버정보 : " + updatedMember.toStringExceptLazyLoading());


// 각 그룹채팅방 ID에 대해 updatedRoomDto 생성 및 메세지 발행
for (GroupChatroomResponseDto groupChatroomResponseDto : groupChatroomResponseDtoList){
for (GroupChatroomResponseDto groupChatroomResponseDto : groupChatroomResponseDtoList) {
GroupChatroomId groupChatroomId = groupChatroomResponseDto.getGroupChatroomId();

// 캐시 키 생성
Expand All @@ -59,7 +62,7 @@ public void handleProfileImageUpdatedEvent(ProfileImageUpdatedEvent event) throw
String cachedJson = (String) redisTemplate.opsForValue().get(cacheKey);

List<GroupChatroomMemberResponse> cachedMembers;
if (cachedJson != null){
if (cachedJson != null) {
// 캐시된 데이터(Json 문자열)를 List<GroupChatroomMemberResponse>로 역직렬화
cachedMembers = objectMapper.readValue(cachedJson, new TypeReference<>() {
});
Expand All @@ -72,8 +75,8 @@ public void handleProfileImageUpdatedEvent(ProfileImageUpdatedEvent event) throw
);

// 기존에 캐시된 멤버리스트에서, 업데이트된 멤버정보만 수정
for (int i = 0; i < cachedMembers.size(); i++){
if (cachedMembers.get(i).getMemberId().equals(memberId)){
for (int i = 0; i < cachedMembers.size(); i++) {
if (cachedMembers.get(i).getMemberId().equals(memberId)) {
cachedMembers.set(i, updatedMemberResponse);
break;
}
Expand All @@ -85,10 +88,10 @@ public void handleProfileImageUpdatedEvent(ProfileImageUpdatedEvent event) throw

// UpdateRoomDto 생성 및 발행
UpdateRoomDto updateRoomDto = new UpdateRoomDto(
groupChatroomId,
MessageType.GROUP,
EventType.MEMBER_PROFILE_UPDATE,
memberId
groupChatroomId,
MessageType.GROUP,
EventType.MEMBER_PROFILE_UPDATE,
memberId
);

redisMessagePublisher.updateRoom(updateRoomDto);
Expand Down
Loading

0 comments on commit 63b4ddd

Please sign in to comment.