[quantHelper] 개발 - API2

JUJU·2024년 4월 11일
0

프로젝트

목록 보기
6/26

■ 구현 내용

  1. GetStockRequest 클래스
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetStockRequest {
    @NotBlank(message = "{not_blank}")
    @Schema(
            name = "PDNO",
            description = "stock id",
            type = "String",
            requiredMode = Schema.RequiredMode.REQUIRED,
            example = "000660"
    )
    private String stockCode;
}

/*
 * API를 사용해서 주식 정보를 가져올 때 주식 코드가 필요하다.
 * User는 주식 코드를 query Parameter에 포함해서 보내고, 
 * 서버는 이것을 받아서 GetStockRequest 객체로 저장한다.
 */
  1. KisService의 getStcokInfoByCode() 메소드
public Mono<String> getStockInfoByCode(String code){
        code = code.substring(Math.max(code.length() - 6, 0));
        log.info(code);
        String fullUrl = baseUrl + "/uapi/domestic-stock/v1/quotations/inquire-price" + "?fid_cond_mrkt_div_code=J&fid_input_iscd=" + code;
        return webClient.get()
                .uri(fullUrl)
                .header("content-type","application/json")
                .header("authorization","Bearer " + accessToken)
                .header("appkey",appKey)
                .header("appsecret",appSecretKey)
                .header("tr_id","FHKST01010100")
                .retrieve()
                .bodyToMono(String.class);
    }
    
/*
 * User가 넘긴 stockCode로 주식 정보를 한투 서버에서 받아온다.
 */
  1. StockController 클래스
// Swagger 관련 어노테이션 생략

    public ResponseEntity<StockDTO> stock(@ModelAttribute("PDNO") GetStockRequest request) throws JsonProcessingException {
        System.out.println(request.getStockCode());
        if (request.getStockCode().length() > 6){
            System.out.println("stock id should be smaller than 6 digits");
            return ResponseEntity.badRequest().body(null);
        }
        Mono<String> stockNameByCode = kisService.getStockNameByCode(request.getStockCode()); // 주식 코드로 주식 이름 조회 
        Mono<String> stockInfoByCode = kisService.getStockInfoByCode(request.getStockCode()); // 주식 코드로 주식 정보 조회
        String stockNameResponse = stockNameByCode.block();
        String stockInfoResponse = stockInfoByCode.block();

        log.info(stockNameResponse);
        log.info(stockInfoResponse);

        ObjectMapper objectMapper = new ObjectMapper();
        Map<String,String> stockMap = 
                (Map<String, String>) objectMapper.readValue(stockNameResponse, Map.class).get("output");
        Map<String,String> stockInfoMap = 
                (Map<String, String>) objectMapper.readValue(stockInfoResponse, Map.class).get("output");
        // 응답 Json이 두번 감싸져 있는 형태 {"output" : {"stockName" : "samsung"}} 이런식으로
        
        log.info(stockInfoMap.toString());

        StockDTO stockDTO = StockDTO.builder()
                .stockCode(stockMap.get("pdno")) // 주식 코드
                .stockName(stockMap.get("prdt_abrv_name")) // 주식 이름
                .stockPriceIndex(stockInfoMap.get("rprs_mrkt_kor_name")) // KOSPI200, KOSPI, KOSDAQ 등
                .price(Long.valueOf(stockInfoMap.get("stck_oprc"))) // 가격 (당일 종가)
                .theme(stockInfoMap.get("bstp_kor_isnm")) // 분야
                .status(stockInfoMap.get("iscd_stat_cls_code")) // status Code
                .build();


        stockService.save(stockDTO);
        return ResponseEntity.ok().body(stockDTO);
    }

