2024.10.09 - [웹/Spring vue 웹 개발] - 소켓 통신 인터페이스 만들기

 

소켓 통신 인터페이스 만들기

요즘 간단하게 주식관련 사이트를 만들어보려고했는데용.흠 친구들도 간간히 주식얘기를 하다보니 퇴근해서 개발을 시작하게 되었습니다. 흠 ... 일단 한국투자증권 open api를 이용을 했고요. 

kwaksh2319.tistory.com

 

 

지난번에 이어서 한투 open api를 통해서 아래 한투 open api

https://apiportal.koreainvestment.com/login

 

 

미국 주식 '애플' 매수 , 매도 데이터를 실시간 소켓통신을 통해서 가져오게했습니다.

이제 사이트에서 데이터를 가져와서 실시간으로 그래프를 그려주고 있습니다.

다음은 간단하게 종목들을 가져오는 것들을 구현할것을 목표로 생각하고 있습니다. 퇴근하고 간간히 하는거라 쉽지 않네요 ㅎ 

 

 

 

 

 

요즘 간단하게 주식관련 사이트를 만들어보려고했는데용.
흠 친구들도 간간히 주식얘기를 하다보니 퇴근해서 개발을 시작하게 되었습니다. 
흠 ...
 
일단 한국투자증권 open api를 이용을 했고요.
 
현재는 대략적으로 실시간으로 소켓통신을 개발을 했습니다. 
머 간략히 얘기해서 소켓통신에 관해서 개발했던게 대학생때 했던거라 기억이 가물가물하기도 해서  간단하게 정리를 해봤습니다. 
대략적인 아이디어는 아래 그림과 같습니다.
 

번호 순대로 일단 가져오고요.
 
예시코드를 간단히 설명하고 끝내겠습니다. 
아래는 코드고 함수 설명은 아래 코드 밑에 작성하겠습니다.
백엔드 코드 : 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

@RestController
public class WebSocketController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @GetMapping("/connect")
    public ResponseEntity<String> connectToWebSocket() {
        try {
            URI uri = new URI("ws://external-websocket-url");

            WebSocketClient webSocketClient = new WebSocketClient(uri) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    System.out.println("WebSocket connected to external server");

                    // WebSocket으로 데이터를 전송
                    send("{\"input\":{\"tr_id\":\"HDFSASP0\",\"tr_key\":\"DNASAAPL\"}}");
                }

                @Override
                public void onMessage(String message) {
                    System.out.println("Received from WebSocket: " + message);  // 받은 데이터 로그 출력

                    // 메시지가 JSON 형식인지 확인
                    if (message.trim().startsWith("{")) {
                        System.out.println("Message is a JSON object");
                    } else {
                        System.out.println("Message is not a JSON object");
                    }

                    // 받은 메시지를 프론트엔드로 전달
                    messagingTemplate.convertAndSend("/topic/stock-data", message);
                }

                @Override
                public void onClose(int code, String reason, boolean remote) {
                    System.out.println("WebSocket connection closed: " + reason);
                }

                @Override
                public void onError(Exception ex) {
                    ex.printStackTrace();
                }
            };

            webSocketClient.connect();

        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<>("Error occurred while connecting to WebSocket", HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return new ResponseEntity<>("WebSocket connection initiated", HttpStatus.OK);
    }
}

 
onOpen은 WebSocket 연결이 성공적으로 이루어졌을 때 호출되는 콜백 함수입니다. WebSocket 클라이언트STOMP 클라이언트에서 WebSocket 핸드셰이크가 성공하고 연결이 열렸을 때, onOpen 함수가 실행됩니다. 이 시점에서 클라이언트는 서버와 WebSocket 통신을 할 준비가 된 상태
 
onMessageWebSocket 서버로부터 메시지를 수신할 때 호출됩니다. WebSocket을 통해 실시간으로 데이터를 주고받는 데 있어 가장 중요한 이벤트 핸들러 중 하나입니다.
(여기 함수를 통해서 받고 아래 코드에 ws url message를 수신합니다.)

