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

feat: 방 인증, 입장 동시성 처리 #157

Merged
merged 22 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bd89f04
feat: ClockHolder LocalDate 추가
Shin-Jae-Yoon Nov 23, 2023
a58f56e
refactor: RoomService 리팩토링
Shin-Jae-Yoon Nov 23, 2023
3c20938
refactor: SearchService 리팩토링
Shin-Jae-Yoon Nov 23, 2023
ac0a1de
refactor: 방 입장, 퇴장 리팩토링
Shin-Jae-Yoon Nov 23, 2023
3ff5d17
refactor: CertifiactionService 리팩토링
Shin-Jae-Yoon Nov 23, 2023
73c7e7e
refactor: RoomController 리팩토링
Shin-Jae-Yoon Nov 23, 2023
65ce795
test: InventorySearchRepository 테스트 추가
Shin-Jae-Yoon Nov 23, 2023
c1b39f8
Merge branch 'develop' into refactor/#130-room-refactor
Shin-Jae-Yoon Nov 23, 2023
534edd6
Merge remote-tracking branch 'origin/refactor/#130-room-refactor' int…
ymkim97 Nov 23, 2023
8975d64
Merge branch 'develop' into feature/#145-room-concurrency
ymkim97 Nov 26, 2023
efef377
chore: 테스트 코드 In-memory H2에서 MySQL로 변경
ymkim97 Nov 26, 2023
d25285a
feat: CertifyRoom Transaction 분리, 비관적 락 적용
ymkim97 Nov 26, 2023
2628f7e
feat: 방 입장 낙관적 락 적용
ymkim97 Nov 26, 2023
fd24ee2
refactor: MySQL 변경으로 일부 테스트 수정
ymkim97 Nov 26, 2023
1b36748
test: 방 인증, 입장 동시성 테스트 작성
ymkim97 Nov 26, 2023
7160de6
test: 방장 위임 테스트 작성
ymkim97 Nov 26, 2023
9b363af
fix: 방 입장 낙관적 락 -> 비관적 락으로 변경
ymkim97 Nov 26, 2023
930a886
Merge branch 'develop' into feature/#145-room-concurrency
ymkim97 Nov 26, 2023
56a926a
refactor: Room version 삭제
ymkim97 Nov 27, 2023
cd90732
Merge branch 'develop' into feature/#145-room-concurrency
ymkim97 Nov 27, 2023
ab2062b
fix: 코드 수정
ymkim97 Nov 27, 2023
8128c6b
feat: Image Type 추가
ymkim97 Nov 27, 2023
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ jobs:
- name: Gradle Grant 권한 부여
run: chmod +x gradlew

- name: 테스트용 MySQL 도커 컨테이너 실행
run: |
sudo docker run -d -p 3305:3306 --env MYSQL_DATABASE=moabam --env MYSQL_ROOT_PASSWORD=1234 mysql:8.0.33

- name: SonarCloud 캐싱
uses: actions/cache@v3
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.moabam.api.domain.room.repository.DailyRoomCertificationRepository;
import com.moabam.api.domain.room.repository.ParticipantSearchRepository;
import com.moabam.api.domain.room.repository.RoutineRepository;
import com.moabam.api.dto.room.CertifiedMemberInfo;
import com.moabam.global.common.util.ClockHolder;
import com.moabam.global.common.util.UrlSubstringParser;
import com.moabam.global.error.exception.BadRequestException;
Expand All @@ -53,7 +54,7 @@ public class CertificationService {
private final ClockHolder clockHolder;

@Transactional
public void certifyRoom(Long memberId, Long roomId, List<String> imageUrls) {
public CertifiedMemberInfo getCertifiedMemberInfo(Long memberId, Long roomId, List<String> imageUrls) {
LocalDate today = clockHolder.date();
Participant participant = participantSearchRepository.findOne(memberId, roomId)
.orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND));
Expand All @@ -63,22 +64,31 @@ public void certifyRoom(Long memberId, Long roomId, List<String> imageUrls) {
case MORNING -> BugType.MORNING;
case NIGHT -> BugType.NIGHT;
};
int roomLevel = room.getLevel();

validateCertifyTime(clockHolder.times(), room.getCertifyTime());
validateAlreadyCertified(memberId, roomId, today);

certifyMember(memberId, roomId, participant, member, imageUrls);

return CertificationsMapper.toCertifiedMemberInfo(today, bugType, room, member);
}

