Skip to content

Commit

Permalink
Fix/#495 (#496) (#498)
Browse files Browse the repository at this point in the history
* Fix/#495 (#496)

* fix:다락방 초대링크 코드 수정

* fix:다락방 참여 api를 사용할 때 내 모임으로 제대로 가지지 않는 버그 수정

* fix: 알림이 undefined로 오는 문제를 수정

* fix: 채팅방 알림에 사용되는 경로 수정

---------

Co-authored-by: ss0526100 <[email protected]>
Co-authored-by: jaeml06 <[email protected]>

* feat: 채팅방 목록을 조회할 때 가장 최근에 온 메시지를 기준으로 정렬하는 기능 추가

* refactor: 채팅방 목록 정렬시 같은 기준이면 참여 순서대로 정렬하도록 수정

* feat: 해주세요 목록 조회시 관심이 많은 순서대로 조회하고, 관심이 같다면 생성된 순서대로 정렬하는 기능 추가

* refactor: 채팅방 목록 정렬에서 동일한 조건시 모임 생성 순으로 정렬하도록 수정

* refactor: 기능 수정으로 인해 실패하는 테스트 수정

* feat: 프록시 헤더를 받아들이는 설정 추가

* refactor: 사용하지 않는 메서드 제거

* feat: 비관적 쓰기 락을 통해 동시성 문제 해결

* test: 같은 회원이 동시에 참여하는 경우에 대한 동시성 테스트

* refactor: 참여 서비스 테스트에 동시성 테스트를 추가

---------

Co-authored-by: 이상진 <[email protected]>
Co-authored-by: ss0526100 <[email protected]>
Co-authored-by: jaeml06 <[email protected]>
Co-authored-by: MingyeomKim <[email protected]>
  • Loading branch information
5 people authored Aug 29, 2024
1 parent 2431797 commit 651d44e
Show file tree
Hide file tree
Showing 16 changed files with 38,416 additions and 14 deletions.
703 changes: 703 additions & 0 deletions backend/hs_err_pid19740.log

Large diffs are not rendered by default.

733 changes: 733 additions & 0 deletions backend/hs_err_pid20488.log

Large diffs are not rendered by default.

1,087 changes: 1,087 additions & 0 deletions backend/hs_err_pid20992.log

Large diffs are not rendered by default.

10,114 changes: 10,114 additions & 0 deletions backend/replay_pid19740.log

Large diffs are not rendered by default.

13,012 changes: 13,012 additions & 0 deletions backend/replay_pid20488.log

Large diffs are not rendered by default.

12,542 changes: 12,542 additions & 0 deletions backend/replay_pid20992.log

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public ChamyoFindAllResponses findAllChamyo(Long darakbangId, Long moimId) {
}

