From 4b60efd816f04f031fcca3e9f5a2d0cc594b6a7b Mon Sep 17 00:00:00 2001 From: JadeKim042386 Date: Sat, 27 Jul 2024 17:33:53 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20RedisCacheManager=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=98=EC=97=AC=20Hikaricp=20Connection?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-=20TTL=EC=9D=84=201?= =?UTF-8?q?=EC=9D=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95=ED=95=98=EA=B3=A0=20id?= =?UTF-8?q?=EC=99=80=20=EC=9C=A0=EC=A0=80=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=81=EA=B0=81=20key=EC=99=80=20value=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20-=20MemberPrincipal=EC=97=90=20UserRole=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20aut?= =?UTF-8?q?horities=20=EB=8C=80=EC=8B=A0=20=EC=BA=90=EC=8B=B1=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B0=98=EC=98=81=20-=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=A0=90=20=EB=AA=A8=EB=91=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FreshTrashBackendApplication.java | 2 + .../freshtrashbackend/config/RedisConfig.java | 33 ++++++++++++- .../controller/AuctionController.java | 2 +- .../controller/ProductController.java | 2 +- .../dto/security/MemberPrincipal.java | 47 +++++++++---------- .../service/MemberService.java | 16 +++---- .../service/ProductService.java | 2 +- 7 files changed, 66 insertions(+), 38 deletions(-) diff --git a/src/main/java/freshtrash/freshtrashbackend/FreshTrashBackendApplication.java b/src/main/java/freshtrash/freshtrashbackend/FreshTrashBackendApplication.java index 1783da9a..b48fe81d 100644 --- a/src/main/java/freshtrash/freshtrashbackend/FreshTrashBackendApplication.java +++ b/src/main/java/freshtrash/freshtrashbackend/FreshTrashBackendApplication.java @@ -3,10 +3,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.retry.annotation.EnableRetry; import org.springframework.scheduling.annotation.EnableScheduling; @EnableRetry +@EnableCaching @EnableScheduling @SpringBootApplication @ConfigurationPropertiesScan diff --git a/src/main/java/freshtrash/freshtrashbackend/config/RedisConfig.java b/src/main/java/freshtrash/freshtrashbackend/config/RedisConfig.java index affc6b05..8a0755fc 100644 --- a/src/main/java/freshtrash/freshtrashbackend/config/RedisConfig.java +++ b/src/main/java/freshtrash/freshtrashbackend/config/RedisConfig.java @@ -2,13 +2,23 @@ import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.converter.Converter; +import org.springframework.data.redis.cache.CacheKeyPrefix; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; @Configuration @EnableRedisRepositories @@ -21,4 +31,25 @@ public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory( new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort())); } + + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { + + RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues() + .entryTtl(Duration.ofDays(1L)) + .computePrefixWith(CacheKeyPrefix.simple()) + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer())); + + Map redisCacheConfigurationMap = new HashMap<>(); + redisCacheConfigurationMap.put("memberCache", configuration); + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory) + .cacheDefaults(configuration) + .withInitialCacheConfigurations(redisCacheConfigurationMap) + .build(); + } } diff --git a/src/main/java/freshtrash/freshtrashbackend/controller/AuctionController.java b/src/main/java/freshtrash/freshtrashbackend/controller/AuctionController.java index 36dac424..abc620f9 100644 --- a/src/main/java/freshtrash/freshtrashbackend/controller/AuctionController.java +++ b/src/main/java/freshtrash/freshtrashbackend/controller/AuctionController.java @@ -69,7 +69,7 @@ public ResponseEntity addAuction( @DeleteMapping("/{auctionId}") public ResponseEntity cancelAuction( @PathVariable Long auctionId, @AuthenticationPrincipal MemberPrincipal memberPrincipal) { - auctionEventService.cancelAuction(auctionId, memberPrincipal.getUserRole(), memberPrincipal.id()); + auctionEventService.cancelAuction(auctionId, memberPrincipal.userRole(), memberPrincipal.id()); return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); } diff --git a/src/main/java/freshtrash/freshtrashbackend/controller/ProductController.java b/src/main/java/freshtrash/freshtrashbackend/controller/ProductController.java index 42869cb9..568365fc 100644 --- a/src/main/java/freshtrash/freshtrashbackend/controller/ProductController.java +++ b/src/main/java/freshtrash/freshtrashbackend/controller/ProductController.java @@ -92,7 +92,7 @@ public ResponseEntity updateProduct( public ResponseEntity deleteProduct( @PathVariable Long productId, @AuthenticationPrincipal MemberPrincipal memberPrincipal) { String savedFileName = productService.findFileNameOfProduct(productId).fileName(); - productService.deleteProduct(productId, memberPrincipal.getUserRole(), memberPrincipal.id()); + productService.deleteProduct(productId, memberPrincipal.userRole(), memberPrincipal.id()); fileService.deleteFileIfExists(savedFileName); return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); diff --git a/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java b/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java index 81587eab..05bae2fc 100644 --- a/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java +++ b/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java @@ -4,10 +4,7 @@ import freshtrash.freshtrashbackend.entity.Address; import freshtrash.freshtrashbackend.entity.Member; import freshtrash.freshtrashbackend.entity.constants.UserRole; -import freshtrash.freshtrashbackend.security.TokenInfo; import lombok.Builder; -import org.springframework.data.annotation.Id; -import org.springframework.data.redis.core.RedisHash; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -15,15 +12,16 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.Set; @Builder -@RedisHash(value = "Member", timeToLive = 24 * 60 * 60) public record MemberPrincipal( - @Id Long id, + Long id, String email, @JsonIgnore String password, @JsonIgnore Collection authorities, + UserRole userRole, String nickname, double rating, String fileName, @@ -38,6 +36,7 @@ public MemberPrincipalBuilder authorities(UserRole userRole) { } } + @JsonIgnore public static MemberPrincipal fromEntity(Member member) { return MemberPrincipal.builder() .id(member.getId()) @@ -45,36 +44,28 @@ public static MemberPrincipal fromEntity(Member member) { .password(member.getPassword()) .nickname(member.getNickname()) .authorities(member.getUserRole()) + .userRole(member.getUserRole()) .rating(member.getRating()) .fileName(member.getFileName()) .address(member.getAddress()) .build(); } - public static MemberPrincipal fromTokenInfo(TokenInfo tokenInfo) { - return MemberPrincipal.builder() - .id(tokenInfo.id()) - .email(tokenInfo.email()) - .nickname(tokenInfo.fileName()) - .authorities(tokenInfo.userRole()) - .rating(tokenInfo.rating()) - .fileName(tokenInfo.fileName()) - .address(tokenInfo.address()) - .build(); - } - - public UserRole getUserRole() { - return authorities.stream() - .map(r -> UserRole.valueOf(r.getAuthority().substring(5))) - .findFirst() - .orElse(UserRole.ANONYMOUS); - } +// @JsonIgnore +// public UserRole getUserRole() { +// return authorities.stream() +// .map(r -> UserRole.valueOf(r.getAuthority().substring(5))) +// .findFirst() +// .orElse(UserRole.ANONYMOUS); +// } + @JsonIgnore @Override public Map getAttributes() { return oAuth2Attributes; } + @JsonIgnore @Override public String getName() { return email; @@ -82,34 +73,42 @@ public String getName() { @Override public Collection getAuthorities() { - return authorities; + return Objects.isNull(authorities) || authorities.isEmpty() + ? Set.of(new SimpleGrantedAuthority(userRole.getName())) + : authorities; } + @JsonIgnore @Override public String getPassword() { return password; } + @JsonIgnore @Override public String getUsername() { return email; } + @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } + @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } + @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } + @JsonIgnore @Override public boolean isEnabled() { return true; diff --git a/src/main/java/freshtrash/freshtrashbackend/service/MemberService.java b/src/main/java/freshtrash/freshtrashbackend/service/MemberService.java index 23b84068..6f9ace5d 100644 --- a/src/main/java/freshtrash/freshtrashbackend/service/MemberService.java +++ b/src/main/java/freshtrash/freshtrashbackend/service/MemberService.java @@ -10,13 +10,13 @@ import freshtrash.freshtrashbackend.exception.AuthException; import freshtrash.freshtrashbackend.exception.MemberException; import freshtrash.freshtrashbackend.exception.constants.ErrorCode; -import freshtrash.freshtrashbackend.repository.MemberCacheRepository; import freshtrash.freshtrashbackend.repository.MemberRepository; import freshtrash.freshtrashbackend.security.TokenProvider; import freshtrash.freshtrashbackend.utils.FileUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,7 +29,6 @@ @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; - private final MemberCacheRepository memberCacheRepository; private final PasswordEncoder encoder; private final TokenProvider tokenProvider; private final FileService fileService; @@ -42,10 +41,9 @@ public Member getMemberById(Long memberId) { return memberRepository.findById(memberId).orElseThrow(() -> new MemberException(ErrorCode.NOT_FOUND_MEMBER)); } + @Cacheable(value = "memberCache", key = "#memberId") public MemberPrincipal getMemberCache(Long memberId) { - return memberCacheRepository - .findById(memberId) - .orElseGet(() -> MemberPrincipal.fromEntity(getMemberById(memberId))); + return MemberPrincipal.fromEntity(getMemberById(memberId)); } /** @@ -67,7 +65,6 @@ public LoginResponse signIn(String email, String password) { // 토큰 발급 String accessToken = tokenProvider.generateAccessToken(member.getId()); - memberCacheRepository.save(MemberPrincipal.fromEntity(member)); return LoginResponse.of(accessToken); } @@ -93,6 +90,7 @@ public void checkNicknameDuplication(String nickname) { * member 정보 수정 */ @Transactional + @CacheEvict(value = "memberCache", key = "#memberPrincipal.id") public Member updateMember(MemberPrincipal memberPrincipal, MemberRequest memberRequest, MultipartFile imgFile) { if (!Objects.equals(memberPrincipal.nickname(), memberRequest.nickname())) { checkNicknameDuplication(memberRequest.nickname()); @@ -116,7 +114,6 @@ public Member updateMember(MemberPrincipal memberPrincipal, MemberRequest member else { member.setFileName(null); } - memberCacheRepository.save(MemberPrincipal.fromEntity(member)); return member; } @@ -148,9 +145,8 @@ public void changePassword(ChangePasswordRequest changePasswordRequest, MemberPr updatePassword(memberPrincipal.email(), changePasswordRequest.newPassword()); } - public void logout(Long memberId) { - memberCacheRepository.deleteById(memberId); - } + @CacheEvict(value = "memberCache", key = "#memberId") + public void logout(Long memberId) {} /** * 비밀번호 일치 확인 diff --git a/src/main/java/freshtrash/freshtrashbackend/service/ProductService.java b/src/main/java/freshtrash/freshtrashbackend/service/ProductService.java index 84f59eec..b2c0a15b 100644 --- a/src/main/java/freshtrash/freshtrashbackend/service/ProductService.java +++ b/src/main/java/freshtrash/freshtrashbackend/service/ProductService.java @@ -54,7 +54,7 @@ public ProductResponse addProduct( @Transactional public ProductResponse updateProduct( Long productId, MultipartFile imgFile, ProductRequest productRequest, MemberPrincipal memberPrincipal) { - checkIfWriterOrAdmin(productId, memberPrincipal.getUserRole(), memberPrincipal.id()); + checkIfWriterOrAdmin(productId, memberPrincipal.userRole(), memberPrincipal.id()); if (!FileUtils.isValid(imgFile)) { throw new FileException(ErrorCode.INVALID_FIlE); } From 2424ed8f62dc955877a641410bf566558b4d4355 Mon Sep 17 00:00:00 2001 From: JadeKim042386 Date: Sat, 27 Jul 2024 17:45:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20-=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A0=95=EB=B3=B4=20=EC=BA=90=EC=8B=B1=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=A9=B4?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD=EB=90=9C=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../freshtrashbackend/service/MemberServiceTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/test/java/freshtrash/freshtrashbackend/service/MemberServiceTest.java b/src/test/java/freshtrash/freshtrashbackend/service/MemberServiceTest.java index 0a698145..25e3eaa4 100644 --- a/src/test/java/freshtrash/freshtrashbackend/service/MemberServiceTest.java +++ b/src/test/java/freshtrash/freshtrashbackend/service/MemberServiceTest.java @@ -82,7 +82,7 @@ void given_email_when_getMember_then_returnMember() { void given_memberId_when_getMemberCache_then_returnMemberPrincipal() { // given Long memberId = 1L; - given(memberCacheRepository.findById(memberId)).willReturn(Optional.of(FixtureDto.createMemberPrincipal())); + given(memberRepository.findById(memberId)).willReturn(Optional.of(Fixture.createMember())); // when MemberPrincipal memberPrincipal = memberService.getMemberCache(memberId); // then @@ -112,11 +112,9 @@ void given_EmailAndPassword_when_generateAccessToken_then_saveCacheAndReturnToke String email = "testUser@gmail.com", password = "pw"; Member member = Fixture.createMember(); String accessToken = "accessToken"; - MemberPrincipal memberPrincipal = MemberPrincipal.fromEntity(member); given(memberRepository.findByEmail(email)).willReturn(Optional.of(member)); given(encoder.matches(password, member.getPassword())).willReturn(true); given(tokenProvider.generateAccessToken(member.getId())).willReturn(accessToken); - given(memberCacheRepository.save(memberPrincipal)).willReturn(memberPrincipal); // when LoginResponse loginResponse = memberService.signIn(email, password); // then @@ -180,7 +178,6 @@ void given_memberRequestAndImgFile_when_updateMember_then_memberRequestEqualsToU MockMultipartFile imgFile = Fixture.createMultipartFileOfImage("test_image_content"); MemberRequest memberRequest = FixtureDto.createMemberRequest(); given(memberRepository.findById(memberId)).willReturn(Optional.of(Fixture.createMember())); - given(memberCacheRepository.save(any(MemberPrincipal.class))).willReturn(memberPrincipal); willDoNothing().given(fileService).uploadFile(eq(imgFile), anyString()); // when Member member = memberService.updateMember(memberPrincipal, memberRequest, imgFile); @@ -270,7 +267,6 @@ void given_changePasswordRequestAndPrincipal_when_unmatchedOldPassword_then_thro void given_memberId_when_then_deleteUserCache() { //given Long memberId = 123L; - willDoNothing().given(memberCacheRepository).deleteById(memberId); //when assertThatCode(() -> memberService.logout(memberId)).doesNotThrowAnyException(); //then From 2c115b60663256e7c384a42bcfca8d515582576a Mon Sep 17 00:00:00 2001 From: JadeKim042386 Date: Sat, 27 Jul 2024 17:50:37 +0900 Subject: [PATCH 3/3] =?UTF-8?q?style:=20MemberPrincipal=EC=97=90=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../freshtrashbackend/dto/security/MemberPrincipal.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java b/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java index 05bae2fc..131a974e 100644 --- a/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java +++ b/src/main/java/freshtrash/freshtrashbackend/dto/security/MemberPrincipal.java @@ -51,14 +51,6 @@ public static MemberPrincipal fromEntity(Member member) { .build(); } -// @JsonIgnore -// public UserRole getUserRole() { -// return authorities.stream() -// .map(r -> UserRole.valueOf(r.getAuthority().substring(5))) -// .findFirst() -// .orElse(UserRole.ANONYMOUS); -// } - @JsonIgnore @Override public Map getAttributes() {