-
Notifications
You must be signed in to change notification settings - Fork 0
✨ [FEAT] 주가 예측 게임 CRUD 구현 및 마이페이지 api(GET /api/v1/users) 수정 #36
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
base: main
Are you sure you want to change the base?
Changes from all commits
f2836b4
e452346
7da4bab
a6cb008
004c11a
0179dba
b8ba56e
d189128
ee01d2d
65a431d
6d4b3b8
072f200
6aab2a4
c3b9cbe
470c590
3093e60
88a3ebb
4afb2ad
7478371
3e56ebc
d7f860f
d3864eb
fc63665
38afb41
642e1e8
5d552f7
3ec69b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.example.demo.api.kis.client; | ||
|
|
||
| import com.example.demo.api.kis.dto.KisResponseDto; | ||
| import com.example.demo.api.kis.dto.KisTokenRequestDto; | ||
| import com.example.demo.api.kis.dto.KisTokenResponseDto; | ||
| import org.springframework.cloud.openfeign.FeignClient; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestHeader; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
|
|
||
| @FeignClient(name = "kisClient", url = "${kis.base-url}") | ||
| public interface KisFeignClient { | ||
|
|
||
| @PostMapping("/oauth2/tokenP") | ||
| KisTokenResponseDto issueToken(@RequestBody KisTokenRequestDto request); | ||
|
|
||
| @GetMapping("/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice") | ||
| KisResponseDto getDailyStockPrice( | ||
| @RequestHeader("authorization") String authorization, | ||
| @RequestHeader("appkey") String appKey, | ||
| @RequestHeader("appsecret") String appSecret, | ||
| @RequestHeader("tr_id") String trId, | ||
| @RequestParam("FID_COND_MRKT_DIV_CODE") String fidCondMrktDivCode, | ||
| @RequestParam("FID_INPUT_ISCD") String fidInputIscd, | ||
| @RequestParam("FID_INPUT_DATE_1") String fidInputDate1, | ||
| @RequestParam("FID_INPUT_DATE_2") String fidInputDate2, | ||
| @RequestParam("FID_PERIOD_DIV_CODE") String fidPeriodDivCode, | ||
| @RequestParam("FID_ORG_ADJ_PRC") String fidOrgAdjPrc | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.example.demo.api.kis.dto; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public class KisResponseDto { | ||
|
|
||
| @JsonProperty("rt_cd") | ||
| private String rtCd; | ||
|
|
||
| @JsonProperty("msg1") | ||
| private String msg1; | ||
|
|
||
| @JsonProperty("output2") | ||
| private List<DailyData> output2; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public static class DailyData { | ||
| @JsonProperty("stck_bsop_date") | ||
| private String date; | ||
|
|
||
| @JsonProperty("stck_oprc") | ||
| private String openPrice; | ||
|
|
||
| @JsonProperty("stck_clpr") | ||
| private String closePrice; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.example.demo.api.kis.dto; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| public class KisTokenRequestDto { | ||
|
|
||
| @JsonProperty("grant_type") | ||
| private String grantType; | ||
|
|
||
| @JsonProperty("appkey") | ||
| private String appKey; | ||
|
|
||
| @JsonProperty("appsecret") | ||
| private String appSecret; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.example.demo.api.kis.dto; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public class KisTokenResponseDto { | ||
|
|
||
| @JsonProperty("access_token") | ||
| private String accessToken; | ||
|
|
||
| @JsonProperty("token_type") | ||
| private String tokenType; | ||
|
|
||
| @JsonProperty("expires_in") | ||
| private Long expiresIn; | ||
|
|
||
| @JsonProperty("access_token_token_expired") | ||
| private String accessTokenTokenExpired; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||||||||||||||
| package com.example.demo.api.kis.service; | ||||||||||||||||||
|
|
||||||||||||||||||
| import com.example.demo.api.kis.client.KisFeignClient; | ||||||||||||||||||
| import com.example.demo.api.kis.dto.KisResponseDto; | ||||||||||||||||||
| import com.example.demo.domain.stock.exception.StockHandler; | ||||||||||||||||||
| import feign.FeignException; | ||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.util.Objects; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Slf4j | ||||||||||||||||||
| @Service | ||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||
| public class KisService { | ||||||||||||||||||
|
|
||||||||||||||||||
| private final KisFeignClient kisFeignClient; | ||||||||||||||||||
| private final KisTokenService kisTokenService; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Value("${kis.app-key}") | ||||||||||||||||||
| private String appKey; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Value("${kis.app-secret}") | ||||||||||||||||||
| private String appSecret; | ||||||||||||||||||
|
|
||||||||||||||||||
| public KisResponseDto getDailyStockPrice(String stockCode, String startDate, String endDate) { | ||||||||||||||||||
| String token = kisTokenService.getAccessToken(); | ||||||||||||||||||
|
|
||||||||||||||||||
| try { | ||||||||||||||||||
| KisResponseDto response = kisFeignClient.getDailyStockPrice( | ||||||||||||||||||
| "Bearer " + token, | ||||||||||||||||||
| appKey, | ||||||||||||||||||
| appSecret, | ||||||||||||||||||
| "FHKST03010100", | ||||||||||||||||||
| "J", | ||||||||||||||||||
| stockCode, | ||||||||||||||||||
| startDate, | ||||||||||||||||||
| endDate, | ||||||||||||||||||
| "D", | ||||||||||||||||||
| "0" | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (response == null || !"0".equals(response.getRtCd())) { | ||||||||||||||||||
| log.error("KIS API 응답 오류. rt_cd={}, msg={}", Objects.requireNonNull(response).getRtCd(), response.getMsg1()); | ||||||||||||||||||
| throw StockHandler.kisApiError(); | ||||||||||||||||||
|
Comment on lines
+45
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win null 응답을 로깅할 때
수정 예시- log.error("KIS API 응답 오류. rt_cd={}, msg={}", Objects.requireNonNull(response).getRtCd(), response.getMsg1());
+ log.error("KIS API 응답 오류. rt_cd={}, msg={}",
+ response == null ? null : response.getRtCd(),
+ response == null ? null : response.getMsg1());📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return response; | ||||||||||||||||||
|
|
||||||||||||||||||
| } catch (FeignException e) { | ||||||||||||||||||
| log.error("KIS API 호출 실패. status={}, stockCode={}", e.status(), stockCode); | ||||||||||||||||||
| throw StockHandler.kisApiError(); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| package com.example.demo.api.kis.service; | ||
|
|
||
| import com.example.demo.api.kis.client.KisFeignClient; | ||
| import com.example.demo.api.kis.dto.KisTokenRequestDto; | ||
| import com.example.demo.api.kis.dto.KisTokenResponseDto; | ||
| import com.example.demo.common.service.RedisService; | ||
| import com.example.demo.common.util.RedisUtil; | ||
| import com.example.demo.domain.stock.exception.StockHandler; | ||
| import feign.FeignException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.time.Duration; | ||
|
|
||
| import static com.example.demo.common.consts.StaticVariable.GRANT_TYPE; | ||
| import static com.example.demo.common.consts.StaticVariable.TOKEN_KEY; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class KisTokenService { | ||
|
|
||
| private final KisFeignClient kisFeignClient; | ||
| private final RedisService redisService; | ||
| private final RedisUtil redisUtil; | ||
|
|
||
| @Value("${kis.app-key}") | ||
| private String appKey; | ||
|
|
||
| @Value("${kis.app-secret}") | ||
| private String appSecret; | ||
|
|
||
| public String getAccessToken() { | ||
| log.info("[KIS 토큰] 1. Redis 캐시 조회 시작. key={}", TOKEN_KEY); | ||
| String cached = redisService.getValue(TOKEN_KEY); | ||
|
|
||
| if (cached != null && !cached.isBlank()) { | ||
| log.info("[KIS 토큰] 2. 캐시 히트 - 저장된 토큰 반환"); | ||
| return cached; | ||
| } | ||
| log.info("[KIS 토큰] 2. 캐시 미스 - 신규 발급 시작"); | ||
|
|
||
| log.info("[KIS 토큰] 3. KIS API 토큰 발급 요청. appKey={}", appKey); | ||
| try { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰 발급 메서드를 private 메서드로 따로 두는게 어떨까요? |
||
| KisTokenResponseDto tokenResponse = kisFeignClient.issueToken( | ||
| new KisTokenRequestDto(GRANT_TYPE, appKey, appSecret) | ||
| ); | ||
| log.info("[KIS 토큰] 4. KIS API 응답 수신. response={}", tokenResponse); | ||
|
|
||
| if (tokenResponse == null || tokenResponse.getAccessToken() == null | ||
| || tokenResponse.getAccessToken().isBlank()) { | ||
| log.error("[KIS 토큰] 5. 응답 토큰 값 없음 (null 또는 blank)"); | ||
| throw StockHandler.kisApiError(); | ||
| } | ||
| log.info("[KIS 토큰] 5. 응답 토큰 정상 확인. expiredAt={}", tokenResponse.getAccessTokenTokenExpired()); | ||
|
|
||
| String token = tokenResponse.getAccessToken(); | ||
|
|
||
| log.info("[KIS 토큰] 6. TTL 계산 시작. expiredAt={}", tokenResponse.getAccessTokenTokenExpired()); | ||
| Duration ttl = redisUtil.calculateTtl(tokenResponse.getAccessTokenTokenExpired()); | ||
| log.info("[KIS 토큰] 7. TTL 계산 완료. ttl={}s", ttl.getSeconds()); | ||
|
|
||
| log.info("[KIS 토큰] 8. Redis 저장 시작. key={}, ttl={}s", TOKEN_KEY, ttl.getSeconds()); | ||
| redisService.setKisTokenExpiresValueWithTtl(TOKEN_KEY, token, ttl); | ||
| log.info("[KIS 토큰] 9. Redis 저장 완료"); | ||
|
|
||
| return token; | ||
|
|
||
| } catch (FeignException e) { | ||
| log.error("[KIS 토큰] FeignException 발생. status={}, message={}", e.status(), e.getMessage()); | ||
| throw StockHandler.kisApiError(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
FavoriteStock.days생성 계약을 복구하세요.days입력 제거와 변환기 매핑 제거로 인해 새FavoriteStock의 null 불가days값이 항상 누락됩니다. 관심 종목 생성 요청은 저장 시 실패합니다.src/main/java/com/example/demo/api/favoriteStock/dto/FavoriteStockRequestDto.java#L8-L10:days를 요청 계약에 유지하거나, 해당 필드를 제거하는 도메인 변경을 완료하세요.src/main/java/com/example/demo/api/favoriteStock/mapper/FavoriteStockConverter.java#L13-L19:days를 엔티티에 설정하거나,days제거 후의 새 영속 모델에 맞게 변환기를 변경하세요.📍 Affects 2 files
src/main/java/com/example/demo/api/favoriteStock/dto/FavoriteStockRequestDto.java#L8-L10(this comment)src/main/java/com/example/demo/api/favoriteStock/mapper/FavoriteStockConverter.java#L13-L19🤖 Prompt for AI Agents