messagingTemplate.convertAndSend("/topic/stock-data", message);

 
 
onClose: WebSocket 연결이 닫힐 때 호출.
 
onError: WebSocket 통신 중 오류가 발생할 때 호출.
 
그렇다면 /topic/stock-data는 어디서 url을 맵핑해주는지 알아야겠죠 

WebSocketMessageBrokerConfigurer

클래스를 사용해서 

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    // 클라이언트가 구독할 수 있는 경로 설정
    config.enableSimpleBroker("/topic", "/queue"); // /topic 경로로 구독
    config.setApplicationDestinationPrefixes("/app"); // 클라이언트가 서버에 메시지 전송 시 경로
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    // 클라이언트가 연결할 엔드포인트 설정
    registry.addEndpoint("/ws")
            .setAllowedOriginPatterns("http://localhost:*")  // 포트가 다른 로컬 호스트 허용
            .withSockJS();  // SockJS 사용
}

여기 configure 에 등록해주면 됩니다.
 
프론트 : stompClient에서 커넥트를 하고 subscibe를 통해서 데이터를 받아오면 끝~~ 

const socket = new SockJS('/ws');  // 백엔드에서 설정한 WebSocket 엔드포인트
const stompClient = Stomp.over(socket);

stompClient.connect({}, function (frame) {
    console.log('Connected: ' + frame);

    stompClient.subscribe('/topic/stock-data', function (message) {
        console.log('Received: ', message.body);
    });
}, function (error) {
    console.error('Error in STOMP connection: ', error);
});

 
제가 만든 데이터들을 
가져온 결과물 :

설치:

https://www.elastic.co/kr/elasticsearch

 

Elasticsearch: 공식 분산형 검색 및 분석 엔진 | Elastic

Elasticsearch는 속도, 수평적 확장성, 안정성 및 간편한 관리를 위해 설계된 선도적인 분산형 RESTful 무료 오픈 소스 검색 및 분석 엔진입니다. 무료로 시작하세요....

www.elastic.co

1. Elasticsearch

Elasticsearch는 분산형 검색 및 분석 엔진으로, JSON 기반의 문서들을 색인하여 고속 검색 및 데이터 분석을 제공합니다.

설치 및 실행

  • Elastic의 공식 웹사이트에서 Elasticsearch를 다운로드하고 설치합니다.
  • elasticsearch.yml 설정 파일을 편집하여 필요한 설정을 변경합니다.
  • 터미널에서 bin/elasticsearch를 실행하여 Elasticsearch 서버를 시작합니다.

2. Logstash

Logstash는 다양한 소스에서 로그 및 이벤트 데이터를 수집하고 변환한 다음 Elasticsearch로 전달하는 역할을 합니다.

설치 및 실행

  • Elastic의 공식 웹사이트에서 Logstash를 다운로드하고 설치합니다.
  • logstash.conf와 같은 설정 파일을 만들어 다음과 같이 구성합니다
input {
  file {
    path => "/path/to/your/logfile.log"
    start_position => "beginning"
  }
}
filter {
  # 필요한 경우 필터 설정을 추가
}
output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "log-index"
  }
}

3. Spring

Spring 애플리케이션에서 Elasticsearch와 통합하는 데는 spring-data-elasticsearch를 사용할 수 있습니다.

설치 및 사용

  • Spring 프로젝트에 spring-data-elasticsearch 의존성을 추가합니다.
<!-- Maven -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  <version>3.0.0</version>
</dependency>

 

application. yml

spring:
  elasticsearch:
    uris: http://localhost:9200

 

EntitiyClass

@Document(indexName = "your-index")
public class YourEntity {
  @Id
  private String id;
  private String field1;
  // 필드 추가
}

public interface YourEntityRepository extends ElasticsearchRepository<YourEntity, String> {
  // 맞춤형 쿼리 메소드 정의
}

' > ElasticSearch' 카테고리의 다른 글

