Skip to content

Commit eb4706e

Browse files
authored
Merge pull request #115 from CommitField/dev
chore : ์ค‘๊ฐ„ ๋ฐฐํฌ
2 parents 9c1b7f7 + 75fe116 commit eb4706e

File tree

10 files changed

+146
-50
lines changed

10 files changed

+146
-50
lines changed

โ€Žbuild.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ dependencies {
7676
// Spring Security OAuth2
7777
implementation ("org.springframework.security:spring-security-oauth2-client:6.4.2") // Or the version you're using
7878
implementation ("org.springframework.security:spring-security-oauth2-core:6.4.2") // Or the version you're using
79+
80+
implementation("org.springframework.boot:spring-boot-starter-actuator")
81+
7982
}
8083

8184
tasks.withType<Test> {

โ€Žsrc/main/java/cmf/commitField/domain/noti/noti/controller/ApiV1NotiController.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import cmf.commitField.global.error.ErrorCode;
88
import cmf.commitField.global.exception.CustomException;
99
import cmf.commitField.global.globalDto.GlobalResponse;
10+
import cmf.commitField.global.websocket.NotiWebSocketHandler;
1011
import lombok.RequiredArgsConstructor;
1112
import lombok.extern.slf4j.Slf4j;
1213
import org.springframework.security.core.Authentication;
@@ -28,6 +29,7 @@
2829
public class ApiV1NotiController {
2930
private final NotiService notiService;
3031
private final UserRepository userRepository;
32+
private final NotiWebSocketHandler notiWebSocketHandler;
3133

3234
@GetMapping("")
3335
public GlobalResponse<List<NotiDto>> getNoti() {
@@ -39,6 +41,9 @@ public GlobalResponse<List<NotiDto>> getNoti() {
3941
String username = (String) attributes.get("login"); // GitHub ID
4042
User user = userRepository.findByUsername(username).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
4143
List<NotiDto> notis = notiService.getNotReadNoti(user);
44+
// ์›น์†Œ์ผ“์œผ๋กœ ์•Œ๋ฆผ ์ „์†ก
45+
notiWebSocketHandler.sendNotification(user, notis);
46+
4247
return GlobalResponse.success(notis);
4348
}
4449

@@ -49,4 +54,19 @@ public GlobalResponse<List<NotiDto>> getNoti() {
4954
public void createNoti() {
5055

5156
}
52-
}
57+
58+
@PostMapping("/read")
59+
public GlobalResponse<Object> readNoti() {
60+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
61+
62+
if (authentication instanceof OAuth2AuthenticationToken) {
63+
OAuth2User principal = (OAuth2User) authentication.getPrincipal();
64+
Map<String, Object> attributes = principal.getAttributes();
65+
String username = (String) attributes.get("login"); // GitHub ID
66+
User user = userRepository.findByUsername(username).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
67+
notiService.read(user);
68+
return GlobalResponse.success("์•Œ๋ฆผ์„ ์ฝ์Œ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.");
69+
}
70+
return GlobalResponse.error(ErrorCode.LOGIN_REQUIRED);
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cmf.commitField.domain.noti.noti.event;
2+
3+
import lombok.Getter;
4+
import org.springframework.context.ApplicationEvent;
5+
6+
@Getter
7+
public class NotiEvent extends ApplicationEvent {
8+
private final String username;
9+
private final String message;
10+
11+
public NotiEvent(Object source, String username, String message) {
12+
super(source);
13+
this.username = username;
14+
this.message = message;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package cmf.commitField.domain.noti.noti.event;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.context.event.EventListener;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
@RequiredArgsConstructor
9+
public class NotiListener {
10+
@EventListener
11+
public void handleNotiEvent(NotiEvent event) {
12+
System.out.println("NotiEvent: " + event.getMessage());
13+
}
14+
}

โ€Žsrc/main/java/cmf/commitField/domain/noti/noti/repository/NotiRepository.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ public interface NotiRepository extends JpaRepository<Noti, Long> {
1717
@Query("SELECT new cmf.commitField.domain.noti.noti.dto.NotiDto(n.id, n.message, n.createdAt) " +
1818
"FROM Noti n JOIN n.receiver u WHERE u.id = :receiverId AND n.isRead = :isRead")
1919
Optional<List<NotiDto>> findNotiDtoByReceiverId(@Param("receiverId") Long receiverId, @Param("isRead") boolean isRead);
20-
21-
}
20+
Optional<List<Noti>> findNotiByReceiver(User receiver);
21+
}

โ€Žsrc/main/java/cmf/commitField/domain/noti/noti/service/NotiService.java

+24-17
Original file line numberDiff line numberDiff line change
@@ -64,28 +64,35 @@ public List<Noti> getSeasonNotiCheck(User receiver, long seasonId) {
6464

6565
// ์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ
6666
@Transactional
67-
public void createNewSeason(Season season) {
67+
public void createNewSeasonNoti(Season season, User user) {
6868
System.out.println("์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ");
6969
// ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
7070
String message = NotiService.generateMessage(NotiDetailType.SEASON_START, season.getName());
7171

72-
// ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ
73-
Iterable<User> users = userRepository.findAll();
72+
Noti noti = Noti.builder()
73+
.typeCode(NotiType.SEASON)
74+
.type2Code(NotiDetailType.SEASON_START)
75+
.receiver(user)
76+
.isRead(false)
77+
.message(message)
78+
.relId(season.getId())
79+
.relTypeCode(season.getModelName())
80+
.build();
7481

75-
// ๋ชจ๋“  ์œ ์ € ์•Œ๋ฆผ ๊ฐ์ฒด ์ƒ์„ฑ
76-
users.forEach(user -> {
77-
Noti noti = Noti.builder()
78-
.typeCode(NotiType.SEASON)
79-
.type2Code(NotiDetailType.SEASON_START)
80-
.receiver(user)
81-
.isRead(false)
82-
.message(message)
83-
.relId(season.getId())
84-
.relTypeCode(season.getModelName())
85-
.build();
82+
notiRepository.save(noti);
8683

87-
notiRepository.save(noti);
88-
});
8984
System.out.println("์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ ๋");
9085
}
91-
}
86+
87+
// ์ฝ์Œ ์ฒ˜๋ฆฌ
88+
@Transactional
89+
public List<Noti> read(User receiver) {
90+
System.out.println("์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ");
91+
List<Noti> notis = notiRepository.findNotiByReceiver(receiver).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
92+
notis.forEach(noti -> {
93+
noti.setRead(true);
94+
});
95+
System.out.println("์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ ๋");
96+
return notis;
97+
}
98+
}

โ€Žsrc/main/java/cmf/commitField/domain/user/service/CustomOAuth2UserService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) {
101101
if(notiService.getSeasonNotiCheck(user, season.getId()).isEmpty()){
102102
log.info("User {} does not have season noti", user.getUsername());
103103
// ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด ์•Œ๋ฆผ์„ ์ถ”๊ฐ€
104-
notiService.createNewSeason(season);
104+
notiService.createNewSeasonNoti(season, user);
105105
// redisTemplate.opsForValue().set(season_key, String.valueOf(count), Duration.ofHours(3)); // 3์‹œ๊ฐ„ ์บ์‹ฑ
106106
}
107107

Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
package cmf.commitField.global.scheduler;
22

33
import cmf.commitField.domain.noti.noti.service.NotiService;
4+
import cmf.commitField.domain.user.entity.User;
45
import cmf.commitField.domain.user.repository.UserRepository;
6+
import cmf.commitField.global.error.ErrorCode;
7+
import cmf.commitField.global.exception.CustomException;
58
import lombok.RequiredArgsConstructor;
9+
import org.springframework.context.ApplicationEventPublisher;
10+
import org.springframework.scheduling.annotation.Scheduled;
611
import org.springframework.stereotype.Component;
712

813
@Component
914
@RequiredArgsConstructor
1015
public class NotiTestScheduler {
1116
private final NotiService notiService;
1217
private final UserRepository userRepository;
18+
private final ApplicationEventPublisher eventPublisher;
1319

14-
// @Scheduled(cron = "0 44 * * * *")
15-
// public void test() {
16-
// System.out.println("test ์‹คํ–‰");
17-
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
18-
//
19-
// if (authentication instanceof OAuth2AuthenticationToken) {
20-
// OAuth2User principal = (OAuth2User) authentication.getPrincipal();
21-
// Map<String, Object> attributes = principal.getAttributes();
22-
// String username = (String) attributes.get("login"); // GitHub ID
23-
// User user = userRepository.findByUsername(username).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
24-
// notiService.createNoti(user);
25-
// }
26-
//
27-
// }
20+
@Scheduled(cron = "30 14 * * * *")
21+
public void test() {
22+
System.out.println("test ์‹คํ–‰");
23+
24+
User user = userRepository.findById(1L).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
25+
notiService.createNoti(user);
26+
// eventPublisher.publishEvent();
27+
}
2828
}

โ€Žsrc/main/java/cmf/commitField/global/scheduler/SeasonScheduler.java

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// java/cmf/commitField/global/scheduler/SeasonScheduler.java
22
package cmf.commitField.global.scheduler;
33

4+
import cmf.commitField.domain.noti.noti.service.NotiService;
45
import cmf.commitField.domain.season.entity.Rank;
56
import cmf.commitField.domain.season.entity.Season;
67
import cmf.commitField.domain.season.entity.SeasonStatus;
@@ -29,6 +30,7 @@ public class SeasonScheduler {
2930
private final UserSeasonRepository userSeasonRepository;
3031
private final UserRepository userRepository;
3132
private final SeasonService seasonService;
33+
private final NotiService notiService;
3234

3335
// ๋งค๋…„ 3, 6, 9, 12์›” 1์ผ ์ž์ •๋งˆ๋‹ค ์‹œ์ฆŒ ํ™•์ธ ๋ฐ ์ƒ์„ฑ
3436
@Scheduled(cron = "0 0 0 1 3,6,9,12 *")
@@ -53,6 +55,11 @@ public void checkAndCreateNewSeason() {
5355

5456
Season newSeason = seasonService.createNewSeason(seasonName, startDate, endDate);
5557

58+
// ๋ชจ๋“  ์œ ์ €์—๊ฒŒ ์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ
59+
userRepository.findAll().forEach(user -> {
60+
notiService.createNewSeasonNoti(newSeason, user);
61+
});
62+
5663
// ๋ชจ๋“  ์œ ์ €์˜ ๋žญํฌ ์ดˆ๊ธฐํ™”
5764
resetUserRanks(newSeason);
5865

โ€Žsrc/main/java/cmf/commitField/global/websocket/NotiWebSocketHandler.java

+44-15
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,82 @@
11
package cmf.commitField.global.websocket;
22

3+
import cmf.commitField.domain.noti.noti.dto.NotiDto;
4+
import cmf.commitField.domain.noti.noti.entity.Noti;
5+
import cmf.commitField.domain.noti.noti.service.NotiService;
6+
import cmf.commitField.domain.user.entity.User;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import lombok.RequiredArgsConstructor;
39
import lombok.extern.slf4j.Slf4j;
410
import org.springframework.stereotype.Component;
511
import org.springframework.web.socket.*;
612

713
import java.io.IOException;
8-
import java.util.ArrayList;
14+
import java.time.LocalDateTime;
15+
import java.util.HashMap;
916
import java.util.List;
17+
import java.util.Map;
18+
import java.util.concurrent.ConcurrentHashMap;
1019

1120
@Component
21+
@RequiredArgsConstructor
1222
@Slf4j
1323
public class NotiWebSocketHandler implements WebSocketHandler {
14-
15-
private final List<WebSocketSession> sessions = new ArrayList<>();
24+
private final NotiService notiService;
25+
private final ObjectMapper objectMapper;
26+
private final Map<Long, WebSocketSession> sessions = new ConcurrentHashMap<>();
1627

1728
@Override
1829
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
19-
sessions.add(session);
20-
log.info("์•Œ๋ฆผ WebSocket ์—ฐ๊ฒฐ๋จ: " + session);
30+
log.info("ํด๋ผ์ด์–ธํŠธ ์ ‘์†: {}", session.getId());
31+
32+
// ์—ฐ๊ฒฐ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ์ „์†ก
33+
Map<String, Object> connectMessage = new HashMap<>();
34+
connectMessage.put("type", "SYSTEM");
35+
connectMessage.put("connect", "์•Œ๋ฆผ ์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
36+
connectMessage.put("timestamp", LocalDateTime.now().toString());
37+
38+
try {
39+
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(connectMessage)));
40+
} catch (Exception e) {
41+
log.error("์—ฐ๊ฒฐ ๋ฉ”์‹œ์ง€ ์ „์†ก ์‹คํŒจ: {}", e.getMessage());
42+
}
2143
}
2244

2345
@Override
2446
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
25-
// ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง (ํ•„์š” ์‹œ ๊ตฌํ˜„)
47+
if (message instanceof TextMessage) {
48+
String payload = ((TextMessage) message).getPayload();
49+
log.info("Received message: {}", payload);
50+
} else {
51+
log.warn("Received unsupported message type: {}", message.getClass().getSimpleName());
52+
}
2653
}
2754

2855
@Override
2956
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
30-
log.error("์•Œ๋ฆผ WebSocket ์˜ค๋ฅ˜: " + exception.getMessage());
57+
log.error("WebSocket error: ", exception);
58+
session.close(CloseStatus.SERVER_ERROR);
3159
}
3260

3361
@Override
34-
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
35-
sessions.remove(session);
36-
log.info("์•Œ๋ฆผ WebSocket ์—ฐ๊ฒฐ ์ข…๋ฃŒ๋จ: " + session);
62+
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
63+
sessions.values().remove(session);
64+
log.info("WebSocket disconnected: {}", status);
3765
}
3866

3967
@Override
4068
public boolean supportsPartialMessages() {
4169
return false;
4270
}
4371

44-
// ๋ชจ๋“  ์œ ์ €์—๊ฒŒ ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ „์†ก
45-
public void sendNotificationToAllUsers(String message) {
46-
for (WebSocketSession session : sessions) {
72+
public void sendNotification(User receiver, List<NotiDto> noti) {
73+
WebSocketSession session = sessions.get(receiver.getId());
74+
if (session != null && session.isOpen()) {
4775
try {
48-
session.sendMessage(new TextMessage(message));
76+
String payload = objectMapper.writeValueAsString(noti);
77+
session.sendMessage(new TextMessage(payload));
4978
} catch (IOException e) {
50-
log.error("์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ „์†ก ์‹คํŒจ: " + e.getMessage());
79+
log.error("Failed to send WebSocket notification", e);
5180
}
5281
}
5382
}

0 commit comments

Comments
ย (0)