Skip to content

Commit 7927758

Browse files
committed
feat: 병합 충돌 해결
2 parents 7765845 + b98d014 commit 7927758

File tree

16 files changed

+281
-64
lines changed

16 files changed

+281
-64
lines changed

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ dependencies {
4747

4848
//redis
4949
implementation("org.springframework.boot:spring-boot-starter-data-redis")
50+
implementation ("org.redisson:redisson-spring-boot-starter:3.42.0") // redis message broker(lock)
51+
implementation ("org.springframework.session:spring-session-data-redis")
52+
5053

5154
// Security
5255
implementation("org.springframework.boot:spring-boot-starter-security")

src/main/java/cmf/commitField/CommitFieldApplication.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
6+
import org.springframework.scheduling.annotation.EnableScheduling;
67

78
@SpringBootApplication
89
@EnableJpaAuditing
10+
// 스케쥴링 활성화
11+
// 테스트시에만 주석 풀기
12+
@EnableScheduling
913
public class CommitFieldApplication {
1014

1115
public static void main(String[] args) {

src/main/java/cmf/commitField/domain/chat/chatRoom/repository/ChatRoomRepository.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
package cmf.commitField.domain.chat.chatRoom.repository;
22

33
import cmf.commitField.domain.chat.chatRoom.entity.ChatRoom;
4-
import jakarta.persistence.LockModeType;
5-
import jakarta.persistence.QueryHint;
64
import org.springframework.data.domain.Page;
75
import org.springframework.data.domain.Pageable;
86
import org.springframework.data.jpa.repository.JpaRepository;
9-
import org.springframework.data.jpa.repository.Lock;
107
import org.springframework.data.jpa.repository.Query;
11-
import org.springframework.data.jpa.repository.QueryHints;
128
import org.springframework.data.repository.query.Param;
139
import org.springframework.stereotype.Repository;
1410

@@ -17,8 +13,8 @@
1713
@Repository
1814
public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {
1915

20-
@Lock(LockModeType.PESSIMISTIC_WRITE)
21-
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="1000")})
16+
// @Lock(LockModeType.PESSIMISTIC_WRITE)
17+
// @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="1000")})
2218
Optional<ChatRoom> findById(Long aLong);
2319

2420
Optional<ChatRoom> findChatRoomById(Long id);

src/main/java/cmf/commitField/domain/chat/chatRoom/service/ChatRoomServiceImpl.java

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,33 @@
1212
import cmf.commitField.global.error.ErrorCode;
1313
import cmf.commitField.global.exception.CustomException;
1414
import lombok.RequiredArgsConstructor;
15+
import org.redisson.api.RLock;
16+
import org.redisson.api.RedissonClient;
1517
import org.springframework.data.domain.Page;
1618
import org.springframework.data.domain.Pageable;
1719
import org.springframework.stereotype.Service;
20+
import org.springframework.transaction.annotation.Isolation;
1821
import org.springframework.transaction.annotation.Transactional;
1922

2023
import java.time.LocalDateTime;
2124
import java.util.ArrayList;
2225
import java.util.List;
2326
import java.util.Objects;
27+
import java.util.concurrent.TimeUnit;
2428

2529
@Service
2630
@RequiredArgsConstructor
2731
public class ChatRoomServiceImpl implements ChatRoomService {
2832

2933
private final ChatRoomRepository chatRoomRepository;
34+
35+
private final ChatMessageRepository chatMessageRepository;
36+
3037
private final UserRepository userRepository;
38+
3139
private final UserChatRoomRepository userChatRoomRepository;
32-
private final ChatMessageRepository chatMessageRepository;
40+
41+
private final RedissonClient redissonClient;
3342

3443
@Override
3544
@Transactional
@@ -61,38 +70,6 @@ public void createRoom(ChatRoomRequest chatRoomRequest, Long userId) {
6170
userChatRoomRepository.save(userChatRoom);
6271
}
6372

64-
@Override
65-
@Transactional
66-
public void joinRoom(Long roomId, Long userId) {
67-
// 유저 조회
68-
User findUser = userRepository.findById(userId)
69-
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
70-
71-
// room 조회
72-
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
73-
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROOM));
74-
75-
// 현재 인원 수 조회
76-
Long currentUserCount = userChatRoomRepository.countByChatRoomId(roomId);
77-
// 채팅방이 꽉 찼을 경우 예외 처리
78-
if (currentUserCount >= chatRoom.getUserCountMax()) {
79-
throw new CustomException(ErrorCode.ROOM_USER_FULL);
80-
}
81-
82-
// 이미 참여한 방인지 확인
83-
if (userChatRoomRepository.existsByChatRoomIdAndUserId(roomId, userId)) {
84-
throw new CustomException(ErrorCode.ALREADY_JOIN_ROOM);
85-
}
86-
87-
88-
// user_chatroom 관계 생성
89-
UserChatRoom userChatRoom = UserChatRoom.builder()
90-
.user(findUser)
91-
.chatRoom(chatRoom)
92-
.build();
93-
userChatRoomRepository.save(userChatRoom);
94-
}
95-
9673
// 방 조회 DTO 변환 메서드 추출
9774
private static List<ChatRoomDto> getChatRoomDtos(Page<ChatRoom> all) {
9875
List<ChatRoomDto> chatRoomList = new ArrayList<>();
@@ -110,6 +87,7 @@ private static List<ChatRoomDto> getChatRoomDtos(Page<ChatRoom> all) {
11087
return chatRoomList;
11188
}
11289

90+
11391
// 채팅방 전체 조회
11492
@Override
11593
@Transactional(readOnly = true)
@@ -135,12 +113,57 @@ public List<ChatRoomDto> getUserByRoomPartList(Long userId, Pageable pageable) {
135113
return getChatRoomDtos(allByUserIdAndUserChatRooms);
136114
}
137115

116+
@Override
117+
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
118+
public void joinRoom(Long roomId, Long userId) {
119+
RLock lock = redissonClient.getLock("joinRoomLock:" + roomId);
120+
try {
121+
boolean available = lock.tryLock(1, TimeUnit.SECONDS);
122+
123+
if (!available) {
124+
throw new CustomException(ErrorCode.FAILED_GET_LOCK);
125+
}
126+
// 유저 조회
127+
User findUser = getUser(userId);
128+
129+
// room 조회
130+
ChatRoom chatRoom = chatRoomRepository.findById(roomId) // lock (기존)
131+
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROOM));
132+
133+
// user_chatroom 현재 인원 카운트 (비즈니스 로직)
134+
Long currentUserCount = userChatRoomRepository.countNonLockByChatRoomId(roomId); // lock (기존)
135+
136+
List<Long> userChatRoomByChatRoomId = userChatRoomRepository
137+
.findUserChatRoomByChatRoom_Id(roomId);
138+
139+
if (userChatRoomByChatRoomId.contains(userId)) {
140+
throw new CustomException(ErrorCode.ALREADY_JOIN_ROOM);
141+
}
142+
143+
// chatroom 입장
144+
if (currentUserCount >= chatRoom.getUserCountMax()) {
145+
throw new CustomException(ErrorCode.ROOM_USER_FULL);
146+
}
147+
148+
UserChatRoom userChatRoom = UserChatRoom.builder()
149+
.user(findUser)
150+
.chatRoom(chatRoom)
151+
.build();
152+
userChatRoomRepository.save(userChatRoom);
153+
// 비즈니스 로직 끝
154+
} catch (InterruptedException e) {
155+
throw new CustomException(ErrorCode.FAILED_GET_LOCK);
156+
} finally {
157+
lock.unlock();
158+
}
159+
160+
161+
}
162+
138163
@Override
139164
@Transactional
140165
public void outRoom(Long userId, Long roomId) {
141-
ChatRoom room = chatRoomRepository
142-
.findChatRoomById(roomId)
143-
.orElseThrow(() -> new CustomException(ErrorCode.NO_ROOM_FOUND));
166+
ChatRoom room = getChatRoom(roomId);
144167
// 방장이 아니라면
145168
if (!Objects.equals(room.getRoomCreator(), userId)) {
146169
userChatRoomRepository.deleteUserChatRoomByChatRoom_IdAndUserId(roomId, userId);
@@ -153,13 +176,10 @@ public void outRoom(Long userId, Long roomId) {
153176

154177
}
155178

156-
157179
@Override
158180
@Transactional
159181
public void deleteRoom(Long userId, Long roomId) {
160-
ChatRoom room = chatRoomRepository
161-
.findChatRoomById(roomId)
162-
.orElseThrow(() -> new CustomException(ErrorCode.NO_ROOM));
182+
ChatRoom room = getChatRoom(roomId);
163183
//방장이 아닐 경우, 삭제 불가
164184
if (!Objects.equals(room.getRoomCreator(), userId)) {
165185
throw new CustomException(ErrorCode.NOT_ROOM_CREATOR);
@@ -169,8 +189,19 @@ public void deleteRoom(Long userId, Long roomId) {
169189
userChatRoomRepository.deleteUserChatRoomByChatRoom_Id(roomId);
170190
chatRoomRepository.deleteById(roomId);
171191

172-
}
192+
}
193+
194+
private User getUser(Long userId) {
195+
return userRepository.findById(userId)
196+
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
197+
}
173198

174199

200+
private ChatRoom getChatRoom(Long roomId) {
201+
return chatRoomRepository
202+
.findChatRoomById(roomId)
203+
.orElseThrow(() -> new CustomException(ErrorCode.NONE_ROOM));
204+
205+
}
175206

176207
}

src/main/java/cmf/commitField/domain/chat/userChatRoom/repository/UserChatRoomRepository.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
package cmf.commitField.domain.chat.userChatRoom.repository;
22

33
import cmf.commitField.domain.chat.userChatRoom.entity.UserChatRoom;
4-
import jakarta.persistence.LockModeType;
5-
import jakarta.persistence.QueryHint;
64
import org.springframework.data.jpa.repository.JpaRepository;
7-
import org.springframework.data.jpa.repository.Lock;
85
import org.springframework.data.jpa.repository.Query;
9-
import org.springframework.data.jpa.repository.QueryHints;
106
import org.springframework.data.repository.query.Param;
117
import org.springframework.stereotype.Repository;
128

@@ -15,8 +11,8 @@
1511
@Repository
1612
public interface UserChatRoomRepository extends JpaRepository<UserChatRoom, Long> {
1713

18-
@Lock(LockModeType.PESSIMISTIC_WRITE)
19-
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "1000")})
14+
// @Lock(LockModeType.PESSIMISTIC_WRITE)
15+
// @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "1000")})
2016
Long countByChatRoomId(Long roomId); // 비관적 락
2117

