Skip to content

Commit 29e598d

Browse files
authored
Merge pull request #110 from kjiyun/chat
2 parents 8cdf511 + 999067d commit 29e598d

File tree

8 files changed

+61
-34
lines changed

8 files changed

+61
-34
lines changed

src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ public interface ChartDataRepository extends JpaRepository<ChartData, Long> {
3030

3131
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin);
3232

33-
//TODO: 위아래 코드 합치는 방법 찾기
34-
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin, Pageable pageable);
33+
List<ChartData> findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(MemeCoin memeCoin, LocalDateTime recordedTime, Pageable pageable);
3534

3635
@Query("SELECT c FROM ChartData c " +
3736
"WHERE c.memeCoin.id = :coinId " +

src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import com.memesphere.domain.memecoin.entity.MemeCoin;
99
import com.memesphere.domain.memecoin.repository.MemeCoinRepository;
1010
import com.memesphere.domain.memecoin.service.MemeCoinQueryService;
11+
import com.memesphere.global.jwt.CustomUserDetails;
1112
import lombok.RequiredArgsConstructor;
1213
import org.springframework.scheduling.annotation.Scheduled;
14+
import org.springframework.security.core.Authentication;
15+
import org.springframework.security.core.context.SecurityContextHolder;
1316
import org.springframework.stereotype.Component;
1417
import org.springframework.transaction.annotation.Transactional;
1518

@@ -24,9 +27,18 @@ public class ChartDataScheduler {
2427
private final BinanceQueryService binanceQueryService;
2528
private final MemeCoinQueryService memeCoinQueryService;
2629

27-
@Scheduled(cron = "0 0/10 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행
30+
@Scheduled(cron = "0 0/1 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행
2831
@Transactional
2932
public void updateChartData() {
33+
34+
Long userId = null;
35+
36+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
37+
// `authentication.getPrincipal()`을 `CustomUserDetails`로 변환
38+
if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) {
39+
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
40+
userId = userDetails.getUser().getId();
41+
}
3042
List<MemeCoin> memeCoins = memeCoinRepository.findAll();
3143

3244
for (MemeCoin memeCoin : memeCoins) {
@@ -36,7 +48,7 @@ public void updateChartData() {
3648

3749
ChartData chartData = toChartData(memeCoin,response);
3850

39-
memeCoinQueryService.updateChartData(memeCoin.getId(), chartData);
51+
memeCoinQueryService.updateChartData(memeCoin.getId(), chartData, userId);
4052

4153
} catch (Exception e) {
4254
throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA);

src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
import com.memesphere.domain.memecoin.entity.MemeCoin;
55

66
public interface ChartDataQueryService {
7-
void saveChartData(MemeCoin memeCoin, ChartData chartData);
7+
void saveChartData(MemeCoin memeCoin, ChartData chartData, Long userId);
88
}

src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.memesphere.domain.chartdata.entity.ChartData;
44
import com.memesphere.domain.chartdata.repository.ChartDataRepository;
55
import com.memesphere.domain.memecoin.entity.MemeCoin;
6+
import com.memesphere.domain.notification.service.PushNotificationService;
67
import lombok.RequiredArgsConstructor;
78
import org.springframework.stereotype.Service;
89
import org.springframework.transaction.annotation.Transactional;
@@ -12,12 +13,13 @@
1213
@Service
1314
@RequiredArgsConstructor
1415
public class ChartDataQueryServiceImpl implements ChartDataQueryService {
16+
private final PushNotificationService pushNotificationService;
1517
private final ChartDataRepository chartDataRepository;
1618
private static final int MAX_CHART_DATA_CNT = 6;
1719

1820
@Override
1921
@Transactional
20-
public void saveChartData(MemeCoin memeCoin, ChartData newChartData) {
22+
public void saveChartData(MemeCoin memeCoin, ChartData newChartData, Long userId) {
2123
List<ChartData> chartDataList = chartDataRepository.findByMemeCoinOrderByRecordedTimeDesc(memeCoin);
2224

2325
if (chartDataList.size() >= MAX_CHART_DATA_CNT) {
@@ -28,5 +30,7 @@ public void saveChartData(MemeCoin memeCoin, ChartData newChartData) {
2830

2931
newChartData.setMemeCoin(memeCoin);
3032
chartDataRepository.save(newChartData);
33+
34+
pushNotificationService.send(userId);
3135
}
3236
}

src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
import com.memesphere.domain.chartdata.entity.ChartData;
44

55
public interface MemeCoinQueryService {
6-
void updateChartData(Long memeCoinId, ChartData newChartData);
6+
void updateChartData(Long memeCoinId, ChartData newChartData, Long userId);
77
}

src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ public class MemeCoinQueryServiceImpl implements MemeCoinQueryService {
1818

1919
@Transactional
2020
@Override
21-
public void updateChartData(Long memeCoinId, ChartData newChartData) {
21+
public void updateChartData(Long memeCoinId, ChartData newChartData, Long userId) {
2222
MemeCoin memeCoin = memeCoinRepository.findById(memeCoinId)
2323
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND));
2424

25-
chartDataService.saveChartData(memeCoin, newChartData);
25+
chartDataService.saveChartData(memeCoin, newChartData, userId);
2626
}
2727
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package com.memesphere.domain.notification.service;
22

3-
import com.memesphere.domain.notification.entity.Notification;
43
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
54

65
public interface PushNotificationService {
76
SseEmitter subscribe(Long userId, String lastEventId);
8-
void send(Notification notification, Long userId);
7+
void send(Long userId);
98
}

src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.memesphere.global.apipayload.ApiResponse;
1212
import com.memesphere.global.apipayload.code.status.ErrorStatus;
1313
import com.memesphere.global.apipayload.exception.GeneralException;
14+
import com.memesphere.global.jwt.CustomUserDetails;
1415
import lombok.RequiredArgsConstructor;
1516
import lombok.extern.log4j.Log4j2;
1617
import org.springframework.data.domain.PageRequest;
@@ -24,6 +25,8 @@
2425
import java.math.RoundingMode;
2526
import java.time.LocalDateTime;
2627
import java.util.*;
28+
import java.util.concurrent.ExecutorService;
29+
import java.util.concurrent.Executors;
2730
import java.util.stream.Collectors;
2831

2932
@Log4j2
@@ -34,6 +37,7 @@ public class PushNotificationServiceImpl implements PushNotificationService {
3437
private final EmitterRepository emitterRepository;
3538
private final NotificationRepository notificationRepository;
3639
private final ChartDataRepository chartDataRepository;
40+
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
3741

3842
// 연결 지속 시간 설정 : 한시간
3943
private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60;
@@ -61,18 +65,6 @@ public SseEmitter subscribe(Long userId, String lastEventId) {
6165
.forEach(entry -> sendToClient(emitter, entry.getKey(), entry.getValue()));
6266
}
6367

64-
List<Notification> notifications = notificationRepository.findAllByUserId(userId); // 사용자가 등록한 알림 전부 가져오기
65-
66-
// 변동성을 초과하는 알림 필터링
67-
List<Notification> filteredNotifications = notifications.stream()
68-
.filter(notification -> isVolatilityExceeded(notification))
69-
.collect(Collectors.toList());
70-
71-
if (!filteredNotifications.isEmpty()) {
72-
notifications.forEach(notification -> {
73-
send(notification, userId);
74-
});
75-
}
7668
return emitter;
7769
}
7870

@@ -91,15 +83,35 @@ private void sendToClient(SseEmitter emitter, String emitterId, Object data) {
9183
}
9284

9385
@Override
94-
public void send(Notification notification, Long userId) {
86+
public void send(Long userId) {
9587

96-
// 실시간 알림 전송 - 로그인 한 유저의 SseEmitter 모두 가져오기
97-
Map<String, SseEmitter> sseEmitters = emitterRepository.findAllEmitterStartWithByUserId(String.valueOf(userId));
88+
List<Notification> notifications = notificationRepository.findAllByUserId(userId); // 사용자가 등록한 알림 전부 가져오기
9889

99-
sseEmitters.forEach((key, emitter) -> {
100-
emitterRepository.saveEventCache(key, notification);
101-
sendToClient(emitter, key, NotificationConverter.toNotificationCreateResponse(notification, notification.getMemeCoin()));
102-
});
90+
// 변동성을 초과하는 알림 필터링
91+
List<Notification> filteredNotifications = notifications.stream()
92+
.filter(notification -> isVolatilityExceeded(notification))
93+
.collect(Collectors.toList());
94+
95+
if (!filteredNotifications.isEmpty()) {
96+
// 실시간 알림 전송 - 로그인 한 유저의 SseEmitter 모두 가져오기
97+
Map<String, SseEmitter> sseEmitters = emitterRepository.findAllEmitterStartWithByUserId(String.valueOf(userId));
98+
99+
sseEmitters.forEach((key, emitter) -> {
100+
executorService.submit(() -> {
101+
filteredNotifications.forEach(notification -> {
102+
103+
emitterRepository.saveEventCache(key, notification);
104+
sendToClient(emitter, key, NotificationConverter.toNotificationCreateResponse(notification, notification.getMemeCoin()));
105+
106+
try {
107+
Thread.sleep(500); // 0.5초 간격으로 전송
108+
} catch (InterruptedException e) {
109+
Thread.currentThread().interrupt();
110+
}
111+
});
112+
});
113+
});
114+
}
103115
}
104116

105117
private boolean isVolatilityExceeded(Notification notification) {
@@ -113,18 +125,20 @@ private boolean isVolatilityExceeded(Notification notification) {
113125
throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA);
114126
}
115127

128+
LocalDateTime notificationTime = notification.getCreatedAt();
129+
116130
Integer count = notification.getStTime() / 10; //몇 번 가져올 것인지 결정
117131
Pageable pageable = (Pageable) PageRequest.of(0, count, Sort.by(Sort.Direction.DESC, "createdAt"));
118-
List<ChartData> lastNData = chartDataRepository.findByMemeCoinOrderByRecordedTimeDesc(memeCoin, pageable);
132+
List<ChartData> lastNData = chartDataRepository.findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(memeCoin, notificationTime, pageable);
119133

120134
if (lastNData.size() < count) {
121135
return false; // 비교할 데이터가 부족하면 알림을 보내지 않음
122136
}
123137

124138
BigDecimal sum = lastNData.stream()
125-
.map(ChartData::getPrice)
139+
.map(ChartData::getPriceChangeRate)
126140
.reduce(BigDecimal.ZERO, BigDecimal::add);
127-
BigDecimal average = sum.divide(BigDecimal.valueOf(lastNData.size()), 4, RoundingMode.HALF_UP);
141+
BigDecimal average = sum.divide(BigDecimal.valueOf(count), 4, RoundingMode.HALF_UP);
128142
BigDecimal definedVolatility = new BigDecimal(notification.getVolatility());
129143

130144
if (notification.getIsRising()) { // 상승인 경우
@@ -133,5 +147,4 @@ private boolean isVolatilityExceeded(Notification notification) {
133147
return average.compareTo(definedVolatility) < 0;
134148
}
135149
}
136-
137150
}

0 commit comments

Comments
 (0)