클라이언트에게 주식 코드를 받아서 저장한다.
해당 주식 코드에 해당되는 주식 이름, 가격, 분야, 상태 코드를 DB에 저장한다.
자세하게 말하자면, kisService의 메소드를 사용해서 응답 메시지 바디를 Mono<String> 타입으로 받아온다.
받아온 JSON 형식의 바디를 ObjectMapper 로 Map 객체로 변환한다.
Map 객체에서 원하는 정보를 빼내어 stockDTO로 변환한 후, stockServcie를 사용해서 DB에 저장한다.




■ 배운 점

  1. WebClient에 대해서

webClient 객체를 사용하다가 이런 의문이 들었다.

Webclient는 요청 때마다 생성 안해도 되나?
Webclient는 스프링 빈으로 등록되어 한 유저가 하나의 Webclient만을 사용한다.

결론부터 말하자면 webClient 객체를 하나만 생성해도 여러 요청에서 공유해서 사용할 수 있다.
webClient.get() 또는 webClient.post() 등 하나의 webClient 객체에 Http Method를 적용하면, Mono<ResponseEntity<T>> 타입의 객체를 리턴한다.
Mono<ResponseEntity<T>> 객체를 커스터마이징해서 Http 요청을 구조화한다고 생각하면 된다.
요청 메시지를 만든 뒤에는 retrieve 메소드를 호출해서 응답 결과를 어떻게 추출할 것인지 명시한다. (아직 실제 요청이 보내진 것이 아니다!!)

block() 메소드를 호출하면 실제로 Http 요청 메시지를 보내고 응답 메시지를 받아온다.


  1. @ModelAttribute에는 이름을 꼭 쓰자.
    @PathVariable
    @ModelAttribute 등에는 옵션으로 꼭 이름을 붙여주도록 하자.
    오류가 날 가능성이 있다...

  1. 에러 메시지를 잘 읽자.
    java.lang.illegalargumentexception: host is not specified 오류가 발생하면 Uri를 다시 한번 확인하도록 하자.
    원인은 스프링이 알려준다. 잘 고치기만 하면 된다.



■ 오류

  1. java.lang.illegalargumentexception: host is not specified 오류 발생
    4시간동안 찾았는데 결국 uri 문제였다.
.uri(uriBuilder -> uriBuilder
	.path(baseUrl + "/uapi/domestic-stock/v1/quotations/search-info")
	.queryParam("PDNO","000660")
	.queryParam("PRDT_TYPE_CD","300")
	.build())

결론: 인코딩 문제 있으니까 다른 방법 사용하자.

// 해결
String fullUrl = baseUrl + "/uapi/domestic-stock/v1/quotations/inquire-price" + "?fid_cond_mrkt_div_code=J&fid_input_iscd=" + code;
        return webClient.get()
                .uri(fullUrl)

스프링 부트 자체 오류인듯
https://github.com/spring-projects/spring-framework/issues/24787


  1. java.lang.illegalargumentexception: host is not specified 오류 여전히 발생

    accessToken이 JSON 형식의 String으로 되어있었다.
    {"accessToken":"3k2ewqfkjblvkjk"}
    따라서, value만 가져오려면 ObjectMapper를 사용해서 accessToken만 빼와야 한다.

  1. org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet [ERROR: relation "stock_seq" does not exist Position: 16][select nextval('stock_seq')]; SQL [select nextval('stock_seq')] 발생

    이거는 Postgresql 문제였다.
    stock_id를 자동 증가하는 PK로 바꾸기위해 DB 구성을 변경해야 했다.
    근데 liquibase xml 코드를 아무리 바꿔도 위의 오류가 해결되지 않았다.
    결론은 postgresql에서 자동 증가를 사용하기 위해서는 sequence 객체를 생성해야 하는데, stock 테이블의 sequence 객체가 생성되지 않아서 발생한 문제였다.

    docker를 다시 껐다 키면 해결됐을지도 모르겠다.
    근데 필자는 직접 sequence 객체를 추가해서 해결했다.
profile
개발자 지망생

0개의 댓글

관련 채용 정보