Skip to content

Commit ab907f8

Browse files
committed
Fix: scheduler 레디스에 맞게
1 parent 1d9f6e9 commit ab907f8

File tree

8 files changed

+130
-127
lines changed

8 files changed

+130
-127
lines changed

back/src/main/java/com/back/BackApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
66
import org.springframework.cache.annotation.EnableCaching;
77
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
8+
import org.springframework.scheduling.annotation.EnableScheduling;
89

910
@EnableJpaAuditing
1011
@SpringBootApplication
1112
@EnableCaching
13+
@EnableScheduling
1214
@ConfigurationPropertiesScan
1315
public class BackApplication {
1416

back/src/main/java/com/back/domain/user/controller/UserAuthController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,4 @@ public ResponseEntity<UserResponse> me(@AuthenticationPrincipal CustomUserDetail
8888
}
8989
return ResponseEntity.ok(UserResponse.from(cud.getUser()));
9090
}
91-
}
91+
}

back/src/main/java/com/back/domain/user/repository/UserRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import org.springframework.data.jpa.repository.JpaRepository;
66
import org.springframework.stereotype.Repository;
77

8-
import java.time.LocalDateTime;
8+
import java.util.List;
99
import java.util.Optional;
1010

1111
/**
@@ -18,5 +18,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
1818
boolean existsByEmail(String email);
1919
boolean existsByNickname(String nickname);
2020

21-
int deleteByRoleAndCreatedDateBefore(Role role, LocalDateTime cutoff);
21+
List<User> findByRole(Role role);
2222
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.back.global.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.data.redis.connection.RedisConnectionFactory;
6+
import org.springframework.data.redis.core.RedisTemplate;
7+
import org.springframework.data.redis.serializer.StringRedisSerializer;
8+
9+
/**
10+
* Redis 설정
11+
* - RedisTemplate 빈 생성
12+
* - 세션 스케줄러에서 Redis 키 확인용
13+
*/
14+
@Configuration
15+
public class RedisConfig {
16+
17+
@Bean
18+
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
19+
RedisTemplate<String, Object> template = new RedisTemplate<>();
20+
template.setConnectionFactory(connectionFactory);
21+
22+
template.setKeySerializer(new StringRedisSerializer());
23+
template.setHashKeySerializer(new StringRedisSerializer());
24+
25+
return template;
26+
}
27+
}
Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,119 @@
11
package com.back.global.scheduler;
22

33
import com.back.domain.user.entity.Role;
4+
import com.back.domain.user.entity.User;
45
import com.back.domain.user.repository.UserRepository;
6+
import com.back.global.security.CustomUserDetails;
57
import lombok.RequiredArgsConstructor;
68
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.data.redis.core.RedisTemplate;
710
import org.springframework.scheduling.annotation.Scheduled;
11+
import org.springframework.security.core.context.SecurityContext;
12+
import org.springframework.session.Session;
13+
import org.springframework.session.SessionRepository;
814
import org.springframework.stereotype.Component;
915
import org.springframework.transaction.annotation.Transactional;
1016

11-
import java.time.LocalDateTime;
17+
import java.util.ArrayList;
18+
import java.util.HashSet;
19+
import java.util.List;
20+
import java.util.Set;
1221

