Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class SendSignalRequestDto {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.hertz.hertz_be.domain.channel.dto.request.v2;

import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class SignalMatchingRequestDto {

@NotNull
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.hertz.hertz_be.domain.channel.dto.request.v3;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class SendMessageRequestDto {

@NotBlank
private String message;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.hertz.hertz_be.domain.channel.entity.enums.Category;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class SendSignalRequestDto {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,8 @@ public SendSignalResponseDto sendSignal(Long senderUserId, SendSignalRequestDto
signalMessageRepository.save(signalMessage);

entityManager.flush();

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
asyncChannelService.sendNewMessageNotifyToPartner(signalRoom, signalMessage, receiver.getId(), true);
}
registerAfterCommitCallback(() -> {
asyncChannelService.sendNewMessageNotifyToPartner(signalRoom, signalMessage, receiver.getId(), true);
});

return new SendSignalResponseDto(signalRoom.getId());
Expand Down Expand Up @@ -360,62 +356,6 @@ private TuningResponseDto buildTuningResponseDTO(Long requesterId, User target)
);
}


@Transactional(readOnly = true)
public boolean hasNewMessages(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(
UserResponseCode.USER_NOT_FOUND.getCode(),
UserResponseCode.USER_NOT_FOUND.getHttpStatus(),
UserResponseCode.USER_NOT_FOUND.getMessage()
));

List<SignalRoom> allRooms = Stream.concat(
user.getSentSignalRooms().stream(),
user.getReceivedSignalRooms().stream()
).collect(Collectors.toList());

if (allRooms.isEmpty()) return false;

return signalMessageRepository.existsBySignalRoomInAndSenderUserNotAndIsReadFalse(allRooms, user);

}

public Map<String, List<String>> getUserInterests(Long userId) {
Map<String, List<String>> interestsMap = new LinkedHashMap<>();

userInterestsRepository.findByUserId(userId).stream()
.filter(ui -> ui.getCategoryItem().getCategory().getCategoryType() == InterestsCategoryType.INTEREST)
.forEach(ui -> {
String categoryName = ui.getCategoryItem().getCategory().getName();
String itemName = ui.getCategoryItem().getName();
interestsMap.computeIfAbsent(categoryName, k -> new ArrayList<>()).add(itemName);
});

return interestsMap;
}

public Map<String, List<String>> extractSameInterests(Map<String, List<String>> interests1, Map<String, List<String>> interests2) {
Map<String, List<String>> sameInterests = new LinkedHashMap<>();

for (String category : interests1.keySet()) {
List<String> list1 = interests1.getOrDefault(category, Collections.emptyList());
List<String> list2 = interests2.getOrDefault(category, Collections.emptyList());

Set<String> common = new HashSet<>(list1);
common.retainAll(list2);

if (!common.isEmpty()) {
sameInterests.put(category, List.of(common.iterator().next()));
} else {
sameInterests.put(category, Collections.emptyList());
}
}

return sameInterests;

}

@Transactional(readOnly = true)
public ChannelListResponseDto getPersonalSignalRoomList(Long userId, int page, int size) {
Pageable pageable = PageRequest.of(page, size);
Expand Down Expand Up @@ -527,12 +467,8 @@ public ChannelRoomResponseDto getChannelRoom(Long roomId, Long userId, int page,
.toList();

entityManager.flush();

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
asyncChannelService.updateNavbarMessageNotification(userId);
}
registerAfterCommitCallback(() -> {
asyncChannelService.updateNavbarMessageNotification(userId);
});

return ChannelRoomResponseDto.of(roomId, partner, room.getRelationType(), isPartnerExited, messages, messagePage);
Expand Down Expand Up @@ -581,13 +517,24 @@ public void sendChannelMessage(Long roomId, Long userId, SendMessageRequestDto r

signalMessageRepository.save(signalMessage);
entityManager.flush();

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
asyncChannelService.notifyMatchingConverted(room);
asyncChannelService.sendNewMessageNotifyToPartner(room, signalMessage, partnerId, false);
}
registerAfterCommitCallback(() -> {
asyncChannelService.notifyMatchingConverted(room);
asyncChannelService.sendNewMessageNotifyToPartner(room, signalMessage, partnerId, false);
});

}

protected void registerAfterCommitCallback(Runnable callback) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
callback.run();
}
});
} else {
log.debug("⚠️ 트랜잭션 비활성 상태: 콜백 즉시 실행");
callback.run();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,9 @@ public String channelMatchingStatusUpdate(Long userId, SignalMatchingRequestDto
}

entityManager.flush();
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
asyncChannelService.notifyMatchingResultToPartner(room, userId, matchingStatus);
asyncChannelService.createMatchingAlarm(room, userId);
}
registerAfterCommitCallback(() -> {
asyncChannelService.notifyMatchingResultToPartner(room, userId, matchingStatus);
asyncChannelService.createMatchingAlarm(room, userId);
});

