Skip to content

Commit

Permalink
[feat] 종목 및 배당일정 초기화 코드 추가 (#430)
Browse files Browse the repository at this point in the history
* feat: 종목 및 배당 일정 최신화 기능 cron 표현식 수정 (#428)

릴리즈 서버는 7시에 수행, 프로덕션은 8시에 수행

* [refactor] 종목 및 배당일정 초기화 코드 개선 (#429)

* feat: 접근 제한자 private 변경 및 트랜잭션 애노테이션 제거

* feat: usd 환율 생성 로직 추가

* style: 코드 정리

* style: 메서드 순서 정리

* feat: 관리자 및 매니저 정보 프로퍼티화

* feat: 관리자 및 매니저 프로퍼티 추가

* refactor: createMemberIfNotFound 메서드 코드 수정

* refactor: 알림 환경설정 초기화 메서드 분리

* refactor: createRoleIfNotFound 메서드명 변경

db에 저장하는 것이기 때문에 save가 더 적절하다고 생각함

* refactor: findOrCreateRole 메서드로 분리

* style: 코드 정리

* rename: SetUpDataLoader 클래스명 변경

* feat: SetupDataLoader 클래스로 분리

* refactor: supplierNotFoundRoleExpceiton 메서드로 분리

* refactor: createExchangeRateIfNotFount 메서드명 변경

* refactor: 스트림 형식으로 변경

* feat: 시크릿 정보 수정

- 역할 정보 추가

* feat: RoleProperties 추가

* feat: 역할별 getter 추가

* refactor: Role 리소스 생성 로직 스트림으로 변경

* refactor: getter 제거 및 클래스명 RoleProperty로 변경

* style: 코드 정리

* test: setupResources 테스트 추가

* test: setupResources 테스트에 멤버 검증문 추가

* test: Authentication 검증문 추가

* test: 검증문 추가

* style: 주석 추가

* test: 환율 검증문 추가

* teset: kisService 객체 모킹

* test: 종목 검증문 추가 및 종목 초기화 로직 추가

* feat: readDividendCsv 메서드 구현

* rename: stocks.csv, dividends.csv 파일을 test/resources로 이동

* feat: 배당금 초기화 추가

* feat: UserProperties 추가

* rename: properties 파일 이동

* feat: 시크릿 정보 변경

- 샘플 유저 추가

* docs: mysql script 제거

* feat: Stock.parse 정적 팩토리 메서드에 예외 캐치 추가

* feat: stocks.csv -> stocks.txt로 변경

* test: 테스트 문제 해결

* feat: parseCsvLine 구현

- Ltd가 포함된 종목은 쉼표를 포함하고 있기 때문에 원할한 쉼표 분할을 위해서 별도의 패턴을 구현함

* feat: Pattern 변경

* test: 테스트 실패 해결

* feat: 시크릿 정보 변경

- csv 파일로 변경
  • Loading branch information
yonghwankim-dev authored Aug 11, 2024
1 parent 02d52f9 commit e4ddbdc
Show file tree
Hide file tree
Showing 34 changed files with 888 additions and 3,989 deletions.
2 changes: 1 addition & 1 deletion secret
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package codesquad.fineants.domain.kis.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

import codesquad.fineants.domain.kis.properties.OauthKisProperties;

@EnableConfigurationProperties(value = OauthKisProperties.class)
@Configuration
public class KisConfig {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
Expand All @@ -30,6 +31,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@ToString(exclude = {"notificationPreference", "roles"})
@EqualsAndHashCode(of = {"email", "nickname", "provider"}, callSuper = false)
public class Member extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
Expand All @@ -18,6 +19,7 @@
@Table(name = "ROLE")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(of = {"roleName", "roleDescription"})
@ToString
@Getter
public class Role {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import codesquad.fineants.domain.member.domain.entity.Member;

public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findMemberByEmailAndProvider(String email, String provider);
@Query("select distinct m from Member m join fetch m.roles where m.email = :email and m.provider = :provider")
Optional<Member> findMemberByEmailAndProvider(@Param("email") String email, @Param("provider") String provider);

@Query("select m from Member m where m.nickname = :nickname and m.id != :memberId")
Optional<Member> findMemberByNicknameAndNotMemberId(@Param("nickname") String nickname,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import java.util.Arrays;

public enum Market {
KOSPI, KOSDAQ, KONEX, KOSDAQ_GLOBAL, NONE;
KOSPI("KOSPI"), KOSDAQ("KOSDAQ"), KONEX("KONEX"), KOSDAQ_GLOBAL("KOSDAQ GLOBAL"), NONE("NONE");

private final String name;

Market(String name) {
this.name = name;
}

public static Market ofMarket(String dbData) {
return Arrays.stream(values())
.filter(market -> market.name().equals(dbData))
.filter(market -> market.name.equals(dbData))
.findAny()
.orElse(NONE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -65,13 +66,20 @@ public static Stock of(String tickerSymbol, String companyName, String companyNa
}

public static Stock parse(String[] data) {
String stockCode = data[0];
String tickerSymbol = data[1];
String companyName = data[2];
String companyNameEng = data[3];
Market market = Market.ofMarket(data[4]);
String sector = data[5];
return new Stock(tickerSymbol, companyName, companyNameEng, stockCode, sector, market);
try {
String stockCode = data[0];
String tickerSymbol = data[1];
String companyName = data[2];
String companyNameEng = data[3];
Market market = Market.ofMarket(data[4]);
String sector = "none";
if (data.length >= 6) {
sector = data[5];
}
return new Stock(tickerSymbol, companyName, companyNameEng, stockCode, sector, market);
} catch (ArrayIndexOutOfBoundsException e) {
throw new ArrayIndexOutOfBoundsException("out of index, data:" + Arrays.toString(data));
}
}

public void addStockDividend(StockDividend stockDividend) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,64 @@
package codesquad.fineants.domain.stock.service;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.logging.log4j.util.Strings;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import codesquad.fineants.domain.stock.domain.dto.response.StockDataResponse;
import codesquad.fineants.domain.common.money.Money;
import codesquad.fineants.domain.dividend.domain.entity.StockDividend;
import codesquad.fineants.domain.stock.domain.dto.response.StockSectorResponse;
import codesquad.fineants.domain.stock.domain.entity.Market;
import codesquad.fineants.domain.stock.domain.entity.Stock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@RequiredArgsConstructor
@Slf4j
public class StockCsvReader {

public Set<StockDataResponse.StockInfo> readStockCsv() {
public Set<Stock> readStockCsv() {
Resource resource = new ClassPathResource("stocks.csv");

Set<StockDataResponse.StockInfo> result = new HashSet<>();
Set<Stock> result = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
Iterable<CSVRecord> records = CSVFormat.DEFAULT
.withHeader("stockCode", "tickerSymbol", "companyName", "companyNameEng", "market", "sector")
.withSkipHeaderRecord()
.parse(reader);

for (CSVRecord record : records) {
StockDataResponse.StockInfo stockInfo = StockDataResponse.StockInfo.of(
record.get("stockCode"),
Stock stock = Stock.of(
record.get("tickerSymbol"),
record.get("companyName"),
record.get("companyNameEng"),
record.get("market")
record.get("stockCode"),
record.get("sector"),
Market.ofMarket(record.get("market"))
);
result.add(stockInfo);
result.add(stock);
}
} catch (IOException e) {
return Collections.emptySet();
Expand Down Expand Up @@ -110,4 +129,46 @@ public Map<String, StockSectorResponse.SectorInfo> readKosdaqCsv() {
}
return result;
}

public List<StockDividend> readDividendCsv(List<Stock> stocks) {
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(Objects.requireNonNull(classLoader.getResource("dividends.csv")).getFile());

List<StockDividend> result = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
Iterable<CSVRecord> records = CSVFormat.DEFAULT
.withHeader("id", "dividend", "recordDate", "paymentDate", "stockCode")
.withSkipHeaderRecord()
.parse(reader);

Map<String, Stock> stockMap = stocks.stream()
.collect(Collectors.toMap(Stock::getStockCode, stock -> stock));
for (CSVRecord record : records) {
Stock stock = stockMap.get(record.get("stockCode"));
if (stock == null) {
continue;
}
StockDividend stockDividend = StockDividend.create(
Long.valueOf(record.get("id")),
Money.won(record.get("dividend")),
basicIso(record.get("recordDate")).orElse(null),
basicIso(record.get("paymentDate")).orElse(null),
stock
);
result.add(stockDividend);
}
} catch (IOException e) {
log.error(e.getMessage());
return Collections.emptyList();
}
return result;
}

@NotNull
private Optional<LocalDate> basicIso(String localDateString) {
if (Strings.isBlank(localDateString)) {
return Optional.empty();
}
return Optional.of(LocalDate.parse(localDateString, DateTimeFormatter.BASIC_ISO_DATE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public StockResponse getDetailedStock(String tickerSymbol) {
return StockResponse.of(stock, currentPriceRepository, closingPriceRepository);
}

@Scheduled(cron = "0 0 8 * * ?") // 매일 오전 8시 (초, 분, 시간)
@Scheduled(cron = "${cron.expression.reload-stocks:0 0 8 * * ?}") // 매일 오전 8시 (초, 분, 시간)
@Transactional
public void scheduledReloadStocks() {
reloadStocks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import codesquad.fineants.domain.kis.properties.OauthKisProperties;
import codesquad.fineants.domain.portfolio.properties.PortfolioProperties;
import codesquad.fineants.global.init.properties.AdminProperties;
import codesquad.fineants.global.init.properties.ManagerProperties;
import codesquad.fineants.global.init.properties.RoleProperties;
import codesquad.fineants.global.init.properties.UserProperties;

@EnableAspectJAutoProxy
@EnableConfigurationProperties(value = PortfolioProperties.class)
@EnableConfigurationProperties(value = {PortfolioProperties.class, AdminProperties.class, ManagerProperties.class,
OauthKisProperties.class, RoleProperties.class, UserProperties.class})
@Configuration
public class SpringConfig {
}
Loading

0 comments on commit e4ddbdc

Please sign in to comment.