From ad0709c9500ee1c8a4f07bd53549f796fdaade9d Mon Sep 17 00:00:00 2001 From: smyoo Date: Fri, 18 Oct 2024 01:44:46 +0900 Subject: [PATCH 01/17] =?UTF-8?q?update=20:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stocksignal/repository/UserQueryRepository.java | 4 ---- .../stocksignal/repository/UserQueryRepositoryImpl.java | 9 --------- 2 files changed, 13 deletions(-) delete mode 100644 back/src/main/java/com/thered/stocksignal/repository/UserQueryRepository.java delete mode 100644 back/src/main/java/com/thered/stocksignal/repository/UserQueryRepositoryImpl.java diff --git a/back/src/main/java/com/thered/stocksignal/repository/UserQueryRepository.java b/back/src/main/java/com/thered/stocksignal/repository/UserQueryRepository.java deleted file mode 100644 index 6ef74a3..0000000 --- a/back/src/main/java/com/thered/stocksignal/repository/UserQueryRepository.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.thered.stocksignal.repository; - -public interface UserQueryRepository { -} diff --git a/back/src/main/java/com/thered/stocksignal/repository/UserQueryRepositoryImpl.java b/back/src/main/java/com/thered/stocksignal/repository/UserQueryRepositoryImpl.java deleted file mode 100644 index 56a75f0..0000000 --- a/back/src/main/java/com/thered/stocksignal/repository/UserQueryRepositoryImpl.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.thered.stocksignal.repository; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class UserQueryRepositoryImpl implements UserQueryRepository{ -} From 05076a0c175463bebfc61467ccf16bbcd7e38818 Mon Sep 17 00:00:00 2001 From: smyoo Date: Fri, 18 Oct 2024 01:47:53 +0900 Subject: [PATCH 02/17] =?UTF-8?q?update=20:=20user=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stocksignal/app/dto/user/UserInfoDto.java | 1 + .../com/thered/stocksignal/domain/entity/User.java | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/user/UserInfoDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/user/UserInfoDto.java index ebc05a0..01ff91a 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/user/UserInfoDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/user/UserInfoDto.java @@ -36,5 +36,6 @@ public static class LoginRequestDto{ public static class kisAccountRequestDto{ public String appKey; public String secretKey; + public String account; } } diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/User.java b/back/src/main/java/com/thered/stocksignal/domain/entity/User.java index 908cf76..6740e78 100644 --- a/back/src/main/java/com/thered/stocksignal/domain/entity/User.java +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/User.java @@ -41,14 +41,23 @@ public class User { @Column(nullable = true) private String accountNumber; - @Column(nullable = true, length = 1024) + @Column(nullable = true, length = 2048) private String kisToken; + @Column(nullable = true, length = 2048) + private String kisTokenExpired; + public void setNickname(String nickname) {this.nickname = nickname;} - public void setKisAccount(String secretKey, String appKey, Boolean isKisLinked){ + public void setKisAccount(String secretKey, String appKey, String accountNumber, Boolean isKisLinked){ this.secretKey = secretKey; this.appKey = appKey; + this.accountNumber = accountNumber; this.isKisLinked = isKisLinked; } + + public void setAccessToken(String kisToken, String kisTokenExpired){ + this.kisToken = kisToken; + this.kisTokenExpired = kisTokenExpired; + } } \ No newline at end of file From 5718a989b2bb9acb7c9c9e91a2fdf689dfa7c08c Mon Sep 17 00:00:00 2001 From: smyoo Date: Fri, 18 Oct 2024 01:48:32 +0900 Subject: [PATCH 03/17] =?UTF-8?q?feat=20:=20=ED=95=9C=ED=88=AC=20Date=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=20=EB=B3=80=EA=B2=BD=20util?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/thered/stocksignal/util/DateUtil.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 back/src/main/java/com/thered/stocksignal/util/DateUtil.java diff --git a/back/src/main/java/com/thered/stocksignal/util/DateUtil.java b/back/src/main/java/com/thered/stocksignal/util/DateUtil.java new file mode 100644 index 0000000..4241c5e --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/util/DateUtil.java @@ -0,0 +1,25 @@ +package com.thered.stocksignal.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +@RequiredArgsConstructor +@Component +public class DateUtil { + + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + public static Date parseDate(String dateString) { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + return dateFormat.parse(dateString); + } catch (ParseException e) { + throw new RuntimeException("파싱할 수 없습니다.", e); + } + } + +} From e4f4ee66c8e5c6c0f37d1857b785fa34ec182d8e Mon Sep 17 00:00:00 2001 From: smyoo Date: Fri, 18 Oct 2024 01:49:31 +0900 Subject: [PATCH 04/17] =?UTF-8?q?feat=20:=20data=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=9D=91=EB=8B=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/thered/stocksignal/apiPayload/ApiResponse.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/back/src/main/java/com/thered/stocksignal/apiPayload/ApiResponse.java b/back/src/main/java/com/thered/stocksignal/apiPayload/ApiResponse.java index a1cfd69..d4afd12 100644 --- a/back/src/main/java/com/thered/stocksignal/apiPayload/ApiResponse.java +++ b/back/src/main/java/com/thered/stocksignal/apiPayload/ApiResponse.java @@ -24,4 +24,9 @@ public static ApiResponse onSuccess(Status status, T data){ public static ApiResponse onFailure(Status status){ return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), null); } + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(Status status, T data){ + return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), data); + } } From 77934d1f9240ddf62b53525c2ef628b1c06fb2e5 Mon Sep 17 00:00:00 2001 From: smyoo Date: Fri, 18 Oct 2024 01:51:25 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat=20:=20=EB=A7=A4=EC=88=98/=EB=A7=A4?= =?UTF-8?q?=EB=8F=84=20=EA=B0=9C=EB=B0=9C=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thered/stocksignal/apiPayload/Status.java | 10 +- .../app/controller/TradeController.java | 26 +++- .../thered/stocksignal/app/dto/TradeDto.java | 21 +-- .../app/dto/kis/KisAccountDto.java | 17 +++ .../stocksignal/app/dto/kis/KisTradeDto.java | 33 ++++ .../stocksignal/domain/entity/Trade.java | 40 +++++ .../stocksignal/domain/enums/TradeType.java | 5 + .../repository/CompanyRepository.java | 2 + .../repository/TradeRepository.java | 8 + .../service/trade/TradeService.java | 10 ++ .../service/trade/TradeServiceImpl.java | 142 ++++++++++++++++++ .../service/user/UserAccountService.java | 4 + .../service/user/UserAccountServiceImpl.java | 75 ++++++++- .../com/thered/stocksignal/util/KisUtil.java | 17 +++ 14 files changed, 383 insertions(+), 27 deletions(-) create mode 100644 back/src/main/java/com/thered/stocksignal/app/dto/kis/KisAccountDto.java create mode 100644 back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java create mode 100644 back/src/main/java/com/thered/stocksignal/domain/entity/Trade.java create mode 100644 back/src/main/java/com/thered/stocksignal/domain/enums/TradeType.java create mode 100644 back/src/main/java/com/thered/stocksignal/repository/TradeRepository.java create mode 100644 back/src/main/java/com/thered/stocksignal/service/trade/TradeService.java create mode 100644 back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java create mode 100644 back/src/main/java/com/thered/stocksignal/util/KisUtil.java diff --git a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java index cd664dd..d8e655f 100644 --- a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java +++ b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java @@ -28,7 +28,7 @@ public enum Status { */ INGA_SUCCESS("200", "SUCCESS", "카카오 인가코드입니다."), LOGIN_SUCCESS("200", "SUCCESS", "로그인이 완료되었습니다."), - TOKEN_INVALID("201", "SUCCESS", "유효하지 않은 토큰입니다."), + TOKEN_INVALID("401", "SUCCESS", "유효하지 않은 토큰입니다."), QUESTION_FOUND("200", "SUCCESS", "답변 정보를 찾았습니다."), @@ -49,14 +49,16 @@ public enum Status { GET_USERINFO_SUCCESS("200", "SUCCESS", "회원 정보가 조회되었습니다."), SET_USERINFO_SUCCESS("200", "SUCCESS", "회원 정보가 수정되었습니다."), NICKNAME_SUCCESS("200", "SUCCESS", "사용 가능한 닉네임입니다."), - NICKNAME_INVALID("201", "SUCCESS", "이미 존재하는 닉네임입니다."), + NICKNAME_INVALID("409", "SUCCESS", "이미 존재하는 닉네임입니다."), KIS_CONNECT_SUCCESS("200", "SUCCESS", "한국투자증권 계좌를 연동했습니다."), - KIS_CONNECT_FAILED("201", "SUCCESS", "한국투자증권 계좌를 연동할 수 없습니다."), + KIS_CONNECT_FAILED("401", "SUCCESS", "한국투자증권 계좌를 연동할 수 없습니다."), TRADE_BUY_SUCCESS("200", "SUCCESS", "매수를 성공했습니다."), + TRADE_BUY_FAILED("400", "FAILED", "매수를 실패했습니다."), TRADE_SELL_SUCCESS("200", "SUCCESS", "매도를 성공했습니다."), - + TRADE_SELL_FAILED("400", "FAILED", "매도를 실패했습니다."), + MYSTOCK_SUCCESS("200", "SUCCESS", "나의 전체 주식현황을 읽는데 성공했습니다."), MYSTOCK_SHORT_SUCCESS("200", "SUCCESS", "나의 주식현황 요약을 읽는데 성공했습니다."), diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/TradeController.java b/back/src/main/java/com/thered/stocksignal/app/controller/TradeController.java index 5ac8cc4..17187a4 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/TradeController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/TradeController.java @@ -3,27 +3,41 @@ import com.thered.stocksignal.apiPayload.ApiResponse; import com.thered.stocksignal.apiPayload.Status; import com.thered.stocksignal.app.dto.TradeDto; +import com.thered.stocksignal.service.trade.TradeService; +import com.thered.stocksignal.service.user.UserAccountService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api/trade") public class TradeController { + private final TradeService tradeService; + private final UserAccountService userAccountService; + @Operation(summary = "주식 매수") @PostMapping("/buy") - public ApiResponse buyStock(@RequestBody TradeDto.buyRequestDto dto){ + public ApiResponse buyStock(@RequestHeader("Authorization") String token, + @RequestBody TradeDto dto){ + Long userId = userAccountService.getUserIdFromToken(token); + if(userId == -1) return ApiResponse.onSuccess(Status.TOKEN_INVALID, null); + + String message = tradeService.buy(userId, dto); + if(!message.equals("true")) return ApiResponse.onFailure(Status.TRADE_BUY_FAILED, message); return ApiResponse.onSuccess(Status.TRADE_BUY_SUCCESS, null); } @Operation(summary = "주식 매도") @PostMapping("/sell") - public ApiResponse sellStock(@RequestBody TradeDto.sellRequestDto dto){ + public ApiResponse sellStock(@RequestHeader("Authorization") String token, + @RequestBody TradeDto dto){ + Long userId = userAccountService.getUserIdFromToken(token); + if(userId == -1) return ApiResponse.onSuccess(Status.TOKEN_INVALID, null); + + String message = tradeService.sell(userId, dto); + if(!message.equals("true")) return ApiResponse.onFailure(Status.TRADE_SELL_FAILED, message); return ApiResponse.onSuccess(Status.TRADE_SELL_SUCCESS, null); } diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/TradeDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/TradeDto.java index 5c18095..e8a4309 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/TradeDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/TradeDto.java @@ -4,21 +4,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; +@Getter +@AllArgsConstructor +@NoArgsConstructor public class TradeDto { - - @Getter - @AllArgsConstructor - @NoArgsConstructor - public static class buyRequestDto { - public String scode; - public int week; - } - - @Getter - @AllArgsConstructor - @NoArgsConstructor - public static class sellRequestDto{ - public String scode; - public int week; - } + public String scode; // 종목코드 + public String week; // 주문수량 } diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisAccountDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisAccountDto.java new file mode 100644 index 0000000..1ce01b3 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisAccountDto.java @@ -0,0 +1,17 @@ +package com.thered.stocksignal.app.dto.kis; + +import lombok.*; + +public class KisAccountDto { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class AccessTokenResponseDto { + private String access_token; + private String token_type; + private Integer expires_in; + private String access_token_token_expired; + } +} + diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java new file mode 100644 index 0000000..26bd804 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java @@ -0,0 +1,33 @@ +package com.thered.stocksignal.app.dto.kis; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.result.Output; + +import java.util.List; + +public class KisTradeDto { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class TradeResponseDto{ + + String rt_cd; + String msg_cd; + String msg1; + @JsonProperty("output") + List outputs; + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class Output { + String KRX_FWDG_ORD_ORGNO; + String ODNO; + String ORD_TMD; + } + } +} diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/Trade.java b/back/src/main/java/com/thered/stocksignal/domain/entity/Trade.java new file mode 100644 index 0000000..20fef92 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/Trade.java @@ -0,0 +1,40 @@ +package com.thered.stocksignal.domain.entity; + +import com.thered.stocksignal.domain.enums.TradeType; +import jakarta.persistence.*; +import lombok.*; + +import java.util.Date; + +@Entity +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "trade") +public class Trade { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "company_id") + private Company company; + + @Column(nullable = false) + private Date tradeDate; + + @Column(nullable = false) + private String tradeQuantity; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private TradeType tradeType; + + +} diff --git a/back/src/main/java/com/thered/stocksignal/domain/enums/TradeType.java b/back/src/main/java/com/thered/stocksignal/domain/enums/TradeType.java new file mode 100644 index 0000000..61ab18a --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/domain/enums/TradeType.java @@ -0,0 +1,5 @@ +package com.thered.stocksignal.domain.enums; + +public enum TradeType { + BUY, SELL; +} diff --git a/back/src/main/java/com/thered/stocksignal/repository/CompanyRepository.java b/back/src/main/java/com/thered/stocksignal/repository/CompanyRepository.java index f6a6fd2..cf2e3ea 100644 --- a/back/src/main/java/com/thered/stocksignal/repository/CompanyRepository.java +++ b/back/src/main/java/com/thered/stocksignal/repository/CompanyRepository.java @@ -9,4 +9,6 @@ public interface CompanyRepository extends JpaRepository { Optional findByCompanyName(String companyName); + Optional findByCompanyCode(String companyCode); + } diff --git a/back/src/main/java/com/thered/stocksignal/repository/TradeRepository.java b/back/src/main/java/com/thered/stocksignal/repository/TradeRepository.java new file mode 100644 index 0000000..9ef6b49 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/repository/TradeRepository.java @@ -0,0 +1,8 @@ +package com.thered.stocksignal.repository; + +import com.thered.stocksignal.domain.entity.Trade; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TradeRepository extends JpaRepository { + +} diff --git a/back/src/main/java/com/thered/stocksignal/service/trade/TradeService.java b/back/src/main/java/com/thered/stocksignal/service/trade/TradeService.java new file mode 100644 index 0000000..4043903 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/service/trade/TradeService.java @@ -0,0 +1,10 @@ +package com.thered.stocksignal.service.trade; + +import com.thered.stocksignal.app.dto.TradeDto; + +public interface TradeService { + String buy(Long userId, TradeDto dto); + + String sell(Long userId, TradeDto dto); + +} diff --git a/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java new file mode 100644 index 0000000..9885f90 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java @@ -0,0 +1,142 @@ +package com.thered.stocksignal.service.trade; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.thered.stocksignal.app.dto.TradeDto; +import com.thered.stocksignal.app.dto.kis.KisTradeDto; +import com.thered.stocksignal.domain.entity.Company; +import com.thered.stocksignal.domain.entity.Trade; +import com.thered.stocksignal.domain.entity.User; +import com.thered.stocksignal.domain.enums.TradeType; +import com.thered.stocksignal.repository.CompanyRepository; +import com.thered.stocksignal.repository.TradeRepository; +import com.thered.stocksignal.service.user.UserAccountService; +import com.thered.stocksignal.util.KisUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Transactional +public class TradeServiceImpl implements TradeService { + + private static final String baseUrl = "https://openapivts.koreainvestment.com:29443"; + + private final UserAccountService userAccountService; + private final TradeRepository tradeRepository; + private final CompanyRepository companyRepository; + + private HttpHeaders makeHeaders(Long userId, String tr_id){ + User user = userAccountService.findById(userId).get(); + String accessToken = user.getKisToken(); + String appKey = user.getAppKey(); + String secretKey = user.getSecretKey(); + + HttpHeaders header = new HttpHeaders(); // Http header + header.add("content-type", "application/json; charset=utf-8"); + header.add("authorization", "Bearer " +accessToken); + header.add("appkey", appKey); + header.add("appsecret", secretKey); + header.add("tr_id", tr_id); + + return header; + } + + private Map makeBody( + User user, String PDNO, String ORD_DVSN, String ORD_QTY, String ORD_UNPR) + { + String account = user.getAccountNumber(); + String CANO = KisUtil.getCANO(account); + String ACNT_PRDT_CD = KisUtil.getACNT_PRDT_CD(account); + + Map body = new HashMap<>(); + body.put("CANO", CANO); + body.put("ACNT_PRDT_CD", ACNT_PRDT_CD); + body.put("PDNO", PDNO); + body.put("ORD_DVSN", ORD_DVSN); + body.put("ORD_QTY", ORD_QTY); + body.put("ORD_UNPR", ORD_UNPR); + + return body; + } + + private String trade(Long userId, TradeDto dto, String tr_id) { + + userAccountService.refreshKisToken(userId); + + User user = userAccountService.findById(userId) + .orElseThrow(() -> new RuntimeException("존재하지 않는 회원입니다.")); + + HttpHeaders header = makeHeaders(userId, tr_id); + Map body = makeBody(user, dto.getScode(), "01", dto.getWeek(), "0"); + + ObjectMapper objectMapper = new ObjectMapper(); + String jsonBody; + try { + jsonBody = objectMapper.writeValueAsString(body); // JSON 직렬화 + } catch (JsonProcessingException e) { + throw new RuntimeException("JSON 변환 오류", e); + } + + HttpEntity request = new HttpEntity<>(jsonBody, header); + + RestTemplate tokenRt = new RestTemplate(); + + ResponseEntity response = tokenRt.exchange( + baseUrl+"/uapi/domestic-stock/v1/trading/order-cash", + HttpMethod.POST, + request, + String.class + ); // Request to Kis + + KisTradeDto.TradeResponseDto tradeResponseDto; + + String message = "true"; + try{ + tradeResponseDto = objectMapper.readValue(response.getBody(), KisTradeDto.TradeResponseDto.class); + if(!tradeResponseDto.getRt_cd().equals("0")) message = "원인 : " + tradeResponseDto.getMsg1(); + else{ + Company company = companyRepository.findByCompanyCode(dto.getScode()) + .orElseThrow(() -> new RuntimeException("존재하지 않는 회사 코드입니다.")); + + Trade newTrade = Trade.builder() + .user(user) + .tradeDate(new Date()) + .tradeQuantity(dto.getWeek()) + .company(company) + .tradeType(TradeType.BUY) + .build(); + + tradeRepository.save(newTrade); + } + }catch(JsonMappingException e){ + throw new RuntimeException("JSON 파싱을 실패했습니다.", e); + }catch (JsonProcessingException e){ + throw new RuntimeException("주문이 체결되지 않았습니다.", e); + } + return message; + } + + @Override + public String buy(Long userId, TradeDto dto) { + + return trade(userId, dto, "VTTC0802U"); + } + + @Override + public String sell(Long userId, TradeDto dto) { + + return trade(userId, dto, "VTTC0801U"); + } +} diff --git a/back/src/main/java/com/thered/stocksignal/service/user/UserAccountService.java b/back/src/main/java/com/thered/stocksignal/service/user/UserAccountService.java index dec28e5..fe8f4a3 100644 --- a/back/src/main/java/com/thered/stocksignal/service/user/UserAccountService.java +++ b/back/src/main/java/com/thered/stocksignal/service/user/UserAccountService.java @@ -20,4 +20,8 @@ public interface UserAccountService { Long getUserIdFromToken(String token); void connectKisAccount(Long userId, UserInfoDto.kisAccountRequestDto dto); + + void editKisAccessToken(Long userId, String accessToken, String accessTokenExpired); + + void refreshKisToken(Long userId); } diff --git a/back/src/main/java/com/thered/stocksignal/service/user/UserAccountServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/user/UserAccountServiceImpl.java index 75cc5b5..ac50949 100644 --- a/back/src/main/java/com/thered/stocksignal/service/user/UserAccountServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/user/UserAccountServiceImpl.java @@ -1,14 +1,26 @@ package com.thered.stocksignal.service.user; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.thered.stocksignal.app.dto.kis.KisAccountDto; import com.thered.stocksignal.app.dto.user.UserInfoDto; import com.thered.stocksignal.domain.entity.User; import com.thered.stocksignal.domain.enums.OauthType; import com.thered.stocksignal.jwt.JWTUtil; import com.thered.stocksignal.repository.UserRepository; +import com.thered.stocksignal.util.DateUtil; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; @Service @@ -18,6 +30,7 @@ public class UserAccountServiceImpl implements UserAccountService{ private final UserRepository userRepository; private final JWTUtil jwtUtil; + private static final String baseUrl = "https://openapivts.koreainvestment.com:29443"; @Override public Optional saveKakaoUser(String email) { @@ -74,8 +87,68 @@ public void connectKisAccount(Long userId, UserInfoDto.kisAccountRequestDto dto) if(user == null) throw new IllegalArgumentException("존재하지 않는 userId 입니다 : " + userId); User updateUser = user.get(); - updateUser.setKisAccount(dto.getSecretKey(), dto.getAppKey(), true); + updateUser.setKisAccount(dto.getSecretKey(), dto.getAppKey(), dto.getAccount(), true); userRepository.save(updateUser); } + + @Override + public void editKisAccessToken(Long userId, String accessToken, String accessTokenExpired) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 userId 입니다 : " + userId)); + user.setAccessToken(accessToken, accessTokenExpired); + userRepository.save(user); + } + + @Override + public void refreshKisToken(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 userId 입니다 : " + userId)); + + if(user.getIsKisLinked() == false) throw new RuntimeException("한국투자증권 연동이 되어 있지 않습니다."); + + Date expirationDate; + if (user.getKisTokenExpired() != null) expirationDate = DateUtil.parseDate(user.getKisTokenExpired()); + else expirationDate = new Date(0); + Date now = new Date(); + + // 유효기간이 만료되지 않은 경우 + if(!expirationDate.before(now)) return; + + // 유효기간이 만료된 경우 + RestTemplate tokenRt = new RestTemplate(); + Map params = new HashMap<>(); + params.put("grant_type", "client_credentials"); + params.put("appkey", user.getAppKey()); + params.put("appsecret", user.getSecretKey()); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonParams; + try { + jsonParams = objectMapper.writeValueAsString(params); // JSON으로 변환 + } catch (JsonProcessingException e) { + throw new RuntimeException("JSON 변환 오류", e); + } + HttpEntity request = new HttpEntity<>(jsonParams); + + ResponseEntity response = tokenRt.exchange( + baseUrl+"/oauth2/tokenP", + HttpMethod.POST, + request, + String.class + ); // Request to Kis + + KisAccountDto.AccessTokenResponseDto tokenResponseDto; + + try{ + tokenResponseDto = objectMapper.readValue(response.getBody(), KisAccountDto.AccessTokenResponseDto.class); + String newAccessToken = tokenResponseDto.getAccess_token(); + String newTokenExpired = tokenResponseDto.getAccess_token_token_expired(); + editKisAccessToken(user.getId(), newAccessToken, newTokenExpired); + }catch(JsonMappingException e){ + e.printStackTrace(); + }catch (JsonProcessingException e){ + e.printStackTrace(); + } + } + } diff --git a/back/src/main/java/com/thered/stocksignal/util/KisUtil.java b/back/src/main/java/com/thered/stocksignal/util/KisUtil.java new file mode 100644 index 0000000..adb0b7b --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/util/KisUtil.java @@ -0,0 +1,17 @@ +package com.thered.stocksignal.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class KisUtil { + + public static String getCANO(String account) { + return account.substring(0, 8); + } + + public static String getACNT_PRDT_CD(String account) { + return account.substring(9, 11); + } +} From d5ee1695109a77a1a218cedda34e46089ea0033a Mon Sep 17 00:00:00 2001 From: smyoo Date: Fri, 18 Oct 2024 14:31:59 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat=20:=20=EB=A7=A4=EC=88=98/=EB=A7=A4?= =?UTF-8?q?=EB=8F=84=20JSON=20=ED=8C=8C=EC=8B=B1=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stocksignal/app/dto/kis/KisTradeDto.java | 11 ++++++++--- .../service/trade/TradeServiceImpl.java | 16 ++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java index 26bd804..fa4509b 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/kis/KisTradeDto.java @@ -6,8 +6,6 @@ import lombok.NoArgsConstructor; import org.hibernate.result.Output; -import java.util.List; - public class KisTradeDto { @Getter @@ -15,19 +13,26 @@ public class KisTradeDto { @NoArgsConstructor public static class TradeResponseDto{ + @JsonProperty("rt_cd") String rt_cd; + @JsonProperty("msg_cd") String msg_cd; + @JsonProperty("msg1") String msg1; @JsonProperty("output") - List outputs; + Output output; @Getter @AllArgsConstructor @NoArgsConstructor public static class Output { + @JsonProperty("KRX_FWDG_ORD_ORGNO") String KRX_FWDG_ORD_ORGNO; + @JsonProperty("ODNO") String ODNO; + @JsonProperty("ORD_TMD") String ORD_TMD; } + } } diff --git a/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java index 9885f90..6490b35 100644 --- a/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/trade/TradeServiceImpl.java @@ -44,11 +44,11 @@ private HttpHeaders makeHeaders(Long userId, String tr_id){ String secretKey = user.getSecretKey(); HttpHeaders header = new HttpHeaders(); // Http header - header.add("content-type", "application/json; charset=utf-8"); - header.add("authorization", "Bearer " +accessToken); - header.add("appkey", appKey); - header.add("appsecret", secretKey); - header.add("tr_id", tr_id); + header.set("content-type", "application/json; charset=utf-8"); + header.set("authorization", "Bearer " +accessToken); + header.set("appkey", appKey); + header.set("appsecret", secretKey); + header.set("tr_id", tr_id); return header; } @@ -110,12 +110,16 @@ private String trade(Long userId, TradeDto dto, String tr_id) { Company company = companyRepository.findByCompanyCode(dto.getScode()) .orElseThrow(() -> new RuntimeException("존재하지 않는 회사 코드입니다.")); + TradeType type; + if(tr_id.equals("VTTC0802U")) type = TradeType.BUY; + else type = TradeType.SELL; + Trade newTrade = Trade.builder() .user(user) .tradeDate(new Date()) .tradeQuantity(dto.getWeek()) .company(company) - .tradeType(TradeType.BUY) + .tradeType(type) .build(); tradeRepository.save(newTrade); From a92f2e706ec182d3fd1142409d0d894136e871a9 Mon Sep 17 00:00:00 2001 From: price126 Date: Fri, 18 Oct 2024 23:52:32 +0900 Subject: [PATCH 07/17] =?UTF-8?q?[FEAT]=20=EC=8B=9C=EB=82=98=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/ScenarioController.java | 15 ++++++++ .../stocksignal/app/dto/ScenarioDto.java | 38 +++++++++++++++++++ .../stocksignal/domain/enums/BuysellType.java | 5 +++ .../stocksignal/domain/enums/MethodType.java | 5 +++ .../stocksignal/domain/enums/OauthType.java | 2 +- .../service/scenario/ScenarioService.java | 4 ++ .../service/scenario/ScenarioServiceImpl.java | 2 + 7 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java create mode 100644 back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java create mode 100644 back/src/main/java/com/thered/stocksignal/domain/enums/BuysellType.java create mode 100644 back/src/main/java/com/thered/stocksignal/domain/enums/MethodType.java create mode 100644 back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java create mode 100644 back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java new file mode 100644 index 0000000..abfeb07 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java @@ -0,0 +1,15 @@ +package com.thered.stocksignal.app.controller; + +import com.thered.stocksignal.service.scenario.ScenarioService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class ScenarioController { + + private final ScenarioService scenarioService; + + // +} diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java new file mode 100644 index 0000000..226f324 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java @@ -0,0 +1,38 @@ +package com.thered.stocksignal.app.dto; + +import com.thered.stocksignal.domain.enums.BuysellType; +import com.thered.stocksignal.domain.enums.MethodType; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +public class ScenarioDto { + + @Getter + @Setter + @Builder + public static class ScenarioResponseDto { + private Long scenarioId; + private String scenarioName; + private String companyName; + private Long initialPrice; + private Long currentPrice; + } + + @Getter + @Setter + @Builder + // 현재가 정보 + public static class ConditionResponseDto { + private Long conditionId; + private BuysellType buysellType; + private MethodType methodType; + private Long initialPrice; + private Long currentPrice; + private Long targetPrice1; + private Long targetPrice2; + private Long targetPrice3; + private Long targetPrice4; + private Long quantity; + } +} diff --git a/back/src/main/java/com/thered/stocksignal/domain/enums/BuysellType.java b/back/src/main/java/com/thered/stocksignal/domain/enums/BuysellType.java new file mode 100644 index 0000000..1a474a3 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/domain/enums/BuysellType.java @@ -0,0 +1,5 @@ +package com.thered.stocksignal.domain.enums; + +public enum BuysellType { + BUY, SELL; +} diff --git a/back/src/main/java/com/thered/stocksignal/domain/enums/MethodType.java b/back/src/main/java/com/thered/stocksignal/domain/enums/MethodType.java new file mode 100644 index 0000000..7ddc1a4 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/domain/enums/MethodType.java @@ -0,0 +1,5 @@ +package com.thered.stocksignal.domain.enums; + +public enum MethodType { + RATE, PRICE, TRADING; +} diff --git a/back/src/main/java/com/thered/stocksignal/domain/enums/OauthType.java b/back/src/main/java/com/thered/stocksignal/domain/enums/OauthType.java index 4982be8..99a7994 100644 --- a/back/src/main/java/com/thered/stocksignal/domain/enums/OauthType.java +++ b/back/src/main/java/com/thered/stocksignal/domain/enums/OauthType.java @@ -2,4 +2,4 @@ public enum OauthType { KAKAO, NAVER; -} +} \ No newline at end of file diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java new file mode 100644 index 0000000..6199f3f --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java @@ -0,0 +1,4 @@ +package com.thered.stocksignal.service.scenario; + +public interface ScenarioService { +} diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java new file mode 100644 index 0000000..bacec64 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java @@ -0,0 +1,2 @@ +package com.thered.stocksignal.service.scenario;public class ScenarioServiceImpl { +} From 7ac751b08a80af5e2261387116c03a9082b2d69a Mon Sep 17 00:00:00 2001 From: price126 Date: Sat, 19 Oct 2024 23:58:01 +0900 Subject: [PATCH 08/17] =?UTF-8?q?[FEAT]=20=EB=82=B4=EC=9E=94=EA=B3=A0=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EB=A1=9C=EA=B3=A0=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=95=A8=EA=BB=98=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/thered/stocksignal/app/dto/MyBalanceDto.java | 1 + .../service/myBalance/MyBalanceServiceImpl.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/MyBalanceDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/MyBalanceDto.java index 852e52d..61aab58 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/MyBalanceDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/MyBalanceDto.java @@ -14,6 +14,7 @@ public class MyBalanceDto { @Builder public static class StockResponseDto { private String stockName; // 종목명 + private String logoImage; // 로고 이미지 경로 private Long quantity; // 보유 수량 private Long avgPrice; // 매입 평균가 private Long currentPrice; // 현재가 diff --git a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java index f317b27..1bfaab3 100644 --- a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java @@ -1,11 +1,13 @@ package com.thered.stocksignal.service.myBalance; import com.thered.stocksignal.kisApi.KisApiRequest; +import com.thered.stocksignal.service.company.CompanyService; import lombok.RequiredArgsConstructor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.springframework.stereotype.Service; +import com.thered.stocksignal.app.dto.CompanyDto.CompanyLogoResponseDto; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -24,6 +26,7 @@ public class MyBalanceServiceImpl implements MyBalanceService{ private final KisApiRequest apiRequest; private final OkHttpClient client; private final ObjectMapper objectMapper; + private final CompanyService companyService; // 내 잔고 조회 public Optional getMyBalance(String accountNumber, String accessToken, String appKey, String appSecret) { @@ -70,11 +73,18 @@ public Optional getMyBalance(String accountNumber, String StockResponseDto stock = StockResponseDto.builder().build(); - stock.setStockName(stockNode.path("prdt_name").asText()); // 종목명 + String companyName = stockNode.path("prdt_name").asText(); + stock.setStockName(companyName); // 종목명 stock.setQuantity(stockNode.path("hldg_qty").asLong()); // 수량 stock.setAvgPrice(stockNode.path("pchs_avg_pric").asLong()); // 매입 평균가 stock.setCurrentPrice(stockNode.path("prpr").asLong()); // 현재가 stock.setPL(stockNode.path("evlu_pfls_amt").asLong()); // 손익 + String logoImage = companyService.findLogoByName(companyName) + .map(CompanyLogoResponseDto::getLogoImage) + .orElse(null); // TODO: null 대신 디폴트이미지 경로 + + stock.setLogoImage(logoImage); + // 로고 이미지 stocks.add(stock); // 해당 주식을 리스트에 추가 } From 15700487214f6eaa5cf04cc949a13421cc23f3e6 Mon Sep 17 00:00:00 2001 From: price126 Date: Sun, 20 Oct 2024 23:58:37 +0900 Subject: [PATCH 09/17] =?UTF-8?q?[UPDATE]=20refreshKisToken=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/CompanyController.java | 18 ++------ .../app/controller/MyBalanceController.java | 8 +--- .../service/company/CompanyService.java | 6 +-- .../service/company/CompanyServiceImpl.java | 44 ++++++++++++++----- .../service/myBalance/MyBalanceService.java | 2 +- .../myBalance/MyBalanceServiceImpl.java | 18 +++++--- 6 files changed, 53 insertions(+), 43 deletions(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java index 6f2f1f5..bdcd579 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java @@ -55,14 +55,9 @@ public ApiResponse getCompanyInfo( Long userId = userAccountService.getUserIdFromToken(token); if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); - Optional user = userAccountService.findById(userId); - if (user.isEmpty()) return ApiResponse.onFailure(Status.USER_NOT_FOUND); - Optional responseDto = companyService.findCompanyInfoByCode( companyCode, - user.get().getKisToken(), - user.get().getAppKey(), - user.get().getSecretKey() + userId ); return responseDto @@ -80,14 +75,9 @@ public ApiResponse getCurrentPrice( Long userId = userAccountService.getUserIdFromToken(token); if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); - Optional user = userAccountService.findById(userId); - if (user.isEmpty()) return ApiResponse.onFailure(Status.USER_NOT_FOUND); - Optional responseDto = companyService.findCurrentPriceByCode( companyCode, - user.get().getKisToken(), - user.get().getAppKey(), - user.get().getSecretKey() + userId ); return responseDto @@ -113,9 +103,7 @@ public ApiResponse getPeriodPrice( companyCode, startDate, endDate, - user.get().getKisToken(), - user.get().getAppKey(), - user.get().getSecretKey() + userId ); return responseDto diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java b/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java index 2c0771a..5c5f785 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java @@ -32,14 +32,8 @@ public ApiResponse getMyBalance( Long userId = userAccountService.getUserIdFromToken(token); if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); - Optional user = userAccountService.findById(userId); - if (user.isEmpty()) return ApiResponse.onFailure(Status.USER_NOT_FOUND); - Optional responseDto = myBalanceService.getMyBalance( - user.get().getAccountNumber(), - user.get().getKisToken(), - user.get().getAppKey(), - user.get().getSecretKey() + userId ); return responseDto.map(dto -> ApiResponse.onSuccess(Status.MY_BALANCE_SUCCESS, dto)) diff --git a/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java b/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java index 4fe03c0..16b2507 100644 --- a/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java +++ b/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java @@ -17,13 +17,13 @@ public interface CompanyService { Optional findLogoByName(String companyName); // 종목 코드 -> 회사 정보 (분석 탭에 들어갈 내용) - Optional findCompanyInfoByCode(String companyCode, String accessToken, String appKey, String appSecret); + Optional findCompanyInfoByCode(String companyCode, Long userId); // 종목 코드 -> 현재가 조회 - Optional findCurrentPriceByCode(String companyCode, String accessToken, String appKey, String appSecret); + Optional findCurrentPriceByCode(String companyCode, Long userId); // 종목 코드 -> 일봉 조회 - Optional findPeriodPriceByCode(String companyCode, String startDate, String endDate, String accessToken, String appKey, String appSecret); + Optional findPeriodPriceByCode(String companyCode, String startDate, String endDate, Long userId); // 인기 종목 10개 조회 Optional> getPopularStocks(); diff --git a/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java index 93332d9..9a040a3 100644 --- a/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java @@ -2,10 +2,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.thered.stocksignal.apiPayload.ApiResponse; +import com.thered.stocksignal.apiPayload.Status; import com.thered.stocksignal.app.dto.StockDto.popularStockResponseDto; import com.thered.stocksignal.domain.entity.Company; +import com.thered.stocksignal.domain.entity.User; import com.thered.stocksignal.kisApi.KisApiRequest; import com.thered.stocksignal.repository.CompanyRepository; +import com.thered.stocksignal.service.user.UserAccountService; import lombok.RequiredArgsConstructor; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -32,6 +36,7 @@ public class CompanyServiceImpl implements CompanyService { private final KisApiRequest apiRequest; private final OkHttpClient client; private final ObjectMapper objectMapper; + private final UserAccountService userAccountService; @Override public Optional findCodeByName(String companyName) { @@ -55,7 +60,12 @@ public Optional findLogoByName(String companyName) { } @Override - public Optional findCompanyInfoByCode(String companyCode, String accessToken, String appKey, String appSecret) { + public Optional findCompanyInfoByCode(String companyCode, Long userId) { + + userAccountService.refreshKisToken(userId); + + Optional user = userAccountService.findById(userId); + if (user.isEmpty()) return Optional.empty(); //USER_NOT_FOUND // API url String endpoint = "/uapi/domestic-stock/v1/quotations/inquire-price"; @@ -69,9 +79,9 @@ public Optional findCompanyInfoByCode(String companyCode // 요청 헤더 생성 Request request = new Request.Builder() .url(url) - .addHeader("authorization", "Bearer " + accessToken) - .addHeader("appkey", appKey) - .addHeader("appsecret", appSecret) + .addHeader("authorization", "Bearer " + user.get().getKisToken()) + .addHeader("appkey", user.get().getAppKey()) + .addHeader("appsecret", user.get().getSecretKey()) .addHeader("tr_id", "FHKST01010100") .build(); @@ -101,7 +111,12 @@ public Optional findCompanyInfoByCode(String companyCode } @Override - public Optional findCurrentPriceByCode(String companyCode, String accessToken, String appKey, String appSecret){ + public Optional findCurrentPriceByCode(String companyCode, Long userId){ + + userAccountService.refreshKisToken(userId); + + Optional user = userAccountService.findById(userId); + if (user.isEmpty()) return Optional.empty(); //USER_NOT_FOUND // API url String endpoint = "/uapi/domestic-stock/v1/quotations/inquire-price"; @@ -115,9 +130,9 @@ public Optional findCurrentPriceByCode(String companyCo // 요청 헤더 생성 Request request = new Request.Builder() .url(url) - .addHeader("authorization", "Bearer " + accessToken) - .addHeader("appkey", appKey) - .addHeader("appsecret", appSecret) + .addHeader("authorization", "Bearer " + user.get().getKisToken()) + .addHeader("appkey", user.get().getAppKey()) + .addHeader("appsecret", user.get().getSecretKey()) .addHeader("tr_id", "FHKST01010100") .build(); @@ -140,7 +155,12 @@ public Optional findCurrentPriceByCode(String companyCo } @Override - public Optional findPeriodPriceByCode(String companyCode, String startDate, String endDate, String accessToken, String appKey, String appSecret){ + public Optional findPeriodPriceByCode(String companyCode, String startDate, String endDate, Long userId){ + + userAccountService.refreshKisToken(userId); + + Optional user = userAccountService.findById(userId); + if (user.isEmpty()) return Optional.empty(); //USER_NOT_FOUND // API url String endpoint = "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice"; @@ -159,9 +179,9 @@ public Optional findPeriodPriceByCode(String companyCode Request request = new Request.Builder() .url(url) .addHeader("content-type", "application/json; charset=utf-8") - .addHeader("authorization", "Bearer " + accessToken) - .addHeader("appkey", appKey) - .addHeader("appsecret", appSecret) + .addHeader("authorization", "Bearer " + user.get().getKisToken()) + .addHeader("appkey", user.get().getAppKey()) + .addHeader("appsecret", user.get().getSecretKey()) .addHeader("tr_id", "FHKST03010100") .build(); diff --git a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceService.java b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceService.java index adbf92f..206b3e7 100644 --- a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceService.java +++ b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceService.java @@ -7,6 +7,6 @@ public interface MyBalanceService { // 내 잔고 조회 - Optional getMyBalance(String accountNumber, String accessToken, String appKey, String appSecret); + Optional getMyBalance(Long userId); } diff --git a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java index 1bfaab3..4c6673c 100644 --- a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java @@ -1,7 +1,9 @@ package com.thered.stocksignal.service.myBalance; +import com.thered.stocksignal.domain.entity.User; import com.thered.stocksignal.kisApi.KisApiRequest; import com.thered.stocksignal.service.company.CompanyService; +import com.thered.stocksignal.service.user.UserAccountService; import lombok.RequiredArgsConstructor; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -27,16 +29,22 @@ public class MyBalanceServiceImpl implements MyBalanceService{ private final OkHttpClient client; private final ObjectMapper objectMapper; private final CompanyService companyService; + private final UserAccountService userAccountService; // 내 잔고 조회 - public Optional getMyBalance(String accountNumber, String accessToken, String appKey, String appSecret) { + public Optional getMyBalance(Long userId) { + + userAccountService.refreshKisToken(userId); + + Optional user = userAccountService.findById(userId); + if (user.isEmpty()) return Optional.empty(); //USER_NOT_FOUND // API url String endpoint = "/uapi/domestic-stock/v1/trading/inquire-balance"; // API 쿼리 파라미터 String url = apiRequest.buildUrl(endpoint, - "CANO=" + accountNumber, + "CANO=" + user.get().getAccountNumber(), "ACNT_PRDT_CD=01", "AFHR_FLPR_YN=N", "INQR_DVSN=02", @@ -53,9 +61,9 @@ public Optional getMyBalance(String accountNumber, String Request request = new Request.Builder() .url(url) .addHeader("content-type", "application/json") - .addHeader("authorization", "Bearer " + accessToken) - .addHeader("appkey", appKey) - .addHeader("appsecret", appSecret) + .addHeader("authorization", "Bearer " + user.get().getKisToken()) + .addHeader("appkey", user.get().getAppKey()) + .addHeader("appsecret", user.get().getSecretKey()) .addHeader("tr_id", "VTTC8434R") .build(); From fc84be87aa6883afd4e7da6847abc87ae93152e0 Mon Sep 17 00:00:00 2001 From: price126 Date: Wed, 23 Oct 2024 18:47:13 +0900 Subject: [PATCH 10/17] =?UTF-8?q?[FEAT]=20=EC=8B=9C=EB=82=98=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thered/stocksignal/apiPayload/Status.java | 2 + .../app/controller/ScenarioController.java | 26 +++- .../stocksignal/app/dto/ScenarioDto.java | 1 - .../stocksignal/domain/entity/Scenario.java | 31 +++++ .../domain/entity/ScenarioCondition.java | 47 +++++++ .../repository/ScenarioRepository.java | 12 ++ .../service/scenario/ScenarioService.java | 6 + .../service/scenario/ScenarioServiceImpl.java | 116 +++++++++++++++++- 8 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 back/src/main/java/com/thered/stocksignal/domain/entity/Scenario.java create mode 100644 back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java create mode 100644 back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java diff --git a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java index d8e655f..ee03460 100644 --- a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java +++ b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java @@ -62,6 +62,8 @@ public enum Status { MYSTOCK_SUCCESS("200", "SUCCESS", "나의 전체 주식현황을 읽는데 성공했습니다."), MYSTOCK_SHORT_SUCCESS("200", "SUCCESS", "나의 주식현황 요약을 읽는데 성공했습니다."), + SCENARIO_FOUND("200", "SUCCESS", "시나리오 조회에 성공했습니다."), + //실패 USER_NOT_FOUND("404", "FAILURE", "사용자를 찾을 수 없습니다"), diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java index abfeb07..45f75cb 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java @@ -1,15 +1,35 @@ package com.thered.stocksignal.app.controller; +import com.thered.stocksignal.apiPayload.ApiResponse; +import com.thered.stocksignal.apiPayload.Status; +import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; import com.thered.stocksignal.service.scenario.ScenarioService; +import com.thered.stocksignal.service.user.UserAccountService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequiredArgsConstructor +@RequestMapping("/api/scenario") public class ScenarioController { private final ScenarioService scenarioService; + private final UserAccountService userAccountService; + + @GetMapping() + @Operation(summary = "시나리오 조회", description = "시나리오를 조회합니다.") + public ApiResponse> getScenario(@RequestHeader("Authorization") String token) { + + Long userId = userAccountService.getUserIdFromToken(token); + if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); + + List responseDto = scenarioService.getScenario( + userId + ); - // + return ApiResponse.onSuccess(Status.SCENARIO_FOUND, responseDto); + } } diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java index 226f324..a04227f 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java @@ -22,7 +22,6 @@ public static class ScenarioResponseDto { @Getter @Setter @Builder - // 현재가 정보 public static class ConditionResponseDto { private Long conditionId; private BuysellType buysellType; diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/Scenario.java b/back/src/main/java/com/thered/stocksignal/domain/entity/Scenario.java new file mode 100644 index 0000000..fb3b28c --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/Scenario.java @@ -0,0 +1,31 @@ +package com.thered.stocksignal.domain.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "scenario") +public class Scenario { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "company_id", nullable = false) + private Company company; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "scenario_name", nullable = false) + private String scenarioName; + + @Column(name = "initial_price", nullable = false) + private Long initialPrice; +} diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java new file mode 100644 index 0000000..22a1e3b --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java @@ -0,0 +1,47 @@ +package com.thered.stocksignal.domain.entity; + +import com.thered.stocksignal.domain.enums.BuysellType; +import com.thered.stocksignal.domain.enums.MethodType; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "scenario_condition") +public class ScenarioCondition { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "scenario_id", nullable = false) + private Scenario scenario; + + @Enumerated(EnumType.STRING) + @Column(name = "buysell_type", nullable = false) + private BuysellType buysell_type; + + @Enumerated(EnumType.STRING) + @Column(name = "method_type", nullable = false) + private MethodType method_type; + + @Column(name = "target_price1") + private Long targetPrice1; + + @Column(name = "target_price2") + private Long targetPrice2; + + @Column(name = "target_price3") + private Long targetPrice3; + + @Column(name = "target_price4") + private Long targetPrice4; + + @Column(name = "quantity", nullable = false) + private Long quantity; + +} diff --git a/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java b/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java new file mode 100644 index 0000000..5d92585 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java @@ -0,0 +1,12 @@ +package com.thered.stocksignal.repository; + +import com.thered.stocksignal.domain.entity.Scenario; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface ScenarioRepository extends JpaRepository { + + List findByUserId(Long userId); +} diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java index 6199f3f..eb1fc52 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java @@ -1,4 +1,10 @@ package com.thered.stocksignal.service.scenario; +import com.thered.stocksignal.app.dto.ScenarioDto; + +import java.util.List; + + public interface ScenarioService { + List getScenario(Long userId); } diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java index bacec64..2c1cd03 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java @@ -1,2 +1,116 @@ -package com.thered.stocksignal.service.scenario;public class ScenarioServiceImpl { +package com.thered.stocksignal.service.scenario; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.thered.stocksignal.app.dto.CompanyDto; +import com.thered.stocksignal.app.dto.kakao.KakaoLoginDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; +import com.thered.stocksignal.domain.entity.Scenario; +import com.thered.stocksignal.repository.ScenarioRepository; +import com.thered.stocksignal.service.company.CompanyService; +import com.thered.stocksignal.app.dto.StockDto.CurrentPriceResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.simp.stomp.StompSession; +import org.springframework.messaging.simp.stomp.StompSessionHandler; +import org.springframework.messaging.simp.stomp.StompHeaders; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.messaging.WebSocketStompClient; + +import jakarta.annotation.PostConstruct; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional +public class ScenarioServiceImpl implements ScenarioService { + + private final ScenarioRepository scenarioRepository; + private final CompanyService companyService; + +// private String websocketUrl = "ws://ops.koreainvestment.com:31000"; +// private StompSession stompSession; +// +// @PostConstruct +// public void connect() { +// WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient()); +// stompClient.setMessageConverter(new MappingJackson2MessageConverter()); +// +// stompClient.connect(websocketUrl, new StompSessionHandler() { +// @Override +// public void afterConnected(StompSession session, StompHeaders connectedHeaders) { +// stompSession = session; +// subscribeToStock("005930"); // 삼성전자 +// } +// +// @Override +// public void handleFrame(StompHeaders headers, Object payload) { +// // 메시지 처리 +// System.out.println("Received message: " + payload); +// } +// +// @Override +// public Type getPayloadType(StompHeaders headers) { +// return String.class; +// } +// +// @Override +// public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) { +// System.err.println("Error: " + exception.getMessage()); +// } +// +// @Override +// public void handleTransportError(StompSession session, Throwable error) { +// System.err.println("Transport error: " + error.getMessage()); +// } +// }); +// } +// +// private void subscribeToStock(String stockCode) { +// String subscribeMessage = createSubscribeMessage(stockCode); +// stompSession.send("/topic/subscribe", subscribeMessage); +// } +// +// private String createSubscribeMessage(String stockCode) { +// return String.format("{\"header\": {\"approval_key\": \"발급받은_접속키\", \"custtype\": \"P\", \"tr_type\": \"1\", \"content-type\": \"utf-8\"}, \"body\": {\"input\": {\"tr_id\": \"H0STCNT0\", \"tr_key\": \"%s\"}}}", stockCode); +// } + + public List getScenario(Long userId) { + List scenarios = scenarioRepository.findByUserId(userId); + + List scenarioList = new ArrayList<>(); + + for (Scenario scenario : scenarios) { + Long currentPrice = companyService.findCurrentPriceByCode(scenario.getCompany().getCompanyCode(), userId) + .map(CurrentPriceResponseDto::getCurrentPrice) + .orElse(null); // current price 정보를 찾을 수 없음 + + ScenarioResponseDto responseDto = ScenarioResponseDto.builder() + .scenarioId(scenario.getId()) + .scenarioName(scenario.getScenarioName()) + .companyName(scenario.getCompany().getCompanyName()) + .initialPrice(scenario.getInitialPrice()) + .currentPrice(currentPrice) + .build(); + + scenarioList.add(responseDto); + } + + return scenarioList; + } + } From c5bb3a3f54d66b9e658955002286c5e02339eda6 Mon Sep 17 00:00:00 2001 From: price126 Date: Wed, 23 Oct 2024 20:15:49 +0900 Subject: [PATCH 11/17] =?UTF-8?q?[FEAT]=20=EC=8B=9C=EB=82=98=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20=EC=83=9D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thered/stocksignal/apiPayload/Status.java | 7 ++- .../app/controller/CompanyController.java | 2 +- .../app/controller/ScenarioController.java | 18 +++++++ .../stocksignal/app/dto/ScenarioDto.java | 27 +++++++++-- .../domain/entity/ScenarioCondition.java | 4 +- .../ScenarioConditionRepository.java | 7 +++ .../repository/ScenarioRepository.java | 1 - .../service/company/CompanyService.java | 1 + .../myBalance/MyBalanceServiceImpl.java | 12 ++--- .../service/scenario/ScenarioService.java | 7 ++- .../service/scenario/ScenarioServiceImpl.java | 48 +++++++++++++++++++ 11 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java diff --git a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java index ee03460..49eea28 100644 --- a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java +++ b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java @@ -63,16 +63,19 @@ public enum Status { MYSTOCK_SHORT_SUCCESS("200", "SUCCESS", "나의 주식현황 요약을 읽는데 성공했습니다."), SCENARIO_FOUND("200", "SUCCESS", "시나리오 조회에 성공했습니다."), + SCENARIO_CREATED("200", "SUCCESS", "시나리오 생성에 성공했습니다."), //실패 USER_NOT_FOUND("404", "FAILURE", "사용자를 찾을 수 없습니다"), COMPANY_NOT_FOUND("404", "FAILURE", "회사 정보를 찾을 수 없습니다"), - COMPANY_RANKING_FAILURE("400", "FAILURE", "인기 종목 조회에 실패했습니다."), + COMPANY_RANKING_FAILED("400", "FAILURE", "인기 종목 조회에 실패했습니다."), MY_BALANCE_NOT_FOUND("404", "FAILURE", "잔고 정보를 찾을 수 없습니다"), - NEWS_NOT_FOUND("404", "FAILURE", "뉴스 정보를 찾을 수 없습니다"); + NEWS_NOT_FOUND("404", "FAILURE", "뉴스 정보를 찾을 수 없습니다"), + + SCENARIO_CREATION_FAILED("400", "FAILURE", "시나리오 생성에 실패하였습니다."); private final String code; private final String result; diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java index bdcd579..fe5235d 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java @@ -119,6 +119,6 @@ public ApiResponse> getPopularStocks() { return responseDto .map(dto -> ApiResponse.onSuccess(Status.COMPANY_RANKING_SUCCESS, dto)) - .orElseGet(() -> ApiResponse.onFailure(Status.COMPANY_RANKING_FAILURE)); + .orElseGet(() -> ApiResponse.onFailure(Status.COMPANY_RANKING_FAILED)); } } diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java index 45f75cb..2d36d08 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java @@ -2,6 +2,8 @@ import com.thered.stocksignal.apiPayload.ApiResponse; import com.thered.stocksignal.apiPayload.Status; +import com.thered.stocksignal.app.dto.ScenarioDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; import com.thered.stocksignal.service.scenario.ScenarioService; import com.thered.stocksignal.service.user.UserAccountService; @@ -32,4 +34,20 @@ public ApiResponse> getScenario(@RequestHeader("Author return ApiResponse.onSuccess(Status.SCENARIO_FOUND, responseDto); } + + @PostMapping("/create") + @Operation(summary = "시나리오 생성", description = "새로운 시나리오를 생성합니다.") + public ApiResponse createScenario( + @RequestHeader("Authorization") String token, + @RequestBody ScenarioRequestDto newScenario) { + + Long userId = userAccountService.getUserIdFromToken(token); + if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); + + boolean responseDto = scenarioService.createScenario(userId, newScenario); + if(!responseDto){ + return ApiResponse.onFailure(Status.SCENARIO_CREATION_FAILED, null); + } + return ApiResponse.onSuccess(Status.SCENARIO_CREATED, null); + } } diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java index a04227f..b597aa2 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java @@ -2,15 +2,34 @@ import com.thered.stocksignal.domain.enums.BuysellType; import com.thered.stocksignal.domain.enums.MethodType; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; public class ScenarioDto { @Getter @Setter @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ScenarioRequestDto { + private String scenarioName; + private String companyName; + private BuysellType buysellType; + private MethodType methodType; + private Long initialPrice; + + private Long targetPrice1; + private Long targetPrice2; + private Long targetPrice3; + private Long targetPrice4; + private Long quantity; + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor public static class ScenarioResponseDto { private Long scenarioId; private String scenarioName; @@ -22,6 +41,8 @@ public static class ScenarioResponseDto { @Getter @Setter @Builder + @NoArgsConstructor + @AllArgsConstructor public static class ConditionResponseDto { private Long conditionId; private BuysellType buysellType; diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java index 22a1e3b..9f2da3a 100644 --- a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java @@ -23,11 +23,11 @@ public class ScenarioCondition { @Enumerated(EnumType.STRING) @Column(name = "buysell_type", nullable = false) - private BuysellType buysell_type; + private BuysellType buysellType; @Enumerated(EnumType.STRING) @Column(name = "method_type", nullable = false) - private MethodType method_type; + private MethodType methodType; @Column(name = "target_price1") private Long targetPrice1; diff --git a/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java b/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java new file mode 100644 index 0000000..1f54a96 --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java @@ -0,0 +1,7 @@ +package com.thered.stocksignal.repository; + +import com.thered.stocksignal.domain.entity.ScenarioCondition; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScenarioConditionRepository extends JpaRepository { +} diff --git a/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java b/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java index 5d92585..ad9b636 100644 --- a/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java +++ b/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java @@ -4,7 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -import java.util.Optional; public interface ScenarioRepository extends JpaRepository { diff --git a/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java b/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java index 16b2507..25b57fa 100644 --- a/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java +++ b/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java @@ -1,6 +1,7 @@ package com.thered.stocksignal.service.company; import com.thered.stocksignal.app.dto.StockDto.popularStockResponseDto; +import com.thered.stocksignal.domain.entity.Company; import java.util.List; import java.util.Optional; diff --git a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java index 4c6673c..bc1a125 100644 --- a/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/myBalance/MyBalanceServiceImpl.java @@ -73,7 +73,7 @@ public Optional getMyBalance(Long userId) { String jsonResponse = Objects.requireNonNull(response.body()).string(); JsonNode jsonNode = objectMapper.readTree(jsonResponse); - MyBalanceResponseDto myBalanceDto = MyBalanceResponseDto.builder().build(); + MyBalanceResponseDto myBalance = MyBalanceResponseDto.builder().build(); List stocks = new ArrayList<>(); // 주식 리스트 // output1 : 보유 주식 개별 정보 @@ -98,12 +98,12 @@ public Optional getMyBalance(Long userId) { } // output2 : 보유 주식 총합 정보 - myBalanceDto.setCash(jsonNode.path("output2").get(0).path("dnca_tot_amt").asLong()); // 예수금 - myBalanceDto.setStocks(stocks); - myBalanceDto.setTotalStockPrice(jsonNode.path("output2").get(0).path("evlu_amt_smtl_amt").asLong()); // 보유 주식 전체 가치 - myBalanceDto.setTotalStockPL(jsonNode.path("output2").get(0).path("evlu_pfls_smtl_amt").asLong()); // 보유 주식 전체 손익 + myBalance.setCash(jsonNode.path("output2").get(0).path("dnca_tot_amt").asLong()); // 예수금 + myBalance.setStocks(stocks); + myBalance.setTotalStockPrice(jsonNode.path("output2").get(0).path("evlu_amt_smtl_amt").asLong()); // 보유 주식 전체 가치 + myBalance.setTotalStockPL(jsonNode.path("output2").get(0).path("evlu_pfls_smtl_amt").asLong()); // 보유 주식 전체 손익 - return Optional.of(myBalanceDto); + return Optional.of(myBalance); } catch (Exception e) { return Optional.empty(); diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java index eb1fc52..2f07783 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java @@ -1,10 +1,13 @@ package com.thered.stocksignal.service.scenario; -import com.thered.stocksignal.app.dto.ScenarioDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; import java.util.List; public interface ScenarioService { - List getScenario(Long userId); + List getScenario(Long userId); + + boolean createScenario(Long userId, ScenarioRequestDto scenarioCreateDto); } diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java index 2c1cd03..4a1dd3e 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java @@ -4,10 +4,18 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.thered.stocksignal.app.dto.CompanyDto; +import com.thered.stocksignal.app.dto.ScenarioDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; import com.thered.stocksignal.app.dto.kakao.KakaoLoginDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; +import com.thered.stocksignal.domain.entity.Company; import com.thered.stocksignal.domain.entity.Scenario; +import com.thered.stocksignal.domain.entity.ScenarioCondition; +import com.thered.stocksignal.domain.entity.User; +import com.thered.stocksignal.repository.CompanyRepository; +import com.thered.stocksignal.repository.ScenarioConditionRepository; import com.thered.stocksignal.repository.ScenarioRepository; +import com.thered.stocksignal.repository.UserRepository; import com.thered.stocksignal.service.company.CompanyService; import com.thered.stocksignal.app.dto.StockDto.CurrentPriceResponseDto; import lombok.RequiredArgsConstructor; @@ -41,6 +49,9 @@ public class ScenarioServiceImpl implements ScenarioService { private final ScenarioRepository scenarioRepository; private final CompanyService companyService; + private final CompanyRepository companyRepository; + private final UserRepository userRepository; + private final ScenarioConditionRepository scenarioConditionRepository; // private String websocketUrl = "ws://ops.koreainvestment.com:31000"; // private StompSession stompSession; @@ -113,4 +124,41 @@ public List getScenario(Long userId) { return scenarioList; } + public boolean createScenario(Long userId, ScenarioRequestDto newScenario) { + + Optional company = companyRepository.findByCompanyName(newScenario.getCompanyName()); + Optional user = userRepository.findById(userId); + + if(company.isEmpty()) return false; + if(user.isEmpty()) return false; + + // 시나리오 객체 생성 + Scenario scenario = Scenario.builder() + .scenarioName(newScenario.getScenarioName()) + .company(company.get()) + .initialPrice(newScenario.getInitialPrice()) + .user(user.get()) + .build(); + + // 시나리오 저장 + scenario = scenarioRepository.save(scenario); + + // 조건 객체 생성 + ScenarioCondition condition = ScenarioCondition.builder() + .scenario(scenario) + .buysellType(newScenario.getBuysellType()) + .methodType(newScenario.getMethodType()) + .targetPrice1(newScenario.getTargetPrice1()) + .targetPrice2(newScenario.getTargetPrice2()) + .targetPrice3(newScenario.getTargetPrice3()) + .targetPrice4(newScenario.getTargetPrice4()) + .quantity(newScenario.getQuantity()) + .build(); + + // 조건 저장 + scenarioConditionRepository.save(condition); + + return true; + } + } From acd021ee9a072064af1d90f74b9fad6fac639c3f Mon Sep 17 00:00:00 2001 From: price126 Date: Thu, 24 Oct 2024 02:35:16 +0900 Subject: [PATCH 12/17] =?UTF-8?q?[FEAT]=20=EC=8B=9C=EB=82=98=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20=EC=82=AD=EC=A0=9C,=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C,=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thered/stocksignal/apiPayload/Status.java | 9 +- .../app/controller/ScenarioController.java | 52 +++++- .../stocksignal/app/dto/ScenarioDto.java | 16 ++ .../domain/entity/ScenarioCondition.java | 8 + .../stocksignal/handler/WebSocketHandler.java | 166 ++++++++++++++++++ .../ScenarioConditionRepository.java | 10 ++ .../repository/ScenarioRepository.java | 3 + .../service/scenario/ScenarioService.java | 9 + .../service/scenario/ScenarioServiceImpl.java | 119 ++++++++----- 9 files changed, 343 insertions(+), 49 deletions(-) create mode 100644 back/src/main/java/com/thered/stocksignal/handler/WebSocketHandler.java diff --git a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java index 49eea28..cb0823c 100644 --- a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java +++ b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java @@ -64,6 +64,10 @@ public enum Status { SCENARIO_FOUND("200", "SUCCESS", "시나리오 조회에 성공했습니다."), SCENARIO_CREATED("200", "SUCCESS", "시나리오 생성에 성공했습니다."), + SCENARIO_DELETED("200", "SUCCESS", "시나리오가 삭제되었습니다."), + + CONDITION_FOUND("200", "SUCCESS", "조건 조회에 성공하였습니다."), + CONDITION_ADDED("200", "SUCCESS", "조건 추가에 성공했습니다."), //실패 USER_NOT_FOUND("404", "FAILURE", "사용자를 찾을 수 없습니다"), @@ -75,7 +79,10 @@ public enum Status { NEWS_NOT_FOUND("404", "FAILURE", "뉴스 정보를 찾을 수 없습니다"), - SCENARIO_CREATION_FAILED("400", "FAILURE", "시나리오 생성에 실패하였습니다."); + SCENARIO_CREATION_FAILED("400", "FAILURE", "시나리오 생성에 실패하였습니다."), + SCENARIO_DELETION_FAILED("400", "FAILURE", "시나리오 삭제에 실패하였습니다."), + + ADDING_CONDITION_FAILED("400", "FAILURE", "조건 추가에 실패했습니다."); private final String code; private final String result; diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java index 2d36d08..70a455e 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java @@ -3,6 +3,8 @@ import com.thered.stocksignal.apiPayload.ApiResponse; import com.thered.stocksignal.apiPayload.Status; import com.thered.stocksignal.app.dto.ScenarioDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ConditionRequestDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ConditionResponseDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; import com.thered.stocksignal.service.scenario.ScenarioService; @@ -37,7 +39,7 @@ public ApiResponse> getScenario(@RequestHeader("Author @PostMapping("/create") @Operation(summary = "시나리오 생성", description = "새로운 시나리오를 생성합니다.") - public ApiResponse createScenario( + public ApiResponse createScenario( @RequestHeader("Authorization") String token, @RequestBody ScenarioRequestDto newScenario) { @@ -50,4 +52,52 @@ public ApiResponse createScenario( } return ApiResponse.onSuccess(Status.SCENARIO_CREATED, null); } + + @DeleteMapping("/{scenarioId}/delete") + @Operation(summary = "시나리오 삭제", description = "특정 시나리오를 삭제합니다.") + public ApiResponse deleteScenario( + @RequestHeader("Authorization") String token, + @PathVariable Long scenarioId) { + + Long userId = userAccountService.getUserIdFromToken(token); + if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); + + boolean isDeleted = scenarioService.deleteScenario(userId, scenarioId); + if (!isDeleted) { + return ApiResponse.onFailure(Status.SCENARIO_DELETION_FAILED, null); + } + return ApiResponse.onSuccess(Status.SCENARIO_DELETED, null); + } + + @GetMapping("/{scenarioId}/conditions") + @Operation(summary = "시나리오 조건 조회", description = "특정 시나리오의 조건을 조회합니다.") + public ApiResponse> getConditions( + @RequestHeader("Authorization") String token, + @PathVariable Long scenarioId) { + + Long userId = userAccountService.getUserIdFromToken(token); + if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); + + List responseDto = scenarioService.getConditions(userId, scenarioId); + + return ApiResponse.onSuccess(Status.CONDITION_FOUND, responseDto); + } + + @PatchMapping("/{scenarioId}/conditions/create") + @Operation(summary = "시나리오 조건 추가", description = "특정 시나리오에 조건을 추가합니다.") + public ApiResponse addCondition( + @RequestHeader("Authorization") String token, + @RequestBody ConditionRequestDto newScenario) { + + Long userId = userAccountService.getUserIdFromToken(token); + if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); + + boolean responseDto = scenarioService.addCondition(userId, newScenario); + + if(!responseDto){ + return ApiResponse.onFailure(Status.ADDING_CONDITION_FAILED, null); + } + return ApiResponse.onSuccess(Status.CONDITION_ADDED, null); + } + } diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java index b597aa2..4570390 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java @@ -25,6 +25,22 @@ public static class ScenarioRequestDto { private Long quantity; } + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ConditionRequestDto { + private Long scenarioId; + private BuysellType buysellType; + private MethodType methodType; + private Long targetPrice1; + private Long targetPrice2; + private Long targetPrice3; + private Long targetPrice4; + private Long quantity; + } + @Getter @Setter @Builder diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java index 9f2da3a..c736a04 100644 --- a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java @@ -44,4 +44,12 @@ public class ScenarioCondition { @Column(name = "quantity", nullable = false) private Long quantity; + @Setter + @Column(name = "is_price1_reached", nullable = false) + private boolean isPrice1Reached; + + @Setter + @Column(name = "is_price3_reached", nullable = false) + private boolean isPrice3Reached; + } diff --git a/back/src/main/java/com/thered/stocksignal/handler/WebSocketHandler.java b/back/src/main/java/com/thered/stocksignal/handler/WebSocketHandler.java new file mode 100644 index 0000000..2e02c3a --- /dev/null +++ b/back/src/main/java/com/thered/stocksignal/handler/WebSocketHandler.java @@ -0,0 +1,166 @@ +/* + +package com.thered.stocksignal.handler; + +import com.thered.stocksignal.domain.entity.Scenario; +import com.thered.stocksignal.domain.entity.ScenarioCondition; +import com.thered.stocksignal.domain.enums.BuysellType; +import com.thered.stocksignal.domain.enums.MethodType; +import com.thered.stocksignal.repository.ScenarioConditionRepository; +import com.thered.stocksignal.repository.ScenarioRepository; +import com.thered.stocksignal.service.trade.TradeService; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompSessionHandler; +import org.springframework.messaging.simp.stomp.StompHeaders; +import org.springframework.messaging.simp.stomp.StompSession; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class PriceHandler implements StompSessionHandler { + + private final TradeService tradingService; + private final ScenarioRepository scenarioRepository; + private final ScenarioConditionRepository scenarioConditionRepository; + + @Override + public void afterConnected(StompSession session, StompHeaders connectedHeaders) { + String approvalKey = "cfwf322432"; // 발급받은 웹소켓 접속키 + String trId = "H0STCNT0"; // 실시간 주식 체결가 + + // 데이터베이스에서 모든 시나리오를 로드 + List scenarios = scenarioRepository.findAll(); + + // 각 시나리오에 대해 구독 요청 전송 + for (Scenario scenario : scenarios) { + String trKey = scenario.getCompany().getCompanyCode(); // 종목 코드 + + // JSON 형식의 구독 요청 본문 + String requestBody = String.format( + "{\"header\":{\"approval_key\":\"%s\",\"custtype\":\"P\",\"tr_type\":\"1\",\"content-type\":\"utf-8\"}," + + "\"body\":{\"input\":{\"tr_id\":\"%s\",\"tr_key\":\"%s\"}}}", + approvalKey, trId, trKey + ); + + // 구독 요청 전송 + session.send("/topic/subscribe", requestBody); + } + } + + + @Override + public Type getPayloadType(StompHeaders headers) { + return String.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + String message = (String) payload; + processMessage(message); + } + + private void processMessage(String message) { + // 메시지를 "|"로 구분하고 매도호가/매수호가 추출 + String[] parts = message.split("\\|"); + + // 응답 형식 체크 (암호화 유무, TR_ID, 데이터 건수) + if (parts.length < 4) return; // 데이터가 부족하면 리턴 + + int dataCount = Integer.parseInt(parts[2]); // 데이터 건수 + + for (int i = 0; i < dataCount; i++) { + String[] responseData = parts[i + 3].split("\\^"); + String sellPrice = responseData[10]; // 매도호가 + String buyPrice = responseData[11]; // 매수호가 + + // 데이터베이스에서 모든 시나리오 조건들 로드 + List scList = scenarioConditionRepository.findAll(); + + // 모든 시나리오 조건에 대해 체크 + for (ScenarioCondition sc : scList) { + executeTrade(sc, sellPrice, buyPrice); + } + } + } + + private void executeTrade(ScenarioCondition sc, String sellPrice, String buyPrice) { + Long currentSellPrice = Long.parseLong(sellPrice); // 매도호가 + Long currentBuyPrice = Long.parseLong(buyPrice); // 매수호가 + + if (sc.getMethodType() == MethodType.RATE || sc.getMethodType() == MethodType.PRICE) { + + if (sc.getBuysellType() == BuysellType.BUY) { + + if (sc.getTargetPrice1() != null && currentBuyPrice >= sc.getTargetPrice1()) { + // buy(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + if (sc.getTargetPrice2() != null && currentBuyPrice <= sc.getTargetPrice2()) { + // buy(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + + } else if (sc.getBuysellType() == BuysellType.SELL) { + + if (sc.getTargetPrice1() != null && currentBuyPrice >= sc.getTargetPrice1()) { + // sell(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + if (sc.getTargetPrice2() != null && currentBuyPrice <= sc.getTargetPrice2()) { + // sell(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + } + } + + if (sc.getMethodType() == MethodType.TRADING) { + + if (sc.getBuysellType() == BuysellType.BUY) { + + // 상승 -> 하락 + if (sc.getTargetPrice1() != null && currentBuyPrice >= sc.getTargetPrice1()) { + sc.setPrice1Reached(true); + scenarioConditionRepository.save(sc); + } + else if (sc.getTargetPrice2() != null && currentBuyPrice <= sc.getTargetPrice2() && sc.isPrice1Reached() == true) { + // buy(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + + // 하락 -> 상승 + if (sc.getTargetPrice3() != null && currentBuyPrice <= sc.getTargetPrice3()) { + sc.setPrice3Reached(true); + scenarioConditionRepository.save(sc); + } + else if (sc.getTargetPrice4() != null && currentBuyPrice >= sc.getTargetPrice4() && sc.isPrice3Reached() == true) { + // buy(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + + } else if (sc.getBuysellType() == BuysellType.SELL) { + + // 상승 -> 하락 + if (sc.getTargetPrice1() != null && currentBuyPrice >= sc.getTargetPrice1()) { + sc.setPrice1Reached(true); + scenarioConditionRepository.save(sc); + } + if (sc.getTargetPrice2() != null && currentBuyPrice <= sc.getTargetPrice2() && sc.isPrice1Reached() == true) { + // sell(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + + // 하락 -> 상승 + if (sc.getTargetPrice3() != null && currentBuyPrice <= sc.getTargetPrice3()) { + sc.setPrice3Reached(true); + scenarioConditionRepository.save(sc); + } + else if (sc.getTargetPrice4() != null && currentBuyPrice >= sc.getTargetPrice4() && sc.isPrice3Reached() == true) { + // sell(sc.getUserId(), sc.getScenario().getCompany().getCompanyCode(), sc.getQuantity()); + } + } + } + } + + public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception){}; + + public void handleTransportError(StompSession session, Throwable error){}; +} + +*/ diff --git a/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java b/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java index 1f54a96..5b098f1 100644 --- a/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java +++ b/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java @@ -1,7 +1,17 @@ package com.thered.stocksignal.repository; +import com.thered.stocksignal.domain.entity.Scenario; import com.thered.stocksignal.domain.entity.ScenarioCondition; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; public interface ScenarioConditionRepository extends JpaRepository { + @Modifying + @Query("DELETE FROM ScenarioCondition sc WHERE sc.scenario = :scenario") + void deleteByScenario(Scenario scenario); + + List findByScenarioId(Long scenarioId); } diff --git a/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java b/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java index ad9b636..9023439 100644 --- a/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java +++ b/back/src/main/java/com/thered/stocksignal/repository/ScenarioRepository.java @@ -4,8 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; public interface ScenarioRepository extends JpaRepository { List findByUserId(Long userId); + + Optional findByIdAndUserId(Long id, Long userId); } diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java index 2f07783..8c8408b 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java @@ -1,5 +1,8 @@ package com.thered.stocksignal.service.scenario; +import com.thered.stocksignal.app.dto.ScenarioDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ConditionRequestDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ConditionResponseDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; @@ -10,4 +13,10 @@ public interface ScenarioService { List getScenario(Long userId); boolean createScenario(Long userId, ScenarioRequestDto scenarioCreateDto); + + boolean deleteScenario(Long userId, Long scenarioId); + + List getConditions(Long userId, Long scenarioId); + + boolean addCondition(Long userId, ConditionRequestDto condtionRequestDto); } diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java index 4a1dd3e..a4ed771 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.thered.stocksignal.app.dto.CompanyDto; import com.thered.stocksignal.app.dto.ScenarioDto; +import com.thered.stocksignal.app.dto.ScenarioDto.ConditionResponseDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; import com.thered.stocksignal.app.dto.kakao.KakaoLoginDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; @@ -12,6 +13,8 @@ import com.thered.stocksignal.domain.entity.Scenario; import com.thered.stocksignal.domain.entity.ScenarioCondition; import com.thered.stocksignal.domain.entity.User; +import com.thered.stocksignal.domain.enums.BuysellType; +import com.thered.stocksignal.domain.enums.MethodType; import com.thered.stocksignal.repository.CompanyRepository; import com.thered.stocksignal.repository.ScenarioConditionRepository; import com.thered.stocksignal.repository.ScenarioRepository; @@ -53,53 +56,6 @@ public class ScenarioServiceImpl implements ScenarioService { private final UserRepository userRepository; private final ScenarioConditionRepository scenarioConditionRepository; -// private String websocketUrl = "ws://ops.koreainvestment.com:31000"; -// private StompSession stompSession; -// -// @PostConstruct -// public void connect() { -// WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient()); -// stompClient.setMessageConverter(new MappingJackson2MessageConverter()); -// -// stompClient.connect(websocketUrl, new StompSessionHandler() { -// @Override -// public void afterConnected(StompSession session, StompHeaders connectedHeaders) { -// stompSession = session; -// subscribeToStock("005930"); // 삼성전자 -// } -// -// @Override -// public void handleFrame(StompHeaders headers, Object payload) { -// // 메시지 처리 -// System.out.println("Received message: " + payload); -// } -// -// @Override -// public Type getPayloadType(StompHeaders headers) { -// return String.class; -// } -// -// @Override -// public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) { -// System.err.println("Error: " + exception.getMessage()); -// } -// -// @Override -// public void handleTransportError(StompSession session, Throwable error) { -// System.err.println("Transport error: " + error.getMessage()); -// } -// }); -// } -// -// private void subscribeToStock(String stockCode) { -// String subscribeMessage = createSubscribeMessage(stockCode); -// stompSession.send("/topic/subscribe", subscribeMessage); -// } -// -// private String createSubscribeMessage(String stockCode) { -// return String.format("{\"header\": {\"approval_key\": \"발급받은_접속키\", \"custtype\": \"P\", \"tr_type\": \"1\", \"content-type\": \"utf-8\"}, \"body\": {\"input\": {\"tr_id\": \"H0STCNT0\", \"tr_key\": \"%s\"}}}", stockCode); -// } - public List getScenario(Long userId) { List scenarios = scenarioRepository.findByUserId(userId); @@ -161,4 +117,73 @@ public boolean createScenario(Long userId, ScenarioRequestDto newScenario) { return true; } + public boolean deleteScenario(Long userId, Long scenarioId) { + + Optional scenario = scenarioRepository.findByIdAndUserId(scenarioId, userId); + + // 시나리오가 존재하지 않으면 삭제 실패 + if (scenario.isEmpty()) { + return false; + } + + // 시나리오와 관련된 조건 삭제 + scenarioConditionRepository.deleteByScenario(scenario.get()); + + // 시나리오 삭제 + scenarioRepository.delete(scenario.get()); + + return true; + } + + public List getConditions(Long userId, Long scenarioId) { + List conditions = scenarioConditionRepository.findByScenarioId(scenarioId); + + List conditionList = new ArrayList<>(); + + for (ScenarioCondition condition : conditions) { + Long currentPrice = companyService.findCurrentPriceByCode(condition.getScenario().getCompany().getCompanyCode(), userId) + .map(CurrentPriceResponseDto::getCurrentPrice) + .orElse(null); // current price 정보를 찾을 수 없음 + + ConditionResponseDto responseDto = ConditionResponseDto.builder() + .conditionId(condition.getId()) + .buysellType(condition.getBuysellType()) + .methodType(condition.getMethodType()) + .initialPrice(condition.getScenario().getInitialPrice()) + .currentPrice(currentPrice) + .targetPrice1(condition.getTargetPrice1()) + .targetPrice2(condition.getTargetPrice2()) + .targetPrice3(condition.getTargetPrice3()) + .targetPrice4(condition.getTargetPrice4()) + .quantity(condition.getQuantity()) + .build(); + + conditionList.add(responseDto); + } + + return conditionList; + } + + public boolean addCondition(Long userId, ScenarioDto.ConditionRequestDto newCondition){ + Optional scenario = scenarioRepository.findById(newCondition.getScenarioId()); + + if(scenario.isEmpty()) return false; + + // 조건 객체 생성 + ScenarioCondition condition = ScenarioCondition.builder() + .scenario(scenario.get()) + .buysellType(newCondition.getBuysellType()) + .methodType(newCondition.getMethodType()) + .targetPrice1(newCondition.getTargetPrice1()) + .targetPrice2(newCondition.getTargetPrice2()) + .targetPrice3(newCondition.getTargetPrice3()) + .targetPrice4(newCondition.getTargetPrice4()) + .quantity(newCondition.getQuantity()) + .build(); + + // 조건 저장 + scenarioConditionRepository.save(condition); + + return true; + } } From 7e8e9060d14745ef8edb36c85ef8762b36047055 Mon Sep 17 00:00:00 2001 From: price126 Date: Thu, 24 Oct 2024 03:55:07 +0900 Subject: [PATCH 13/17] =?UTF-8?q?[FEAT]=20=EC=A1=B0=EA=B1=B4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/build.gradle | 2 +- .../thered/stocksignal/apiPayload/Status.java | 4 +- .../app/controller/CompanyController.java | 33 +++++++------ .../app/controller/MyBalanceController.java | 1 - .../app/controller/ScenarioController.java | 35 ++++++++++---- .../stocksignal/app/dto/ScenarioDto.java | 4 +- .../ScenarioConditionRepository.java | 3 +- .../service/company/CompanyService.java | 1 - .../service/company/CompanyServiceImpl.java | 2 - .../service/scenario/ScenarioService.java | 3 +- .../service/scenario/ScenarioServiceImpl.java | 46 +++++++++---------- 11 files changed, 76 insertions(+), 58 deletions(-) diff --git a/back/build.gradle b/back/build.gradle index 9fce2b4..2e6cf25 100644 --- a/back/build.gradle +++ b/back/build.gradle @@ -44,7 +44,7 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱을 위해 Jackson 사용 implementation 'org.springframework.boot:spring-boot-starter-security' - //scrapping + // scrapping implementation 'org.jsoup:jsoup:1.14.3' } diff --git a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java index cb0823c..bb8ea1b 100644 --- a/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java +++ b/back/src/main/java/com/thered/stocksignal/apiPayload/Status.java @@ -68,6 +68,7 @@ public enum Status { CONDITION_FOUND("200", "SUCCESS", "조건 조회에 성공하였습니다."), CONDITION_ADDED("200", "SUCCESS", "조건 추가에 성공했습니다."), + CONDITION_DELETED("200", "SUCCESS", "조건 삭제에 성공했습니다."), //실패 USER_NOT_FOUND("404", "FAILURE", "사용자를 찾을 수 없습니다"), @@ -82,7 +83,8 @@ public enum Status { SCENARIO_CREATION_FAILED("400", "FAILURE", "시나리오 생성에 실패하였습니다."), SCENARIO_DELETION_FAILED("400", "FAILURE", "시나리오 삭제에 실패하였습니다."), - ADDING_CONDITION_FAILED("400", "FAILURE", "조건 추가에 실패했습니다."); + ADDING_CONDITION_FAILED("400", "FAILURE", "조건 추가에 실패했습니다."), + DELETING_CONDITION_FAILED("400", "FAILURE", "조건 삭제에 실패했습니다."); private final String code; private final String result; diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java index fe5235d..50a32b8 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java @@ -24,7 +24,7 @@ public class CompanyController { private final CompanyService companyService; private final UserAccountService userAccountService; - @GetMapping("/code/{companyName}") + @GetMapping("/{companyName}/code") @Operation(summary = "종목 코드 조회", description = "회사명으로 종목 코드를 가져옵니다.") public ApiResponse getCompanyCode(@PathVariable String companyName) { @@ -35,7 +35,7 @@ public ApiResponse getCompanyCode(@PathVariable String c .orElseGet(() -> ApiResponse.onFailure(Status.COMPANY_NOT_FOUND)); } - @GetMapping("/logo/{companyName}") + @GetMapping("/{companyName}/logo") @Operation(summary = "로고 이미지 URL 조회", description = "회사명으로 로고 이미지 URL을 가져옵니다.") public ApiResponse getCompanyLogo(@PathVariable String companyName) { @@ -45,18 +45,20 @@ public ApiResponse getCompanyLogo(@PathVariable String c .orElseGet(() -> ApiResponse.onFailure(Status.COMPANY_NOT_FOUND)); } - @GetMapping("/{companyCode}") - @Operation(summary = "회사 정보 조회(분석 탭)", description = "종목 코드로 회사 정보 조회") + @GetMapping("/{companyName}") + @Operation(summary = "회사 정보 조회(분석 탭)", description = "종목명으로 회사 정보 조회") public ApiResponse getCompanyInfo( - @PathVariable String companyCode, + @PathVariable String companyName, @RequestHeader("Authorization") String token ) { + Optional companyCode = companyService.findCodeByName(companyName); + if(companyCode.isEmpty()) return ApiResponse.onFailure(Status.COMPANY_NOT_FOUND); Long userId = userAccountService.getUserIdFromToken(token); if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); Optional responseDto = companyService.findCompanyInfoByCode( - companyCode, + companyCode.get().getCompanyCode(), userId ); @@ -65,18 +67,20 @@ public ApiResponse getCompanyInfo( .orElseGet(() -> ApiResponse.onFailure(Status.COMPANY_NOT_FOUND)); } - @GetMapping("/current-price/{companyCode}") - @Operation(summary = "현재가 조회", description = "종목 코드로 현재가 조회") + @GetMapping("/{companyName}/current-price") + @Operation(summary = "현재가 조회", description = "종목명으로 현재가 조회") public ApiResponse getCurrentPrice( - @PathVariable String companyCode, + @PathVariable String companyName, @RequestHeader("Authorization") String token ){ + Optional companyCode = companyService.findCodeByName(companyName); + if(companyCode.isEmpty()) return ApiResponse.onFailure(Status.COMPANY_NOT_FOUND); Long userId = userAccountService.getUserIdFromToken(token); if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); Optional responseDto = companyService.findCurrentPriceByCode( - companyCode, + companyCode.get().getCompanyCode(), userId ); @@ -85,14 +89,17 @@ public ApiResponse getCurrentPrice( .orElseGet(() -> ApiResponse.onFailure(Status.COMPANY_NOT_FOUND)); } - @GetMapping("/period-price") + @GetMapping("/{companyName}/period-price") @Operation(summary = "일봉 조회", description = "종목 코드로 일봉 조회") public ApiResponse getPeriodPrice( - @RequestParam String companyCode, + @PathVariable String companyName, @RequestParam String startDate, @RequestParam String endDate, @RequestHeader("Authorization") String token ){ + Optional companyCode = companyService.findCodeByName(companyName); + if(companyCode.isEmpty()) return ApiResponse.onFailure(Status.COMPANY_NOT_FOUND); + Long userId = userAccountService.getUserIdFromToken(token); if(userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); @@ -100,7 +107,7 @@ public ApiResponse getPeriodPrice( if (user.isEmpty()) return ApiResponse.onFailure(Status.USER_NOT_FOUND); Optional responseDto = companyService.findPeriodPriceByCode( - companyCode, + companyCode.get().getCompanyCode(), startDate, endDate, userId diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java b/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java index 5c5f785..17e9d47 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/MyBalanceController.java @@ -2,7 +2,6 @@ import com.thered.stocksignal.apiPayload.ApiResponse; import com.thered.stocksignal.apiPayload.Status; -import com.thered.stocksignal.domain.entity.User; import com.thered.stocksignal.service.myBalance.MyBalanceService; import com.thered.stocksignal.service.user.UserAccountService; import lombok.RequiredArgsConstructor; diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java index 70a455e..1c05ce8 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java @@ -2,7 +2,6 @@ import com.thered.stocksignal.apiPayload.ApiResponse; import com.thered.stocksignal.apiPayload.Status; -import com.thered.stocksignal.app.dto.ScenarioDto; import com.thered.stocksignal.app.dto.ScenarioDto.ConditionRequestDto; import com.thered.stocksignal.app.dto.ScenarioDto.ConditionResponseDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; @@ -39,15 +38,15 @@ public ApiResponse> getScenario(@RequestHeader("Author @PostMapping("/create") @Operation(summary = "시나리오 생성", description = "새로운 시나리오를 생성합니다.") - public ApiResponse createScenario( + public ApiResponse createScenario( @RequestHeader("Authorization") String token, @RequestBody ScenarioRequestDto newScenario) { Long userId = userAccountService.getUserIdFromToken(token); if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); - boolean responseDto = scenarioService.createScenario(userId, newScenario); - if(!responseDto){ + boolean isCreated = scenarioService.createScenario(userId, newScenario); + if(!isCreated){ return ApiResponse.onFailure(Status.SCENARIO_CREATION_FAILED, null); } return ApiResponse.onSuccess(Status.SCENARIO_CREATED, null); @@ -83,21 +82,37 @@ public ApiResponse> getConditions( return ApiResponse.onSuccess(Status.CONDITION_FOUND, responseDto); } - @PatchMapping("/{scenarioId}/conditions/create") - @Operation(summary = "시나리오 조건 추가", description = "특정 시나리오에 조건을 추가합니다.") - public ApiResponse addCondition( + @PatchMapping("/conditions/create") + @Operation(summary = "시나리오 조건 추가", description = "특정 시나리오에 새 조건을 추가합니다.") + public ApiResponse addCondition( @RequestHeader("Authorization") String token, - @RequestBody ConditionRequestDto newScenario) { + @RequestBody ConditionRequestDto newCondition) { Long userId = userAccountService.getUserIdFromToken(token); if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); - boolean responseDto = scenarioService.addCondition(userId, newScenario); + boolean isCreated = scenarioService.addCondition(userId, newCondition); - if(!responseDto){ + if(!isCreated){ return ApiResponse.onFailure(Status.ADDING_CONDITION_FAILED, null); } return ApiResponse.onSuccess(Status.CONDITION_ADDED, null); } + @DeleteMapping("/conditions/{conditionId}/delete") + @Operation(summary = "조건 삭제", description = "시나리오 내 특정 조건을 삭제합니다.") + public ApiResponse deleteCondition( + @RequestHeader("Authorization") String token, + @PathVariable Long conditionId) { + + Long userId = userAccountService.getUserIdFromToken(token); + if (userId == -1) return ApiResponse.onFailure(Status.TOKEN_INVALID); + + boolean isDeleted = scenarioService.deleteCondition(userId, conditionId); + if (!isDeleted) { + return ApiResponse.onFailure(Status.DELETING_CONDITION_FAILED, null); + } + return ApiResponse.onSuccess(Status.CONDITION_DELETED, null); + } + } diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java index 4570390..29db8b6 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/ScenarioDto.java @@ -61,8 +61,8 @@ public static class ScenarioResponseDto { @AllArgsConstructor public static class ConditionResponseDto { private Long conditionId; - private BuysellType buysellType; - private MethodType methodType; + private BuysellType buysellType; // BUY, SELL + private MethodType methodType; // RATE, PRICE, TRADING private Long initialPrice; private Long currentPrice; private Long targetPrice1; diff --git a/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java b/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java index 5b098f1..2e8df12 100644 --- a/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java +++ b/back/src/main/java/com/thered/stocksignal/repository/ScenarioConditionRepository.java @@ -9,9 +9,8 @@ import java.util.List; public interface ScenarioConditionRepository extends JpaRepository { - @Modifying - @Query("DELETE FROM ScenarioCondition sc WHERE sc.scenario = :scenario") void deleteByScenario(Scenario scenario); List findByScenarioId(Long scenarioId); + } diff --git a/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java b/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java index 25b57fa..16b2507 100644 --- a/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java +++ b/back/src/main/java/com/thered/stocksignal/service/company/CompanyService.java @@ -1,7 +1,6 @@ package com.thered.stocksignal.service.company; import com.thered.stocksignal.app.dto.StockDto.popularStockResponseDto; -import com.thered.stocksignal.domain.entity.Company; import java.util.List; import java.util.Optional; diff --git a/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java index 9a040a3..16047f5 100644 --- a/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.thered.stocksignal.apiPayload.ApiResponse; -import com.thered.stocksignal.apiPayload.Status; import com.thered.stocksignal.app.dto.StockDto.popularStockResponseDto; import com.thered.stocksignal.domain.entity.Company; import com.thered.stocksignal.domain.entity.User; diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java index 8c8408b..12a6e01 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioService.java @@ -1,6 +1,5 @@ package com.thered.stocksignal.service.scenario; -import com.thered.stocksignal.app.dto.ScenarioDto; import com.thered.stocksignal.app.dto.ScenarioDto.ConditionRequestDto; import com.thered.stocksignal.app.dto.ScenarioDto.ConditionResponseDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; @@ -19,4 +18,6 @@ public interface ScenarioService { List getConditions(Long userId, Long scenarioId); boolean addCondition(Long userId, ConditionRequestDto condtionRequestDto); + + boolean deleteCondition(Long userId, Long conditionId); } diff --git a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java index a4ed771..56e869f 100644 --- a/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/scenario/ScenarioServiceImpl.java @@ -1,20 +1,14 @@ package com.thered.stocksignal.service.scenario; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.thered.stocksignal.app.dto.CompanyDto; + import com.thered.stocksignal.app.dto.ScenarioDto; import com.thered.stocksignal.app.dto.ScenarioDto.ConditionResponseDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioRequestDto; -import com.thered.stocksignal.app.dto.kakao.KakaoLoginDto; import com.thered.stocksignal.app.dto.ScenarioDto.ScenarioResponseDto; import com.thered.stocksignal.domain.entity.Company; import com.thered.stocksignal.domain.entity.Scenario; import com.thered.stocksignal.domain.entity.ScenarioCondition; import com.thered.stocksignal.domain.entity.User; -import com.thered.stocksignal.domain.enums.BuysellType; -import com.thered.stocksignal.domain.enums.MethodType; import com.thered.stocksignal.repository.CompanyRepository; import com.thered.stocksignal.repository.ScenarioConditionRepository; import com.thered.stocksignal.repository.ScenarioRepository; @@ -22,28 +16,12 @@ import com.thered.stocksignal.service.company.CompanyService; import com.thered.stocksignal.app.dto.StockDto.CurrentPriceResponseDto; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.messaging.converter.MappingJackson2MessageConverter; -import org.springframework.messaging.simp.stomp.StompSession; -import org.springframework.messaging.simp.stomp.StompSessionHandler; -import org.springframework.messaging.simp.stomp.StompHeaders; -import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.messaging.WebSocketStompClient; - -import jakarta.annotation.PostConstruct; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -186,4 +164,24 @@ public boolean addCondition(Long userId, ScenarioDto.ConditionRequestDto newCond return true; } + + public boolean deleteCondition(Long userId, Long conditionId) { + + Optional condition = scenarioConditionRepository.findById(conditionId); + + // 조건이 존재하지 않으면 삭제 실패 + if (condition.isEmpty()) { + return false; + } + + // 해당 조건이 사용자의 것이 아니면 삭제 실패 + if(!Objects.equals(condition.get().getScenario().getUser().getId(), userId)){ + return false; + } + + // 조건 삭제 + scenarioConditionRepository.delete(condition.get()); + + return true; + } } From 12ac10bc3adfeaf1ff7c2a6ef3db177d897605c2 Mon Sep 17 00:00:00 2001 From: price126 Date: Thu, 24 Oct 2024 04:25:53 +0900 Subject: [PATCH 14/17] =?UTF-8?q?[FIX]=20=EC=9D=BC=EB=B6=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(cascade=20=EB=93=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thered/stocksignal/app/controller/ScenarioController.java | 2 +- .../com/thered/stocksignal/domain/entity/ScenarioCondition.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java index 1c05ce8..7df8991 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/ScenarioController.java @@ -82,7 +82,7 @@ public ApiResponse> getConditions( return ApiResponse.onSuccess(Status.CONDITION_FOUND, responseDto); } - @PatchMapping("/conditions/create") + @PostMapping("/conditions/create") @Operation(summary = "시나리오 조건 추가", description = "특정 시나리오에 새 조건을 추가합니다.") public ApiResponse addCondition( @RequestHeader("Authorization") String token, diff --git a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java index c736a04..ab42a3f 100644 --- a/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java +++ b/back/src/main/java/com/thered/stocksignal/domain/entity/ScenarioCondition.java @@ -17,7 +17,7 @@ public class ScenarioCondition { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(cascade = CascadeType.REMOVE) @JoinColumn(name = "scenario_id", nullable = false) private Scenario scenario; From 1e0ae2982936ce0dff036ada23e81c4fb117e95a Mon Sep 17 00:00:00 2001 From: price126 Date: Thu, 24 Oct 2024 04:33:19 +0900 Subject: [PATCH 15/17] =?UTF-8?q?gitignore=20=ED=86=A0=ED=81=B0=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ee2cb96..8f6ac0c 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ out/ *.xml !crawling_data.zip !data-importer-2023-12-20.zip +back/src/test/java/com/thered/stocksignal/test/TokenTest.java From 4dc047d6005149e0dd7ced3af44443c811ee38ad Mon Sep 17 00:00:00 2001 From: price126 Date: Thu, 24 Oct 2024 05:16:45 +0900 Subject: [PATCH 16/17] =?UTF-8?q?[FIX]=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stocksignal/app/controller/CompanyController.java | 10 +++++----- .../java/com/thered/stocksignal/app/dto/StockDto.java | 2 +- .../service/company/CompanyServiceImpl.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java index 50a32b8..bca1454 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/CompanyController.java @@ -25,7 +25,7 @@ public class CompanyController { private final UserAccountService userAccountService; @GetMapping("/{companyName}/code") - @Operation(summary = "종목 코드 조회", description = "회사명으로 종목 코드를 가져옵니다.") + @Operation(summary = "종목 코드 조회", description = "종목명으로 종목 코드를 가져옵니다.") public ApiResponse getCompanyCode(@PathVariable String companyName) { Optional responseDto = companyService.findCodeByName(companyName); @@ -36,7 +36,7 @@ public ApiResponse getCompanyCode(@PathVariable String c } @GetMapping("/{companyName}/logo") - @Operation(summary = "로고 이미지 URL 조회", description = "회사명으로 로고 이미지 URL을 가져옵니다.") + @Operation(summary = "로고 이미지 URL 조회", description = "종목명으로 로고 이미지 경로를 불러옵니다.") public ApiResponse getCompanyLogo(@PathVariable String companyName) { Optional responseDto = companyService.findLogoByName(companyName); @@ -46,7 +46,7 @@ public ApiResponse getCompanyLogo(@PathVariable String c } @GetMapping("/{companyName}") - @Operation(summary = "회사 정보 조회(분석 탭)", description = "종목명으로 회사 정보 조회") + @Operation(summary = "회사 정보 조회(분석 탭)", description = "종목명으로 회사 정보를 조회합니다.") public ApiResponse getCompanyInfo( @PathVariable String companyName, @RequestHeader("Authorization") String token @@ -68,7 +68,7 @@ public ApiResponse getCompanyInfo( } @GetMapping("/{companyName}/current-price") - @Operation(summary = "현재가 조회", description = "종목명으로 현재가 조회") + @Operation(summary = "현재가 조회", description = "종목명으로 현재가를 조회합니다.") public ApiResponse getCurrentPrice( @PathVariable String companyName, @RequestHeader("Authorization") String token @@ -90,7 +90,7 @@ public ApiResponse getCurrentPrice( } @GetMapping("/{companyName}/period-price") - @Operation(summary = "일봉 조회", description = "종목 코드로 일봉 조회") + @Operation(summary = "일봉 조회", description = "종목명으로 일봉을 조회합니다.") public ApiResponse getPeriodPrice( @PathVariable String companyName, @RequestParam String startDate, diff --git a/back/src/main/java/com/thered/stocksignal/app/dto/StockDto.java b/back/src/main/java/com/thered/stocksignal/app/dto/StockDto.java index eea5bac..280ec93 100644 --- a/back/src/main/java/com/thered/stocksignal/app/dto/StockDto.java +++ b/back/src/main/java/com/thered/stocksignal/app/dto/StockDto.java @@ -38,7 +38,7 @@ public static class PeriodPriceResponseDto { // 주식 순위 public static class popularStockResponseDto { private Integer rank; - private String stockName; + private String companyName; } @Getter diff --git a/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java b/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java index 16047f5..9eadfee 100644 --- a/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java +++ b/back/src/main/java/com/thered/stocksignal/service/company/CompanyServiceImpl.java @@ -233,7 +233,7 @@ public Optional> getPopularStocks() { popularStockResponseDto popularStock = popularStockResponseDto.builder().build(); popularStock.setRank(Integer.parseInt(rankText)); // 순위 - popularStock.setStockName(row.select("td a.tltle").text()); // 종목명 + popularStock.setCompanyName(row.select("td a.tltle").text()); // 종목명 popularStocks.add(popularStock); } From 889057a207ed78d1fdbb073837b714d5503c47ce Mon Sep 17 00:00:00 2001 From: smyoo Date: Wed, 30 Oct 2024 00:02:11 +0900 Subject: [PATCH 17/17] =?UTF-8?q?fix=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20P?= =?UTF-8?q?OST=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stocksignal/app/controller/KakaoLoginController.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/back/src/main/java/com/thered/stocksignal/app/controller/KakaoLoginController.java b/back/src/main/java/com/thered/stocksignal/app/controller/KakaoLoginController.java index dbdf3a6..4988b78 100644 --- a/back/src/main/java/com/thered/stocksignal/app/controller/KakaoLoginController.java +++ b/back/src/main/java/com/thered/stocksignal/app/controller/KakaoLoginController.java @@ -9,10 +9,7 @@ import com.thered.stocksignal.service.user.UserAccountService; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import io.swagger.v3.oas.annotations.Operation; import java.util.Optional; @@ -27,7 +24,7 @@ public class KakaoLoginController { @Operation(summary = "프론트로부터 카카오 인가코드 전달받기") @Parameter(name = "code", description = "카카오에서 받은 인카코드, RequestParam") - @GetMapping("/login") + @PostMapping("/login") public ApiResponse kakaoLoginCode(@RequestParam("code") String code){ String token = kakaoLoginService.getKakaoToken(code);