Skip to content

Commit 524758d

Browse files
committed
feat: 레디스 및 레드판다 세팅, 예제 확인
1 parent 09fd6b4 commit 524758d

File tree

17 files changed

+329
-8
lines changed

17 files changed

+329
-8
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ docker-compose.yaml
7575

7676
### db파일
7777
db/
78+
.docker
79+
data/
7880

7981
### secret 프로필
8082
application-secret.yml

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
testImplementation("org.springframework.boot:spring-boot-starter-test")
3636
testImplementation("org.springframework.security:spring-security-test")
3737
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
38+
implementation ("org.springframework.kafka:spring-kafka") // Kafka 통합
3839

3940
// Lombok
4041
compileOnly("org.projectlombok:lombok")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmf.commitField.domain.commit.scheduler;
2+
3+
import cmf.commitField.domain.commit.sinceCommit.service.CommitCacheService;
4+
import cmf.commitField.domain.commit.sinceCommit.service.GithubService;
5+
import cmf.commitField.domain.redpanda.RedpandaProducer;
6+
import cmf.commitField.domain.user.entity.User;
7+
import cmf.commitField.domain.user.repository.UserRepository;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.scheduling.annotation.Scheduled;
11+
import org.springframework.stereotype.Service;
12+
13+
import java.util.List;
14+
15+
@Slf4j
16+
@Service
17+
@RequiredArgsConstructor
18+
public class CommitScheduler {
19+
private final GithubService githubService;
20+
private final CommitCacheService commitCacheService;
21+
private final RedpandaProducer redpandaProducer;
22+
private final UserRepository userRepository;
23+
24+
@Scheduled(fixedRate = 60000) // 1분마다 실행
25+
public void updateUserCommits() {
26+
List<User> activeUsers = userRepository.findAll(); // 💫 변경 필요, 차후 active 상태인 user만 찾게끔 변경해야 함.
27+
28+
for (User user : activeUsers) {
29+
Integer cachedCount = commitCacheService.getCachedCommitCount(user.getUsername());
30+
int newCommitCount = githubService.getUserCommitCount(user.getUsername());
31+
32+
if (cachedCount == null || cachedCount != newCommitCount) { // 변화가 있을 때만 처리
33+
commitCacheService.updateCachedCommitCount(user.getUsername(), newCommitCount);
34+
redpandaProducer.sendCommitUpdate(user.getUsername(), newCommitCount);
35+
}
36+
}
37+
}
38+
}

src/main/java/cmf/commitField/domain/commit/sinceCommit/controller/SinceCommitController.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package cmf.commitField.domain.commit.sinceCommit.controller;
22

33
import cmf.commitField.domain.commit.sinceCommit.dto.SinceCommitResponseDto;
4+
import cmf.commitField.domain.commit.sinceCommit.service.GithubService;
45
import cmf.commitField.domain.commit.sinceCommit.service.SinceCommitService;
56
import lombok.RequiredArgsConstructor;
67
import org.springframework.format.annotation.DateTimeFormat;
78
import org.springframework.http.ResponseEntity;
89
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PathVariable;
911
import org.springframework.web.bind.annotation.RequestParam;
1012
import org.springframework.web.bind.annotation.RestController;
1113

