From cc5bff14b40af705f8acb6f1fd390b8142994d66 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Thu, 12 Jun 2025 19:13:58 +0900 Subject: [PATCH 01/11] =?UTF-8?q?fix:=20annotation=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cleanengine/coin/realitybot/api/ApiScheduler.java | 1 - .../com/cleanengine/coin/realitybot/config/SchedulerConfig.java | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java b/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java index e55a2059..9bf89e12 100644 --- a/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java +++ b/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java @@ -21,7 +21,6 @@ @Slf4j @Component -@WorkingServerProfile @RequiredArgsConstructor public class ApiScheduler { diff --git a/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java b/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java index 86aa2a3a..c5cf789f 100644 --- a/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java +++ b/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java @@ -1,5 +1,6 @@ package com.cleanengine.coin.realitybot.config; +import com.cleanengine.coin.common.annotation.WorkingServerProfile; import com.cleanengine.coin.realitybot.api.ApiScheduler; import com.cleanengine.coin.realitybot.api.UnitPriceRefresher; import org.springframework.beans.factory.annotation.Value; @@ -11,6 +12,7 @@ import java.time.Duration; @Configuration +@WorkingServerProfile @EnableScheduling //@RequiredArgsConstructor public class SchedulerConfig implements SchedulingConfigurer { From 975216aef41509c106ad6fd04c1d5108b51f68b3 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Thu, 12 Jun 2025 19:17:11 +0900 Subject: [PATCH 02/11] =?UTF-8?q?config:=20apm=20=EC=84=B8=ED=8C=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mircrometer, opentelemetry, jaeger, grafana 기반의 apm 세팅을 추가했습니다. --- .gitignore | 4 +- build.gradle | 1 + monitoring/docker-compose.yml | 106 ++++++++++++++++++ .../provisioning/datsources/datasource.yml | 9 ++ monitoring/prometheus/prometheus.yml | 11 ++ .../coin/common/annotation/StartNewTrace.java | 12 ++ .../annotation/StartNewTraceAspect.java | 43 +++++++ 7 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 monitoring/docker-compose.yml create mode 100644 monitoring/grafana/provisioning/datsources/datasource.yml create mode 100644 monitoring/prometheus/prometheus.yml create mode 100644 src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java create mode 100644 src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java diff --git a/.gitignore b/.gitignore index 661ab51d..f395d417 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ out/ ### 로컬 환경변수 ### local.properties /logs -docker-compose.override.yml \ No newline at end of file + +docker-compose.override.yml +opentelemetry-javaagent.jar \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1e947b67..83ce59b6 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' + implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.16.0") implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.google.code.gson:gson:2.13.1' diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml new file mode 100644 index 00000000..1337a0b1 --- /dev/null +++ b/monitoring/docker-compose.yml @@ -0,0 +1,106 @@ +version: '3.8' + +services: + app: + image: eclipse-temurin:21-jre-alpine + container_name: my-spring-app2 + volumes: + - ../build/libs/coin-0.0.1-SNAPSHOT.jar:/app/coin-0.0.1-SNAPSHOT.jar + - /etc/localtime:/etc/localtime:ro + - ./opentelemetry-javaagent.jar:/app/opentelemetry-javaagent.jar + working_dir: /app + command: [ "java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,mariadb-local,actuator,apm" ] + ports: + - "8080:8080" + env_file: + - ../docker/local.properties + environment: + - TZ=Asia/Seoul + - OTEL_SERVICE_NAME=my-spring-app + - OTEL_TRACES_EXPORTER=otlp + - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 + - OTEL_LOGS_EXPORTER=none + - OTEL_METRICS_EXPORTER=none + - OTEL_INSTRUMENTATION_METHODS_ENABLED=true + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005, -javaagent:/app/opentelemetry-javaagent.jar + depends_on: + mariadb: + condition: service_healthy + networks: + - app-network + - monitoring-net + + mariadb: + image: mariadb:latest + container_name: mariadb2 + ports: + - "3306:3306" + env_file: + - ../docker/local.properties + environment: + - TZ=Asia/Seoul + volumes: + - mariadb_data:/var/lib/mysql + - ../docker/mariadb/init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: [ "CMD", "healthcheck.sh", "--connect", "--innodb_initialized" ] + interval: 10s + timeout: 5s + retries: 10 + networks: + - app-network + - monitoring-net + + prometheus: + image: prom/prometheus:v2.53.0 + container_name: prometheus + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.retention.time=30d' + ports: + - "9090:9090" + networks: + - monitoring-net + restart: unless-stopped + + grafana: + image: grafana/grafana:11.0.0 + container_name: grafana + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + ports: + - "3000:3000" + networks: + - monitoring-net + restart: unless-stopped + depends_on: + - prometheus + - jaeger + + jaeger: + image: jaegertracing/all-in-one:latest + container_name: jaeger + ports: + - "16686:16686" + - "4317:4317" + - "4318:4318" + networks: + - monitoring-net + +volumes: + prometheus_data: {} + grafana_data: {} + mariadb_data: + driver: local + +networks: + app-network: + name: app-network + driver: bridge + monitoring-net: + name: monitoring-net + driver: bridge diff --git a/monitoring/grafana/provisioning/datsources/datasource.yml b/monitoring/grafana/provisioning/datsources/datasource.yml new file mode 100644 index 00000000..834801f3 --- /dev/null +++ b/monitoring/grafana/provisioning/datsources/datasource.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + url: http://prometheus:9090 + access: proxy + isDefault: true + editable: true \ No newline at end of file diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml new file mode 100644 index 00000000..d1690101 --- /dev/null +++ b/monitoring/prometheus/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 10s + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['prometheus:9090'] + - job_name: 'my-app' + static_configs: + - targets: [ 'app:8080' ] + metrics_path: /actuator/prometheus \ No newline at end of file diff --git a/src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java new file mode 100644 index 00000000..1d94c1d7 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java @@ -0,0 +1,12 @@ +package com.cleanengine.coin.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface StartNewTrace { + String value() default ""; +} diff --git a/src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java new file mode 100644 index 00000000..5c292600 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java @@ -0,0 +1,43 @@ +package com.cleanengine.coin.common.annotation; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import java.lang.reflect.Method; + +@Aspect +@Component +@RequiredArgsConstructor +public class StartNewTraceAspect { + + @Around("@annotation(com.cleanengine.coin.common.annotation.StartNewTrace)") + public Object createNewTrace(ProceedingJoinPoint joinPoint) throws Throwable { + Tracer tracer = GlobalOpenTelemetry.getTracer("com.cleanengine.coin"); + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + StartNewTrace newTraceAnnotation = method.getAnnotation(StartNewTrace.class); + + String spanName = newTraceAnnotation.value().isEmpty() ? + signature.getDeclaringType().getSimpleName() + "." + method.getName() : + newTraceAnnotation.value(); + + Span span = tracer.spanBuilder(spanName).setNoParent().startSpan(); + + try (Scope scope = span.makeCurrent()) { + return joinPoint.proceed(); + } catch (Exception e) { + span.recordException(e); + throw e; + } finally { + span.end(); + } + } +} \ No newline at end of file From 2dfe9ee2ef0785b3d9e80d511efe2df9c007b13b Mon Sep 17 00:00:00 2001 From: caniro Date: Thu, 12 Jun 2025 22:11:15 +0900 Subject: [PATCH 03/11] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e7402301..9eda9878 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -40,8 +40,7 @@ spring: hibernate: jdbc: time_zone: Asia/Seoul -order: - tickers: BTC, TRUMP + server: forward-headers-strategy: native From a4e7992a8a2411a26c8cf29c9f2904e26a6d2aa6 Mon Sep 17 00:00:00 2001 From: caniro Date: Thu, 12 Jun 2025 22:20:10 +0900 Subject: [PATCH 04/11] =?UTF-8?q?config:=20Swagger=20Endpoint=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiSwagger/SwaggerConfig.java | 25 ++++--------------- src/main/resources/application.yml | 9 ++++++- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/configuration/apiSwagger/SwaggerConfig.java b/src/main/java/com/cleanengine/coin/configuration/apiSwagger/SwaggerConfig.java index 8c4ee557..9c52f45d 100644 --- a/src/main/java/com/cleanengine/coin/configuration/apiSwagger/SwaggerConfig.java +++ b/src/main/java/com/cleanengine/coin/configuration/apiSwagger/SwaggerConfig.java @@ -3,41 +3,26 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; @Configuration public class SwaggerConfig { + @Bean public OpenAPI openAPI() { // API 기본 정보 설정 Info info = new Info() - .title("investFuture API Document") + .title("InvestFuture API Document") .version("1.0") - .description( - "환영합니다.\n") + .description("Private API 호출 시 Cookie에 직접 설정해주세요!\n") .contact(new io.swagger.v3.oas.models.info.Contact().email("billage.official@gmail.com")); - // JWT 인증 방식 설정 - String jwtScheme = "jwtAuth"; - SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtScheme); - Components components = new Components() - .addSecuritySchemes(jwtScheme, new SecurityScheme() - .name("Authorization") - .type(SecurityScheme.Type.HTTP) - .in(SecurityScheme.In.HEADER) - .scheme("Bearer") - .bearerFormat("JWT")); - // Swagger UI 설정 및 보안 추가 return new OpenAPI() .addServersItem(new Server().url("http://localhost:8080")) // 추가적인 서버 URL 설정 가능 - .components(components) - .info(info) - .addSecurityItem(securityRequirement); + .info(info); } + } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9eda9878..82271c26 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,7 +27,7 @@ spring: allowed-origins: http://localhost:63342,http://localhost:8080,http://localhost:5500,http://localhost:5173,https://investfuture.my endpoints: public: - paths: /api/login,/api/asset,/api/oauth2,/api/healthcheck,/api/coin/realtime,/api/coin/min,/api/minute-ohlc,/v3/api-docs,/swagger,/swagger-ui,/swagger-ui.html,/swagger-resources,/webjars,/h2-console,/favicon.ico,/actuator,/test + paths: /api/login,/api/asset,/api/oauth2,/api/healthcheck,/api/coin/realtime,/api/coin/min,/api/minute-ohlc,/api/swagger,/h2-console,/actuator,/test websocket: paths: /api/coin/min,/api/coin/realtime,/api/coin/orderbook jwt: @@ -41,6 +41,13 @@ spring: jdbc: time_zone: Asia/Seoul +springdoc: + api-docs: + path: /api/swagger/v3/api-docs + swagger-ui: + path: /api/swagger/swagger-ui.html + url: /api/swagger/v3/api-docs + server: forward-headers-strategy: native From aa44b7dac2f598d83ca9db8b9a46a76ef5cb7972 Mon Sep 17 00:00:00 2001 From: caniro Date: Thu, 12 Jun 2025 22:24:53 +0900 Subject: [PATCH 05/11] =?UTF-8?q?config:=20WebSocket=20Endpoint=EB=8A=94?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coin/configuration/WebSocketConfig.java | 17 +++++------------ src/main/resources/application.yml | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/configuration/WebSocketConfig.java b/src/main/java/com/cleanengine/coin/configuration/WebSocketConfig.java index 8c34c4bd..2341cfd7 100644 --- a/src/main/java/com/cleanengine/coin/configuration/WebSocketConfig.java +++ b/src/main/java/com/cleanengine/coin/configuration/WebSocketConfig.java @@ -1,6 +1,7 @@ package com.cleanengine.coin.configuration; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; @@ -11,14 +12,8 @@ @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private static final String[] ALLOWED_ORIGINS = { - "http://localhost:63342", - "http://localhost:63343", - "http://localhost:8080", - "http://localhost:5500", - "http://localhost:5173", - "https://investfuture.my" - }; + @Value("${spring.security.allowed-origins}") + private String[] allowedOrigins; @Override public void configureMessageBroker(MessageBrokerRegistry config) { @@ -34,9 +29,7 @@ public void registerStompEndpoints(StompEndpointRegistry registry) { private void registerEndpoint(StompEndpointRegistry registry, String endpoint) { registry.addEndpoint(endpoint) - .setAllowedOrigins(ALLOWED_ORIGINS); + .setAllowedOrigins(allowedOrigins); } -} - - +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82271c26..fcca3b36 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,7 +24,7 @@ spring: token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id - allowed-origins: http://localhost:63342,http://localhost:8080,http://localhost:5500,http://localhost:5173,https://investfuture.my + allowed-origins: http://localhost:63342,http://localhost:63343,http://localhost:8080,http://localhost:5500,http://localhost:5173,https://investfuture.my endpoints: public: paths: /api/login,/api/asset,/api/oauth2,/api/healthcheck,/api/coin/realtime,/api/coin/min,/api/minute-ohlc,/api/swagger,/h2-console,/actuator,/test From 1866ace49d8b11583ffe1422a92f1ed4ce397e3d Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 13 Jun 2025 12:56:22 +0900 Subject: [PATCH 06/11] =?UTF-8?q?fix:=20=ED=98=B8=EA=B0=80=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=EC=8B=9C=20sync=20+=20null=20=EB=8C=80=EC=9D=91=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit orderbook을 갱신, 삭제할 때 syncrhonized를 추가해, 동시 접근시 이슈가 발생하지 않도록 했습니다. --- .../coin/orderbook/domain/OrderBook.java | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java b/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java index adc60911..666b3209 100644 --- a/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java +++ b/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java @@ -22,18 +22,14 @@ public void updateOrderBookOnNewOrder(boolean isBuyOrder, Double price, Double o if(isBuyOrder){ BuyOrderBookUnit buyOrderBookUnit = buyOrderBookUnitMap.get(price); if(buyOrderBookUnit == null){ - buyOrderBookUnit = new BuyOrderBookUnit(price, orderSize); - buyOrderBookUnitMap.put(price, buyOrderBookUnit); - buyOrderBookUnitListSet.add(buyOrderBookUnit); + addBuyOrderBookUnit(price, orderSize); } else { buyOrderBookUnit.addOrder(orderSize); } } else { SellOrderBookUnit sellOrderBookUnit = sellOrderBookUnitMap.get(price); if(sellOrderBookUnit == null){ - sellOrderBookUnit = new SellOrderBookUnit(price, orderSize); - sellOrderBookUnitMap.put(price, sellOrderBookUnit); - sellOrderBookUnitListSet.add(sellOrderBookUnit); + addSellOrderBookUnit(price, orderSize); } else { sellOrderBookUnit.addOrder(orderSize); } @@ -43,17 +39,21 @@ public void updateOrderBookOnNewOrder(boolean isBuyOrder, Double price, Double o public void updateOrderBookOnTradeExecuted(boolean isBuyOrder, Double price, Double orderSize) { if(isBuyOrder){ BuyOrderBookUnit buyOrderBookUnit = buyOrderBookUnitMap.get(price); + if(buyOrderBookUnit == null) { + return; + } buyOrderBookUnit.executeTrade(orderSize); if(approxEquals(buyOrderBookUnit.getSize(), 0.0)){ - buyOrderBookUnitMap.remove(price); - buyOrderBookUnitListSet.remove(buyOrderBookUnit); + removeBuyOrderBookUnit(buyOrderBookUnit, price); } } else { SellOrderBookUnit sellOrderBookUnit = sellOrderBookUnitMap.get(price); + if(sellOrderBookUnit == null) { + return; + } sellOrderBookUnit.executeTrade(orderSize); if(approxEquals(sellOrderBookUnit.getSize(), 0.0)){ - sellOrderBookUnitMap.remove(price); - sellOrderBookUnitListSet.remove(sellOrderBookUnit); + removeSellOrderBookUnit(sellOrderBookUnit, price); } } } @@ -71,4 +71,40 @@ public List getSellOrderBookList(int size){ .limit(size) .collect(Collectors.toList()); } + + protected synchronized void addBuyOrderBookUnit(Double price, Double size) { + BuyOrderBookUnit buyOrderBookUnit = buyOrderBookUnitMap.get(price); + if(buyOrderBookUnit != null){ + return; + } + + buyOrderBookUnit = new BuyOrderBookUnit(price, size); + buyOrderBookUnitMap.put(price, buyOrderBookUnit); + buyOrderBookUnitListSet.add(buyOrderBookUnit); + } + + protected synchronized void addSellOrderBookUnit(Double price, Double size) { + SellOrderBookUnit sellOrderBookUnit = sellOrderBookUnitMap.get(price); + if(sellOrderBookUnit != null){ + return; + } + + sellOrderBookUnit = new SellOrderBookUnit(price, size); + sellOrderBookUnitMap.put(price, sellOrderBookUnit); + sellOrderBookUnitListSet.add(sellOrderBookUnit); + } + + protected synchronized void removeBuyOrderBookUnit(BuyOrderBookUnit buyOrderBookUnit, Double price) { + if(approxEquals(buyOrderBookUnit.getSize(), 0.0)) { + buyOrderBookUnitMap.remove(price); + buyOrderBookUnitListSet.remove(buyOrderBookUnitMap.get(price)); + } + } + + protected synchronized void removeSellOrderBookUnit(SellOrderBookUnit sellOrderBookUnit, Double price) { + if(approxEquals(sellOrderBookUnit.getSize(), 0.0)) { + sellOrderBookUnitMap.remove(price); + sellOrderBookUnitListSet.remove(sellOrderBookUnitMap.get(price)); + } + } } From b3c13b5bb14d100892979cc304fea2b9b9dc19f5 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 13 Jun 2025 12:57:29 +0900 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20=EC=A3=BC=EB=AC=B8=20=EC=88=98?= =?UTF-8?q?=EB=9F=89=EC=9D=B4=EB=82=98=20=EA=B0=80=EA=B2=A9=EC=9D=B4=200?= =?UTF-8?q?=EC=9D=BC=20=EB=95=8C=EC=97=90=20=ED=98=B8=EA=B0=80=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 체결이 이루어졌을 때의 주문 수량이나 가격이 0원이었을 경우에는 호가를 갱신하지 않도록 수정했습니다 --- .../com/cleanengine/coin/orderbook/domain/OrderBook.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java b/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java index 666b3209..f09725b6 100644 --- a/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java +++ b/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java @@ -37,7 +37,11 @@ public void updateOrderBookOnNewOrder(boolean isBuyOrder, Double price, Double o } public void updateOrderBookOnTradeExecuted(boolean isBuyOrder, Double price, Double orderSize) { - if(isBuyOrder){ + if(approxEquals(orderSize, 0.0) || approxEquals(price, 0.0)){ + throw new IllegalArgumentException("orderSize or price cannot be 0.0"); + } + + if(isBuyOrder) { BuyOrderBookUnit buyOrderBookUnit = buyOrderBookUnitMap.get(price); if(buyOrderBookUnit == null) { return; From 0e7623e58541a643cd208b857770e041bd51deea Mon Sep 17 00:00:00 2001 From: caniro Date: Fri, 13 Jun 2025 13:15:53 +0900 Subject: [PATCH 08/11] =?UTF-8?q?config:=20React=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD(3000=20=ED=8F=AC=ED=8A=B8)=EC=9D=84=20CORS?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index fcca3b36..88ad9a86 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,7 +24,7 @@ spring: token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id - allowed-origins: http://localhost:63342,http://localhost:63343,http://localhost:8080,http://localhost:5500,http://localhost:5173,https://investfuture.my + allowed-origins: http://localhost:63342,http://localhost:63343,http://localhost:8080,http://localhost:5500,http://localhost:5173,http://localhost:3000,https://investfuture.my endpoints: public: paths: /api/login,/api/asset,/api/oauth2,/api/healthcheck,/api/coin/realtime,/api/coin/min,/api/minute-ohlc,/api/swagger,/h2-console,/actuator,/test From 19cca17d72edb63ec619dd377fcec6ed46d12bce Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 13 Jun 2025 13:59:49 +0900 Subject: [PATCH 09/11] =?UTF-8?q?fix:=20=EC=9D=B8=EC=9E=90=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전달받은 매개변수를 사용하는 것이 아니라, 값을 다시 불러오던 이슈를 수정했습니다. --- .../java/com/cleanengine/coin/orderbook/domain/OrderBook.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java b/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java index f09725b6..fb9fc341 100644 --- a/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java +++ b/src/main/java/com/cleanengine/coin/orderbook/domain/OrderBook.java @@ -101,14 +101,14 @@ protected synchronized void addSellOrderBookUnit(Double price, Double size) { protected synchronized void removeBuyOrderBookUnit(BuyOrderBookUnit buyOrderBookUnit, Double price) { if(approxEquals(buyOrderBookUnit.getSize(), 0.0)) { buyOrderBookUnitMap.remove(price); - buyOrderBookUnitListSet.remove(buyOrderBookUnitMap.get(price)); + buyOrderBookUnitListSet.remove(buyOrderBookUnit); } } protected synchronized void removeSellOrderBookUnit(SellOrderBookUnit sellOrderBookUnit, Double price) { if(approxEquals(sellOrderBookUnit.getSize(), 0.0)) { sellOrderBookUnitMap.remove(price); - sellOrderBookUnitListSet.remove(sellOrderBookUnitMap.get(price)); + sellOrderBookUnitListSet.remove(sellOrderBookUnit); } } } From 99ded7286cfc94bd499c450c8f202297c87e66cf Mon Sep 17 00:00:00 2001 From: caniro Date: Fri, 13 Jun 2025 21:44:26 +0900 Subject: [PATCH 10/11] =?UTF-8?q?add:=20=EB=B4=87=20=EC=9E=90=EC=82=B0=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=8B=9C=20=EC=A3=BC=EB=AC=B8,?= =?UTF-8?q?=20=EC=B2=B4=EA=B2=B0=EA=B3=BC=20=EB=8F=99=EC=9D=BC=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20=EA=B2=A9?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A4=80=20=EB=B3=80=EA=B2=BD(Read=20Comm?= =?UTF-8?q?itted)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cleanengine/coin/user/info/application/AccountService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanengine/coin/user/info/application/AccountService.java b/src/main/java/com/cleanengine/coin/user/info/application/AccountService.java index 94329ad2..af743b5d 100644 --- a/src/main/java/com/cleanengine/coin/user/info/application/AccountService.java +++ b/src/main/java/com/cleanengine/coin/user/info/application/AccountService.java @@ -6,6 +6,7 @@ import com.cleanengine.coin.user.info.infra.WalletRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -38,7 +39,7 @@ public Account createNewAccount(Integer userId, double cash) { return accountRepository.save(account); } - @Transactional + @Transactional(isolation = Isolation.READ_COMMITTED) public void resetBot(String ticker) { Account sellBotAccount = accountRepository.findByUserId(SELL_ORDER_BOT_ID).orElseThrow(); sellBotAccount.setCash(0.0); From 2229a569b525652721e9aa8eb07e199db0fd14bd Mon Sep 17 00:00:00 2001 From: caniro Date: Fri, 13 Jun 2025 21:45:08 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20Deadlock=20=EB=B0=A9=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20account=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cleanengine/coin/trade/application/TradeExecutor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java b/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java index cb7338f4..b8e4ca14 100644 --- a/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java +++ b/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java @@ -71,15 +71,16 @@ public void executeTrade(WaitingOrders waitingOrders, TradePair tr tradeService.updateOrder(sellOrder); // 예수금 처리 + // - 매도 예수금 처리 + this.increaseAccountCash(sellOrder, totalTradedPrice); + // - 매수 잔여금액 반환 if (!isMarketOrder(buyOrder) && buyOrder.getPrice() > tradedPrice) { // 매도 호가보다 높은 가격에 매수를 시도한 경우, 차액 반환 + log.debug("[{}] 매도 호가보다 높은 가격에 매수를 시도한 경우, 차액 반환", Thread.currentThread().threadId()); double totalRefundAmount = (buyOrder.getPrice() - tradedPrice) * tradedSize; this.increaseAccountCash(buyOrder, totalRefundAmount); } - // - 매도 예수금 처리 - this.increaseAccountCash(sellOrder, totalTradedPrice); - // 지갑 누적계산 this.updateWalletAfterTrade(buyOrder, ticker, tradedSize, totalTradedPrice); this.updateWalletAfterTrade(sellOrder, ticker, tradedSize, totalTradedPrice);