2218
// @Query("select count(*) from UserChatRoom u where u.chatRoom.id = ?1 ")
@@ -32,7 +28,8 @@ public interface UserChatRoomRepository extends JpaRepository<UserChatRoom, Long
3228
boolean existsByChatRoomIdAndUserId(Long roomId, Long userId);
3329
// 특정 방에 참여한 모든 UserChatRoom 관계 조회
3430
List<UserChatRoom> findByChatRoom_Id(Long chatRoomId);
35-
31+
@Query("select u.user.id from UserChatRoom u where u.chatRoom.id = ?1")
32+
List<Long> findUserChatRoomByChatRoom_Id(Long chatRoomId);
3633

3734

3835
}

src/main/java/cmf/commitField/domain/commit/scheduler/CommitScheduler.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ public class CommitScheduler {
2323

2424
@Scheduled(fixedRate = 60000) // 1분마다 실행
2525
public void updateUserCommits() {
26-
List<User> activeUsers = userRepository.findActiveUser(); // 💫 변경 필요, 차후 active 상태인 user만 찾게끔 변경해야 함.
26+
log.info("🔍 updateUserCommits 실행중");
27+
List<User> activeUsers = userRepository.findAll(); // 💫 변경 필요, 차후 active 상태인 user만 찾게끔 변경해야 함.
28+
29+
log.info("🔍 Active User Count: {}", activeUsers.size());
2730

2831
for (User user : activeUsers) {
2932
Integer cachedCount = commitCacheService.getCachedCommitCount(user.getUsername());
3033
int newCommitCount = githubService.getUserCommitCount(user.getUsername());
3134

35+
log.info("🔍 User: {}, Commit Count: {}", user.getUsername(), newCommitCount);
36+
3237
if (cachedCount == null || cachedCount != newCommitCount) { // 변화가 있을 때만 처리
3338
commitCacheService.updateCachedCommitCount(user.getUsername(), newCommitCount);
3439
redpandaProducer.sendCommitUpdate(user.getUsername(), newCommitCount);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package cmf.commitField.domain.commit.sinceCommit.entity;
2+
3+
import cmf.commitField.global.jpa.BaseEntity;
4+
import jakarta.persistence.*;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.time.LocalDate;
10+
11+
@Entity
12+
@Table(name = "commit_history")
13+
@Getter
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
public class CommitHistory extends BaseEntity {
17+
18+
@Column(nullable = false)
19+
private String username; // GitHub 사용자명
20+
21+
@Column(nullable = false)
22+
private int streak; // 연속 커밋 수
23+
24+
@Column(nullable = false)
25+
private LocalDate commitDate; // 커밋한 날짜
26+
}
27+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cmf.commitField.domain.commit.sinceCommit.repositoty;
2+
3+
import cmf.commitField.domain.commit.sinceCommit.entity.CommitHistory;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
import java.util.Optional;
8+
9+
@Repository
10+
public interface CommitHistoryRepository extends JpaRepository<CommitHistory, Long> {
11+
// 특정 유저의 최신 커밋 기록 조회
12+
Optional<CommitHistory> findTopByUsernameOrderByCommitDateDesc(String username);
13+
}

src/main/java/cmf/commitField/domain/commit/sinceCommit/service/CommitCacheService.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ public class CommitCacheService {
1414
private final StringRedisTemplate redisTemplate;
1515

1616
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;
17+
log.info("Redis Template: {}", redisTemplate);
18+
String key = "commit:" + username; // Redis 키 생성 (ex: commit:hongildong)
19+
String value = redisTemplate.opsForValue().get(key); // Redis에서 값 가져오기
20+
return value != null ? Integer.parseInt(value) : null; // 값이 있으면 정수 변환, 없으면 null 반환
2021
}
2122

2223
public void updateCachedCommitCount(String username, int count) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmf.commitField.domain.noti.noti.entity;
2+
3+
import cmf.commitField.domain.user.entity.User;
4+
import cmf.commitField.global.jpa.BaseEntity;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.ManyToOne;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
import lombok.Setter;
11+
import lombok.experimental.SuperBuilder;
12+
13+
import static lombok.AccessLevel.PROTECTED;
14+
15+
@Entity
16+
@NoArgsConstructor(access = PROTECTED)
17+
@AllArgsConstructor(access = PROTECTED)
18+
@SuperBuilder
19+
@Getter
20+
@Setter
21+
public class Noti extends BaseEntity {
22+
@ManyToOne
23+
private User actor;
24+
@ManyToOne
25+
private User receiver;
26+
private String relTypeCode;
27+
private long relId;
28+
private String typeCode;
29+
private String type2Code;
30+
private boolean read;
31+
}

0 commit comments

Comments
 (0)