diff --git a/src/main/java/cmf/commitField/domain/commit/scheduler/CommitScheduler.java b/src/main/java/cmf/commitField/domain/commit/scheduler/CommitScheduler.java index 6679b06..133106f 100644 --- a/src/main/java/cmf/commitField/domain/commit/scheduler/CommitScheduler.java +++ b/src/main/java/cmf/commitField/domain/commit/scheduler/CommitScheduler.java @@ -1,43 +1,102 @@ package cmf.commitField.domain.commit.scheduler; import cmf.commitField.domain.commit.sinceCommit.service.CommitCacheService; -import cmf.commitField.domain.commit.sinceCommit.service.GithubService; +import cmf.commitField.domain.commit.totalCommit.service.TotalCommitService; import cmf.commitField.domain.redpanda.RedpandaProducer; import cmf.commitField.domain.user.entity.User; import cmf.commitField.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; @Slf4j @Service @RequiredArgsConstructor public class CommitScheduler { - private final GithubService githubService; + private final TotalCommitService totalCommitService; private final CommitCacheService commitCacheService; private final RedpandaProducer redpandaProducer; private final UserRepository userRepository; + private final StringRedisTemplate redisTemplate; + private final AtomicInteger counter = new AtomicInteger(0); @Scheduled(fixedRate = 60000) // 1분마다 실행 public void updateUserCommits() { log.info("🔍 updateUserCommits 실행중"); - List activeUsers = userRepository.findAll(); // 💫 변경 필요, 차후 active 상태인 user만 찾게끔 변경해야 함. + int count = counter.incrementAndGet(); - log.info("🔍 Active User Count: {}", activeUsers.size()); + if (count % 10 == 0) { + List allUsers = userRepository.findAll(); + log.info("🔍 All User Count: {}", allUsers.size()); - for (User user : activeUsers) { - Integer cachedCount = commitCacheService.getCachedCommitCount(user.getUsername()); - int newCommitCount = githubService.getUserCommitCount(user.getUsername()); - - log.info("🔍 User: {}, Commit Count: {}", user.getUsername(), newCommitCount); + for (User user : allUsers) { + processUserCommit(user); + } + } else { + Set activeUsers = redisTemplate.keys("commit_active:*"); + log.info("🔍 Active User Count: {}", activeUsers.size()); - if (cachedCount == null || cachedCount != newCommitCount) { // 변화가 있을 때만 처리 - commitCacheService.updateCachedCommitCount(user.getUsername(), newCommitCount); - redpandaProducer.sendCommitUpdate(user.getUsername(), newCommitCount); + for (String key : activeUsers) { + String username = key.replace("commit_active:", ""); + User user = userRepository.findByUsername(username).orElse(null); + if (user != null) { + processUserCommit(user); + } } } } + + // 🔹 유저 커밋 검사 및 반영 + 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값 사용; + } + + // 현재 커밋 개수 조회 + long currentCommitCount = totalCommitService.getSeasonCommits( + 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); + } + + log.info("🔍 User: {}, New Commits: {}, Total Commits: {}", user.getUsername(), newCommitCount, currentCommitCount); + } + + // 🔹 새 커밋이 있으면 데이터 업데이트 + 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); + + // 2️⃣ Redis에 최신 커밋 개수 저장 (3시간 동안 유지) + commitCacheService.updateCachedCommitCount(user.getUsername(), currentCommitCount); + + // 3️⃣ 메시지 큐 전송 + redpandaProducer.sendCommitUpdate(user.getUsername(), newCommitCount); + + log.info("✅ 커밋 반영 완료 - User: {}, New Commits: {}", user.getUsername(), newCommitCount); + } } diff --git a/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/CommitCacheService.java b/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/CommitCacheService.java index c94d3e5..f8873c7 100644 --- a/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/CommitCacheService.java +++ b/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/CommitCacheService.java @@ -14,14 +14,20 @@ public class CommitCacheService { private final StringRedisTemplate redisTemplate; public Integer getCachedCommitCount(String username) { - log.info("Redis Template: {}", redisTemplate); - String key = "commit:" + username; // Redis 키 생성 (ex: commit:hongildong) + String key = "commit_active:" + username; // Redis 키 생성 (ex: commit:hongildong) String value = redisTemplate.opsForValue().get(key); // Redis에서 값 가져오기 - return value != null ? Integer.parseInt(value) : null; // 값이 있으면 정수 변환, 없으면 null 반환 + + if (value != null) { + log.info("✅ Redis Hit - {} : {}", key, value); + return Integer.parseInt(value); + } else { + log.info("❌ Redis Miss - {}", key); + return null; + } } - public void updateCachedCommitCount(String username, int count) { - String key = "commit:" + username; - redisTemplate.opsForValue().set(key, String.valueOf(count), Duration.ofHours(1)); // 1시간 캐싱 + public void updateCachedCommitCount(String username, long count) { + String key = "commit_active:" + username; + redisTemplate.opsForValue().set(key, String.valueOf(count), Duration.ofHours(3)); // 3시간 캐싱 } } diff --git a/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java b/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java index 59572fc..2dc9fc5 100644 --- a/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java +++ b/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java @@ -34,4 +34,5 @@ public class Noti extends BaseEntity { // TODO: 알림이 연결된 객체 어떻게 처리할지 고민 필요. // private String relTypeCode; // 알림이 연결된 실제 객체 유형 // private long relId; // 알림 객체의 Id + } diff --git a/src/main/java/cmf/commitField/domain/pet/entity/Pet.java b/src/main/java/cmf/commitField/domain/pet/entity/Pet.java index 0126316..2d2b707 100644 --- a/src/main/java/cmf/commitField/domain/pet/entity/Pet.java +++ b/src/main/java/cmf/commitField/domain/pet/entity/Pet.java @@ -9,6 +9,8 @@ import lombok.Setter; import lombok.experimental.SuperBuilder; +import java.util.Random; + @Entity @Getter @Setter @@ -17,11 +19,36 @@ @SuperBuilder @Table(name = "pet") public class Pet extends BaseEntity { - private int type; // 펫 타입 넘버, 현재 1~3까지 존재 + private int type; // 펫 타입 넘버, 현재 0~2까지 존재 private String name; private String imageUrl; + private int exp; // 펫 경험치 + + @Enumerated(EnumType.STRING) // DB에 저장될 때 String 형태로 저장됨 + private Grow grow; // 성장 정도 + + public enum Grow { + EGG, HATCH, GROWN + } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + + public Pet(String name, User user){ + Random random = new Random(); + this.type = random.nextInt(3); + switch (type){ //정해진 알 타입에 따라 다른 url을 주입 + case 0: this.imageUrl = "temp0"; + break; + case 1: this.imageUrl = "temp1"; + break; + case 2: this.imageUrl = "temp2"; + break; + } + this.name = name; + this.exp = 0; + this.grow = Grow.EGG; + this.user = user; + } } diff --git a/src/main/java/cmf/commitField/domain/pet/service/PetService.java b/src/main/java/cmf/commitField/domain/pet/service/PetService.java index 831e9ff..9580f43 100644 --- a/src/main/java/cmf/commitField/domain/pet/service/PetService.java +++ b/src/main/java/cmf/commitField/domain/pet/service/PetService.java @@ -29,7 +29,7 @@ public Pet createPet(String name, MultipartFile imageFile, User user) throws IOE imageUrl = s3Service.uploadFile(imageFile, "pet-images"); } Random random = new Random(); - Pet pet = new Pet(random.nextInt(3), name, imageUrl, user); + Pet pet = new Pet(name, user); return petRepository.save(pet); } diff --git a/src/main/java/cmf/commitField/domain/redpanda/RedpandaProducer.java b/src/main/java/cmf/commitField/domain/redpanda/RedpandaProducer.java index 73699c3..38d990f 100644 --- a/src/main/java/cmf/commitField/domain/redpanda/RedpandaProducer.java +++ b/src/main/java/cmf/commitField/domain/redpanda/RedpandaProducer.java @@ -20,8 +20,8 @@ public void sendMessage(String message) { } // 커밋 업데이트 전송 메서드 - public void sendCommitUpdate(String username, int commitCount) { - String message = String.format("{\"user\": \"%s\", \"commits\": %d}", username, commitCount); + public void sendCommitUpdate(String username, long commitCount) { + String message = String.format("{\"user\": \"%s\", \"update-commits\": %d}", username, commitCount); kafkaTemplate.send(TOPIC, message); System.out.println("📨 Sent commit update to Redpanda: " + message); } diff --git a/src/main/java/cmf/commitField/domain/season/entity/Rank.java b/src/main/java/cmf/commitField/domain/season/entity/Rank.java index 1776cab..6198f91 100644 --- a/src/main/java/cmf/commitField/domain/season/entity/Rank.java +++ b/src/main/java/cmf/commitField/domain/season/entity/Rank.java @@ -3,7 +3,6 @@ public enum Rank { SEED, // 씨앗 SPROUT, // 새싹 - STEM, // 줄기 FLOWER, // 꽃 FRUIT, // 열매 TREE // 나무 diff --git a/src/main/java/cmf/commitField/domain/user/entity/User.java b/src/main/java/cmf/commitField/domain/user/entity/User.java index f0e5749..7a79f67 100644 --- a/src/main/java/cmf/commitField/domain/user/entity/User.java +++ b/src/main/java/cmf/commitField/domain/user/entity/User.java @@ -3,6 +3,7 @@ import cmf.commitField.domain.chat.chatMessage.entity.ChatMsg; import cmf.commitField.domain.chat.chatRoom.entity.ChatRoom; import cmf.commitField.domain.chat.userChatRoom.entity.UserChatRoom; +import cmf.commitField.domain.pet.entity.Pet; import cmf.commitField.global.jpa.BaseEntity; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -11,6 +12,7 @@ import lombok.Setter; import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -25,6 +27,7 @@ public class User extends BaseEntity { private String email; // 이메일 private String nickname; // 닉네임 private String avatarUrl; //아바타 + private LocalDateTime lastCommitted; // 마지막 커밋 시간 @Enumerated(EnumType.STRING) // DB에 저장될 때 String 형태로 저장됨 private Role role; @@ -32,7 +35,7 @@ public class User extends BaseEntity { public enum Role { USER, ADMIN } - //추가 + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) private List chatRooms = new ArrayList<>(); @@ -42,4 +45,22 @@ public enum Role { @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) private List chatMsgs = new ArrayList<>(); + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + private List pets = new ArrayList<>(); + + public void addPets(Pet pet){ + pets.add(pet); + } + + public User(String username, String email, String nickname, String avatarUrl, List cr, List ucr, List cmsg){ + this.username=username; + this.email=email; + this.nickname=nickname; + this.avatarUrl=avatarUrl; + this.role = Role.USER; + this.chatRooms = cr; + this.userChatRooms = ucr; + this.chatMsgs = cmsg; + this.lastCommitted = LocalDateTime.now(); + } } diff --git a/src/main/java/cmf/commitField/domain/user/service/CustomOAuth2UserService.java b/src/main/java/cmf/commitField/domain/user/service/CustomOAuth2UserService.java index 203dfbe..a392b93 100644 --- a/src/main/java/cmf/commitField/domain/user/service/CustomOAuth2UserService.java +++ b/src/main/java/cmf/commitField/domain/user/service/CustomOAuth2UserService.java @@ -1,5 +1,6 @@ package cmf.commitField.domain.user.service; +import cmf.commitField.domain.commit.sinceCommit.service.CommitCacheService; import cmf.commitField.domain.pet.entity.Pet; import cmf.commitField.domain.pet.repository.PetRepository; import cmf.commitField.domain.user.entity.CustomOAuth2User; @@ -14,13 +15,13 @@ import java.util.ArrayList; import java.util.Map; import java.util.Optional; -import java.util.Random; @Service @RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; private final PetRepository petRepository; + private final CommitCacheService commitCacheService; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) { @@ -49,12 +50,16 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) { } else { //유저 정보가 db에 존재하지 않을 경우 회원가입 시킨다. //유저 생성 및 펫 생성 - user = new User(username, email, name, avatarUrl, User.Role.USER, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + user = new User(username, email, name, avatarUrl, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); userRepository.save(user); - Random random = new Random(); //펫 랜덤 생성 - pet = new Pet(random.nextInt(3), "알알", "temp-Url", user); // 변경 필요 + pet = new Pet("알알", user); // 변경 필요 petRepository.save(pet); + + user.addPets(pet); + + // 회원가입한 유저는 커밋 기록에 상관없이 Redis에 입력해둔다. + commitCacheService.updateCachedCommitCount(user.getUsername(),0); } return new CustomOAuth2User(oauthUser, user);