Skip to content

Commit

Permalink
Merge pull request #191 from fresh-trash-project/feature/#190-refacto…
Browse files Browse the repository at this point in the history
…r-user-caching
  • Loading branch information
JadeKim042386 authored Jul 27, 2024
2 parents e64584d + 2c115b6 commit 4b71835
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("memberCache", configuration);

return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory)
.cacheDefaults(configuration)
.withInitialCacheConfigurations(redisCacheConfigurationMap)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public ResponseEntity<AuctionResponse> addAuction(
@DeleteMapping("/{auctionId}")
public ResponseEntity<Void> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public ResponseEntity<ProductResponse> updateProduct(
public ResponseEntity<Void> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,24 @@
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;
import org.springframework.security.oauth2.core.user.OAuth2User;

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<? extends GrantedAuthority> authorities,
UserRole userRole,
String nickname,
double rating,
String fileName,
Expand All @@ -38,78 +36,71 @@ public MemberPrincipalBuilder authorities(UserRole userRole) {
}
}

@JsonIgnore
public static MemberPrincipal fromEntity(Member member) {
return MemberPrincipal.builder()
.id(member.getId())
.email(member.getEmail())
.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
@Override
public Map<String, Object> getAttributes() {
return oAuth2Attributes;
}

@JsonIgnore
@Override
public String getName() {
return email;
}

@Override
public Collection<? extends GrantedAuthority> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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));
}

/**
Expand All @@ -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);
}

Expand All @@ -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());
Expand All @@ -116,7 +114,6 @@ public Member updateMember(MemberPrincipal memberPrincipal, MemberRequest member
else {
member.setFileName(null);
}
memberCacheRepository.save(MemberPrincipal.fromEntity(member));

return member;
}
Expand Down Expand Up @@ -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) {}

/**
* 비밀번호 일치 확인
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -112,11 +112,9 @@ void given_EmailAndPassword_when_generateAccessToken_then_saveCacheAndReturnToke
String email = "[email protected]", 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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 4b71835

Please sign in to comment.