Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix] 누락된 필드값 및 엔티티 수정 #148

Merged
merged 4 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class DeliveryMeeting extends Meeting {
private String accountNumber;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private BankName bankName;

public void updateDeliveryMeeting(
Expand All @@ -47,13 +48,14 @@ public void updateDeliveryMeeting(
String storeName,
String pickupLocation,
String accountNumber,
BankName bankName,
Image backgroundImage
) {
super.updateMeeting(meetingName, description, backgroundImage);

this.foodCategory = foodCategory;
this.storeName = storeName;
this.pickupLocation = pickupLocation;
this.accountNumber = accountNumber;
this.bankName = bankName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ private ConstructorExpression<MeetingListResponseDto> createDeliveryMeetingProje
meeting.participantLimit.maxParticipants,
deliveryMeeting.storeName.as("location"),
meeting.createdAt,
deliveryMeeting.orderDeadline.as("dueDateTime")
deliveryMeeting.orderDeadline.as("dueDateTime"),
meeting.chatRoom.lastChatAt
);
}

Expand All @@ -326,7 +327,8 @@ private ConstructorExpression<MeetingListResponseDto> createOfflineMeetingProjec
meeting.participantLimit.maxParticipants,
offlineMeeting.meetingPlace.as("location"),
meeting.createdAt,
offlineMeeting.meetingDate.as("dueDateTime")
offlineMeeting.meetingDate.as("dueDateTime"),
meeting.chatRoom.lastChatAt
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ public class MeetingDetailResponseDto {
private LocalDateTime dueDateTime;
private String backgroundImage;
private Boolean isOwner;
private Boolean isCurrentUser;
private List<ParticipantDto> participants;

@Builder
private MeetingDetailResponseDto(String meetingType, String meetingName, String meetingDescription,
GenderRestriction genderRestriction, String location, LocalDateTime dueDateTime,
String backgroundImage, Boolean isOwner, List<ParticipantDto> participants) {
String backgroundImage, Boolean isOwner, Boolean isCurrentUser, List<ParticipantDto> participants) {
this.meetingType = meetingType;
this.meetingName = meetingName;
this.meetingDescription = meetingDescription;
Expand All @@ -32,29 +33,35 @@ private MeetingDetailResponseDto(String meetingType, String meetingName, String
this.dueDateTime = dueDateTime;
this.backgroundImage = backgroundImage;
this.isOwner = isOwner;
this.isCurrentUser = isCurrentUser;
this.participants = participants;
}

@Getter
public static class ParticipantDto {
private Long userId;
private String name;
private String userProfileImage;
private Boolean isOwner;
private Boolean isCurrentUser;

@Builder
private ParticipantDto(Long userId, String name, Boolean isOwner, Boolean isCurrentUser) {
private ParticipantDto(Long userId, String name, Boolean isOwner, Boolean isCurrentUser,
String userProfileImage) {
this.userId = userId;
this.name = name;
this.userProfileImage = userProfileImage;
this.isOwner = isOwner;
this.isCurrentUser = isCurrentUser;
}

public static ParticipantDto createParticipantDto(Long userId, String name, Boolean isOwner,
public static ParticipantDto createParticipantDto(Long userId, String name, String userProfileImage,
Boolean isOwner,
Boolean isCurrentUser) {
return ParticipantDto.builder()
.userId(userId)
.name(name)
.userProfileImage(userProfileImage)
.isOwner(isOwner)
.isCurrentUser(isCurrentUser)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ public class MeetingListResponseDto {
private String location;
private LocalDateTime createdAt;
private LocalDateTime dueDateTime;
private LocalDateTime lastChatAt;

public MeetingListResponseDto(Long meetingId, String meetingName, String meetingDescription,
Long currentParticipantCount, Long maxParticipants, String location, LocalDateTime createdAt,
LocalDateTime dueDateTime) {
LocalDateTime dueDateTime, LocalDateTime lastChatAt) {
this.meetingId = meetingId;
this.meetingName = meetingName;
this.meetingDescription = meetingDescription;
Expand All @@ -26,6 +27,7 @@ public MeetingListResponseDto(Long meetingId, String meetingName, String meeting
this.location = location;
this.createdAt = createdAt;
this.dueDateTime = dueDateTime;
this.lastChatAt = lastChatAt;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.springframework.web.multipart.MultipartFile;

import com.example.eatmate.app.domain.meeting.domain.BankName;
import com.example.eatmate.app.domain.meeting.domain.FoodCategory;

import jakarta.validation.constraints.Future;
Expand Down Expand Up @@ -34,5 +35,7 @@ public class UpdateDeliveryMeetingRequestDto {
@Pattern(regexp = "^[0-9-]*$", message = "올바른 계좌번호 형식이 아닙니다")
private String accountNumber;

private BankName bankName;

private MultipartFile backgroundImage;
}
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,9 @@ public MeetingDetailResponseDto getMeetingDetail(Long meetingId, UserDetails use
Meeting meeting = meetingRepository.findById(meetingId)
.orElseThrow(() -> new CommonException(ErrorCode.MEETING_NOT_FOUND));

Long currentUserId = securityUtils.getMember(userDetails).getMemberId();
Member member = securityUtils.getMember(userDetails);

List<MeetingDetailResponseDto.ParticipantDto> participants = getParticipants(meeting, currentUserId);
List<MeetingDetailResponseDto.ParticipantDto> participants = getParticipants(meeting, member.getMemberId());

return MeetingDetailResponseDto.builder()
.meetingType(meeting.getType())
Expand All @@ -284,7 +284,8 @@ public MeetingDetailResponseDto getMeetingDetail(Long meetingId, UserDetails use
.location(getLocation(meeting))
.dueDateTime(getDueDateTime(meeting))
.backgroundImage(Optional.ofNullable(meeting.getBackgroundImage()).map(Image::getImageUrl).orElse(null))
.isOwner(isOwner(meeting, currentUserId))
.isOwner(isOwner(meeting, member.getMemberId()))
.isCurrentUser(isCurrentUser(meeting, member))
.participants(participants)
.build();
}
Expand Down Expand Up @@ -313,6 +314,10 @@ private Boolean isOwner(Meeting meeting, Long currentUserId) {
.orElse(false);
}

private Boolean isCurrentUser(Meeting meeting, Member member) {
return meetingParticipantRepository.existsByMeetingAndMember(meeting, member);
}

private List<MeetingDetailResponseDto.ParticipantDto> getParticipants(Meeting meeting, Long currentUserId) {
return meetingParticipantRepository.findByMeeting(meeting).stream()
.sorted((p1, p2) -> {
Expand All @@ -327,6 +332,9 @@ private List<MeetingDetailResponseDto.ParticipantDto> getParticipants(Meeting me
.map(participant -> MeetingDetailResponseDto.ParticipantDto.createParticipantDto(
participant.getMember().getMemberId(),
participant.getMember().getNickname(),
Optional.ofNullable(participant.getMember().getProfileImage())
.map(Image::getImageUrl)
.orElse(null),
participant.getRole() == ParticipantRole.HOST,
participant.getMember().getMemberId().equals(currentUserId)
))
Expand Down Expand Up @@ -534,6 +542,7 @@ public void updateDeliveryMeeting(Long meetingId, UpdateDeliveryMeetingRequestDt
updateDeliveryMeetingRequestDto.getStoreName(),
updateDeliveryMeetingRequestDto.getPickupLocation(),
updateDeliveryMeetingRequestDto.getAccountNumber(),
updateDeliveryMeetingRequestDto.getBankName(),
backgroundImage
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import static org.junit.jupiter.api.Assertions.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -221,4 +225,97 @@ void concurrentJoinTest() throws InterruptedException {
log.info("최종 참가자 수 - 기대값: {}, 실제값: {}", 2, finalParticipantCount);
assertEquals(2, finalParticipantCount, "최종 참가자 수는 2명이어야 합니다 (호스트 1명 + 성공한 참가자 1명)");
}

@Test
@DisplayName("동시에 마지막 자리 참여 시도 테스트")
void massiveConcurrentRequestTest() throws InterruptedException {
int totalRequests = 100; // 총 100개 요청
int concurrentUsers = 10; // 10명의 사용자

// 테스트용 유저들 생성
List<Member> testMembers = new ArrayList<>();
List<UserDetails> testUserDetails = new ArrayList<>();

for (int i = 0; i < concurrentUsers; i++) {
Member member = Member.builder()
.email("mocktest" + i + "@test.com")
.nickname("tester" + i)
.gender(Gender.MALE)
.build();
memberRepository.save(member);
testMembers.add(member);

UserDetails userDetails = User.withUsername(member.getEmail())
.password("password")
.roles("USER")
.build();
testUserDetails.add(userDetails);
}

AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger failCount = new AtomicInteger(0);
AtomicInteger timeoutCount = new AtomicInteger(0);
AtomicInteger deadlockCount = new AtomicInteger(0);
AtomicInteger normalFailCount = new AtomicInteger(0);

ExecutorService executorService = Executors.newFixedThreadPool(concurrentUsers);
CountDownLatch latch = new CountDownLatch(totalRequests);

// 시작 시간 기록
long startTime = System.currentTimeMillis();

// 100개 요청 시뮬레이션
for (int i = 0; i < totalRequests; i++) {
final int userIndex = i % concurrentUsers; // 유저 순환
executorService.submit(() -> {
try {
meetingService.joinDeliveryMeeting(testMeeting.getId(),
testUserDetails.get(userIndex));
successCount.incrementAndGet();
} catch (CommonException e) {
if (e.getErrorCode() == ErrorCode.PARTICIPANT_LIMIT_EXCEEDED) {
normalFailCount.incrementAndGet();
}
failCount.incrementAndGet();
} catch (Exception e) {
if (e instanceof TimeoutException) {
timeoutCount.incrementAndGet();
} else {
log.warn("데드락/기타 오류 발생: {}", e.getMessage());
deadlockCount.incrementAndGet();
}
failCount.incrementAndGet();
} finally {
latch.countDown();
}
});
}

// 10초 타임아웃 설정
boolean completed = latch.await(10, TimeUnit.SECONDS);

// 종료 시간 기록
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;

executorService.shutdown();

// 결과 로깅
log.info("총 요청 수: {}", totalRequests);
log.info("성공한 요청 수: {}", successCount.get());
log.info("실패한 요청 수: {}", failCount.get());
log.info("일반 실패 (인원초과): {}", normalFailCount.get());
log.info("타임아웃: {}", timeoutCount.get());
log.info("데드락/기타: {}", deadlockCount.get());
log.info("타임아웃 발생여부: {}", !completed);
log.info("총 소요 시간: {}ms", totalTime);
log.info("초당 처리량: {} requests/second",
(double)totalRequests / (totalTime / 1000.0));

// 검증
assertEquals(1, successCount.get(), "정원(2)에서 호스트(1)를 제외하고 1명만 성공해야 함");
assertEquals(totalRequests - 1, failCount.get(), "나머지는 모두 실패해야 함");
assertEquals(0, timeoutCount.get(), "타임아웃이 발생하면 안됨");
assertEquals(0, deadlockCount.get(), "데드락이 발생하면 안됨");
}
}