Skip to content

Commit 9e58920

Browse files
authored
Merge pull request #805 from sopt-makers/fix/#804
fix: 로그인 사용자 동기화 캐시 미적용 문제 해결
2 parents d860d81 + 3b2fe82 commit 9e58920

8 files changed

Lines changed: 365 additions & 44 deletions
Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package org.sopt.makers.crew.main.auth.v2.service;
22

33
import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto;
4-
import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository;
54
import org.sopt.makers.crew.main.entity.user.User;
65
import org.sopt.makers.crew.main.entity.user.UserRepository;
76
import org.sopt.makers.crew.main.external.auth.AuthService;
87
import org.sopt.makers.crew.main.external.auth.dto.request.AuthUserRequestDto;
98
import org.sopt.makers.crew.main.external.auth.dto.response.AuthUserResponseDto;
10-
import org.sopt.makers.crew.main.global.dto.OrgIdListDto;
11-
import org.springframework.cache.annotation.CacheEvict;
12-
import org.springframework.cache.annotation.CachePut;
13-
import org.springframework.cache.annotation.Caching;
149
import org.springframework.stereotype.Service;
1510
import org.springframework.transaction.annotation.Transactional;
1611

@@ -24,9 +19,8 @@
2419
public class AuthV2ServiceImpl implements AuthV2Service {
2520

2621
private final UserRepository userRepository;
27-
private final CoLeaderRepository coLeaderRepository;
28-
2922
private final AuthService authService;
23+
private final AuthV2UserCacheInvalidationService authV2UserCacheInvalidationService;
3024

3125
@Override
3226
@Transactional
@@ -38,7 +32,7 @@ public AuthV2ResponseDto loginUser(Integer userId) {
3832
.orElseGet(() -> signUpNewUser(responseDto));
3933

4034
if (updateUserIfChanged(curUser, responseDto)) {
41-
clearCacheForUser(curUser.getId());
35+
authV2UserCacheInvalidationService.refreshCachesAfterUserUpdate(curUser.getId());
4236
}
4337

4438
log.info("로그인 완료: userId={}", curUser.getId());
@@ -59,7 +53,7 @@ private User signUpNewUser(AuthUserResponseDto responseDto) {
5953
User savedUser = userRepository.save(newUser);
6054
log.info("New user signup: {} {}", savedUser.getId(), savedUser.getName());
6155

62-
updateOrgIds();
56+
authV2UserCacheInvalidationService.refreshCachesAfterUserCreate();
6357

6458
return savedUser;
6559
}
@@ -75,39 +69,4 @@ private boolean updateUserIfChanged(User curUser, AuthUserResponseDto responseDt
7569
return isUpdated;
7670
}
7771

78-
private void clearCacheForUser(Integer userId) {
79-
clearCacheForLeader(userId);
80-
81-
coLeaderRepository.findAllByUserIdWithMeeting(userId).forEach(
82-
coLeader -> clearCacheForCoLeader(coLeader.getMeeting().getId())
83-
);
84-
85-
updateOrgIds();
86-
87-
log.info("Cache cleared for user: {}", userId);
88-
}
89-
90-
@Caching(evict = {
91-
@CacheEvict(value = "meetingLeaderCache", key = "#userId")
92-
})
93-
public void clearCacheForLeader(Integer userId) {
94-
95-
}
96-
97-
@Caching(evict = {
98-
@CacheEvict(value = "coLeadersCache", key = "#meetingId")
99-
})
100-
public void clearCacheForCoLeader(Integer meetingId) {
101-
102-
}
103-
104-
@CachePut(value = "orgIdCache", key = "'allOrgIds'")
105-
public OrgIdListDto updateOrgIds() {
106-
OrgIdListDto latestOrgIds = OrgIdListDto.of(userRepository.findAllOrgIds());
107-
108-
log.info("OrgId cache updated: {}", latestOrgIds);
109-
110-
return latestOrgIds;
111-
}
112-
11372
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.sopt.makers.crew.main.auth.v2.service;
2+
3+
import org.sopt.makers.crew.main.global.dto.OrgIdListDto;
4+
5+
public interface AuthV2UserCacheCommandService {
6+
void evictMeetingLeaderCache(Integer userId);
7+
8+
void evictCoLeadersCache(Integer meetingId);
9+
10+
OrgIdListDto refreshOrgIdCache();
11+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.sopt.makers.crew.main.auth.v2.service;
2+
3+
import org.sopt.makers.crew.main.entity.user.UserRepository;
4+
import org.sopt.makers.crew.main.global.dto.OrgIdListDto;
5+
import org.springframework.cache.annotation.CacheEvict;
6+
import org.springframework.cache.annotation.CachePut;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.transaction.annotation.Transactional;
9+
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
13+
@Service
14+
@RequiredArgsConstructor
15+
@Transactional(readOnly = true)
16+
@Slf4j
17+
public class AuthV2UserCacheCommandServiceImpl implements AuthV2UserCacheCommandService {
18+
19+
private final UserRepository userRepository;
20+
21+
@Override
22+
@CacheEvict(value = "meetingLeaderCache", key = "#userId")
23+
public void evictMeetingLeaderCache(Integer userId) {
24+
}
25+
26+
@Override
27+
@CacheEvict(value = "coLeadersCache", key = "#meetingId")
28+
public void evictCoLeadersCache(Integer meetingId) {
29+
}
30+
31+
@Override
32+
@CachePut(value = "orgIdCache", key = "'allOrgIds'")
33+
public OrgIdListDto refreshOrgIdCache() {
34+
OrgIdListDto latestOrgIds = OrgIdListDto.of(userRepository.findAllOrgIds());
35+
36+
log.info("orgId 캐시 갱신 완료: {}", latestOrgIds);
37+
38+
return latestOrgIds;
39+
}
40+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.sopt.makers.crew.main.auth.v2.service;
2+
3+
public interface AuthV2UserCacheInvalidationService {
4+
void refreshCachesAfterUserUpdate(Integer userId);
5+
6+
void refreshCachesAfterUserCreate();
7+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.sopt.makers.crew.main.auth.v2.service;
2+
3+
import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository;
4+
import org.springframework.stereotype.Service;
5+
import org.springframework.transaction.annotation.Transactional;
6+
7+
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
10+
@Service
11+
@RequiredArgsConstructor
12+
@Transactional(readOnly = true)
13+
@Slf4j
14+
public class AuthV2UserCacheInvalidationServiceImpl implements AuthV2UserCacheInvalidationService {
15+
16+
private final CoLeaderRepository coLeaderRepository;
17+
private final AuthV2UserCacheCommandService authV2UserCacheCommandService;
18+
19+
@Override
20+
public void refreshCachesAfterUserUpdate(Integer userId) {
21+
authV2UserCacheCommandService.evictMeetingLeaderCache(userId);
22+
23+
coLeaderRepository.findAllByUserIdWithMeeting(userId).stream()
24+
.map(coLeader -> coLeader.getMeeting().getId())
25+
.distinct()
26+
.forEach(authV2UserCacheCommandService::evictCoLeadersCache);
27+
28+
authV2UserCacheCommandService.refreshOrgIdCache();
29+
30+
log.info("사용자 정보 변경 후 캐시 갱신 완료: userId={}", userId);
31+
}
32+
33+
@Override
34+
public void refreshCachesAfterUserCreate() {
35+
authV2UserCacheCommandService.refreshOrgIdCache();
36+
log.info("신규 사용자 생성 후 orgId 캐시 갱신 완료");
37+
}
38+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package org.sopt.makers.crew.main.auth.v2.service;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
import static org.mockito.ArgumentMatchers.*;
5+
import static org.mockito.Mockito.*;
6+
7+
import java.util.List;
8+
import java.util.Optional;
9+
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Nested;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.ExtendWith;
14+
import org.mockito.InjectMocks;
15+
import org.mockito.Mock;
16+
import org.mockito.junit.jupiter.MockitoExtension;
17+
import org.sopt.makers.crew.main.entity.user.User;
18+
import org.sopt.makers.crew.main.entity.user.UserFixture;
19+
import org.sopt.makers.crew.main.entity.user.UserRepository;
20+
import org.sopt.makers.crew.main.external.auth.AuthService;
21+
import org.sopt.makers.crew.main.external.auth.dto.request.AuthUserRequestDto;
22+
import org.sopt.makers.crew.main.external.auth.dto.response.AuthUserActivityResponseDto;
23+
import org.sopt.makers.crew.main.external.auth.dto.response.AuthUserResponseDto;
24+
25+
@ExtendWith(MockitoExtension.class)
26+
class AuthV2ServiceTest {
27+
28+
@InjectMocks
29+
private AuthV2ServiceImpl authV2Service;
30+
31+
@Mock
32+
private UserRepository userRepository;
33+
34+
@Mock
35+
private AuthService authService;
36+
37+
@Mock
38+
private AuthV2UserCacheInvalidationService authV2UserCacheInvalidationService;
39+
40+
@Nested
41+
class 로그인 {
42+
@Test
43+
@DisplayName("기존 유저 정보가 변경되면 cache invalidation service에 위임한다")
44+
void 기존_유저_정보가_변경되면_cache_invalidation_service에_위임한다() {
45+
// given
46+
User user = UserFixture.createStaticUser();
47+
user.setUserIdForTest(1);
48+
49+
AuthUserResponseDto responseDto = createAuthUserResponseDto(1, "변경된테스트유저", "010-1111-1111", null);
50+
51+
doReturn(responseDto).when(authService).getAuthUser(any(AuthUserRequestDto.class));
52+
doReturn(Optional.of(user)).when(userRepository).findById(1);
53+
54+
// when
55+
Integer loginUserId = authV2Service.loginUser(1).userId();
56+
57+
// then
58+
assertThat(loginUserId).isEqualTo(1);
59+
verify(authV2UserCacheInvalidationService).refreshCachesAfterUserUpdate(1);
60+
verify(authV2UserCacheInvalidationService, never()).refreshCachesAfterUserCreate();
61+
}
62+
63+
@Test
64+
@DisplayName("기존 유저 정보가 변경되지 않으면 cache invalidation service를 호출하지 않는다")
65+
void 기존_유저_정보가_변경되지_않으면_cache_invalidation_service를_호출하지_않는다() {
66+
// given
67+
User user = UserFixture.createStaticUser();
68+
user.setUserIdForTest(1);
69+
70+
AuthUserResponseDto responseDto = createAuthUserResponseDto(
71+
1,
72+
user.getName(),
73+
user.getPhone(),
74+
user.getProfileImage()
75+
);
76+
77+
doReturn(responseDto).when(authService).getAuthUser(any(AuthUserRequestDto.class));
78+
doReturn(Optional.of(user)).when(userRepository).findById(1);
79+
80+
// when
81+
Integer loginUserId = authV2Service.loginUser(1).userId();
82+
83+
// then
84+
assertThat(loginUserId).isEqualTo(1);
85+
verifyNoInteractions(authV2UserCacheInvalidationService);
86+
}
87+
88+
@Test
89+
@DisplayName("신규 유저 가입 시 orgId cache 갱신을 위임한다")
90+
void 신규_유저_가입시_orgId_cache_갱신을_위임한다() {
91+
// given
92+
AuthUserResponseDto responseDto = createAuthUserResponseDto(7, "신규테스트유저", "010-7777-7777", "profile-image");
93+
User savedUser = responseDto.toEntity();
94+
95+
doReturn(responseDto).when(authService).getAuthUser(any(AuthUserRequestDto.class));
96+
doReturn(Optional.empty()).when(userRepository).findById(7);
97+
doReturn(savedUser).when(userRepository).save(any(User.class));
98+
99+
// when
100+
Integer loginUserId = authV2Service.loginUser(7).userId();
101+
102+
// then
103+
assertThat(loginUserId).isEqualTo(7);
104+
verify(authV2UserCacheInvalidationService).refreshCachesAfterUserCreate();
105+
verify(authV2UserCacheInvalidationService, never()).refreshCachesAfterUserUpdate(anyInt());
106+
}
107+
}
108+
109+
private AuthUserResponseDto createAuthUserResponseDto(Integer userId, String name, String phone,
110+
String profileImage) {
111+
return new AuthUserResponseDto(
112+
userId,
113+
name,
114+
profileImage,
115+
"1998-01-01",
116+
phone,
117+
"test@sopt.org",
118+
34,
119+
List.of(
120+
new AuthUserActivityResponseDto(1L, 33, "서버", "YB"),
121+
new AuthUserActivityResponseDto(2L, 34, "서버", "OB")
122+
)
123+
);
124+
}
125+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.sopt.makers.crew.main.auth.v2.service;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
import static org.mockito.Mockito.*;
5+
6+
import java.util.List;
7+
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.DisplayName;
10+
import org.junit.jupiter.api.Test;
11+
import org.sopt.makers.crew.main.external.CaffeineTestConfig;
12+
import org.sopt.makers.crew.main.external.caffeine.CaffeineConfig;
13+
import org.sopt.makers.crew.main.global.dto.OrgIdListDto;
14+
import org.sopt.makers.crew.main.entity.user.UserRepository;
15+
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.springframework.boot.test.context.SpringBootTest;
17+
import org.springframework.boot.test.mock.mockito.MockBean;
18+
import org.springframework.cache.Cache;
19+
import org.springframework.cache.CacheManager;
20+
import org.springframework.context.annotation.Import;
21+
import org.springframework.test.context.ActiveProfiles;
22+
23+
@SpringBootTest(classes = AuthV2UserCacheCommandServiceImpl.class)
24+
@Import({CaffeineConfig.class, CaffeineTestConfig.class})
25+
@ActiveProfiles("test")
26+
class AuthV2UserCacheCommandServiceIntegrationTest {
27+
28+
@Autowired
29+
private AuthV2UserCacheCommandService authV2UserCacheCommandService;
30+
31+
@Autowired
32+
private CacheManager cacheManager;
33+
34+
@MockBean
35+
private UserRepository userRepository;
36+
37+
@BeforeEach
38+
void setUp() {
39+
cacheManager.getCacheNames().forEach(cacheName -> cacheManager.getCache(cacheName).clear());
40+
}
41+
42+
@Test
43+
@DisplayName("meetingLeader cache eviction 테스트")
44+
void meetingLeader_cache_eviction이_프록시를_통해_실제_적용된다() {
45+
Cache cache = cacheManager.getCache("meetingLeaderCache");
46+
cache.put(1, "leader");
47+
48+
authV2UserCacheCommandService.evictMeetingLeaderCache(1);
49+
50+
assertThat(cache.get(1)).isNull();
51+
}
52+
53+
@Test
54+
@DisplayName("coLeaders cache eviction 테스트")
55+
void coLeaders_cache_eviction이_프록시를_통해_실제_적용된다() {
56+
Cache cache = cacheManager.getCache("coLeadersCache");
57+
cache.put(10, "co-leaders");
58+
59+
authV2UserCacheCommandService.evictCoLeadersCache(10);
60+
61+
assertThat(cache.get(10)).isNull();
62+
}
63+
64+
@Test
65+
@DisplayName("orgId cache put 테스트")
66+
void orgId_cache_put이_프록시를_통해_실제_적용된다() {
67+
doReturn(List.of(1, 2, 3)).when(userRepository).findAllOrgIds();
68+
69+
OrgIdListDto result = authV2UserCacheCommandService.refreshOrgIdCache();
70+
Cache.ValueWrapper cachedValue = cacheManager.getCache("orgIdCache").get("allOrgIds");
71+
72+
assertThat(result.getOrgIds()).containsExactly(1, 2, 3);
73+
assertThat(cachedValue).isNotNull();
74+
assertThat(((OrgIdListDto)cachedValue.get()).getOrgIds()).containsExactly(1, 2, 3);
75+
}
76+
}

0 commit comments

Comments
 (0)