Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

실시간 차트 엔티티 설계 #20

Merged
merged 4 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/main/java/com/flab/mars/api/controller/StockController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import com.flab.mars.api.dto.request.StockFluctuationRequestDto;
import com.flab.mars.api.dto.response.ResultAPIDto;
import com.flab.mars.api.dto.response.StockFluctuationResponseDto;
import com.flab.mars.api.dto.response.StockPriceResponseDto;
import com.flab.mars.domain.service.StockService;
import com.flab.mars.domain.vo.StockPrice;
import com.flab.mars.domain.vo.TokenInfo;
import com.flab.mars.domain.vo.request.StockFluctuationRequestVO;
import com.flab.mars.domain.vo.response.PriceDataResponseVO;
import com.flab.mars.domain.vo.response.StockFluctuationResponseVO;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
Expand All @@ -29,8 +30,8 @@ public class StockController {

/**
* KIS 토큰 발급 요청, 1분당 하나 가능
* @param request
* @param session
* @param request 한화투자증권으로부터 발급받은 appKey, appSecret 값을 전달
* @param session 세션
* @return TokenInfo
*/
@PostMapping({"/accessToken"})
Expand All @@ -52,10 +53,17 @@ public ResponseEntity<ResultAPIDto<TokenInfo>> getAccessToken(@RequestBody ApiCr
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ResultAPIDto.res(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"));
}

/**
* 주식현재가 시세를 조회 및 db 에 저장한다.
* @param stockCode ex) 000660
* @param session 세션
* @return 주식현재가 , 누적 거래 대금, 누적 거래량, 주식 시가, 주식 최고가, 주식 최저가 등을 반환
*/
@GetMapping("/quotations/inquire-price")
public ResponseEntity<ResultAPIDto<StockPrice>> getStockPrice(@RequestParam(name = "stockCode") String stockCode, HttpSession session) {
StockPrice stockPrice = stockService.getStockPrice(stockCode, session);
return ResponseEntity.ok(ResultAPIDto.res(HttpStatus.OK, "Success", stockPrice));
public ResponseEntity<ResultAPIDto<StockPriceResponseDto>> getStockPrice(@RequestParam(name = "stockCode") String stockCode, HttpSession session) {
PriceDataResponseVO stockPriceResponseVO = stockService.getStockPrice(stockCode, session);
StockPriceResponseDto responseDto = StockPriceResponseDto.from(stockPriceResponseVO);
return ResponseEntity.ok(ResultAPIDto.res(HttpStatus.OK, "Success", responseDto));
}

@GetMapping("/domestic-stock/ranking/fluctuation")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.flab.mars.api.dto.response;

import com.flab.mars.db.entity.PriceDataType;
import com.flab.mars.domain.vo.response.PriceDataResponseVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class StockPriceResponseDto {

private Long id;

private String stockId; // StockInfoEntity의 ID와 연결되는 값

private PriceDataType dataType;

private BigDecimal currentPrice; // 현재가
private BigDecimal openPrice; // 시가
private BigDecimal closePrice; // 종가
private BigDecimal highPrice; // 최고가
private BigDecimal lowPrice; // 최저가

private BigDecimal acmlVol; // 누적 거래량 (전체 누적 거래량)
private BigDecimal acmlTrPbmn; // 누적 거래 대금

private LocalDate stockBusinessDate; // 주식 영업일자

// PriceDataResponseVO 객체를 StockPriceResponseDto로 변환하는 메서드
public static StockPriceResponseDto from(PriceDataResponseVO responseVO) {
return StockPriceResponseDto.builder()
.id(responseVO.getId())
.dataType(responseVO.getDataType())
.currentPrice(responseVO.getCurrentPrice())
.openPrice(responseVO.getOpenPrice())
.closePrice(responseVO.getClosePrice())
.highPrice(responseVO.getHighPrice())
.lowPrice(responseVO.getLowPrice())
.acmlVol(responseVO.getAcmlVol())
.acmlTrPbmn(responseVO.getAcmlTrPbmn())
.stockBusinessDate(responseVO.getStockBusinessDate())
.build();
}
}
5 changes: 2 additions & 3 deletions src/main/java/com/flab/mars/client/KISClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.flab.mars.client;

import com.flab.mars.domain.vo.StockPrice;
import com.flab.mars.domain.vo.response.StockFluctuationResponseVO;
import lombok.RequiredArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
Expand Down Expand Up @@ -49,7 +48,7 @@ public String getAccessToken(String appKey, String appSecret, String grantType)

}

public StockPrice getStockPrice(String accessToken, String appKey, String appSecret, String stockCode) {
public KisPriceResponseVO getStockPrice(String accessToken, String appKey, String appSecret, String stockCode) {
return webClient.get()
.uri(uriBuilder -> uriBuilder.path(INQUIRE_PRICE)
.queryParam("FID_COND_MRKT_DIV_CODE", "J")
Expand All @@ -63,7 +62,7 @@ public StockPrice getStockPrice(String accessToken, String appKey, String appSec
headers.setContentType(MediaType.APPLICATION_JSON);
})
.retrieve()
.bodyToMono(StockPrice.class)
.bodyToMono(KisPriceResponseVO.class)
.block();
}

Expand Down
53 changes: 53 additions & 0 deletions src/main/java/com/flab/mars/client/KisPriceResponseVO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.flab.mars.client;


import com.fasterxml.jackson.annotation.JsonProperty;
import com.flab.mars.domain.vo.response.BaseResponseVO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;


@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KisPriceResponseVO extends BaseResponseVO {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선은 모든 응답 필드를 다 처리하기보단 저희 도메인에 필요한 데이터만 취급하시죠~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네넹~

@JsonProperty("output")
private Output output;

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Output {
@JsonProperty("stck_prpr")
private String stckPrpr; // 주식 현재가

@JsonProperty("prdy_vrss")
private String prdyVrss; // 전일 대비

@JsonProperty("prdy_vrss_sign")
private String prdyVrssSign; // 전일 대비 부호

@JsonProperty("prdy_ctrt")
private String prdyCtrt; // 전일 대비율

@JsonProperty("acml_tr_pbmn")
private String acmlTrPbmn; // 누적 거래 대금

@JsonProperty("acml_vol")
private String acmlVol; // 누적 거래량

@JsonProperty("stck_oprc")
private String stckOprc; // 주식 시가

@JsonProperty("stck_hgpr")
private String stckHgpr; // 주식 최고가

@JsonProperty("stck_lwpr")
private String stckLwpr; // 주식 최저가

}

}
39 changes: 39 additions & 0 deletions src/main/java/com/flab/mars/db/entity/PriceDataEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.flab.mars.db.entity;

import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDate;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Getter
@Entity
@Table(name = "price_data")
public class PriceDataEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "stock_info_id")
private StockInfoEntity stockInfoEntity;

@Enumerated(EnumType.STRING)
private PriceDataType dataType; // 데이터 유형 (DAY, WEEK, MONTH, YEAR)

private String currentPrice; // 현재가 (데이터 유형이 실기간인 경우 사용)
private String openPrice; // 시가
private String closePrice; // 종가
private String highPrice; // 최고가
private String lowPrice; // 최저가

private String acmlVol; // 누적 거래량 (전체 누적 거래량)
private String acmlTrPbmn; // 누적 거래 대금

@Column(name = "stock_business_date")
private LocalDate stockBusinessDate; // 주식 영업일자


}
9 changes: 9 additions & 0 deletions src/main/java/com/flab/mars/db/entity/PriceDataType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.flab.mars.db.entity;

public enum PriceDataType {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선은 "분" 단위 데이터만 다룬다고 가정하시는건 어때요??
그 이후에 다른 단위 데이터로 확장하는 것도 좋아 보입니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 우선은 분단위만 다룬다고 가정하고 작업하겠습니다!

REALTIME, // 실시간 데이터
DAY, // 일별 데이터
WEEK, // 주별 데이터
MONTH, // 월별 데이터
YEAR // 연별 데이터
}
21 changes: 21 additions & 0 deletions src/main/java/com/flab/mars/db/entity/StockInfoEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.flab.mars.db.entity;

import jakarta.persistence.*;
import lombok.*;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Getter
@Entity
@Table(name = "stock_info")
public class StockInfoEntity {
@Id
@GeneratedValue
@Column(name = "stock_info_id")
private Long id;

private String stockCode; // 주식 코드
private String stockName; // 주식 이름

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.flab.mars.db.repository;

import com.flab.mars.db.entity.PriceDataEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PriceDataRepository extends JpaRepository<PriceDataEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.flab.mars.db.repository;

import com.flab.mars.db.entity.StockInfoEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StockInfoRepository extends JpaRepository<StockInfoEntity, Long> {
}
37 changes: 34 additions & 3 deletions src/main/java/com/flab/mars/domain/service/StockService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,70 @@

import com.flab.mars.client.KISClient;
import com.flab.mars.client.KISConfig;
import com.flab.mars.domain.vo.StockPrice;
import com.flab.mars.client.KisPriceResponseVO;
import com.flab.mars.db.entity.PriceDataEntity;
import com.flab.mars.db.entity.PriceDataType;
import com.flab.mars.db.repository.PriceDataRepository;
import com.flab.mars.domain.vo.TokenInfo;
import com.flab.mars.domain.vo.response.PriceDataResponseVO;
import com.flab.mars.domain.vo.response.StockFluctuationResponseVO;
import com.flab.mars.exception.AuthException;
import com.flab.mars.support.SessionUtil;
import jakarta.servlet.http.HttpSession;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StockService {

private final KISClient kisClient;

private final KISConfig kisConfig;

private final PriceDataRepository priceDataRepository;


public void getAccessToken(TokenInfo tokenInfo, HttpSession session) {
String accessToken = kisClient.getAccessToken(tokenInfo.getAppKey(), tokenInfo.getAppSecret(), kisConfig.getGrantType());
tokenInfo.setAccessToken(accessToken);
SessionUtil.setSessionAccessToKenValue(session, tokenInfo);
}

public StockPrice getStockPrice(String stockCode, HttpSession session) {
// 실시간 주식 현재가 가져오기
public PriceDataResponseVO getStockPrice(String stockCode, HttpSession session) {
TokenInfo tokenInfo = SessionUtil.getSessionAccessToKenValue(session);


// TODO userInterceptor 로 빼기
if(tokenInfo == null) {
throw new AuthException("로그인에 실패했습니다. ACCESS 토큰을 가져올 수 없습니다.");
}
KisPriceResponseVO stockPrice = kisClient.getStockPrice(tokenInfo.getAccessToken(), tokenInfo.getAppKey(), tokenInfo.getAppSecret(), stockCode);

return insertCurrentStockPrice(stockPrice, stockCode);
}

@Transactional
public PriceDataResponseVO insertCurrentStockPrice(KisPriceResponseVO stockPrice, String stockCode) {

PriceDataEntity priceDataEntity = PriceDataEntity.builder()
.dataType(PriceDataType.REALTIME) // 실시간
.currentPrice(stockPrice.getOutput().getStckPrpr()) // 현재가
.openPrice(stockPrice.getOutput().getStckOprc()) // 주식 시가
.highPrice(stockPrice.getOutput().getStckHgpr()) // 주식 최고가
.lowPrice(stockPrice.getOutput().getStckLwpr()) // 주식 최저가
.acmlVol(stockPrice.getOutput().getAcmlVol()) // 누적 거래량
.acmlTrPbmn(stockPrice.getOutput().getAcmlTrPbmn()) // 누적 거래 대금
.build();

PriceDataEntity savedpriceDataEntity = priceDataRepository.save(priceDataEntity);

return kisClient.getStockPrice(tokenInfo.getAccessToken(), tokenInfo.getAppKey(), tokenInfo.getAppSecret(), stockCode);
return PriceDataResponseVO.toVO(savedpriceDataEntity);
}

public StockFluctuationResponseVO getFluctuationRanking(String url, HttpSession session) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.flab.mars.domain.vo.response;

import com.flab.mars.db.entity.PriceDataEntity;
import com.flab.mars.db.entity.PriceDataType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class PriceDataResponseVO {

private Long id;

private String stockInfoId; // StockInfoEntity의 ID와 연결되는 값

private PriceDataType dataType; // 데이터 유형 (DAY, WEEK, MONTH, YEAR)

private BigDecimal currentPrice; // 현재가
private BigDecimal openPrice; // 시가
private BigDecimal closePrice; // 종가
private BigDecimal highPrice; // 최고가
private BigDecimal lowPrice; // 최저가

private BigDecimal acmlVol; // 누적 거래량 (전체 누적 거래량)
private BigDecimal acmlTrPbmn; // 누적 거래 대금

private LocalDate stockBusinessDate; // 주식 영업일자


public static PriceDataResponseVO toVO(PriceDataEntity entity) {
return PriceDataResponseVO.builder()
.id(entity.getId())
//.stockInfoId(entity.getStockInfoEntity().getId().toString()) // TODO : StockPriceInfoEntity의 ID를 문자열로 변환
.dataType(entity.getDataType())
.currentPrice(new BigDecimal(entity.getCurrentPrice()))
.openPrice(new BigDecimal(entity.getOpenPrice()))
.closePrice(entity.getClosePrice() != null ? new BigDecimal(entity.getClosePrice()) : BigDecimal.ZERO) // 실시간 가격일 경우 종가 없음
.highPrice(new BigDecimal(entity.getHighPrice()))
.lowPrice(new BigDecimal(entity.getLowPrice()))
.acmlVol(new BigDecimal(entity.getAcmlVol()))
.acmlTrPbmn(new BigDecimal(entity.getAcmlTrPbmn()))
.stockBusinessDate(entity.getStockBusinessDate())
.build();
}
}
Loading