Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
Expand All @@ -34,56 +36,69 @@ public void updateUserCommits() {
Set<String> activeUsers = redisTemplate.keys("commit_active:*");
log.info("🔍 Active User Count: {}", activeUsers.size());

// 현재 접속 기록이 있는 유저, 커밋 기록이 있는 유저는 주기적으로 갱신
for (String key : activeUsers) {
String username = key.replace("commit_active:", "");
User user = userRepository.findByUsername(username).orElse(null);
if (user != null) {
processUserCommit(user);
}
}

String lastcmKey = "commit_lastCommitted:" + username; // active유저의 key
String lastCommitted = redisTemplate.opsForValue().get(lastcmKey); // 마지막 커밋 시간

System.out.println("username: "+username);
System.out.println("user lastCommitted: "+lastCommitted);
if(username!=null && lastCommitted!=null) processUserCommit(username);
}
}

// 🔹 유저 커밋 검사 및 반영
private void processUserCommit(User user) {
// Redis에서 lastCommitted 값 가져오기
String redisKey = "commit_last:" + user.getUsername();
String lastCommittedStr = redisTemplate.opsForValue().get(redisKey);
LocalDateTime lastCommitted;
if(lastCommittedStr != null){
lastCommitted=LocalDateTime.parse(lastCommittedStr);
}else{
user.setLastCommitted(LocalDateTime.now()); // 레디스에 저장되어있지 않았다면 등록 시점에 lastCommitted를 갱신
lastCommitted=user.getLastCommitted(); // Redis에 없으면 DB값 사용;
private void processUserCommit(String username) {
// 유저가 접속한 동안 추가한 commit수를 확인.
String key = "commit_active:" + username; // active유저의 key
String lastcmKey = "commit_lastCommitted:" + username; // active유저의 key
String currentCommit = redisTemplate.opsForValue().get(key); // 현재까지 확인한 커밋 개수
String lastcommitted = redisTemplate.opsForValue().get(lastcmKey); // 마지막 커밋 시간
long updateTotalCommit, newCommitCount;


LocalDateTime lastCommittedTime;
try {
lastCommittedTime = LocalDateTime.parse(lastcommitted, DateTimeFormatter.ISO_DATE_TIME);
} catch (DateTimeParseException e) {
System.out.println("lastcommitted 값이 올바르지 않음: " + lastcommitted);
lastCommittedTime = LocalDateTime.now().minusHours(1);
}

// 현재 커밋 개수 조회
long currentCommitCount = totalCommitService.getUpdateCommits(
user.getUsername(),
lastCommitted, // 🚀 Redis에 저장된 lastCommitted 기준으로 조회
LocalDateTime.now()
).getTotalCommitContributions();

// Redis에서 이전 커밋 개수 가져오기
Integer previousCommitCount = commitCacheService.getCachedCommitCount(user.getUsername());
long newCommitCount = previousCommitCount == null ? 0 : (currentCommitCount - previousCommitCount);

if (newCommitCount > 0) {
updateCommitData(user, currentCommitCount, newCommitCount);
}
updateTotalCommit = totalCommitService.getUpdateCommits(
username,
lastCommittedTime, // 🚀 Redis에 저장된 lastCommitted 기준으로 조회
LocalDateTime.now()
).getCommits();
System.out.println("커밋 개수 불러들이기 완료, 현재까지 업데이트 된 커밋 수 : "+updateTotalCommit);

log.info("🔍 User: {}, New Commits: {}, Total Commits: {}", user.getUsername(), newCommitCount, currentCommitCount);
}
if(currentCommit.equals("0") && updateTotalCommit > 0){
User user = userRepository.findByUsername(username).get();
LocalDateTime now = LocalDateTime.now();
//이번 기간에 처음으로 커밋 수가 갱신된 경우, 이 시간을 기점으로 commitCount를 계산한다.
user.setLastCommitted(now);
userRepository.save(user);

String redisKey = "commit_update:" + username; // 변경 알림을 위한 변수
redisTemplate.opsForValue().set(redisKey, String.valueOf(updateTotalCommit), 3, TimeUnit.HOURS);

// 🔹 새 커밋이 있으면 데이터 업데이트
private void updateCommitData(User user, long currentCommitCount, long newCommitCount) {
// 1️⃣ Redis에 lastCommitted 업데이트 (3시간 TTL)
String redisKey = "commit_last:" + user.getUsername();
redisTemplate.opsForValue().set(redisKey, LocalDateTime.now().toString(), 3, TimeUnit.HOURS);
redisTemplate.opsForValue().set(lastcmKey, String.valueOf(now), 3, TimeUnit.HOURS);
}

// 2️⃣ Redis에 최신 커밋 개수 저장 (3시간 동안 유지)
commitCacheService.updateCachedCommitCount(user.getUsername(), currentCommitCount);
//기존 커밋이 있고 커밋 수에 변화가 있는 경우 처리
newCommitCount = updateTotalCommit - Long.parseLong(currentCommit); // 새로 추가된 커밋 수
if(newCommitCount>0){
String redisKey = "commit_update:" + username; // 변경 알림을 위한 변수
redisTemplate.opsForValue().set(redisKey, String.valueOf(newCommitCount), 3, TimeUnit.HOURS);

updateTotalCommit+=newCommitCount;
redisTemplate.opsForValue().set(key, String.valueOf(updateTotalCommit), 3, TimeUnit.HOURS);
}

log.info("✅ 커밋 반영 완료 - User: {}, New Commits: {}", user.getUsername(), newCommitCount);
// FIXME: 차후 리팩토링 필요
log.info("🔍 User: {}, LastCommitted: {}, New Commits: {}, Total Commits: {}", username, lastcommitted, newCommitCount, currentCommit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public UserInfoDto updateUserTier(String username){
userRepository.save(user);

return UserInfoDto.builder()
.userId(user.getId())
.username(user.getUsername())
.email(user.getEmail())
.avatarUrl(user.getAvatarUrl())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cmf.commitField.domain.commit.totalCommit.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class CommitUpdateDTO {
long commits;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cmf.commitField.domain.commit.totalCommit.service;

import cmf.commitField.domain.commit.totalCommit.dto.CommitUpdateDTO;
import cmf.commitField.domain.commit.totalCommit.dto.TotalCommitGraphQLResponse;
import cmf.commitField.domain.commit.totalCommit.dto.TotalCommitResponseDto;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -215,15 +216,15 @@ private StreakResult calculateStreaks(List<LocalDate> commitDates) {
}

// 시간별 커밋 분석
public TotalCommitResponseDto getUpdateCommits(String username, LocalDateTime since, LocalDateTime until) {
public CommitUpdateDTO getUpdateCommits(String username, LocalDateTime since, LocalDateTime until) {
String query = String.format("""
query {
user(login: "%s") {
contributionsCollection(from: "%s", to: "%s") {
commitContributionsByRepository {
contributions(first: 100) {
nodes {
occurredAt # 시간 정보 포함
occurredAt # 시간 정보 포함
}
}
}
Expand All @@ -244,12 +245,13 @@ public TotalCommitResponseDto getUpdateCommits(String username, LocalDateTime si
throw new RuntimeException("Failed to fetch GitHub data");
}


System.out.println("메소드 작동 확인 : "+response.getData().getUser());
TotalCommitGraphQLResponse.ContributionsCollection contributions =
response.getData().getUser().getContributionsCollection();

return new TotalCommitResponseDto(
contributions.getTotalCommitContributions(),
contributions.getRestrictedContributionsCount()
return new CommitUpdateDTO(
contributions.getTotalCommitContributions()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
public interface PetRepository extends JpaRepository<Pet, Long> {
Optional<Pet> findById(Long id);
List<Pet> findByUserEmail(String email);
List<Pet> findByUserUsername(String username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class AuthController {
@GetMapping("/login")
public ResponseEntity<?> user() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

System.out.println("/login 호출");
if (authentication instanceof OAuth2AuthenticationToken) {
OAuth2User principal = (OAuth2User) authentication.getPrincipal();
return ResponseEntity.ok(principal.getAttributes()); // 사용자 정보 반환
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmf.commitField.domain.user.controller;

import cmf.commitField.domain.commit.totalCommit.service.TotalCommitService;
import cmf.commitField.domain.user.dto.UserInfoDto;
import cmf.commitField.domain.user.entity.CustomOAuth2User;
import cmf.commitField.domain.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final TotalCommitService totalCommitService;

@GetMapping("info")
public ResponseEntity<UserInfoDto> getUserInfo(@AuthenticationPrincipal CustomOAuth2User oAuth2User){
String username = oAuth2User.getName();

//유저 정보의 조회
//이 이벤트가 일어나면 유저의 정보를 확인하고, 조회한 유저를 active 상태로 만든다.
UserInfoDto userInfoDto = userService.showUserInfo(username);

return ResponseEntity.ok(userInfoDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
@Getter
public class UserInfoDto {

private Long userId;
private String username;
private String email;
private String avatarUrl;
private LocalDateTime createdAt;
private LocalDateTime lastCommitted;
private long commitCount;

private int petType;
private int petExp;
private String petGrow;
private String tier;

// 펫 생략, 차후 필요시 추가
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/cmf/commitField/domain/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,68 @@
package cmf.commitField.domain.user.service;

import cmf.commitField.domain.commit.scheduler.CommitUpdateService;
import cmf.commitField.domain.commit.totalCommit.service.TotalCommitService;
import cmf.commitField.domain.pet.entity.Pet;
import cmf.commitField.domain.pet.repository.PetRepository;
import cmf.commitField.domain.pet.service.PetService;
import cmf.commitField.domain.user.dto.UserInfoDto;
import cmf.commitField.domain.user.entity.User;
import cmf.commitField.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class UserService {
private final StringRedisTemplate redisTemplate;
private final UserRepository userRepository;
private final PetRepository petRepository;
// FIXME: 수정 필요
private final TotalCommitService totalCommitService;
private final CommitUpdateService commitUpdateService;
private final PetService petService;

@Transactional
public void updateUserStatus(String username, boolean status) {
System.out.println("Updating status for " + username + " to " + status);
userRepository.updateStatus(username, status);
}

@Transactional
public UserInfoDto showUserInfo(String username) {
User user = userRepository.findByUsername(username).get();
Pet pet = petRepository.findByUserEmail(user.getEmail()).get(0); // TODO: 확장시 코드 수정 필요

// 유저 정보 조회 후 변경사항은 업데이트
// TODO: 스케쥴러 수정 후 펫 부분 수정 필요
commitUpdateService.updateUserTier(user.getUsername());
petService.getExpPet(user.getUsername(), 0);

long commit = totalCommitService.getUpdateCommits(username, user.getLastCommitted(), LocalDateTime.now()).getCommits();
System.out.println("커밋수 테스트 : "+commit);

String key = "commit_active:" + user.getUsername();
if(redisTemplate.opsForValue().get(key)==null){
redisTemplate.opsForValue().set(key, String.valueOf(0), 3, TimeUnit.HOURS);
redisTemplate.opsForValue().set("commit_lastCommitted:" + username, LocalDateTime.now().toString(),3, TimeUnit.HOURS);
}

return UserInfoDto.builder()
.username(user.getUsername())
.email(user.getEmail())
.avatarUrl(user.getAvatarUrl())
.tier(user.getTier().toString())
.commitCount(user.getCommitCount())
.createdAt(user.getCreatedAt())
.lastCommitted(user.getLastCommitted())
.petType(pet.getType())
.petExp(pet.getExp())
.petGrow(pet.getGrow().toString())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConne
template.setConnectionFactory(redisConnectionFactory);
return template;
}

}
25 changes: 25 additions & 0 deletions src/main/java/cmf/commitField/global/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@

import cmf.commitField.domain.user.service.CustomOAuth2UserService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private StringRedisTemplate redisTemplate;
private final CustomOAuth2UserService customOAuth2UserService;

public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) {
Expand Down Expand Up @@ -49,6 +56,9 @@ protected SecurityFilterChain config(HttpSecurity http) throws Exception {
OAuth2User principal = (OAuth2User) authentication.getPrincipal();
String username = principal.getAttribute("login");

// Redis에 유저 활성화 정보 저장
setUserActive(username);

// 디버깅 로그
System.out.println("OAuth2 로그인 성공: " + username);
response.sendRedirect("http://localhost:5173/home"); // 로그인 성공 후 리다이렉트
Expand All @@ -60,6 +70,12 @@ protected SecurityFilterChain config(HttpSecurity http) throws Exception {
.clearAuthentication(true) // 인증 정보 지우기
.deleteCookies("JSESSIONID") // 세션 쿠키 삭제
.logoutSuccessHandler((request, response, authentication) -> {
OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
OAuth2User principal = oauth2Token.getPrincipal();
String username = principal.getAttribute("login");

redisTemplate.delete("commit_active:" + username); // Redis에서 삭제

System.out.println("로그아웃 성공");
response.setStatus(HttpServletResponse.SC_OK);
response.sendRedirect("http://localhost:5173/"); // 로그아웃 후 홈으로 이동
Expand All @@ -83,4 +99,13 @@ public CorsConfigurationSource corsConfigurationSource() {
source.registerCorsConfiguration("/**", config);
return source;
}

public void setUserActive(String username) {
redisTemplate.opsForValue().set("commit_active:" + username, "0");
redisTemplate.opsForValue().set("commit_lastCommitted:" + username, LocalDateTime.now().toString(),3, TimeUnit.HOURS);
}

public void removeUserActive(String username) {
redisTemplate.delete("commit_active:" + username);
}
}