Skip to content

Commit

Permalink
Release 0.0.10 (#475)
Browse files Browse the repository at this point in the history
* [feat] 종목 현재가 갱신 시스템 구현 (#462)

* feat: Kis 웹소켓 서비스 구현

* feat: 웹소켓 접근키 저장소 구현

* test: 웹소켓 접근키 저장소의 테스트 추가

* feat: tr-id 프로퍼티 추가

* fix: 웹소켓 접속 문제 해결

* feat: approvalKey에 대한 null 처리 추가

* feat: 정적 팩토리 추가

* test: 테스트 검증문 수정

* test: KisWebSocketClient connect 테스트 추가

* test: onClose 테스트 추가

* test: add sendMessage test

* feat: oauth SecurityFilterChain 순서 변경

- 변경 이유 : 테스트용 SecurityFilterChain이 순서상 앞에 두기 위해서

* test: 테스트용 웹소켓 서버 설정 추가

* feat: 웹소켓 클라이언트에 실시간 종목 체결가 핸들링 메서드 구현

* test: 웹소켓으로 실시간 체결가를 조회하여 레디스에 저장하는 테스트 구현

* style: 투두 추가 및 메인 애플리케이션 이름 변경

* test: 테스트 서포트 클래스 support 패키지로 이동

* feat: StockPrice push 서비스 구현

* feat: WebClientConfig global 패키지로 이동

* fix: api로 요청하는 방식이 아닌 price 모듈에서 가져오는 방식으로 변경

* feat: StockPriceWebSocketClient 구현

* feat: connect 예외 처리

* rename: 패키지명 변경

* style: 코드 정리

* feat: 웹소켓 현재가 조회 API 구현

* feat: StockPriceDispatcher 구현

* feat: add log

* test: solve test fail

* feat: StockPriceWebSocket 스케줄러 구현

- 오전 8시30분에 웹소켓 재연결
- 오후 16시에 웹소켓 연결 해제

* refactor: extract method

* feat: add stream filter

* style: 코드 정리

* style: 코드 정리

* test: 테스트 추가

* test: 테스트 검증 수정

* feat: 포트폴리오 캐시 기능 추가

* feat: 캐시 TTL 설정 추가

* feat: 포트폴리오 종목 서비스에 캐시 로직 추가

* test: 캐시 관련 검증문 추가

* test: mock 설정 추가

* feat: 스케줄러 삭제

* feat: cors 프로파일별 설정 클래스 추가 (#464)

* fix: test 프로파일 설정값 추가 및 클래스명 변경

* feat: appkey, secretkey 변경 (#466)

* [feat] 로깅 추가 (#468)

* feat: appkey, secretkey 변경

* feat: 로깅 추가

* feat: ssl 갱신

* feat: ssl release 추가

* [feat] 더미 데이터 생성 기능 구현 (#471)

* feat: local db username root로 변경

- bulk insert 수행시 권한 문제로 인하여 로컬 실행시 root 권한으로 실행

* feat: dummy csv 파일 생성 클래스 구현

* fix: MemberRole csv 파일 생성 메서드 삭제

* fix: 회원 샘플수 조정

* feat: 조건문 추가

* fix: 조건문 제거

* feat: 퐁 데이터 송신 구현 (#473)

* feat: 종목 현재가 갱신 스케줄러 추가

웹소켓을 이용한 서버가 완성될때까지 임시로 추가
  • Loading branch information
yonghwankim-dev authored Sep 22, 2024
1 parent c74cbd1 commit 6fbadd4
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
/src/main/resources/secret/
/src/main/generated/
/htmlReport/
/src/main/resources/db/
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
DB_HOST: fineAnts_db
DB_PORT: 3306
DB_DATABASE: fineAnts
DB_USERNAME: admin
DB_USERNAME: root
DB_PASSWORD: password1234!
REDIS_HOST: fineAnts_redis
REDOS_PORT: 6379
Expand Down
2 changes: 1 addition & 1 deletion secret
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package co.fineants.api.domain.kis.scheduler;

import java.time.LocalDate;

import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import co.fineants.api.domain.kis.repository.HolidayRepository;
import co.fineants.api.domain.kis.service.KisService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Profile(value = "production")
@Slf4j
@RequiredArgsConstructor
@Service
public class KisProductionScheduler {

private final HolidayRepository holidayRepository;
private final KisService kisService;

@Scheduled(cron = "0/5 * 9-15 ? * MON,TUE,WED,THU,FRI")
@Transactional
public void refreshCurrentPrice() {
// 휴장일인 경우 실행하지 않음
if (holidayRepository.isHoliday(LocalDate.now())) {
return;
}
kisService.refreshAllStockCurrentPrice();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ private String createCurrentPriceRequest(String ticker) {
return ObjectMapperUtil.serialize(requestMap);
}

public void disconnect() {
public void disconnect(CloseStatus status) {
try {
this.session.close(CloseStatus.NORMAL);
this.session.close(status);
} catch (IOException e) {
log.warn("StockPriceWebSocketClient fail close, error message is {}", e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package co.fineants.price.domain.stockprice.client;

import java.io.IOException;

import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
Expand Down Expand Up @@ -37,6 +39,21 @@ public void handleMessage(@NotNull WebSocketSession session, WebSocketMessage<?>
handleStockTextMessage(payload);
} else {
log.info("Received Message : {}", message.getPayload());
sendPongData(session, message);
}
}

private void sendPongData(@NotNull WebSocketSession session, WebSocketMessage<?> message) {
// send pong data
try {
session.sendMessage(message);
} catch (IOException e) {
log.error("StockPriceWebStockClient fail send pong data, errorMessage={}", e.getMessage());
try {
session.close(CloseStatus.SERVER_ERROR);
} catch (IOException ex) {
log.error("session can not close, errorMessage={}", ex.getMessage());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;

import co.fineants.price.domain.stockprice.client.StockPriceWebSocketClient;
import co.fineants.price.domain.stockprice.repository.StockPriceRepository;
Expand All @@ -24,7 +25,7 @@ public void openWebSocketClient() {
@Scheduled(cron = "0 0 16 * * MON,TUE,WED,THU,FRI")
public void closeWebSocketClient() {
log.info("close the StockPriceWebSocketClient");
client.disconnect();
client.disconnect(CloseStatus.NORMAL);
repository.clear();
}
}
3 changes: 1 addition & 2 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ spring:
database: mysql
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: create
ddl-auto: update
properties:
hibernate:
format_sql: true
Expand All @@ -18,7 +18,6 @@ spring:
sql:
init:
mode: always
data-locations: classpath*:db/mysql/data.sql
data:
redis:
host: ${REDIS_HOST:localhost}
Expand Down
100 changes: 100 additions & 0 deletions src/test/java/co/fineants/data/DummyDataCsvGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package co.fineants.data;

import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DummyDataCsvGenerator {

public static void main(String[] args) {
DummyDataCsvGenerator dummyDataCsvGenerator = new DummyDataCsvGenerator();
dummyDataCsvGenerator.writeMemberFile();
dummyDataCsvGenerator.writePortfolioFile();
}

public void writeMemberFile() {
String fileName = "src/main/resources/db/mysql/member.csv";
CSVFormat csvFormat = CSVFormat.Builder.create()
.setHeader("id", "email", "nickname", "provider", "password", "profileUrl", "create_at")
.setSkipHeaderRecord(false)
.build();
List<String[]> members = createMemberDummyData();
boolean result = writeCsvFile(fileName, csvFormat, members);
if (result) {
log.info("success writing the member csv file");
} else {
log.info("fail writing the member csv file");
}
}

private List<String[]> createMemberDummyData() {
int recordCount = 5_000;
List<String[]> result = new ArrayList<>();
for (long i = 1; i <= recordCount; i++) {
String id = String.valueOf(i);
String email = String.format("antuser%d@gmail.com", i);
String nickname = String.format("antuser%d", i);
String provider = "local";
String password = "$2a$10$zT6g60wI9rup2EvGbDRKa.D9N3RB5wMoFTlIGAaoZMxqX7R80pPQq";
String profileUrl = null;
String createAt = LocalDateTime.now().toString();
result.add(new String[] {id, email, nickname, provider, password, profileUrl, createAt});
}
return result;
}

public boolean writeCsvFile(String fileName, CSVFormat csvFormat, List<String[]> data) {
try (FileWriter out = new FileWriter(fileName)) {
CSVPrinter printer = new CSVPrinter(out, csvFormat);
printer.printRecords(data);
} catch (IOException e) {
log.error(e.getMessage());
return false;
}
return true;
}

private void writePortfolioFile() {
String fileName = "src/main/resources/db/mysql/portfolio.csv";
CSVFormat csvFormat = CSVFormat.Builder.create()
.setHeader("id", "name", "securitiesFirm", "budget", "targetGain", "maximumLoss", "targetGainIsActive",
"maximumLossIsActive", "createAt", "member_id")
.setSkipHeaderRecord(false)
.build();
List<String[]> portfolios = createPortfolioDummyData();
boolean result = writeCsvFile(fileName, csvFormat, portfolios);
if (result) {
log.info("success writing the portfolio csv file");
} else {
log.info("fail writing the portfolio csv file");
}
}

private List<String[]> createPortfolioDummyData() {
int recordCount = 5_000;
List<String[]> result = new ArrayList<>();
for (long i = 1; i <= recordCount; i++) {
String id = String.valueOf(i);
String name = String.format("portfolio%d", i);
String securitiesFirm = "토스증권";
String budget = "1000000";
String targetGain = "1500000";
String maximumLoss = "900000";
String targetGainIsActive = "true";
String maximumLossIsActive = "true";
String createAt = LocalDateTime.now().toString();
String memberIdString = "1";
result.add(new String[] {id, name, securitiesFirm, budget, targetGain, maximumLoss, targetGainIsActive,
maximumLossIsActive, createAt, memberIdString});
}
return result;
}
}

0 comments on commit 6fbadd4

Please sign in to comment.