log stash란?  (0) 2024.05.04
엘라스틱 서치 란?  (0) 2024.05.04

Logstash는 Elastic Stack의 일부로, 로그, 이벤트 및 기타 데이터 소스에서 데이터를 수집하고 변환하며 다양한 목적지로 전달하는 오픈 소스 데이터 처리 파이프라인입니다. Logstash를 통해 여러 소스에서 데이터 입력을 받아 이를 실시간으로 처리하여 Elasticsearch 같은 데이터 저장소로 전송할 수 있습니다.

Logstash의 주요 기능

  1. 입력(Input):
    • 파일, 데이터베이스, 메시지 큐, 네트워크 등 다양한 소스에서 데이터를 수집할 수 있습니다.
    • 예를 들어 파일에서 로그를 읽어오는 file 플러그인, 메시지 큐 시스템인 Kafka에서 읽어오는 kafka 플러그인 등이 있습니다.
  2. 필터(Filter):
    • 수집한 데이터를 변환, 파싱, 정규화 등의 작업을 통해 원하는 형식으로 변환할 수 있습니다.
    • 예를 들어 grok 필터는 정규 표현식을 사용하여 로그 데이터를 파싱하고 구조화된 데이터를 생성합니다.
  3. 출력(Output):
    • 처리된 데이터를 Elasticsearch, 파일, 데이터베이스, 메시지 큐 등 다양한 목적지로 전송할 수 있습니다.
    • 대표적으로 Elasticsearch에 데이터를 전송하는 elasticsearch 출력 플러그인, 로컬 파일로 데이터를 쓰는 file 플러그인이 있습니다.

 

 
 
 

' > ElasticSearch' 카테고리의 다른 글

엘라스틱 서치와 logstash , 스프링에서 사용법  (0) 2024.05.12
엘라스틱 서치 란?  (0) 2024.05.04

엘라스틱서치(Elasticsearch)는 강력하고 확장 가능한 오픈 소스 검색 엔진이자 분석 엔진입니다. Elasticsearch는 Apache Lucene 기반으로 구축되었으며, 대용량의 텍스트 데이터를 실시간으로 저장, 검색 및 분석하는 데 최적화되어 있습니다.

 

  1. 분산 아키텍처: 데이터는 여러 노드에 걸쳐 분산 저장될 수 있어 대용량 데이터를 처리하는 데 적합합니다.
  2. 실시간 검색 및 분석: 거의 실시간으로 검색 및 분석을 수행할 수 있어 빠른 응답 시간이 요구되는 애플리케이션에 적합합니다.
  3. RESTful API: Elasticsearch는 JSON 기반의 RESTful API를 제공하여 쉽게 통합 및 확장할 수 있습니다.
  4. 복잡한 쿼리 언어 지원: 다양한 형태의 검색, 필터링 및 집계를 위한 강력한 쿼리 언어를 지원합니다.
  5. 커뮤니티 및 생태계: Kibana와 같은 대시보드 도구나 Logstash 및 Beats와의 통합을 통해 ELK 스택을 구축하여 로그 및 이벤트 데이터를 처리하는 등 다양한 사용 사례를 지원합니다.

https://www.elastic.co/kr/elasticsearch

 

Elasticsearch: 공식 분산형 검색 및 분석 엔진 | Elastic

Elasticsearch는 속도, 수평적 확장성, 안정성 및 간편한 관리를 위해 설계된 선도적인 분산형 RESTful 무료 오픈 소스 검색 및 분석 엔진입니다. 무료로 시작하세요....

www.elastic.co

 

' > ElasticSearch' 카테고리의 다른 글

엘라스틱 서치와 logstash , 스프링에서 사용법  (0) 2024.05.12
log stash란?  (0) 2024.05.04

회사에서 업무 중 미완성된 알림 서비스(스케쥴러) 소스 코드 중에 정말 잘 만든 소스 코드를 보게되었습니다.

관련 코드에 문제가 있어서 코드 수정하는겸 , 코드에 대해서 공부할겸 관련해서 코드 모사를 가볍게 해봤습니다.

