Skip to content

Commit 9b15ef4

Browse files
[Feat] : 블랙리스트 삭제 배치 추가 (#62)
1 parent c81f25c commit 9b15ef4

File tree

7 files changed

+169
-1
lines changed

7 files changed

+169
-1
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ dependencies {
111111

112112
// oauth
113113
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
114+
115+
// batch
116+
implementation 'org.springframework.boot:spring-boot-starter-batch'
117+
implementation'org.springframework.boot:spring-boot-starter-quartz'
114118
}
115119

116120
tasks.named('test') {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package kdt.web_ide.common.config;
2+
3+
import javax.sql.DataSource;
4+
5+
import org.springframework.batch.core.Job;
6+
import org.springframework.batch.core.Step;
7+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
8+
import org.springframework.batch.core.job.builder.JobBuilder;
9+
import org.springframework.batch.core.repository.JobRepository;
10+
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
11+
import org.springframework.batch.core.step.builder.StepBuilder;
12+
import org.springframework.batch.core.step.tasklet.Tasklet;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.springframework.transaction.PlatformTransactionManager;
16+
17+
import kdt.web_ide.members.batch.TokenCleanupTasklet;
18+
import kdt.web_ide.members.entity.repository.TokenBlacklistRepository;
19+
20+
@Configuration
21+
@EnableBatchProcessing
22+
public class BatchConfig {
23+
24+
@Bean
25+
public Job tokenCleanupJob(JobRepository jobRepository, Step tokenCleanupStep) {
26+
return new JobBuilder("tokenCleanupJob", jobRepository) // ✅ JobBuilder 직접 사용
27+
.start(tokenCleanupStep)
28+
.build();
29+
}
30+
31+
@Bean
32+
public Step tokenCleanupStep(
33+
JobRepository jobRepository,
34+
Tasklet tokenCleanupTasklet,
35+
PlatformTransactionManager transactionManager) {
36+
return new StepBuilder("tokenCleanupStep", jobRepository)
37+
.tasklet(tokenCleanupTasklet, transactionManager)
38+
.build();
39+
}
40+
41+
@Bean
42+
public Tasklet tokenCleanupTasklet(TokenBlacklistRepository tokenBlacklistRepository) {
43+
return new TokenCleanupTasklet(tokenBlacklistRepository);
44+
}
45+
46+
@Bean
47+
public JobRepository jobRepository(
48+
DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception {
49+
JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();
50+
factoryBean.setDataSource(dataSource);
51+
factoryBean.setTransactionManager(transactionManager);
52+
factoryBean.afterPropertiesSet();
53+
return factoryBean.getObject();
54+
}
55+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package kdt.web_ide.members.batch;
2+
3+
import java.time.LocalDateTime;
4+
5+
import org.springframework.batch.core.Job;
6+
import org.springframework.batch.core.JobParameters;
7+
import org.springframework.batch.core.launch.JobLauncher;
8+
import org.springframework.scheduling.annotation.Scheduled;
9+
import org.springframework.stereotype.Component;
10+
11+
import kdt.web_ide.members.entity.repository.TokenBlacklistRepository;
12+
import lombok.RequiredArgsConstructor;
13+
import lombok.extern.slf4j.Slf4j;
14+
15+
@Component
16+
@RequiredArgsConstructor
17+
@Slf4j
18+
public class TokenBatchScheduler {
19+
private final TokenBlacklistRepository tokenBlacklistRepository;
20+
private final JobLauncher jobLauncher;
21+
private final Job tokenCleanupJob;
22+
23+
@Scheduled(cron = "0 0 0,6,12,18 * * ?")
24+
public void runTokenCleanupJob() {
25+
if (tokenBlacklistRepository.countExpiredTokens(LocalDateTime.now()) > 0) {
26+
try {
27+
jobLauncher.run(tokenCleanupJob, new JobParameters());
28+
log.info("배치 작업 실행 완료: 블랙리스트 토큰 삭제");
29+
} catch (Exception e) {
30+
log.error("배치 실행 중 오류 발생", e);
31+
}
32+
} else {
33+
log.info("배치 작업 실행 스킵 : 만료된 토큰 없음");
34+
}
35+
}
36+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package kdt.web_ide.members.batch;
2+
3+
import java.time.LocalDateTime;
4+
5+
import org.springframework.batch.core.StepContribution;
6+
import org.springframework.batch.core.scope.context.ChunkContext;
7+
import org.springframework.batch.core.step.tasklet.Tasklet;
8+
import org.springframework.batch.repeat.RepeatStatus;
9+
10+
import kdt.web_ide.members.entity.repository.TokenBlacklistRepository;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
14+
@RequiredArgsConstructor
15+
@Slf4j
16+
public class TokenCleanupTasklet implements Tasklet {
17+
private final TokenBlacklistRepository tokenBlacklistRepository;
18+
19+
@Override
20+
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
21+
int deletedCount = tokenBlacklistRepository.deleteExpiredTokens(LocalDateTime.now());
22+
log.info("배치 작업 실행: 블랙리스트에서 만료된 토큰 {}개 삭제 완료", deletedCount);
23+
return RepeatStatus.FINISHED;
24+
}
25+
}

src/main/java/kdt/web_ide/members/controller/MemberController.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import jakarta.servlet.http.HttpServletRequest;
77
import jakarta.servlet.http.HttpServletResponse;
88

9+
import org.springframework.batch.core.Job;
10+
import org.springframework.batch.core.JobParameters;
11+
import org.springframework.batch.core.launch.JobLauncher;
912
import org.springframework.http.HttpStatus;
1013
import org.springframework.http.MediaType;
1114
import org.springframework.http.ResponseCookie;
@@ -25,13 +28,18 @@
2528
import kdt.web_ide.members.service.CustomUserDetails;
2629
import kdt.web_ide.members.service.MemberService;
2730
import lombok.RequiredArgsConstructor;
31+
import lombok.extern.slf4j.Slf4j;
2832

2933
@RestController
3034
@RequiredArgsConstructor
3135
@Tag(name = "회원 API")
3236
@RequestMapping("/api/auth")
37+
@Slf4j
3338
public class MemberController {
3439

40+
private final JobLauncher jobLauncher;
41+
private final Job tokenCleanupJob;
42+
3543
private final MemberService memberService;
3644

3745
@Operation(summary = "임시 로그인 API", description = "임시 로그인")
@@ -127,10 +135,23 @@ public ResponseEntity<TokenResponse> recreateAccessToken(
127135
}
128136

129137
@Operation(summary = "카카오 엑세스 토큰 재발급 API")
130-
@GetMapping("/kakao")
138+
@PostMapping("/kakao")
131139
public ResponseEntity<TokenResponse> getKakaoAccessToken(
132140
@AuthenticationPrincipal CustomUserDetails userDetails) {
133141
return ResponseEntity.status(HttpStatus.OK)
134142
.body(memberService.getKakaoAccessToken(userDetails.getMember().getMemberId()));
135143
}
144+
145+
@Operation(summary = "블랙리스트 토큰 수동 삭제")
146+
@PostMapping("/cleanup")
147+
public ResponseEntity<String> runTokenCleanUp() {
148+
try {
149+
jobLauncher.run(tokenCleanupJob, new JobParameters());
150+
return ResponseEntity.ok("Token cleanup batch job started.");
151+
} catch (Exception e) {
152+
log.error("에러", e.getMessage(), e);
153+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
154+
.body("Failed to start batch job.");
155+
}
156+
}
136157
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
package kdt.web_ide.members.entity.repository;
22

3+
import java.time.LocalDateTime;
4+
35
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Modifying;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
9+
import org.springframework.transaction.annotation.Transactional;
410

511
import kdt.web_ide.members.entity.TokenBlacklist;
612

713
public interface TokenBlacklistRepository extends JpaRepository<TokenBlacklist, Long> {
814

915
boolean existsByRefreshToken(String refreshToken);
16+
17+
@Modifying
18+
@Transactional
19+
@Query("""
20+
DELETE
21+
FROM TokenBlacklist t
22+
WHERE t.expiredAt <= :now
23+
""")
24+
int deleteExpiredTokens(@Param("now") LocalDateTime now);
25+
26+
@Query("""
27+
SELECT COUNT(t)
28+
FROM TokenBlacklist t
29+
WHERE t.expiredAt <= :now
30+
""")
31+
long countExpiredTokens(@Param("now") LocalDateTime now);
1032
}

src/main/resources/application.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ spring:
33
url: ${SPRING_DATASOURCE_URL}
44
username: ${SPRING_DATASOURCE_USERNAME}
55
password: ${SPRING_DATASOURCE_PASSWORD}
6+
batch:
7+
jdbc:
8+
initialize-schema: never
69
jpa:
710
database-platform: ${SPRING_JPA_DATABASE_PLATFORM}
811
hibernate:
@@ -25,3 +28,5 @@ oauth:
2528
url:
2629
auth: https://kauth.kakao.com
2730
api: https://kapi.kakao.com
31+
32+

0 commit comments

Comments
 (0)