@Transactional
public void certifyRoom(CertifiedMemberInfo certifyInfo) {
LocalDate date = certifyInfo.date();
BugType bugType = certifyInfo.bugType();
Room room = certifyInfo.room();
Member member = certifyInfo.member();

Optional<DailyRoomCertification> dailyRoomCertification =
certificationsSearchRepository.findDailyRoomCertification(roomId, today);
certificationsSearchRepository.findDailyRoomCertification(room.getId(), date);

if (dailyRoomCertification.isEmpty()) {
certifyRoomIfAvailable(roomId, today, room, bugType, roomLevel);
certifyRoomIfAvailable(room.getId(), date, room, bugType, room.getLevel());
return;
}

member.getBug().increase(bugType, roomLevel);
member.getBug().increase(bugType, room.getLevel());
}

public boolean existsMemberCertification(Long memberId, Long roomId, LocalDate date) {
Expand Down
16 changes: 6 additions & 10 deletions src/main/java/com/moabam/api/application/room/RoomService.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
package com.moabam.api.application.room;

import static com.moabam.api.domain.room.RoomType.MORNING;
import static com.moabam.api.domain.room.RoomType.NIGHT;
import static com.moabam.global.error.model.ErrorMessage.MEMBER_ROOM_EXCEED;
import static com.moabam.global.error.model.ErrorMessage.PARTICIPANT_NOT_FOUND;
import static com.moabam.global.error.model.ErrorMessage.ROOM_EXIT_MANAGER_FAIL;
import static com.moabam.global.error.model.ErrorMessage.ROOM_MAX_USER_REACHED;
import static com.moabam.global.error.model.ErrorMessage.ROOM_MODIFY_UNAUTHORIZED_REQUEST;
import static com.moabam.global.error.model.ErrorMessage.ROOM_NOT_FOUND;
import static com.moabam.global.error.model.ErrorMessage.WRONG_ROOM_PASSWORD;
import static com.moabam.api.domain.room.RoomType.*;
import static com.moabam.global.error.model.ErrorMessage.*;

import java.util.List;

Expand Down Expand Up @@ -37,9 +30,11 @@
import com.moabam.global.error.exception.NotFoundException;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class RoomService {

Expand Down Expand Up @@ -90,7 +85,8 @@ public void modifyRoom(Long memberId, Long roomId, ModifyRoomRequest modifyRoomR

@Transactional
public void enterRoom(Long memberId, Long roomId, EnterRoomRequest enterRoomRequest) {
Room room = roomRepository.findById(roomId).orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND));
Room room = roomRepository.findWithPessimisticLockById(roomId).orElseThrow(
() -> new NotFoundException(ROOM_NOT_FOUND));
validateRoomEnter(memberId, enterRoomRequest.password(), room);

Member member = memberService.getById(memberId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
import java.time.LocalDate;
import java.util.List;

import com.moabam.api.domain.bug.BugType;
import com.moabam.api.domain.member.Member;
import com.moabam.api.domain.room.Certification;
import com.moabam.api.domain.room.DailyMemberCertification;
import com.moabam.api.domain.room.DailyRoomCertification;
import com.moabam.api.domain.room.Participant;
import com.moabam.api.domain.room.Room;
import com.moabam.api.domain.room.Routine;
import com.moabam.api.dto.room.CertificationImageResponse;
import com.moabam.api.dto.room.CertificationImagesResponse;
import com.moabam.api.dto.room.CertifiedMemberInfo;
import com.moabam.api.dto.room.TodayCertificateRankResponse;

import lombok.AccessLevel;
Expand Down Expand Up @@ -72,4 +75,13 @@ public static Certification toCertification(Routine routine, Long memberId, Stri
.image(image)
.build();
}

public static CertifiedMemberInfo toCertifiedMemberInfo(LocalDate date, BugType bugType, Room room, Member member) {
return CertifiedMemberInfo.builder()
.date(date)
.bugType(bugType)
.room(room)
.member(member)
.build();
}
}
10 changes: 7 additions & 3 deletions src/main/java/com/moabam/api/domain/image/ImageName.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ public class ImageName {

private static final String CERTIFICATION_PATH = "certifications" + DELIMITER + LocalDate.now() + DELIMITER;
private static final String PROFILE_IMAGE = "members/profile" + DELIMITER;
private static final String BIRD_SKIN = "moabam/skins" + DELIMITER;
private static final String DEFAULT = "moabam/default" + DELIMITER;

private final String fileName;

public static ImageName of(MultipartFile file, ImageType imageType) {
return switch (imageType) {
case CERTIFICATION -> new ImageName(CERTIFICATION_PATH + file.getName() + "_" + UUID.randomUUID());
case PROFILE_IMAGE -> new ImageName(PROFILE_IMAGE + file.getName() + "_" + UUID.randomUUID());
case DEFAULT -> new ImageName(DEFAULT + file.getName());
case CERTIFICATION ->
new ImageName(CERTIFICATION_PATH + file.getName() + "_" + UUID.randomUUID() + IMAGE_EXTENSION);
case PROFILE_IMAGE ->
new ImageName(PROFILE_IMAGE + file.getName() + "_" + UUID.randomUUID() + IMAGE_EXTENSION);
case BIRD_SKIN -> new ImageName(BIRD_SKIN + file.getName() + IMAGE_EXTENSION);
case DEFAULT -> new ImageName(DEFAULT + file.getName() + IMAGE_EXTENSION);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void resizeImageToFixedSize(ImageType imageType) {
ImageSize imageSize = switch (imageType) {
case PROFILE_IMAGE -> ImageSize.PROFILE_IMAGE;
case CERTIFICATION -> ImageSize.CERTIFICATION_IMAGE;
case BIRD_SKIN -> ImageSize.BIRD_SKIN;
case DEFAULT -> ImageSize.CAGE;
};

Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/moabam/api/domain/image/ImageType.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public enum ImageType {

PROFILE_IMAGE,
CERTIFICATION,
BIRD_SKIN,
DEFAULT
}
4 changes: 0 additions & 4 deletions src/main/java/com/moabam/api/domain/room/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,6 @@ public void changeMaxCount(int maxUserCount) {

public void increaseCurrentUserCount() {
this.currentUserCount += 1;

if (this.currentUserCount > this.maxUserCount) {
throw new BadRequestException(ROOM_MAX_USER_REACHED);
}
}

public void decreaseCurrentUserCount() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.moabam.api.domain.room.DailyRoomCertification;
import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.LockModeType;
import lombok.RequiredArgsConstructor;

@Repository
Expand Down Expand Up @@ -67,6 +68,7 @@ public Optional<DailyRoomCertification> findDailyRoomCertification(Long roomId,
dailyRoomCertification.roomId.eq(roomId),
dailyRoomCertification.certifiedAt.eq(date)
)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.moabam.api.domain.room.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.Query;
import org.springframework.data.repository.query.Param;

import com.moabam.api.domain.room.Room;

import jakarta.persistence.LockModeType;

public interface RoomRepository extends JpaRepository<Room, Long> {

@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Room> findWithPessimisticLockById(Long id);

@Query(value = "select distinct rm.* from room rm left join routine rt on rm.id = rt.room_id "
+ "where rm.title like %:keyword% "
+ "or rm.manager_nickname like %:keyword% "
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/moabam/api/dto/room/CertifiedMemberInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.moabam.api.dto.room;

import java.time.LocalDate;

import com.moabam.api.domain.bug.BugType;
import com.moabam.api.domain.member.Member;
import com.moabam.api.domain.room.Room;

import lombok.Builder;

@Builder
public record CertifiedMemberInfo(
LocalDate date,
BugType bugType,
Room room,
Member member
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.moabam.api.application.room.SearchService;
import com.moabam.api.domain.image.ImageType;
import com.moabam.api.domain.room.RoomType;
import com.moabam.api.dto.room.CertifiedMemberInfo;
import com.moabam.api.dto.room.CreateRoomRequest;
import com.moabam.api.dto.room.EnterRoomRequest;
import com.moabam.api.dto.room.GetAllRoomsResponse;
Expand Down Expand Up @@ -98,7 +99,8 @@ public RoomDetailsResponse getRoomDetails(@Auth AuthMember authMember, @PathVari
public void certifyRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId,
@RequestPart List<MultipartFile> multipartFiles) {
List<String> imageUrls = imageService.uploadImages(multipartFiles, ImageType.CERTIFICATION);
certificationService.certifyRoom(authMember.id(), roomId, imageUrls);
CertifiedMemberInfo info = certificationService.getCertifiedMemberInfo(authMember.id(), roomId, imageUrls);
certificationService.certifyRoom(info);
}

@PutMapping("/{roomId}/members/{memberId}/mandate")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ public class GlobalConstant {
public static final int ROOM_FIXED_SEARCH_SIZE = 10;
public static final int LEVEL_DIVISOR = 10;
public static final int DEFAULT_SKIN_SIZE = 2;
public static final String IMAGE_EXTENSION = ".png";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.moabam.api.application;
package com.moabam.api.application.image;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;
Expand All @@ -15,7 +15,6 @@
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import com.moabam.api.application.image.ImageService;
import com.moabam.api.domain.image.ImageType;
import com.moabam.api.domain.image.ResizedImage;
import com.moabam.api.infrastructure.s3.S3Manager;
Expand Down
Loading