Method getterMethod=property.getClass().getMethod("getWorkflowEntity");
                   Object targetEntity = getterMethod.invoke(property);
@CallerSensitive
    public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        Method method = getMethod0(name, parameterTypes, true);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

 

workflow id 를 통해서 => 부모 workflow의 데이터를 가지고 오려고 하는 의도인듯 했습니다.

 

entity 클래스

@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "table")
@EntityListeners(AuditingEntityListener.class)
public class PropertyEntity extends BaseEntity {

	@GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
	long seq_id;

	String workflowid;

	String workid;

	@OneToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "workflow_seq", referencedColumnName = "seq")
	WorkflowEntity workflowEntity;

	public static PropertyEntity of(String workflowid) {
		return PropertyEntity.builder()
				.workflow_definition_key(workflowid)
			.build();
	}

	public static PropertyEntity of(String workflowid, String workid) {
	   return PropertyEntity.builder()
			 .workflow_definition_key(workflowid)
			   .taskId(workid)
		   .build();
	}

	public static PropertyEntity ofProperty(String workid, String workflowid) throws JsonProcessingException {
		return PropertyEntity.builder()
				.taskId(workid)
				.workflow_definition_key(workflowid)
				.build();
	}
}

 

관련 propertyEntity table이고 WorkFlowEntity db Join을 하여 seq를 통해서 one to one 관계를 가집니다. 

이후 데이터를 invoke를 통해서 workflowEntity 테이블의 모든 데이터를 가져올수 있습니다.

 

개인적으로 봐도 정말 잘 만든 코드여서 비슷하게 다시 구현해서 만들어 봤습니다.

 

' > Spring' 카테고리의 다른 글

Async 어노테이션 ,@Async 비동기 처리  (0) 2025.02.19
@Builder? 와 Bulder 패턴?  (0) 2025.02.12
Spring Data JPA - 구성  (0) 2024.02.25
Spring Data JPA- repository 인터페이스의 정의  (0) 2024.02.18
Spring Data JPA - 핵심 개념  (0) 2024.02.03

처음 회사에서 들어 왔을때 개발 환경 세팅을 하는데 몇가지 문제가 발생했었는데 그중에서 가장 기억에 남았던 부분이 바로 q 파일 설치 관련해서 기억에 남았습니다. 

 

Q 클래스(q 파일)은?

QueryDSL에서 Q-파일은 특정 엔티티에 대한 메타모델을 자동으로 생성된 자바 클래스로 제공합니다. 이러한 파일들은 QueryDSL이 제공하는 타입-세이프한 쿼리 구성을 가능하게 하는 중요한 부분입니다. 이 Q-클래스들을 사용함으로써 개발자는 컴파일 시간에 타입 체크를 받을 수 있으며, IDE의 자동완성 기능을 활용하여 더욱 빠르고 정확하게 쿼리를 작성할 수 있습니다

 

Q-클래스 또는 Q-파일의 역할

  • 메타모델 생성: Q-클래스는 각 엔티티의 속성에 대한 메타데이터를 제공하며, 이를 통해 쿼리를 작성할 때 컴파일 시간에 타입 체크가 가능합니다.
  • 타입-세이프 쿼리 작성: 이 클래스들은 쿼리를 작성할 때 변수명이나 타입 오류를 컴파일 단계에서 잡아내어 런타임 에러의 가능성을 줄여줍니다.
  • 코드 자동 완성 지원: IDE에서 Q-클래스의 속성을 사용할 때 자동 완성 기능을 제공받을 수 있어 쿼리 작성이 더 빠르고 정확해집니다.

Q-클래스의 생성

QueryDSL은 APT(Annotation Processing Tool)를 사용하여 엔티티 클래스를 기반으로 Q-클래스를 자동 생성합니다. 이 과정은 Maven이나 Gradle과 같은 빌드 도구의 설정을 통해 자동화할 수 있습니다.

 

ex)

