Skip to content

Commit d8411bb

Browse files
authored
Merge pull request #36 from CleanEngine/dev
Dev
2 parents 046e331 + 98414c3 commit d8411bb

File tree

161 files changed

+7622
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+7622
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ out/
3535

3636
### VS Code ###
3737
.vscode/
38+
39+
### 로컬 환경변수 ###
40+
local.properties

build.gradle

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,37 @@ repositories {
2424
}
2525

2626
dependencies {
27+
28+
implementation 'org.springframework.boot:spring-boot-starter-websocket' // WS + STOMP
29+
2730
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
31+
implementation 'org.springframework.boot:spring-boot-starter-validation'
2832
implementation 'org.springframework.boot:spring-boot-starter-web'
33+
34+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8'
35+
implementation 'org.springframework.boot:spring-boot-starter-actuator'
36+
37+
38+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
39+
implementation 'com.google.code.gson:gson:2.13.1'
2940
compileOnly 'org.projectlombok:lombok'
41+
annotationProcessor 'org.projectlombok:lombok'
42+
3043
runtimeOnly 'com.h2database:h2'
44+
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.3") // ← 드라이버
3145
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
32-
annotationProcessor 'org.projectlombok:lombok'
46+
3347
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3448
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
49+
50+
// Spring Security + OAuth2
51+
implementation 'org.springframework.boot:spring-boot-starter-security'
52+
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
53+
54+
// jwt
55+
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
56+
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
57+
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
3558
}
3659

