diff --git a/Api/src/main/java/allchive/server/api/archiving/controller/ArchivingController.java b/Api/src/main/java/allchive/server/api/archiving/controller/ArchivingController.java index dfdbc71e..f70e31a8 100644 --- a/Api/src/main/java/allchive/server/api/archiving/controller/ArchivingController.java +++ b/Api/src/main/java/allchive/server/api/archiving/controller/ArchivingController.java @@ -9,6 +9,7 @@ import allchive.server.api.archiving.model.dto.response.ArchivingsResponse; import allchive.server.api.archiving.service.*; import allchive.server.api.common.slice.SliceResponse; +import allchive.server.api.config.security.SecurityUtil; import allchive.server.domain.domains.archiving.domain.enums.Category; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -118,13 +119,15 @@ public ArchivingContentsResponse getArchivingContents( @PatchMapping(value = "/{archivingId}/scrap") public void updateArchivingScrap( @RequestParam("cancel") Boolean cancel, @PathVariable("archivingId") Long archivingId) { - updateArchivingScrapUseCase.execute(archivingId, cancel); + Long userId = SecurityUtil.getCurrentUserId(); + updateArchivingScrapUseCase.execute(archivingId, cancel, userId); } @Operation(summary = "아카이빙을 고정합니다.", description = "고정 취소면 cancel에 true 값 보내주세요") @PatchMapping(value = "/{archivingId}/pin") public void updateArchivingPin( @RequestParam("cancel") Boolean cancel, @PathVariable("archivingId") Long archivingId) { - updateArchivingPinUseCase.execute(archivingId, cancel); + Long userId = SecurityUtil.getCurrentUserId(); + updateArchivingPinUseCase.execute(archivingId, cancel, userId); } } diff --git a/Api/src/main/java/allchive/server/api/archiving/service/DeleteArchivingUseCase.java b/Api/src/main/java/allchive/server/api/archiving/service/DeleteArchivingUseCase.java index 3f81f4fa..e7b7639e 100644 --- a/Api/src/main/java/allchive/server/api/archiving/service/DeleteArchivingUseCase.java +++ b/Api/src/main/java/allchive/server/api/archiving/service/DeleteArchivingUseCase.java @@ -4,6 +4,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.api.recycle.model.mapper.RecycleMapper; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.service.ArchivingDomainService; import allchive.server.domain.domains.archiving.validator.ArchivingValidator; import allchive.server.domain.domains.recycle.domain.Recycle; @@ -20,6 +22,7 @@ public class DeleteArchivingUseCase { private final RecycleDomainService recycleDomainService; @Transactional + @DistributedLock(lockType = DistributedLockType.ARCHIVING, identifier ={"archivingId"}) public void execute(Long archivingId) { Long userId = SecurityUtil.getCurrentUserId(); validateExecution(archivingId, userId); diff --git a/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingPinUseCase.java b/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingPinUseCase.java index 4534daf7..559f271c 100644 --- a/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingPinUseCase.java +++ b/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingPinUseCase.java @@ -3,6 +3,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.service.ArchivingDomainService; import allchive.server.domain.domains.archiving.validator.ArchivingValidator; import lombok.RequiredArgsConstructor; @@ -15,8 +17,8 @@ public class UpdateArchivingPinUseCase { private final ArchivingDomainService archivingDomainService; @Transactional - public void execute(Long archivingId, Boolean cancel) { - Long userId = SecurityUtil.getCurrentUserId(); + @DistributedLock(lockType = DistributedLockType.ARCHIVING_PIN, identifier ={"archivingId", "userId"}) + public void execute(Long archivingId, Boolean cancel, Long userId) { validateExecution(archivingId, userId, cancel); archivingDomainService.updatePin(archivingId, userId, !cancel); } diff --git a/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingScrapUseCase.java b/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingScrapUseCase.java index 6a29b529..2bc8e35e 100644 --- a/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingScrapUseCase.java +++ b/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingScrapUseCase.java @@ -5,6 +5,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.service.ArchivingDomainService; import allchive.server.domain.domains.archiving.validator.ArchivingValidator; import allchive.server.domain.domains.user.adaptor.UserAdaptor; @@ -25,8 +27,8 @@ public class UpdateArchivingScrapUseCase { private final ScrapValidator scrapValidator; @Transactional - public void execute(Long archivingId, Boolean cancel) { - Long userId = SecurityUtil.getCurrentUserId(); + @DistributedLock(lockType = DistributedLockType.ARCHIVING_SCRAP, identifier ={"archivingId", "userId"}) + public void execute(Long archivingId, Boolean cancel, Long userId) { validateExecution(archivingId, userId, cancel); User user = userAdaptor.findById(userId); if (cancel) { diff --git a/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingUseCase.java b/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingUseCase.java index f4b76540..f9dc59ea 100644 --- a/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingUseCase.java +++ b/Api/src/main/java/allchive/server/api/archiving/service/UpdateArchivingUseCase.java @@ -5,6 +5,8 @@ import allchive.server.api.common.util.UrlUtil; import allchive.server.api.config.security.SecurityUtil; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.core.event.Event; import allchive.server.core.event.events.s3.S3ImageDeleteEvent; import allchive.server.domain.domains.archiving.adaptor.ArchivingAdaptor; @@ -22,9 +24,9 @@ public class UpdateArchivingUseCase { private final ArchivingDomainService archivingDomainService; private final ArchivingAdaptor archivingAdaptor; private final ArchivingValidator archivingValidator; - private final S3DeleteObjectService s3DeleteObjectService; @Transactional + @DistributedLock(lockType = DistributedLockType.ARCHIVING, identifier ={"archivingId"}) public void execute(Long archivingId, UpdateArchivingRequest request) { validateExecution(archivingId); Archiving archiving = archivingAdaptor.findById(archivingId); diff --git a/Api/src/main/java/allchive/server/api/auth/controller/AuthController.java b/Api/src/main/java/allchive/server/api/auth/controller/AuthController.java index dd3491e9..3874224d 100644 --- a/Api/src/main/java/allchive/server/api/auth/controller/AuthController.java +++ b/Api/src/main/java/allchive/server/api/auth/controller/AuthController.java @@ -5,6 +5,7 @@ import allchive.server.api.auth.service.LogOutUserUseCase; import allchive.server.api.auth.service.TokenRefreshUseCase; import allchive.server.api.auth.service.WithdrawUserUseCase; +import allchive.server.api.config.security.SecurityUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -26,7 +27,8 @@ public class AuthController { public void withDrawUser( @RequestParam(required = false, name = "appleCode", value = "") String appleCode, @RequestHeader(value = "referer", required = false) String referer) { - withdrawUserUseCase.execute(appleCode, referer); + Long userId = SecurityUtil.getCurrentUserId(); + withdrawUserUseCase.execute(appleCode, referer, userId); } @Operation(summary = "회원탈퇴를 합니다. (개발용)", deprecated = true) diff --git a/Api/src/main/java/allchive/server/api/auth/service/WithdrawUserUseCase.java b/Api/src/main/java/allchive/server/api/auth/service/WithdrawUserUseCase.java index 28aaf10f..01aa6dcb 100644 --- a/Api/src/main/java/allchive/server/api/auth/service/WithdrawUserUseCase.java +++ b/Api/src/main/java/allchive/server/api/auth/service/WithdrawUserUseCase.java @@ -5,6 +5,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.core.annotation.UseCase; import allchive.server.core.error.exception.InvalidOauthProviderException; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.adaptor.ArchivingAdaptor; import allchive.server.domain.domains.archiving.domain.Archiving; import allchive.server.domain.domains.archiving.service.ArchivingDomainService; @@ -47,8 +49,8 @@ public class WithdrawUserUseCase { private final ArchivingDomainService archivingDomainService; @Transactional - public void execute(String appleAccessToken, String referer) { - Long userId = SecurityUtil.getCurrentUserId(); + @DistributedLock(lockType = DistributedLockType.USER, identifier ={"userId"}) + public void execute(String appleAccessToken, String referer, Long userId) { User user = userAdaptor.findById(userId); // oauth쪽 탈퇴 withdrawOauth(user.getOauthInfo().getProvider(), appleAccessToken, user, referer); diff --git a/Api/src/main/java/allchive/server/api/block/controller/BlockController.java b/Api/src/main/java/allchive/server/api/block/controller/BlockController.java index 6ad67635..2acb8fed 100644 --- a/Api/src/main/java/allchive/server/api/block/controller/BlockController.java +++ b/Api/src/main/java/allchive/server/api/block/controller/BlockController.java @@ -7,6 +7,7 @@ import allchive.server.api.block.service.CreateBlockUseCase; import allchive.server.api.block.service.DeleteBlockUseCase; import allchive.server.api.block.service.GetBlockUseCase; +import allchive.server.api.config.security.SecurityUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -32,7 +33,8 @@ public BlockResponse createBlock(@RequestBody BlockRequest blockRequest) { @Operation(summary = "유저 차단을 해제합니다.") @DeleteMapping() public BlockResponse deleteBlock(@RequestBody BlockRequest blockRequest) { - return deleteBlockUseCase.execute(blockRequest); + Long userId = SecurityUtil.getCurrentUserId(); + return deleteBlockUseCase.execute(blockRequest, userId, blockRequest.getUserId()); } @Operation(summary = "차단한 유저 정보를 가져옵니다.") diff --git a/Api/src/main/java/allchive/server/api/block/service/DeleteBlockUseCase.java b/Api/src/main/java/allchive/server/api/block/service/DeleteBlockUseCase.java index f9b93d56..18825e7c 100644 --- a/Api/src/main/java/allchive/server/api/block/service/DeleteBlockUseCase.java +++ b/Api/src/main/java/allchive/server/api/block/service/DeleteBlockUseCase.java @@ -5,6 +5,8 @@ import allchive.server.api.block.model.dto.response.BlockResponse; import allchive.server.api.config.security.SecurityUtil; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.block.service.BlockDomainService; import allchive.server.domain.domains.block.validator.BlockValidator; import allchive.server.domain.domains.user.adaptor.UserAdaptor; @@ -21,7 +23,8 @@ public class DeleteBlockUseCase { private final UserAdaptor userAdaptor; @Transactional - public BlockResponse execute(BlockRequest request) { + @DistributedLock(lockType = DistributedLockType.BLOCK, identifier ={"fromUserId, toUserId"}) + public BlockResponse execute(BlockRequest request, Long fromUserId, Long toUserId) { Long userId = SecurityUtil.getCurrentUserId(); validateExecution(userId, request); blockDomainService.deleteByBlockFromAndBlockUser(userId, request.getUserId()); diff --git a/Api/src/main/java/allchive/server/api/content/service/DeleteContentUseCase.java b/Api/src/main/java/allchive/server/api/content/service/DeleteContentUseCase.java index f0aefc8e..7330621c 100644 --- a/Api/src/main/java/allchive/server/api/content/service/DeleteContentUseCase.java +++ b/Api/src/main/java/allchive/server/api/content/service/DeleteContentUseCase.java @@ -5,6 +5,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.api.recycle.model.mapper.RecycleMapper; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.service.ArchivingAsyncDomainService; import allchive.server.domain.domains.content.adaptor.ContentAdaptor; import allchive.server.domain.domains.content.domain.Content; @@ -27,6 +29,7 @@ public class DeleteContentUseCase { private final ArchivingAsyncDomainService archivingAsyncDomainService; @Transactional + @DistributedLock(lockType = DistributedLockType.CONTENT, identifier ={"contentId"}) public void execute(Long contentId) { Long userId = SecurityUtil.getCurrentUserId(); validateExecution(contentId, userId); diff --git a/Api/src/main/java/allchive/server/api/content/service/UpdateContentUseCase.java b/Api/src/main/java/allchive/server/api/content/service/UpdateContentUseCase.java index abc52628..87e0e087 100644 --- a/Api/src/main/java/allchive/server/api/content/service/UpdateContentUseCase.java +++ b/Api/src/main/java/allchive/server/api/content/service/UpdateContentUseCase.java @@ -10,6 +10,8 @@ import allchive.server.core.annotation.UseCase; import allchive.server.core.event.Event; import allchive.server.core.event.events.s3.S3ImageDeleteEvent; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.service.ArchivingAsyncDomainService; import allchive.server.domain.domains.content.adaptor.ContentAdaptor; import allchive.server.domain.domains.content.adaptor.ContentTagGroupAdaptor; @@ -42,6 +44,7 @@ public class UpdateContentUseCase { private final ArchivingAsyncDomainService archivingAsyncDomainService; @Transactional + @DistributedLock(lockType = DistributedLockType.CONTENT, identifier ={"contentId"}) public void execute(Long contentId, UpdateContentRequest request) { validateExecution(contentId, request); regenerateContentTagGroup(contentId, request.getTagIds()); diff --git a/Api/src/main/java/allchive/server/api/recycle/controller/RecycleController.java b/Api/src/main/java/allchive/server/api/recycle/controller/RecycleController.java index 053f8a12..0648fe10 100644 --- a/Api/src/main/java/allchive/server/api/recycle/controller/RecycleController.java +++ b/Api/src/main/java/allchive/server/api/recycle/controller/RecycleController.java @@ -1,6 +1,7 @@ package allchive.server.api.recycle.controller; +import allchive.server.api.config.security.SecurityUtil; import allchive.server.api.recycle.model.dto.request.ClearDeletedObjectRequest; import allchive.server.api.recycle.model.dto.request.RestoreDeletedObjectRequest; import allchive.server.api.recycle.model.dto.response.DeletedObjectResponse; @@ -13,6 +14,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.stream.Collectors; + @RestController @RequestMapping("/recycles") @RequiredArgsConstructor @@ -26,7 +30,8 @@ public class RecycleController { @Operation(summary = "삭제된 아카이빙, 컨텐츠를 복구합니다.") @PatchMapping() public void restoreDeletedObject(@RequestBody RestoreDeletedObjectRequest request) { - restoreDeletedObjectUseCase.execute(request); + Long userId = SecurityUtil.getCurrentUserId(); + restoreDeletedObjectUseCase.execute(request, userId); } @Operation(summary = "삭제된 아카이빙, 컨텐츠를 가져옵니다.") @@ -38,6 +43,7 @@ public DeletedObjectResponse getDeletedObject() { @Operation(summary = "삭제된 아카이빙, 컨텐츠를 영구적으로 삭제합니다.") @DeleteMapping() public void clearDeletedObject(@RequestBody ClearDeletedObjectRequest request) { - clearDeletedObjectUseCase.execute(request); + Long userId = SecurityUtil.getCurrentUserId(); + clearDeletedObjectUseCase.execute(request, userId); } } diff --git a/Api/src/main/java/allchive/server/api/recycle/service/ClearDeletedObjectUseCase.java b/Api/src/main/java/allchive/server/api/recycle/service/ClearDeletedObjectUseCase.java index f03a4f61..aca1b168 100644 --- a/Api/src/main/java/allchive/server/api/recycle/service/ClearDeletedObjectUseCase.java +++ b/Api/src/main/java/allchive/server/api/recycle/service/ClearDeletedObjectUseCase.java @@ -6,6 +6,8 @@ import allchive.server.core.annotation.UseCase; import allchive.server.core.event.Event; import allchive.server.core.event.events.s3.S3ImageDeleteEvent; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.adaptor.ArchivingAdaptor; import allchive.server.domain.domains.archiving.domain.Archiving; import allchive.server.domain.domains.archiving.service.ArchivingDomainService; @@ -44,8 +46,8 @@ public class ClearDeletedObjectUseCase { private final ArchivingAdaptor archivingAdaptor; @Transactional - public void execute(ClearDeletedObjectRequest request) { - Long userId = SecurityUtil.getCurrentUserId(); + @DistributedLock(lockType = DistributedLockType.RECYCLE, identifier ={"userId"}) + public void execute(ClearDeletedObjectRequest request, Long userId) { validateExecution(userId, request); List contents = contentAdaptor.findAllByArchivingIds(request.getArchivingIds()); List contentsId = getContentsId(contents, request); diff --git a/Api/src/main/java/allchive/server/api/recycle/service/RestoreDeletedObjectUseCase.java b/Api/src/main/java/allchive/server/api/recycle/service/RestoreDeletedObjectUseCase.java index 1219e2bf..d0b3d756 100644 --- a/Api/src/main/java/allchive/server/api/recycle/service/RestoreDeletedObjectUseCase.java +++ b/Api/src/main/java/allchive/server/api/recycle/service/RestoreDeletedObjectUseCase.java @@ -5,6 +5,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.api.recycle.model.dto.request.RestoreDeletedObjectRequest; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.archiving.service.ArchivingDomainService; import allchive.server.domain.domains.archiving.validator.ArchivingValidator; import allchive.server.domain.domains.content.adaptor.ContentAdaptor; @@ -28,8 +30,8 @@ public class RestoreDeletedObjectUseCase { private final ContentAdaptor contentAdaptor; @Transactional - public void execute(RestoreDeletedObjectRequest request) { - Long userId = SecurityUtil.getCurrentUserId(); + @DistributedLock(lockType = DistributedLockType.RECYCLE, identifier ={"userId"}) + public void execute(RestoreDeletedObjectRequest request, Long userId) { validateExecution(request, userId); archivingDomainService.restoreByIdIn(request.getArchivingIds()); contentDomainService.restoreByIdIn(request.getContentIds()); diff --git a/Api/src/main/java/allchive/server/api/tag/service/DeleteTagUseCase.java b/Api/src/main/java/allchive/server/api/tag/service/DeleteTagUseCase.java index 63dc129a..9d4c3122 100644 --- a/Api/src/main/java/allchive/server/api/tag/service/DeleteTagUseCase.java +++ b/Api/src/main/java/allchive/server/api/tag/service/DeleteTagUseCase.java @@ -3,6 +3,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.content.adaptor.TagAdaptor; import allchive.server.domain.domains.content.domain.Tag; import allchive.server.domain.domains.content.service.ContentTagGroupDomainService; @@ -20,6 +22,7 @@ public class DeleteTagUseCase { private final TagDomainService tagDomainService; @Transactional + @DistributedLock(lockType = DistributedLockType.TAG, identifier ={"tagId"}) public void execute(Long tagId) { validateExecution(tagId); Tag tag = tagAdaptor.findById(tagId); diff --git a/Api/src/main/java/allchive/server/api/tag/service/UpdateTagUseCase.java b/Api/src/main/java/allchive/server/api/tag/service/UpdateTagUseCase.java index 80866d67..7073e093 100644 --- a/Api/src/main/java/allchive/server/api/tag/service/UpdateTagUseCase.java +++ b/Api/src/main/java/allchive/server/api/tag/service/UpdateTagUseCase.java @@ -4,6 +4,8 @@ import allchive.server.api.config.security.SecurityUtil; import allchive.server.api.tag.model.dto.request.UpdateTagRequest; import allchive.server.core.annotation.UseCase; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.content.service.TagDomainService; import allchive.server.domain.domains.content.validator.TagValidator; import lombok.RequiredArgsConstructor; @@ -16,6 +18,7 @@ public class UpdateTagUseCase { private final TagDomainService tagDomainService; @Transactional + @DistributedLock(lockType = DistributedLockType.TAG, identifier ={"tagId"}) public void execute(Long tagId, UpdateTagRequest request) { validateExecution(tagId); tagDomainService.updateTag(tagId, request.getName()); diff --git a/Api/src/main/java/allchive/server/api/user/controller/UserController.java b/Api/src/main/java/allchive/server/api/user/controller/UserController.java index d44da34c..0d9a3c5e 100644 --- a/Api/src/main/java/allchive/server/api/user/controller/UserController.java +++ b/Api/src/main/java/allchive/server/api/user/controller/UserController.java @@ -1,6 +1,7 @@ package allchive.server.api.user.controller; +import allchive.server.api.config.security.SecurityUtil; import allchive.server.api.user.model.dto.request.CheckUserNicknameRequest; import allchive.server.api.user.model.dto.request.UpdateUserInfoRequest; import allchive.server.api.user.model.dto.response.GetUserInfoResponse; @@ -41,7 +42,8 @@ public GetUserInfoResponse getUserInfo() { @Operation(summary = "내 정보를 수정합니다.") @PostMapping(value = "/info") public void getUserInfo(@RequestBody UpdateUserInfoRequest updateUserInfoRequest) { - updateUserInfoUseCase.execute(updateUserInfoRequest); + Long userId = SecurityUtil.getCurrentUserId(); + updateUserInfoUseCase.execute(updateUserInfoRequest, userId); } @Operation(summary = "닉네임 중복체크합니다.") diff --git a/Api/src/main/java/allchive/server/api/user/service/UpdateUserInfoUseCase.java b/Api/src/main/java/allchive/server/api/user/service/UpdateUserInfoUseCase.java index dc3e8cf2..d78f0a4d 100644 --- a/Api/src/main/java/allchive/server/api/user/service/UpdateUserInfoUseCase.java +++ b/Api/src/main/java/allchive/server/api/user/service/UpdateUserInfoUseCase.java @@ -7,6 +7,8 @@ import allchive.server.core.annotation.UseCase; import allchive.server.core.event.Event; import allchive.server.core.event.events.s3.S3ImageDeleteEvent; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.user.adaptor.UserAdaptor; import allchive.server.domain.domains.user.domain.User; import allchive.server.domain.domains.user.service.UserDomainService; @@ -25,8 +27,8 @@ public class UpdateUserInfoUseCase { private final S3DeleteObjectService s3DeleteObjectService; @Transactional - public void execute(UpdateUserInfoRequest request) { - Long userId = SecurityUtil.getCurrentUserId(); + @DistributedLock(lockType = DistributedLockType.USER, identifier ={"userId"}) + public void execute(UpdateUserInfoRequest request, Long userId) { validateExecution(userId); eliminateOldImage(userId, request.getImgUrl()); userDomainService.updateUserInfo( diff --git a/Core/src/main/java/allchive/server/core/CoreApplication.java b/Core/src/main/java/allchive/server/core/CoreApplication.java new file mode 100644 index 00000000..624109a2 --- /dev/null +++ b/Core/src/main/java/allchive/server/core/CoreApplication.java @@ -0,0 +1,7 @@ +package allchive.server.core; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CoreApplication { +} diff --git a/Core/src/main/java/allchive/server/core/consts/AllchiveConst.java b/Core/src/main/java/allchive/server/core/consts/AllchiveConst.java index 97594d87..ee91f75a 100644 --- a/Core/src/main/java/allchive/server/core/consts/AllchiveConst.java +++ b/Core/src/main/java/allchive/server/core/consts/AllchiveConst.java @@ -10,8 +10,6 @@ public class AllchiveConst { public static final String REFRESH_TOKEN = "REFRESH_TOKEN"; public static final String KID = "kid"; - public static final String KR_YES = "예"; - public static final String KR_NO = "아니요"; public static final String PROD = "prod"; public static final String DEV = "dev"; @@ -47,6 +45,10 @@ public class AllchiveConst { public static final int MINUS_ONE = -1; public static final int ZERO = 0; + public static final Long LOCK_WAIT_TIME = 5L; + public static final Long LOCK_LEASE_TIME = 3L; + public static final String REDISSON_LOCK_PREFIX = "LOCK:"; + public static final int CORE_POOL_SIZE = 1; public static final int MAX_POOL_SIZE = 30; public static final int QUEUE_CAPACITY = 500; diff --git a/Core/src/main/java/allchive/server/core/error/GlobalErrorCode.java b/Core/src/main/java/allchive/server/core/error/GlobalErrorCode.java index aff876e4..69364ba0 100644 --- a/Core/src/main/java/allchive/server/core/error/GlobalErrorCode.java +++ b/Core/src/main/java/allchive/server/core/error/GlobalErrorCode.java @@ -19,6 +19,9 @@ public enum GlobalErrorCode implements BaseErrorCode { _INTERNAL_SERVER_ERROR(INTERNAL_SERVER, "GLOBAL_500_1", "서버 오류. 관리자에게 문의 부탁드립니다."), INVALID_OAUTH_PROVIDER(INTERNAL_SERVER, "GLOBAL_500_2", "지원하지 않는 OAuth Provider 입니다."), SECURITY_CONTEXT_NOT_FOUND(INTERNAL_SERVER, "GLOBAL_500_3", "security context not found"), + INVALID_LOCK_IDENTIFIER(INTERNAL_SERVER, "GLOBAL_500_4", "잘못된 lock identifier 입니다."), + ALREADY_REDISSON_UNLOCK(INTERNAL_SERVER, "GLOBAL_500_5", "Redisson Lock Already UnLock"), + INTERRUPTED_REDISSON(INTERNAL_SERVER, "GLOBAL_500_6", "Redisson interruption"), /** 토큰 에러 * */ INVALID_TOKEN(UNAUTHORIZED, "AUTH_401_2", "올바르지 않은 토큰입니다."), diff --git a/Core/src/main/java/allchive/server/core/error/exception/AlreadyRedissonUnlockException.java b/Core/src/main/java/allchive/server/core/error/exception/AlreadyRedissonUnlockException.java new file mode 100644 index 00000000..04e1e3ca --- /dev/null +++ b/Core/src/main/java/allchive/server/core/error/exception/AlreadyRedissonUnlockException.java @@ -0,0 +1,14 @@ +package allchive.server.core.error.exception; + + +import allchive.server.core.error.BaseErrorException; +import allchive.server.core.error.GlobalErrorCode; + +public class AlreadyRedissonUnlockException extends BaseErrorException { + + public static final BaseErrorException EXCEPTION = new AlreadyRedissonUnlockException(); + + private AlreadyRedissonUnlockException() { + super(GlobalErrorCode.ALREADY_REDISSON_UNLOCK); + } +} diff --git a/Core/src/main/java/allchive/server/core/error/exception/InterruptRedissonException.java b/Core/src/main/java/allchive/server/core/error/exception/InterruptRedissonException.java new file mode 100644 index 00000000..6b35c947 --- /dev/null +++ b/Core/src/main/java/allchive/server/core/error/exception/InterruptRedissonException.java @@ -0,0 +1,14 @@ +package allchive.server.core.error.exception; + + +import allchive.server.core.error.BaseErrorException; +import allchive.server.core.error.GlobalErrorCode; + +public class InterruptRedissonException extends BaseErrorException { + + public static final BaseErrorException EXCEPTION = new InterruptRedissonException(); + + private InterruptRedissonException() { + super(GlobalErrorCode.INTERRUPTED_REDISSON); + } +} diff --git a/Core/src/main/java/allchive/server/core/error/exception/InvalidLockIdentifierException.java b/Core/src/main/java/allchive/server/core/error/exception/InvalidLockIdentifierException.java new file mode 100644 index 00000000..65645fbf --- /dev/null +++ b/Core/src/main/java/allchive/server/core/error/exception/InvalidLockIdentifierException.java @@ -0,0 +1,14 @@ +package allchive.server.core.error.exception; + + +import allchive.server.core.error.BaseErrorException; +import allchive.server.core.error.GlobalErrorCode; + +public class InvalidLockIdentifierException extends BaseErrorException { + + public static final BaseErrorException EXCEPTION = new InvalidLockIdentifierException(); + + private InvalidLockIdentifierException() { + super(GlobalErrorCode.INVALID_LOCK_IDENTIFIER); + } +} diff --git a/Domain/build.gradle b/Domain/build.gradle index 81b129c8..a23fa539 100644 --- a/Domain/build.gradle +++ b/Domain/build.gradle @@ -6,6 +6,7 @@ dependencies { implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' + runtimeOnly 'com.h2database:h2' implementation project(':Core') implementation project(':Infrastructure') diff --git a/Domain/src/main/java/allchive/server/domain/DomainApplication.java b/Domain/src/main/java/allchive/server/domain/DomainApplication.java new file mode 100644 index 00000000..c187b4fc --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/DomainApplication.java @@ -0,0 +1,7 @@ +package allchive.server.domain; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DomainApplication { +} diff --git a/Domain/src/main/java/allchive/server/domain/common/aop/distributedLock/DistributedLock.java b/Domain/src/main/java/allchive/server/domain/common/aop/distributedLock/DistributedLock.java new file mode 100644 index 00000000..cadd209c --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/common/aop/distributedLock/DistributedLock.java @@ -0,0 +1,19 @@ +package allchive.server.domain.common.aop.distributedLock; + + +import allchive.server.domain.common.enums.DistributedLockType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DistributedLock { + // 락 타입 + DistributedLockType lockType(); + + // 분산락을 걸 파라미터 네임 + String[] identifier(); +} diff --git a/Domain/src/main/java/allchive/server/domain/common/aop/distributedLock/DistributedLockAop.java b/Domain/src/main/java/allchive/server/domain/common/aop/distributedLock/DistributedLockAop.java new file mode 100644 index 00000000..486997ca --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/common/aop/distributedLock/DistributedLockAop.java @@ -0,0 +1,56 @@ +package allchive.server.domain.common.aop.distributedLock; + +import static allchive.server.core.consts.AllchiveConst.*; + +import allchive.server.core.error.exception.InvalidLockIdentifierException; +import allchive.server.infrastructure.redis.lock.LockManager; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@Order(Ordered.HIGHEST_PRECEDENCE + 1) +@RequiredArgsConstructor +@Slf4j +public class DistributedLockAop { + private final LockManager lockManager; + + @Around("@annotation(allchive.server.domain.common.aop.distributedLock.DistributedLock)") + public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + DistributedLock distributedLock = method.getAnnotation(DistributedLock.class); + return lockManager.lock(joinPoint, getKey(joinPoint, distributedLock)); + } + + private String getKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + String[] methodParameterNames = methodSignature.getParameterNames(); + return REDISSON_LOCK_PREFIX + distributedLock.lockType().getLockName() + "-" + createDynamicKey(methodParameterNames, joinPoint.getArgs(), distributedLock.identifier()); + } + + private String createDynamicKey(String[] methodParameterNames, Object[] methodArgs, String[] identifiers) { + List resultList = new ArrayList<>(); + for (String identifier : identifiers) { + int indexOfKey = Arrays.asList(methodParameterNames).indexOf(identifier); + Object arg = methodArgs[indexOfKey]; + if (arg == null) { + throw InvalidLockIdentifierException.EXCEPTION; + } + resultList.add(arg.toString()); + } + return String.join(":", resultList); + } +} diff --git a/Domain/src/main/java/allchive/server/domain/common/enums/DistributedLockType.java b/Domain/src/main/java/allchive/server/domain/common/enums/DistributedLockType.java new file mode 100644 index 00000000..61ce065e --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/common/enums/DistributedLockType.java @@ -0,0 +1,21 @@ +package allchive.server.domain.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum DistributedLockType { + ARCHIVING_SCRAP("archivingScrap"), + ARCHIVING_PIN("archivingPin"), + ARCHIVING("archiving"), + + BLOCK("block"), + CONTENT("content"), + RECYCLE("recycle"), + REPORT("report"), + TAG("tag"), + USER("user"); + + private String lockName; +} diff --git a/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingAsyncDomainService.java b/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingAsyncDomainService.java index c0e1ae91..4e10fc06 100644 --- a/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingAsyncDomainService.java +++ b/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingAsyncDomainService.java @@ -2,6 +2,8 @@ import allchive.server.core.annotation.DomainService; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.enums.DistributedLockType; import allchive.server.domain.domains.content.domain.enums.ContentType; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Async; @@ -12,6 +14,7 @@ public class ArchivingAsyncDomainService { private final ArchivingDomainService archivingDomainService; @Async(value = "archivingContentCntTaskExecutor") + @DistributedLock(lockType = DistributedLockType.ARCHIVING, identifier ={"archivingId"}) public void updateContentCnt(Long archivingId, ContentType contentType, int i) { archivingDomainService.updateContentCnt(archivingId, contentType, i); } diff --git a/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java b/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java index a5e4d25b..22d6fef5 100644 --- a/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java +++ b/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java @@ -43,7 +43,6 @@ public void updatePin(Long archivingId, Long userId, boolean pin) { } else { archiving.deletePinUserId(userId); } - archiving.updateScrapCnt(pin ? PLUS_ONE : MINUS_ONE); } public void softDeleteById(Long archivingId) { diff --git a/Domain/src/main/java/allchive/server/domain/domains/recycle/repository/RecycleCustomRepositoryImpl.java b/Domain/src/main/java/allchive/server/domain/domains/recycle/repository/RecycleCustomRepositoryImpl.java index 4c6a0cff..edc9c44f 100644 --- a/Domain/src/main/java/allchive/server/domain/domains/recycle/repository/RecycleCustomRepositoryImpl.java +++ b/Domain/src/main/java/allchive/server/domain/domains/recycle/repository/RecycleCustomRepositoryImpl.java @@ -19,11 +19,17 @@ public List queryRecycleByUserIdAndArchivingIdInOrUserIdAndContentIdIn( List archivingIds, List contentIds, Long userId) { return queryFactory .selectFrom(recycle) - .where(archivingIdInOrContentIdIn(archivingIds, contentIds)) + .where(userIdEq(userId), archivingIdInOrContentIdIn(archivingIds, contentIds)) .orderBy(createdAtDesc()) .fetch(); } + private BooleanExpression userIdEq( + Long userId) { + return recycle.userId.eq(userId); + } + + private BooleanExpression archivingIdInOrContentIdIn( List archivingIdList, List contentIdList) { return recycle.archivingId.in(archivingIdList).or(recycle.contentId.in(contentIdList)); diff --git a/Domain/src/main/resources/application-domain.yml b/Domain/src/main/resources/application-domain.yml index 6ec6aaf7..574a5dd4 100644 --- a/Domain/src/main/resources/application-domain.yml +++ b/Domain/src/main/resources/application-domain.yml @@ -20,7 +20,6 @@ spring: open-in-view: false database-platform: org.hibernate.dialect.MySQL5InnoDBDialect database: mysql - --- # local spring: diff --git a/Domain/src/test/java/allchive/server/domain/CalculateCurrentExecutionSupporter.java b/Domain/src/test/java/allchive/server/domain/CalculateCurrentExecutionSupporter.java new file mode 100644 index 00000000..0e1a4c22 --- /dev/null +++ b/Domain/src/test/java/allchive/server/domain/CalculateCurrentExecutionSupporter.java @@ -0,0 +1,36 @@ +package allchive.server.domain; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.function.Executable; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +@Slf4j +public class CalculateCurrentExecutionSupporter { + static int numberOfThreads = 10; + static int numberOfThreadPool = 5; + + public static void execute(Executable executable, AtomicLong successCount) + throws InterruptedException { + ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreadPool); + CountDownLatch latch = new CountDownLatch(numberOfThreads); + for (long i = 1; i <= numberOfThreads; i++) { + executorService.submit( + () -> { + try { + executable.execute(); + successCount.getAndIncrement(); + } catch (Throwable e) { + log.info(e.toString()); + } finally { + latch.countDown(); + } + }); + } + latch.await(); + } +} + diff --git a/Domain/src/test/java/allchive/server/domain/DomainIntegrateProfileResolver.java b/Domain/src/test/java/allchive/server/domain/DomainIntegrateProfileResolver.java new file mode 100644 index 00000000..86597dcf --- /dev/null +++ b/Domain/src/test/java/allchive/server/domain/DomainIntegrateProfileResolver.java @@ -0,0 +1,13 @@ +package allchive.server.domain; + + +import org.springframework.test.context.ActiveProfilesResolver; + +public class DomainIntegrateProfileResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + // some code to find out your active profiles + return new String[] {"core", "infrastructure", "domain"}; + } +} diff --git a/Domain/src/test/java/allchive/server/domain/DomainIntegrateSpringBootTest.java b/Domain/src/test/java/allchive/server/domain/DomainIntegrateSpringBootTest.java new file mode 100644 index 00000000..bfc90513 --- /dev/null +++ b/Domain/src/test/java/allchive/server/domain/DomainIntegrateSpringBootTest.java @@ -0,0 +1,15 @@ +package allchive.server.domain; + + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.lang.annotation.*; + +/** 도메인 모듈의 통합테스트의 편의성을 위해서 만든 어노테이션 -이찬진 */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@SpringBootTest(classes = DomainIntegrateTestConfig.class) +@ActiveProfiles(resolver = DomainIntegrateProfileResolver.class) +@Documented +public @interface DomainIntegrateSpringBootTest {} diff --git a/Domain/src/test/java/allchive/server/domain/DomainIntegrateTestConfig.java b/Domain/src/test/java/allchive/server/domain/DomainIntegrateTestConfig.java new file mode 100644 index 00000000..407f6de2 --- /dev/null +++ b/Domain/src/test/java/allchive/server/domain/DomainIntegrateTestConfig.java @@ -0,0 +1,15 @@ +package allchive.server.domain; + + +import allchive.server.core.CoreApplication; +import allchive.server.infrastructure.InfrastructureApplication; +import org.springframework.context.annotation.ComponentScan; + +/** 스프링 부트 설정의 컴포넌트 스캔범위를 지정 통합 테스트를 위함 */ +@ComponentScan( + basePackageClasses = { + InfrastructureApplication.class, + DomainApplication.class, + CoreApplication.class + }) +public class DomainIntegrateTestConfig {} diff --git a/Domain/src/test/java/allchive/server/domain/common/aop/DistributedLockAopTest.java b/Domain/src/test/java/allchive/server/domain/common/aop/DistributedLockAopTest.java new file mode 100644 index 00000000..9b28e12c --- /dev/null +++ b/Domain/src/test/java/allchive/server/domain/common/aop/DistributedLockAopTest.java @@ -0,0 +1,79 @@ +package allchive.server.domain.common.aop; + + +import allchive.server.domain.CalculateCurrentExecutionSupporter; +import allchive.server.domain.common.aop.distributedLock.DistributedLock; +import allchive.server.domain.common.aop.distributedLock.DistributedLockAop; +import allchive.server.domain.common.enums.DistributedLockType; +import allchive.server.infrastructure.redis.lock.LockManager; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.stereotype.Service; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.concurrent.atomic.AtomicLong; + +import static org.assertj.core.api.Assertions.assertThat; + +@Slf4j +@SpringBootTest +@ExtendWith(SpringExtension.class) +public class DistributedLockAopTest { + @MockBean + LockManager lockManager; + DistributedLockAop distributedLockAop; + + private final RedissonService redissonService; + + @Autowired + public DistributedLockAopTest(RedissonService redissonService) { + this.redissonService = redissonService; + } + + @BeforeEach + public void beforeEach() { + distributedLockAop = new DistributedLockAop(lockManager); + } + + @Test + void 분산락_적용_동시성_테스트() throws InterruptedException { + AtomicLong successCount = new AtomicLong(); + CalculateCurrentExecutionSupporter.execute( + () -> redissonService.testWithLock(1), + successCount + ); + assertThat(redissonService.apply).isEqualTo((int) successCount.get()); + } + + @Test + void 분산락_미적용_동시성_테스트() throws InterruptedException { + AtomicLong successCount = new AtomicLong(); + CalculateCurrentExecutionSupporter.execute( + () -> redissonService.testWithoutLock(1), + successCount + ); + assertThat(redissonService.apply).isNotEqualTo((int) successCount.get()); + } +} + +@Service +class RedissonService { + int apply = 0; + + @DistributedLock(lockType = DistributedLockType.ARCHIVING_SCRAP, identifier = "archivingId") + void testWithLock(int id) { + apply++; + } + + void testWithoutLock(int id) { + apply++; + } +} + + + diff --git a/Infrastructure/src/main/java/allchive/server/infrastructure/InfrastructureApplication.java b/Infrastructure/src/main/java/allchive/server/infrastructure/InfrastructureApplication.java new file mode 100644 index 00000000..35e23ec0 --- /dev/null +++ b/Infrastructure/src/main/java/allchive/server/infrastructure/InfrastructureApplication.java @@ -0,0 +1,7 @@ +package allchive.server.infrastructure; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class InfrastructureApplication { +} diff --git a/Infrastructure/src/main/java/allchive/server/infrastructure/redis/lock/LockManager.java b/Infrastructure/src/main/java/allchive/server/infrastructure/redis/lock/LockManager.java new file mode 100644 index 00000000..5e0d8885 --- /dev/null +++ b/Infrastructure/src/main/java/allchive/server/infrastructure/redis/lock/LockManager.java @@ -0,0 +1,10 @@ +package allchive.server.infrastructure.redis.lock; + + +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.stereotype.Component; + +@Component +public interface LockManager { + public Object lock(ProceedingJoinPoint joinPoint, String key) throws Throwable; +} diff --git a/Infrastructure/src/main/java/allchive/server/infrastructure/redis/lock/LockManagerImpl.java b/Infrastructure/src/main/java/allchive/server/infrastructure/redis/lock/LockManagerImpl.java new file mode 100644 index 00000000..cc047107 --- /dev/null +++ b/Infrastructure/src/main/java/allchive/server/infrastructure/redis/lock/LockManagerImpl.java @@ -0,0 +1,44 @@ +package allchive.server.infrastructure.redis.lock; + +import static allchive.server.core.consts.AllchiveConst.LOCK_LEASE_TIME; +import static allchive.server.core.consts.AllchiveConst.LOCK_WAIT_TIME; + +import allchive.server.core.error.exception.AlreadyRedissonUnlockException; +import allchive.server.core.error.exception.InterruptRedissonException; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class LockManagerImpl implements LockManager { + private final RedissonClient redissonClient; + + @Transactional(propagation = Propagation.NEVER) + public Object lock(ProceedingJoinPoint joinPoint, String key) throws Throwable { + log.info("lock key : {}", key); + RLock rLock = redissonClient.getLock(key); + try { + boolean available = rLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); + if (!available) { + return false; + } + return joinPoint.proceed(); + } catch (InterruptedException e) { + throw InterruptRedissonException.EXCEPTION; + } finally { + try { + rLock.unlock(); + } catch (IllegalMonitorStateException e) { + throw AlreadyRedissonUnlockException.EXCEPTION; + } + } + } +} diff --git a/build.gradle b/build.gradle index 6c665dda..3bcba069 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,6 @@ subprojects { test { useJUnitPlatform() - finalizedBy jacocoTestReport } }