@Entity
public class Book {
    @Id
    private Long id;
    private String title;
    private String author;
}
public class QBook extends EntityPathBase<Book> {
    public static final QBook book = new QBook("book");

    public final NumberPath<Long> id = createNumber("id", Long.class);
    public final StringPath title = createString("title");
    public final StringPath author = createString("author");

    public QBook(String variable) {
        super(Book.class, forVariable(variable));
    }
}
QBook qBook = QBook.book;
JPAQuery<?> query = new JPAQuery<>(entityManager);
List<Book> books = query.select(qBook)
                        .from(qBook)
                        .where(qBook.author.eq("J.K. Rowling"))
                        .fetch();

 

여기서 문제가 되었던게 서버가 4개로 나눠져있어서 

( A ) <-> ( B ) <-> (C ) <-> (D )    너무 많아.. ㅠ Q 파일이 가끔 제대로 설치가 안될떄가 있었는데 .. 아직 정확한 원인을 봐야할것같은데 원인을 모르겠다.... ㅠㅠ 

 

에러코드

java: cannot find symbol
  symbol:   class QWorkFlowInfoEntity

일단 clean 후 install 해서 해결은 했는데 ... 이게 맞는건가 싶다.. 정확한 queryDsl과 q파일에 대해서 더 정확하게 알아야할것같다.

' > QueryDSL' 카테고리의 다른 글

Query DSL 이란?  (0) 2024.04.20

일단 회사에서 기본적으로 Spring JPA, Query DSL, 등 여러가지 기술들이 쓰기 때문에.. 주말이라도 공부를 안하면 못따라가겠다라고 느낄정도이다. 전 회사에선 ibatis를 기반으로 회사업무를 했다보니.. 이번이직회사에선 상당히 많은걸 알게 된다. 구조가 복잡한 대신 정말 자유도가 넓은 구조라고 느껴졌다. (난 이렇게 못짤것같다고 느낄 정도 였다 경외감이 느껴진다. ㅎㄷㄷ )
 
Query DSL 이란?
QueryDSL은 타입-세이프한 쿼리를 Java 언어로 작성할 수 있게 해주는 프레임워크입니다. SQL, JPA, JDO, 그리고 컬렉션 등 다양한 데이터 소스에 대한 쿼리를 자바 코드로 구현할 수 있게 해주며, 쿼리를 직접 문자열로 작성하는 것보다 오류를 줄이고, 개발 효율성을 높일 수 있습니다.
 

주요 기능

  • 타입-세이프 쿼리: 컴파일 시간에 타입 체크가 가능합니다.
  • 다양한 백엔드 지원: SQL, JPA, MongoDB, Lucene, Hibernate Search, JDO, JDBC 등 다양한 데이터 소스를 지원합니다.
  • 동적 쿼리: 조건에 따라 유연하게 쿼리를 구성할 수 있습니다.

Spring Boot에 QueryDSL 설정하기

maven

 <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>5.0.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>5.0.0</version>
    </dependency

gradle

implementation 'com.querydsl:querydsl-jpa'
implementation 'com.querydsl:querydsl-apt'

엔티티 및 리포지토리 작성

예를 들어, 간단한 Book 엔티티를 생성하고, QueryDSL을 사용하는 리포지토리를 작성

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;

    // getters and setters
}

QueryDSL을 활용하는 리포지토리 인터페이스를 작성합니다.

public interface BookRepository extends JpaRepository<Book, Long>, QuerydslPredicateExecutor<Book> {
}

쿼리 사용 예

BookRepository를 사용하여 QueryDSL 쿼리를 실행하는 예입니다.

@Autowired
private BookRepository bookRepository;

public List<Book> findBooksByAuthor(String author) {
    QBook qBook = QBook.book;
    Predicate predicate = qBook.author.eq(author);
    return (List<Book>) bookRepository.findAll(predicate);
}

' > QueryDSL' 카테고리의 다른 글

q 클래스 !  (1) 2024.04.20

+ Recent posts