Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1bc3bdc
#116 feat(be): findByCodeOrderByMinutePriceIdAsc method 구현
ki-met-hoon Feb 7, 2025
046f6a4
#116 feat(be): findByCodeAndMinutePriceIdGreaterThanOrderByMinutePric…
ki-met-hoon Feb 7, 2025
8883f52
#116 refactor(be): 기존 변수 및 파라미터 삭제 및 추가
ki-met-hoon Feb 7, 2025
20962a7
#116 feat(be): findFirstPage method 구현
ki-met-hoon Feb 7, 2025
5fcc5f5
#116 feat(be): findNextPage method 구현
ki-met-hoon Feb 7, 2025
0488a26
#116 feat(be): findMinutePriceChart method 구현
ki-met-hoon Feb 7, 2025
36e69cf
#116 feat(be): sliceBySize method 구현
ki-met-hoon Feb 7, 2025
2d85050
#116 feat(be): checkHasNext method 구현
ki-met-hoon Feb 7, 2025
2d1206d
#116 refactor(be): size 비교 중복 로직
ki-met-hoon Feb 7, 2025
ddf2082
#116 feat(be): getLastObjectId method 구현
ki-met-hoon Feb 7, 2025
8e4493a
#116 feat(be): validationChartSize 구현
ki-met-hoon Feb 7, 2025
10f5ecc
#116 feat(be): getCandlePriceHistoryByCode method 구현
ki-met-hoon Feb 7, 2025
47d5e4c
#116 refactor(be): handleCandlePriceHistory
ki-met-hoon Feb 7, 2025
09edc60
#116 feat(be): objectId 유효성 검사 로직
ki-met-hoon Feb 7, 2025
8b810e2
#116 refactor(be): StockValidationUtils 구현
ki-met-hoon Feb 7, 2025
4cdc0c2
#116 style(be): 패키지 구조 변경
ki-met-hoon Feb 9, 2025
f9ae003
#116 feat(be): BusinessException 구현
ki-met-hoon Feb 9, 2025
e3b077e
#116 feat(be): BadRequestException 구현
ki-met-hoon Feb 9, 2025
e3561d2
#116 feat(be): ErrorResponse 구현
ki-met-hoon Feb 9, 2025
5c785a6
#116 feat(be): ControllerAdvice 구현
ki-met-hoon Feb 9, 2025
dffde54
#116 feat(be): InvalidObjectIdFormatException
ki-met-hoon Feb 9, 2025
905ef3f
#116 feat(be): NoSuchMinutePriceException
ki-met-hoon Feb 9, 2025
29dd219
#163 style(be): DB 네이밍 전략 변경
ki-met-hoon Feb 9, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.jootalkpia.stock_server.stocks.advice;