1322
/**
14-
* 게스트 계정 자동 정리 스케줄러
15-
* 세션 리스너가 놓친 게스트 계정을 주기적으로 정리하는 안전망
16-
* 실행 시점: 매일 새벽 3시
17-
* 삭제 기준: 생성된 지 24시간이 지난 게스트 계정
23+
* 만료된 게스트 유저를 주기적으로 삭제하는 스케줄러
24+
* - Redis Session을 확인하여 실제로 세션이 없는 게스트만 삭제
25+
* - 매 10분마다 실행
1826
*/
1927
@Slf4j
2028
@Component
2129
@RequiredArgsConstructor
2230
public class GuestCleanupScheduler {
2331

2432
private final UserRepository userRepository;
33+
private final RedisTemplate<String, Object> redisTemplate;
34+
private final SessionRepository sessionRepository;
2535

26-
// 매일 새벽 3시에 실행
27-
@Scheduled(cron = "0 0 3 * * *")
36+
@Scheduled(cron = "0 */10 * * * ?") // 매 10분마다 실행
2837
@Transactional
29-
public void cleanExpiredGuests() {
30-
LocalDateTime cutoff = LocalDateTime.now().minusHours(24);
31-
int deleted = userRepository.deleteByRoleAndCreatedDateBefore(Role.GUEST, cutoff);
32-
if (deleted > 0) {
33-
log.info("배치 작업으로 만료된 게스트 {}건 삭제", deleted);
38+
public void cleanupExpiredGuests() {
39+
log.info("=== 게스트 정리 작업 시작 ===");
40+
41+
try {
42+
Set<String> sessionKeys = redisTemplate.keys("spring:session:sessions:*");
43+
Set<Long> activeGuestIds = new HashSet<>();
44+
45+
if (sessionKeys != null && !sessionKeys.isEmpty()) {
46+
log.debug("현재 활성 세션 수: {}", sessionKeys.size());
47+
48+
for (String key : sessionKeys) {
49+
if (key.contains("expires")) continue; // expires 키는 스킵
50+
51+
String sessionId = key.replace("spring:session:sessions:", "");
52+
53+
try {
54+
Session session = sessionRepository.findById(sessionId);
55+
if (session == null) continue;
56+
57+
SecurityContext securityContext = session.getAttribute("SPRING_SECURITY_CONTEXT");
58+
if (securityContext == null || securityContext.getAuthentication() == null) {
59+
continue;
60+
}
61+
62+
Object principal = securityContext.getAuthentication().getPrincipal();
63+
64+
if (principal instanceof CustomUserDetails) {
65+
CustomUserDetails userDetails = (CustomUserDetails) principal;
66+
User user = userDetails.getUser();
67+
68+
if (user.getRole() == Role.GUEST) {
69+
activeGuestIds.add(user.getId());
70+
log.debug("활성 게스트 세션: id={}", user.getId());
71+
}
72+
}
73+
} catch (Exception e) {
74+
log.warn("세션 읽기 실패: {} - {}", sessionId, e.getMessage());
75+
}
76+
}
77+
}
78+
79+
List<User> allGuests = userRepository.findByRole(Role.GUEST);
80+
81+
if (allGuests.isEmpty()) {
82+
log.info("데이터베이스에 게스트 유저가 없습니다.");
83+
return;
84+
}
85+
86+
log.debug("전체 게스트 유저 수: {}", allGuests.size());
87+
88+
// 3. 세션이 없는 게스트 필터링
89+
List<User> expiredGuests = new ArrayList<>();
90+
91+
for (User guest : allGuests) {
92+
if (!activeGuestIds.contains(guest.getId())) {
93+
expiredGuests.add(guest);
94+
}
95+
}
96+
97+
if (!expiredGuests.isEmpty()) {
98+
userRepository.deleteAll(expiredGuests);
99+
100+
List<Long> deletedIds = expiredGuests.stream()
101+
.map(User::getId)
102+
.toList();
103+
104+
log.info("{}명의 만료된 게스트 삭제 완료 - ID: {}", expiredGuests.size(), deletedIds);
105+
} else {
106+
log.info("삭제할 만료된 게스트가 없습니다.");
107+
}
108+
109+
if (!activeGuestIds.isEmpty()) {
110+
log.info("활성 세션이 있는 게스트: {}명 - ID: {}", activeGuestIds.size(), activeGuestIds);
111+
}
112+
113+
log.info("=== 게스트 정리 작업 완료 ===");
114+
115+
} catch (Exception e) {
116+
log.error("게스트 정리 작업 중 오류 발생", e);
34117
}
35118
}
36-
}
119+
}

back/src/main/java/com/back/global/security/CustomUserDetails.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public Map<String, Object> getAttributes() {
7272
return attributes;
7373
}
7474

75-
@Override public String getName() {
75+
@Override
76+
public String getName() {
7677
if (user.getNickname()!=null && !user.getNickname().isBlank()) return user.getNickname();
7778
return user.getEmail();
7879
}

back/src/main/java/com/back/global/session/GuestSessionListener.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

back/src/test/java/com/back/global/session/GuestSessionListenerTest.java

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)