3760
tasks.named('test') {

src/main/java/com/cleanengine/coin/CoinApplication.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.scheduling.annotation.EnableScheduling;
56

7+
8+
@EnableScheduling
69
@SpringBootApplication
710
public class CoinApplication {
811

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.cleanengine.coin.chart.config;
2+
3+
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
6+
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
7+
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
8+
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
9+
10+
@Configuration
11+
@EnableWebSocketMessageBroker
12+
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
13+
14+
@Override
15+
public void configureMessageBroker(MessageBrokerRegistry config) {
16+
config.enableSimpleBroker("/topic"); //메세지 브로커로 라우팅 되어야한다
17+
config.setApplicationDestinationPrefixes("/app"); //app으로 시작되는 메세지가 message-handling 라우팅 되어야한다
18+
}
19+
20+
@Override
21+
public void registerStompEndpoints(StompEndpointRegistry registry) {
22+
registry
23+
.addEndpoint("/coin/min") //endPoint 지정
24+
//추후 cors url추가
25+
.setAllowedOrigins("http://localhost:63342", "http://localhost:8080", "http://localhost:5500", "http://localhost:5173");
26+
registry
27+
.addEndpoint("/coin/realtime")
28+
.setAllowedOrigins("http://localhost:63342", "http://localhost:8080", "http://localhost:5500", "http://localhost:5173");
29+
registry
30+
.addEndpoint("/coin/orderbook")
31+
.setAllowedOrigins("http://localhost:63342", "http://localhost:8080", "http://localhost:5500", "http://localhost:5173");
32+
}
33+
34+
35+
}
36+
37+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.cleanengine.coin.chart.controller;
2+
3+
4+
import com.cleanengine.coin.chart.dto.RealTimeOhlcDto;
5+
//import com.cleanengine.coin.chart.Dto.RealTimeTradeDto;
6+
import com.cleanengine.coin.chart.service.*;
7+
import com.cleanengine.coin.chart.service.ChartSubscriptionService;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.messaging.simp.SimpMessagingTemplate;
11+
import org.springframework.scheduling.annotation.Scheduled;
12+
import org.springframework.stereotype.Component;
13+
14+
import java.time.LocalDateTime;
15+
16+
@Component
17+
@RequiredArgsConstructor
18+
@Slf4j
19+
public class ChartDataController {
20+
21+
private final ChartSubscriptionService subscriptionService;
22+
private final SimpMessagingTemplate messagingTemplate;
23+
private final RealTimeOhlcService realTimeOhlcService;
24+
25+
/**
26+
* 1초마다 실행 - 실시간 OHLC 데이터 전송
27+
*/
28+
@Scheduled(fixedRate = 1000)
29+
public void publishRealTimeOhlc() {
30+
try {
31+
log.info("△ 실시간 OHLC 데이터 스케줄러 실행");
32+
33+
// 구독된 티커가 없으면 조기 종료
34+
if (subscriptionService.getAllRealTimeOhlcSubscribedTickers().isEmpty()) {
35+
log.info("실시간 OHLC 구독된 티커 없음, 전송 생략");
36+
return;
37+
}
38+
39+
// 모든 구독된 티커에 대해 데이터 전송
40+
for (String ticker : subscriptionService.getAllRealTimeOhlcSubscribedTickers()) {
41+
try {
42+
log.info("티커 {} 실시간 OHLC 데이터 전송 중...", ticker);
43+
44+
// 티커별 최신 OHLC 데이터 조회 및 전송
45+
RealTimeOhlcDto ohlcData = realTimeOhlcService.getRealTimeOhlc(ticker);
46+
47+
if (ohlcData == null) {
48+
log.warn("티커 {}의 실시간 OHLC 데이터가 없습니다. 빈 데이터 전송", ticker);
49+
RealTimeOhlcDto emptyData = new RealTimeOhlcDto();
50+
emptyData.setTicker(ticker);
51+
emptyData.setTimestamp(LocalDateTime.now());
52+
emptyData.setOpen(0.0);
53+
emptyData.setHigh(0.0);
54+
emptyData.setLow(0.0);
55+
emptyData.setClose(0.0);
56+
emptyData.setVolume(0.0);
57+
58+
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, emptyData);
59+
} else {
60+
// 조회된 실시간 OHLC 데이터 전송
61+
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, ohlcData);
62+
log.info("실시간 OHLC 데이터 전송: {}", ohlcData);
63+
}
64+
} catch (Exception e) {
65+
log.error("티커 {} 실시간 OHLC 데이터 처리 중 오류: {}", ticker, e.getMessage(), e);
66+
}
67+
}
68+
} catch (Exception e) {
69+
log.error("△ 실시간 OHLC 데이터 발행 중 오류: {}", e.getMessage(), e);
70+
}
71+
}
72+
73+
74+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.cleanengine.coin.chart.controller;
2+
3+
import com.cleanengine.coin.chart.dto.RealTimeOhlcDto;
4+
import com.cleanengine.coin.chart.service.minute.MinuteOhlcDataService;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.*;
8+
9+
import java.util.List;
10+
11+
@RestController
12+
@RequestMapping("/api/minute-ohlc")
13+
@RequiredArgsConstructor
14+
public class MinuteOhlcDataController {
15+
16+
private final MinuteOhlcDataService service;
17+
18+
/**
19+
* GET /api/minute-ohlc?ticker=BTC
20+
* DB에 있는 과거 거래를 1분 단위로 묶어 OHLC+volume을 계산한 리스트 반환
21+
*/
22+
@GetMapping
23+
public ResponseEntity<List<RealTimeOhlcDto>> getMinuteOhlc(
24+
@RequestParam("ticker") String ticker
25+
) {
26+
List<RealTimeOhlcDto> data = service.getMinuteOhlcData(ticker);
27+
return ResponseEntity.ok(data);
28+
}
29+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.cleanengine.coin.chart.controller;
2+
3+
import com.cleanengine.coin.chart.dto.PrevRateDto;
4+
import com.cleanengine.coin.chart.service.RealTimeDataPrevRateService;
5+
import lombok.RequiredArgsConstructor;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.messaging.handler.annotation.DestinationVariable;
8+
import org.springframework.messaging.handler.annotation.MessageMapping;
9+
import org.springframework.messaging.simp.SimpMessagingTemplate;
10+
import org.springframework.stereotype.Controller;
11+
12+
@Controller
13+
@RequiredArgsConstructor
14+
@Slf4j
15+
public class PrevRateController {
16+
17+
private final RealTimeDataPrevRateService prevRateService;
18+
private final SimpMessagingTemplate messagingTemplate;
19+
20+
@MessageMapping("/subscribe/prevRate/{ticker}")
21+
public void subscribePrevRate(@DestinationVariable String ticker) {
22+
log.info("티커 {} 전일 대비 변동률 구독 요청", ticker);
23+
24+
// 서비스를 통해 전일 대비 변동률 데이터 얻기
25+
PrevRateDto data = prevRateService.generatePrevRateData(ticker);
26+
27+
// 티커별 토픽으로 전송
28+
messagingTemplate.convertAndSend(
29+
"/topic/prevRate/" + ticker,
30+
data
31+
);
32+
log.debug("전송 완료: /topic/prevRate/{} -> {}", ticker, data);
33+
}
34+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.cleanengine.coin.chart.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.scheduling.annotation.Scheduled;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
@RequiredArgsConstructor
9+
public class PrevRateScheduler {
10+
private final PrevRateController prevRateController;
11+
12+
@Scheduled(fixedDelay = 1000)
13+
public void sendPrevRate(){
14+
prevRateController.subscribePrevRate("TRUMP");
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.cleanengine.coin.chart.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.scheduling.annotation.Scheduled;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
@RequiredArgsConstructor
9+
public class RealTimeRateScheduler {
10+
private final RealTimeTradeController realTimeTradeController;
11+
12+
@Scheduled(fixedDelay = 5000)
13+
public void sendPrevRate(){
14+
realTimeTradeController.realTimeTradeRate("TRUMP");
15+
}
16+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.cleanengine.coin.chart.controller;
2+
3+
import com.cleanengine.coin.chart.dto.RealTimeDataDto;
4+
import com.cleanengine.coin.chart.service.RealTimeTradeService;
5+
import lombok.RequiredArgsConstructor;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.messaging.handler.annotation.DestinationVariable;
8+
import org.springframework.messaging.handler.annotation.MessageMapping;
9+
import org.springframework.messaging.simp.SimpMessagingTemplate;
10+
import org.springframework.stereotype.Controller;
11+
12+
@Controller
13+
@RequiredArgsConstructor
14+
@Slf4j
15+
public class RealTimeTradeController {
16+
17+
private final SimpMessagingTemplate messagingTemplate;
18+
private final RealTimeTradeService realTimeTradeService;
19+
20+
/**
21+
* 클라이언트가 /app/subscribe/realTimeTradeRate/{ticker} 로 send() 하면
22+
* {ticker} 값을 받습니다.
23+
*/
24+
@MessageMapping("/subscribe/realTimeTradeRate/{ticker}")
25+
public void realTimeTradeRate(@DestinationVariable String ticker) {
26+
log.info("티커 {} 실시간 구독 요청", ticker);
27+
28+
// 서비스로부터 DTO 생성
29+
RealTimeDataDto data = realTimeTradeService.generateRealTimeData(ticker);
30+
31+
// 티커별 토픽으로 전송
32+
messagingTemplate.convertAndSend(
33+
"/topic/realTimeTradeRate/" + ticker,
34+
data
35+
);
36+
log.debug("전송 완료: /topic/realTimeTradeRate/{} -> {}", ticker, data);
37+
}
38+
}

0 commit comments

Comments
 (0)