if(matchingStatus == MatchingStatus.MATCHED) {
Expand All @@ -85,4 +82,18 @@ public void afterCommit() {
return ChannelResponseCode.MATCH_REJECTION_SUCCESS.getCode();
}
}

protected void registerAfterCommitCallback(Runnable callback) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
callback.run();
}
});
} else {
log.debug("⚠️ 트랜잭션 비활성 상태: 콜백 즉시 실행");
callback.run();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,9 @@ public ChannelRoomResponseDto getChannelRoom(Long roomId, Long userId, int page,
.toList();

entityManager.flush();

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
asyncChannelService.updateNavbarMessageNotification(userId);
asyncChannelService.notifyMatchingConvertedInChannelRoom(room, userId);
}
registerAfterCommitCallback(() -> {
asyncChannelService.updateNavbarMessageNotification(userId);
asyncChannelService.notifyMatchingConvertedInChannelRoom(room, userId);
});

return ChannelRoomResponseDto.of(roomId, partner, room.getRelationType(), isPartnerExited, String.valueOf(room.getCategory()), messages, messagePage);
Expand Down Expand Up @@ -265,12 +261,8 @@ public SendSignalResponseDto sendSignal(Long senderUserId, SendSignalRequestDto
signalMessageRepository.save(signalMessage);

entityManager.flush();

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
asyncChannelService.sendNewMessageNotifyToPartner(signalRoom, signalMessage, receiver.getId(), true);
}
registerAfterCommitCallback(() -> {
asyncChannelService.sendNewMessageNotifyToPartner(signalRoom, signalMessage, receiver.getId(), true);
});

return new SendSignalResponseDto(signalRoom.getId());
Expand Down Expand Up @@ -535,4 +527,18 @@ private boolean handleChatReportResult(ChatReportRequestDto requestDto) {
}
}

protected void registerAfterCommitCallback(Runnable callback) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
callback.run();
}
});
} else {
log.debug("⚠️ 트랜잭션 비활성 상태: 콜백 즉시 실행");
callback.run();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
Expand All @@ -32,17 +34,42 @@
@ExtendWith(MockitoExtension.class)
class AlarmServiceTest {

@Mock private AlarmNotificationRepository alarmNotificationRepository;
@Mock private AlarmReportRepository alarmReportRepository;
@Mock private AlarmMatchingRepository alarmMatchingRepository;
@Mock private AlarmAlertRepository alarmAlertRepository;
@Mock private AlarmRepository alarmRepository;
@Mock private UserAlarmRepository userAlarmRepository;
@Mock private UserRepository userRepository;
@Mock private AsyncAlarmService asyncAlarmService;
@Mock private EntityManager entityManager;

@InjectMocks private AlarmService alarmService;
@Mock
private EntityManager entityManager;

@Mock
private AlarmNotificationRepository alarmNotificationRepository;

@Mock
private AlarmMatchingRepository alarmMatchingRepository;

@Mock
private AlarmAlertRepository alarmAlertRepository;

@Mock
private AlarmRepository alarmRepository;

@Mock
private AlarmReportRepository alarmReportRepository;

@Mock
private UserAlarmRepository userAlarmRepository;

@Mock
private UserRepository userRepository;

@Mock
private AsyncAlarmService asyncAlarmService;

@Mock
private RedissonClient redissonClient;

@Mock
private RLock rLock;

@InjectMocks
private AlarmService alarmService;

private User user;
private User partner;
Expand Down Expand Up @@ -87,29 +114,39 @@ void createNotifyAlarm_userNotFound() {
@Test
@DisplayName("매칭 알람 생성 - 매칭 성공")
void createMatchingAlarm_success() {
when(redissonClient.getLock(anyString())).thenReturn(rLock);
when(rLock.tryLock()).thenReturn(true);

SignalRoom room = SignalRoomFixture.createMatchedRoom(user, partner);

when(alarmMatchingRepository.existsBySignalRoom(room)).thenReturn(false);
when(alarmMatchingRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));

alarmService.createMatchingAlarm(room, user, partner);

verify(alarmMatchingRepository, times(2)).save(any());
verify(userAlarmRepository, times(2)).save(any());
verify(entityManager, times(1)).flush();
verify(entityManager).flush();
verify(rLock).unlock(); // 락 해제 검증
}

@Test
@DisplayName("매칭 알람 생성 - 매칭 실패")
void createMatchingAlarm_failed() {
when(redissonClient.getLock(anyString())).thenReturn(rLock);
when(rLock.tryLock()).thenReturn(true);

SignalRoom room = SignalRoomFixture.createUnmatchedRoom(user, partner);

when(alarmMatchingRepository.existsBySignalRoom(room)).thenReturn(false);
when(alarmMatchingRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));

alarmService.createMatchingAlarm(room, user, partner);

verify(alarmMatchingRepository, times(2)).save(any());
verify(userAlarmRepository, times(2)).save(any());
verify(entityManager, times(1)).flush();
verify(entityManager).flush();
verify(rLock).unlock(); // 락 해제 검증
}

@Test
Expand Down
Loading
Loading