import com.jootalkpia.stock_server.stocks.advice.exception.BadRequestException;
import com.jootalkpia.stock_server.stocks.advice.exception.ErrorResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class ControllerAdvice {

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(BadRequestException e) {
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.jootalkpia.stock_server.stocks.advice;
package com.jootalkpia.stock_server.stocks.advice.caller;

import com.jootalkpia.stock_server.stocks.dto.request.TokenRequestBody;
import com.jootalkpia.stock_server.stocks.dto.response.MinutePriceDetailedResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.jootalkpia.stock_server.stocks.advice.exception;

public class BadRequestException extends BusinessException {

public BadRequestException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.jootalkpia.stock_server.stocks.advice.exception;

public class BusinessException extends RuntimeException {

public BusinessException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.jootalkpia.stock_server.stocks.advice.exception;

public record ErrorResponse(String message) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jootalkpia.stock_server.stocks.advice.exception;

public class InvalidObjectIdFormatException extends BadRequestException {

private static final String MESSAGE = "ObjectId는 24자리의 16진수 문자열이어야 합니다: ";

public InvalidObjectIdFormatException(String cursorId) {
super(MESSAGE + cursorId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jootalkpia.stock_server.stocks.advice.exception;

public class NoSuchMinutePriceException extends BadRequestException {

private static final String MESSAGE = "조회된 분봉 데이터가 없습니다.";

public NoSuchMinutePriceException() {
super(MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.jootalkpia.stock_server.stocks.advice.util;

import com.jootalkpia.stock_server.stocks.advice.exception.InvalidObjectIdFormatException;
import com.jootalkpia.stock_server.stocks.advice.exception.NoSuchMinutePriceException;
import com.jootalkpia.stock_server.stocks.dto.MinutePrice;

import java.util.List;
import java.util.NoSuchElementException;

public class StockValidationUtils {
private StockValidationUtils() {
// private 생성자로 인스턴스화 방지
}

public static void validateChartSize(List<MinutePrice> slicedMinutePriceChart) {
if (slicedMinutePriceChart.isEmpty()) {
throw new NoSuchMinutePriceException();
}
}

public static void validateObjectId(String cursorId) {
if (!isValidObjectId(cursorId)) {
throw new InvalidObjectIdFormatException(cursorId);
}
}

private static boolean isValidObjectId(String cursorId) {
if (cursorId.length() != 24) {
return false;
}

return cursorId.matches("[0-9a-fA-F]{24}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
import com.jootalkpia.stock_server.stocks.dto.response.CandlePriceHistoryResponse;
import com.jootalkpia.stock_server.stocks.service.StockService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
Expand All @@ -16,7 +15,7 @@ public class StockController {
private final StockService stockService;

@GetMapping("/api/v1/stock/candlesticks/{stock_code}")
public ResponseEntity<CandlePriceHistoryResponse> handleCandlePriceHistory(@PathVariable("stock_code") String code, @PageableDefault(size = 180) Pageable pageable) {
return ResponseEntity.ok(stockService.getCandlePriceHistoryByCode(pageable, code));
public ResponseEntity<CandlePriceHistoryResponse> handleCandlePriceHistory(@PathVariable(name = "stock_code") String code,@RequestParam(required = false) String cursorId, @RequestParam(defaultValue = "120") int size) {
return ResponseEntity.ok(stockService.getCandlePriceHistoryByCode(code, cursorId, size));
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,36 @@
package com.jootalkpia.stock_server.stocks.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Getter;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Getter
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Document(collection = "minute_price")
@Document(collection = "minute_prices")
public class MinutePrice {
@Id
private ObjectId minutePriceId;

@Indexed(background = true)
private String code;

@Field("stock_name")
private String stockName;

@Field("business_date")
private String businessDate;

@Field("trading_time")
private String tradingTime;

@Field("current_price")
private String currentPrice;

@Field("open_price")
private String openPrice;

@Field("high_price")
private String highPrice;

@Field("low_price")
private String lowPrice;

@Field("trading_volume")
private String tradingVolume;

@Field("total_trade_amount")
private String totalTradeAmount;

protected MinutePrice() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@

import com.jootalkpia.stock_server.stocks.domain.StockCode;
import com.jootalkpia.stock_server.stocks.dto.MinutePrice;
import org.springframework.data.domain.Page;

import java.util.List;

public record CandlePriceHistoryResponse(
String code,
String stockName,
List<Output> output,
long totalCount
boolean hasNext,
String lastObjectId
) {

public static CandlePriceHistoryResponse of(Page<MinutePrice> minutePricePage, String code) {
public static CandlePriceHistoryResponse of(List<MinutePrice> minutePriceChart, String code, boolean hasNext, String lastObjectId) {
return new CandlePriceHistoryResponse(
code,
StockCode.getNameByCode(code),
minutePricePage.getContent().stream()
minutePriceChart.stream()
.map(Output::of)
.toList(),
minutePricePage.getTotalElements()
hasNext,
lastObjectId
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package com.jootalkpia.stock_server.stocks.repository;

import com.jootalkpia.stock_server.stocks.dto.MinutePrice;
import org.springframework.data.domain.Page;
import org.bson.types.ObjectId;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface MinutePriceRepository extends MongoRepository<MinutePrice, String> {
Page<MinutePrice> findAllByCode(Pageable pageable, String code);
List<MinutePrice> findByCodeOrderByMinutePriceIdAsc(
String code,
Pageable pageable
);

List<MinutePrice> findByCodeAndMinutePriceIdGreaterThanOrderByMinutePriceIdAsc(
String code,
ObjectId minutePriceId,
Pageable pageable
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.jootalkpia.stock_server.stocks.service;

import com.google.gson.Gson;
import com.jootalkpia.stock_server.stocks.advice.StockCaller;
import com.jootalkpia.stock_server.stocks.advice.caller.StockCaller;
import com.jootalkpia.stock_server.stocks.domain.Schedule;
import com.jootalkpia.stock_server.stocks.domain.StockCode;
import com.jootalkpia.stock_server.stocks.dto.MinutePrice;
Expand All @@ -18,21 +18,26 @@
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.bson.types.ObjectId;
import org.springframework.data.domain.PageRequest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import static com.jootalkpia.stock_server.stocks.advice.util.StockValidationUtils.validateChartSize;
import static com.jootalkpia.stock_server.stocks.advice.util.StockValidationUtils.validateObjectId;

@Slf4j
@RequiredArgsConstructor
@Service
public class StockService {
private static final String TOKEN_SEPARATOR = " ";
private static final int CURSOR_PAGE_NUMBER = 0;

private String token;

Expand Down Expand Up @@ -105,8 +110,52 @@ private MinutePriceSimpleResponse getStockPrice(String code) {
return MinutePriceSimpleResponse.from(response, code);
}

public CandlePriceHistoryResponse getCandlePriceHistoryByCode(Pageable pageable, String code) {
Page<MinutePrice> minutePricePage = minutePriceRepository.findAllByCode(pageable, code);
return CandlePriceHistoryResponse.of(minutePricePage, code);
public CandlePriceHistoryResponse getCandlePriceHistoryByCode(String code, String cursorId, int size) {
List<MinutePrice> minutePriceChart = findMinutePriceChart(code, cursorId, size);
boolean hasNext = checkHasNext(minutePriceChart, size);
List<MinutePrice> slicedMinutePriceChart = sliceBySize(minutePriceChart, size, hasNext);

return CandlePriceHistoryResponse.of(slicedMinutePriceChart, code, hasNext, getLastObjectId(slicedMinutePriceChart));
}

private List<MinutePrice> findMinutePriceChart(String code, String cursorId, int size) {
if (cursorId == null || cursorId.isEmpty()) {
return findFirstPage(code, size);
}
return findNextPage(code, cursorId, size);
}

private List<MinutePrice> findFirstPage(String code, int size) {
return minutePriceRepository.findByCodeOrderByMinutePriceIdAsc(
code,
PageRequest.of(CURSOR_PAGE_NUMBER, size + 1));
}

private List<MinutePrice> findNextPage(String code, String cursorId, int size) {
validateObjectId(cursorId);
ObjectId objectId = new ObjectId(cursorId);

return minutePriceRepository.findByCodeAndMinutePriceIdGreaterThanOrderByMinutePriceIdAsc(
code,
objectId,
PageRequest.of(CURSOR_PAGE_NUMBER, size + 1)
);
}

private boolean checkHasNext(List<MinutePrice> minutePriceChart, int size) {
return minutePriceChart.size() > size;
}

private List<MinutePrice> sliceBySize(List<MinutePrice> minutePriceChart, int size, boolean hasNext) {
if (!hasNext) {
return minutePriceChart;
}
return minutePriceChart.subList(0, size);
}

private String getLastObjectId(List<MinutePrice> slicedMinutePriceChart) {
validateChartSize(slicedMinutePriceChart);

return String.valueOf(slicedMinutePriceChart.get(slicedMinutePriceChart.size() - 1).getMinutePriceId());
}
}
Loading