public void chamyoMoim(Long darakbangId, Long moimId, DarakbangMember darakbangMember) {
Moim moim = moimRepository.findById(moimId)
Moim moim = moimRepository.findByIdForUpdate(moimId)
.orElseThrow(() -> new ChamyoException(HttpStatus.NOT_FOUND, ChamyoErrorMessage.MOIM_NOT_FOUND));
if (moim.isNotInDarakbang(darakbangId)) {
throw new ChamyoException(HttpStatus.BAD_REQUEST, ChamyoErrorMessage.MOIM_NOT_FOUND);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package mouda.backend.chat.service;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -65,7 +67,7 @@ public void createChat(Long darakbangId, ChatCreateRequest chatCreateRequest, Da
MoudaNotification notification = MoudaNotification.builder()
.type(notificationType)
.body(notificationType.createMessage(darakbangMember.getNickname()))
.targetUrl(baseUrl + String.format(chatroomUrl, moim.getId()))
.targetUrl(baseUrl + String.format(chatroomUrl, darakbangId, moim.getId()))
.build();

List<Long> membersToSendNotification = chamyoRepository.findAllByMoimId(moim.getId()).stream()
Expand Down Expand Up @@ -148,6 +150,7 @@ public ChatPreviewResponses findChatPreview(Long darakbangId, DarakbangMember da
.findAllByDarakbangMemberIdAndMoim_DarakbangId(darakbangMember.getId(), darakbangId)
.stream()
.filter(chamyo -> chamyo.getMoim().isChatOpened())
.sorted(getChatComparatorByLastCreatedAt())
.map(this::getChatPreviewResponse)
.toList();

Expand All @@ -164,6 +167,14 @@ private ChatPreviewResponse getChatPreviewResponse(Chamyo chamyo) {
return ChatPreviewResponse.toResponse(chamyo, currentPeople, lastContent);
}

private Comparator<Chamyo> getChatComparatorByLastCreatedAt() {
return Comparator.<Chamyo, LocalDateTime>comparing(chamyo ->
chatRepository.findFirstByMoimIdOrderByIdDesc(chamyo.getMoim().getId())
.map(chat -> LocalDateTime.of(chat.getDate(), chat.getTime()))
.orElse(null), Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(chamyo -> chamyo.getMoim().getId(), Comparator.naturalOrder());
}

public void createLastChat(
long darakbangId, long moimId, LastReadChatRequest lastReadChatRequest, DarakbangMember darakbangMember
) {
Expand Down
6 changes: 0 additions & 6 deletions backend/src/main/java/mouda/backend/moim/domain/Moim.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,6 @@ private void validateDescription(String description) {
}
}

public void validateAlreadyFullMoim(int currentPeople) {
if (currentPeople > maxPeople) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.MAX_PEOPLE);
}
}

public void update(String title, LocalDate date, LocalTime time, String place, int maxPeople,
String description, int currentPeople) {
if (!Objects.equals(this.title, title)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package mouda.backend.moim.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import jakarta.persistence.LockModeType;
import mouda.backend.moim.domain.Moim;
import mouda.backend.moim.domain.MoimStatus;

Expand All @@ -26,4 +29,8 @@ public interface MoimRepository extends JpaRepository<Moim, Long> {
ORDER BY m.id DESC
""")
List<Moim> findAllByDarakbangIdOrderByIdDesc(@Param("darakbangId") Long darakbangId);

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select m from Moim m where m.id = :moimId")
Optional<Moim> findByIdForUpdate(@Param("moimId") Long moimId);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mouda.backend.please.service;

import java.util.Comparator;
import java.util.List;

import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -56,6 +57,7 @@ public PleaseFindAllResponses findAllPlease(Long darakbangId, DarakbangMember da
long interestCount = interestRepository.countByPleaseId(please.getId());
return PleaseFindAllResponse.toResponse(please, isInterested, interestCount);
})
.sorted(Comparator.comparing(PleaseFindAllResponse::interestCount).reversed().thenComparing(PleaseFindAllResponse::pleaseId))
.toList());
}
}
6 changes: 4 additions & 2 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
server:
forward-headers-strategy: native

spring:
profiles:
active:
Expand All @@ -8,8 +11,7 @@ security:
token:
secret-key: kksangdolbabokksangdolbabokksangdolbabokksangdolbabokksangdolbabokksangdolbabo
expire-length: 3600000

url:
moim: /darakbang/%d/moim/%d
chat: /darakbang/%d/chat/%d
chatroom: /chatting-room/%d
chatroom: /darakbang/%d/chatting-room/%d
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package mouda.backend.chamyo.service;

import static org.assertj.core.api.Assertions.*;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import mouda.backend.chamyo.exception.ChamyoException;
import mouda.backend.darakbang.domain.Darakbang;
import mouda.backend.darakbang.repository.DarakbangRepository;
import mouda.backend.darakbangmember.domain.DarakbangMember;
import mouda.backend.darakbangmember.repository.repository.DarakbangMemberRepository;
import mouda.backend.fixture.DarakbangFixture;
import mouda.backend.fixture.DarakbangMemberFixture;
import mouda.backend.fixture.MemberFixture;
import mouda.backend.fixture.MoimFixture;
import mouda.backend.member.domain.Member;
import mouda.backend.member.repository.MemberRepository;
import mouda.backend.moim.domain.Moim;
import mouda.backend.moim.repository.MoimRepository;

@SpringBootTest
class ChamyoServiceTest {

@Autowired
private ChamyoService chamyoService;

@Autowired
private DarakbangRepository darakbangRepository;

@Autowired
private DarakbangMemberRepository darakbangMemberRepository;

@Autowired
private MemberRepository memberRepository;

@Autowired
private MoimRepository moimRepository;

@DisplayName("같은 회원이 따로 따로 여러 번 참여 요청을 하면 두 번째 요청 시 참여에 실패한다.")
@Test
void chamyoMoim() throws InterruptedException {
Darakbang darakbang = DarakbangFixture.getDarakbangWithMouda();
darakbangRepository.save(darakbang);

Member member = MemberFixture.getAnna();
memberRepository.save(member);

DarakbangMember darakbangMember = DarakbangMemberFixture
.getDarakbangMemberWithWooteco(darakbang, member);
darakbangMemberRepository.save(darakbangMember);

Moim moim = MoimFixture.getBasketballMoim(darakbang.getId());
moimRepository.save(moim);

chamyoService.chamyoMoim(darakbang.getId(), moim.getId(), darakbangMember);
assertThatThrownBy(() -> chamyoService.chamyoMoim(darakbang.getId(), moim.getId(), darakbangMember))
.isInstanceOf(ChamyoException.class);
assertThat(chamyoService.findAllChamyo(darakbang.getId(), moim.getId()).chamyos()).hasSize(1);
}

@DisplayName("동시 참여 테스트")
@Nested
class ConcurrencyTest {

@DisplayName("같은 회원이 동시에 여러 번 참여 요청을 하면 동시 참여가 불가능하다.")
@Test
void chamyoMoimConcurrently() throws InterruptedException {
int threadCount = 2;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);

Darakbang darakbang = DarakbangFixture.getDarakbangWithMouda();
darakbangRepository.save(darakbang);

Moim moim = MoimFixture.getBasketballMoim(darakbang.getId());
moimRepository.save(moim);

Member member = MemberFixture.getAnna();
memberRepository.save(member);

DarakbangMember darakbangMember = DarakbangMemberFixture
.getDarakbangMemberWithWooteco(darakbang, member);
darakbangMemberRepository.save(darakbangMember);

long startTime = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
executorService.execute(() -> {
try {
chamyoService.chamyoMoim(darakbang.getId(), moim.getId(), darakbangMember);
} finally {
latch.countDown();
}
});
}
latch.await();
executorService.shutdown();

long endTime = System.currentTimeMillis();
System.out.printf("Test time : %d ms\n", endTime - startTime);

assertThat(chamyoService.findAllChamyo(darakbang.getId(), moim.getId()).chamyos()).hasSize(1);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package mouda.backend.chat.service;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -20,6 +21,7 @@
import mouda.backend.chat.dto.request.DateTimeConfirmRequest;
import mouda.backend.chat.dto.request.PlaceConfirmRequest;
import mouda.backend.chat.dto.response.ChatFindUnloadedResponse;
import mouda.backend.chat.dto.response.ChatPreviewResponse;
import mouda.backend.chat.dto.response.ChatPreviewResponses;
import mouda.backend.chat.exception.ChatException;
import mouda.backend.chat.repository.ChatRepository;
Expand Down Expand Up @@ -139,7 +141,7 @@ void cannotConfirmPlaceWhenMemberIsNotMoimer() {
PlaceConfirmRequest request = new PlaceConfirmRequest(1L, place);

// when & then
Assertions.assertThrows(
assertThrows(
ChatException.class,
() -> chatService.confirmPlace(darakbang.getId(), request, darakbangHogee)
);
Expand Down Expand Up @@ -209,4 +211,43 @@ void readDarakbangChatPreview() {
assertThat(chatService.findChatPreview(moudaMoim.getId(), moudaHogee).chatPreviewResponses())
.hasSize(0);
}

@DisplayName("가장 최근에 생성된 채팅을 기준으로 채팅방 목록을 조회하고, 채팅이 없는 채팅방은 가장 아래에 위치한다.")
@Test
void findChatPreview_sortedByLastChatCreatedAt() {
Moim soccerMoim = moimRepository.save(MoimFixture.getSoccerMoim(darakbang.getId()));
Moim coffeeMoim = moimRepository.save(MoimFixture.getCoffeeMoim(darakbang.getId()));
Moim basketballMoim = moimRepository.save(MoimFixture.getBasketballMoim(darakbang.getId()));

chamyoRepository.save(Chamyo.builder()
.moim(soccerMoim)
.darakbangMember(darakbangAnna)
.moimRole(MoimRole.MOIMER)
.build());

chamyoRepository.save(Chamyo.builder()
.moim(coffeeMoim)
.darakbangMember(darakbangAnna)
.moimRole(MoimRole.MOIMER)
.build());

chamyoRepository.save(Chamyo.builder()
.moim(basketballMoim)
.darakbangMember(darakbangAnna)
.moimRole(MoimRole.MOIMER)
.build());

chatService.openChatRoom(darakbang.getId(), soccerMoim.getId(), darakbangAnna);
chatService.openChatRoom(darakbang.getId(), coffeeMoim.getId(), darakbangAnna);
chatService.openChatRoom(darakbang.getId(), basketballMoim.getId(), darakbangAnna);

chatService.createChat(darakbang.getId(), new ChatCreateRequest(soccerMoim.getId(), "1번 채팅"), darakbangAnna);
chatService.createChat(darakbang.getId(), new ChatCreateRequest(coffeeMoim.getId(), "2번 채팅"), darakbangAnna);

List<ChatPreviewResponse> chatPreviewResponses = chatService.findChatPreview(darakbang.getId(), darakbangAnna)
.chatPreviewResponses();

assertThat(chatPreviewResponses).extracting(ChatPreviewResponse::lastContent)
.containsExactly("2번 채팅", "1번 채팅", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,48 @@ void success() {
assertThat(pleaseService.findAllPlease(darakbang.getId(), darakbangHogee).pleases()).hasSize(1);
assertThat(pleaseService.findAllPlease(mouda.getId(), moudaHogee).pleases()).hasSize(0);
}

@DisplayName("해주세요 목록 조회시 관심이 많은 순서대로 조회하고, 관심이 같다면 생성된 순서대로 조회한다.")
@Test
void findAllPlease_isSortedByInterestCount() {
Please please1 = pleaseRepository.save(Please.builder()
.authorId(1L)
.darakbangId(darakbang.getId())
.title("해주세요1~~")
.description("해주세요1 해줘~~")
.build());

Please please2 = pleaseRepository.save(Please.builder()
.authorId(1L)
.darakbangId(darakbang.getId())
.title("해주세요2~~")
.description("해주세요2 해줘~~")
.build());

Please please3 = pleaseRepository.save(Please.builder()
.authorId(1L)
.darakbangId(darakbang.getId())
.title("해주세요3~~")
.description("해주세요3 해줘~~")
.build());

interestService.updateInterest(darakbang.getId(), darakbangHogee,
new InterestUpdateRequest(please1.getId(), true));

interestService.updateInterest(darakbang.getId(), darakbangHogee,
new InterestUpdateRequest(please2.getId(), true));
interestService.updateInterest(darakbang.getId(), darakbangAnna,
new InterestUpdateRequest(please2.getId(), true));

interestService.updateInterest(darakbang.getId(), darakbangHogee,
new InterestUpdateRequest(please3.getId(), true));

PleaseFindAllResponses pleaseFindAllResponses = pleaseService.findAllPlease(
darakbang.getId(), darakbangHogee);

assertThat(pleaseFindAllResponses.pleases()).extracting("pleaseId")
.containsExactly(please2.getId(), please1.getId(), please3.getId());
}
}

@DisplayName("해주세요 생성 테스트")
Expand Down Expand Up @@ -126,7 +168,7 @@ void findAllPlease() {

PleaseFindAllResponses pleaseFindAllResponses = pleaseService.findAllPlease(
darakbang.getId(), darakbangHogee);
assertThat(pleaseFindAllResponses.pleases()).extracting("isInterested").containsExactly(false, true);
assertThat(pleaseFindAllResponses.pleases()).extracting("isInterested").containsExactly(true, false);
}
}
}
2 changes: 1 addition & 1 deletion backend/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ url:
base: http://localhost:8080
moim: /darakbang/%d/moim/%d
chat: /darakbang/%d/chat/%d
chatroom: /chatting-room/%d
chatroom: /darakbang/%d/chatting-room/%d

oauth:
kakao:
Expand Down

0 comments on commit 651d44e

Please sign in to comment.