@@ -17,6 +19,7 @@
1719
@RequiredArgsConstructor
1820
public class SinceCommitController {
1921
private final SinceCommitService sinceCommitService;
22+
private final GithubService githubService;
2023

2124
@GetMapping("/api/github/commits-since")
2225
public ResponseEntity<List<SinceCommitResponseDto>> getCommits(
@@ -92,4 +95,12 @@ public ResponseEntity<List<SinceCommitResponseDto>> getWinterSeasonCommits(
9295

9396
return ResponseEntity.ok(sinceCommits);
9497
}
98+
99+
// api 테스트 메소드 추가
100+
@GetMapping("/api/commit-count/{username}")
101+
public ResponseEntity<Integer> getCommitCount(@PathVariable String username) {
102+
System.out.println("⚡ API 엔드포인트 호출: " + username);
103+
int commitCount = githubService.getUserCommitCount(username);
104+
return ResponseEntity.ok(commitCount);
105+
}
95106
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmf.commitField.domain.commit.sinceCommit.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
import lombok.Setter;
7+
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
@Getter
11+
@Setter
12+
public class CommitData {
13+
String user;
14+
int commits;
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmf.commitField.domain.commit.sinceCommit.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.data.redis.core.StringRedisTemplate;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.time.Duration;
9+
10+
@Slf4j
11+
@Service
12+
@RequiredArgsConstructor
13+
public class CommitCacheService {
14+
private final StringRedisTemplate redisTemplate;
15+
16+
public Integer getCachedCommitCount(String username) {
17+
String key = "commit:" + username;
18+
String value = redisTemplate.opsForValue().get(key);
19+
return value != null ? Integer.parseInt(value) : null;
20+
}
21+
22+
public void updateCachedCommitCount(String username, int count) {
23+
String key = "commit:" + username;
24+
redisTemplate.opsForValue().set(key, String.valueOf(count), Duration.ofHours(1)); // 1시간 캐싱
25+
}
26+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cmf.commitField.domain.commit.sinceCommit.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.core.ParameterizedTypeReference;
6+
import org.springframework.http.*;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.web.client.HttpClientErrorException;
9+
import org.springframework.web.client.RestTemplate;
10+
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
public class GithubService {
17+
private final RestTemplate restTemplate;
18+
private final String GITHUB_API_URL = "https://api.github.com";
19+
20+
@Value("${github.token}")
21+
private String GITHUB_TOKEN;
22+
23+
public int getUserCommitCount(String username) {
24+
HttpHeaders headers = new HttpHeaders();
25+
headers.set("Authorization", "Bearer " + GITHUB_TOKEN);
26+
headers.set("Accept", "application/vnd.github.v3+json"); // 최신 GitHub API 버전 지정
27+
28+
HttpEntity<String> entity = new HttpEntity<>(headers);
29+
String url = String.format("%s/users/%s/events", GITHUB_API_URL, username);
30+
31+
// 📌 API 호출 횟수 확인용 로그 추가
32+
System.out.println("GitHub API 호출: " + url);
33+
34+
try {
35+
ResponseEntity<List<Map<String, Object>>> response =
36+
restTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<>() {});
37+
38+
// GitHub API Rate Limit 확인 (남은 요청 횟수 로깅)
39+
HttpHeaders responseHeaders = response.getHeaders();
40+
String remainingRequests = responseHeaders.getFirst("X-RateLimit-Remaining");
41+
System.out.println("GitHub API 남은 요청 횟수: " + remainingRequests);
42+
43+
int commitCount = 0;
44+
if (response.getBody() != null) {
45+
for (Map<String, Object> event : response.getBody()) {
46+
if ("PushEvent".equals(event.get("type"))) {
47+
Map<String, Object> payload = (Map<String, Object>) event.get("payload");
48+
if (payload != null && payload.containsKey("commits")) {
49+
List<?> commits = (List<?>) payload.get("commits");
50+
commitCount += (commits != null) ? commits.size() : 0;
51+
}
52+
}
53+
}
54+
}
55+
return commitCount;
56+
57+
} catch (HttpClientErrorException e) {
58+
System.err.println("GitHub API 요청 실패: " + e.getStatusCode() + " - " + e.getResponseBodyAsString());
59+
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
60+
throw new RuntimeException("GitHub API 인증 실패: 올바른 토큰을 사용하세요.");
61+
} else if (e.getStatusCode() == HttpStatus.FORBIDDEN) {
62+
throw new RuntimeException("GitHub API 요청 제한 초과 (Rate Limit 초과). 잠시 후 다시 시도하세요.");
63+
}
64+
return 0; // 기본값 반환
65+
} catch (Exception e) {
66+
System.err.println("예기치 않은 오류 발생: " + e.getMessage());
67+
return 0; // 기본값 반환
68+
}
69+
}
70+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cmf.commitField.domain.redpanda;
2+
3+
import org.springframework.kafka.annotation.KafkaListener;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service
7+
public class KafkaConsumer {
8+
9+
@KafkaListener(topics = "commit-topic", groupId = "commit-group")
10+
public void listen(String message) {
11+
System.out.println("Received message: " + message);
12+
}
13+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package cmf.commitField.domain.redpanda;
2+
3+
import org.springframework.kafka.core.KafkaTemplate;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service
7+
public class RedpandaProducer {
8+
9+
private final KafkaTemplate<String, String> kafkaTemplate;
10+
private static final String TOPIC = "commit-topic"; // Redpanda에서 사용할 토픽명
11+
12+
public RedpandaProducer(KafkaTemplate<String, String> kafkaTemplate) {
13+
this.kafkaTemplate = kafkaTemplate;
14+
}
15+
16+
// 메시지 전송 메서드
17+
public void sendMessage(String message) {
18+
kafkaTemplate.send(TOPIC, message);
19+
System.out.println("📨 Sent message to Redpanda: " + message);
20+
}
21+
22+
// 커밋 업데이트 전송 메서드
23+
public void sendCommitUpdate(String username, int commitCount) {
24+
String message = String.format("{\"user\": \"%s\", \"commits\": %d}", username, commitCount);
25+
kafkaTemplate.send(TOPIC, message);
26+
System.out.println("📨 Sent commit update to Redpanda: " + message);
27+
}
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cmf.commitField.domain.redpanda.commit;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.kafka.annotation.KafkaListener;
5+
import org.springframework.messaging.simp.SimpMessagingTemplate;
6+
import org.springframework.stereotype.Component;
7+
8+
@Component
9+
public class CommitConsumer {
10+
11+
@Autowired
12+
private SimpMessagingTemplate messagingTemplate;
13+
14+
@KafkaListener(topics = "commit-events", groupId = "commit-group")
15+
public void consume(String message) {
16+
System.out.println("Received commit event: " + message);
17+
// WebSocket을 통해 Frontend로 전송
18+
messagingTemplate.convertAndSend("/topic/commits", message);
19+
}
20+
21+
}

0 commit comments

Comments
 (0)