diff --git a/pom.xml b/pom.xml
index 870d991..bd70d8f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -185,6 +185,12 @@
30.1-jre
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.13
+
+
diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java
new file mode 100644
index 0000000..7c210e9
--- /dev/null
+++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java
@@ -0,0 +1,41 @@
+package cn.stock.market.infrastructure.db.po;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.Generated;
+import com.querydsl.core.types.Path;
+
+
+/**
+ * QOptionalStockPO is a Querydsl query type for OptionalStockPO
+ */
+@Generated("com.querydsl.codegen.EntitySerializer")
+public class QOptionalStockPO extends EntityPathBase {
+
+ private static final long serialVersionUID = 1161631810L;
+
+ public static final QOptionalStockPO optionalStockPO = new QOptionalStockPO("optionalStockPO");
+
+ public final StringPath company = createString("company");
+
+ public final NumberPath id = createNumber("id", Integer.class);
+
+ public final StringPath symbol = createString("symbol");
+
+ public QOptionalStockPO(String variable) {
+ super(OptionalStockPO.class, forVariable(variable));
+ }
+
+ public QOptionalStockPO(Path extends OptionalStockPO> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QOptionalStockPO(PathMetadata metadata) {
+ super(OptionalStockPO.class, metadata);
+ }
+
+}
+
diff --git a/src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java b/src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java
new file mode 100644
index 0000000..22445c8
--- /dev/null
+++ b/src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java
@@ -0,0 +1,16 @@
+package cn.stock.market.domain.basic.convert;
+
+import cn.qutaojing.common.domain.convert.SimpleEntityPOConvert;
+import cn.qutaojing.common.utils.SpringUtils;
+import cn.stock.market.domain.basic.entity.OptionalStock;
+import cn.stock.market.infrastructure.db.po.OptionalStockPO;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+@Component
+@Lazy
+public class OptionalStockConvert extends SimpleEntityPOConvert {
+ public static OptionalStockConvert of() {
+ return SpringUtils.getBean(OptionalStockConvert.class);
+ }
+}
diff --git a/src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java b/src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java
new file mode 100644
index 0000000..6bb2483
--- /dev/null
+++ b/src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java
@@ -0,0 +1,16 @@
+package cn.stock.market.domain.basic.entity;
+
+import cn.stock.market.infrastructure.db.po.OptionalStockPO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+@EqualsAndHashCode(
+ callSuper = false
+)
+public class OptionalStock extends OptionalStockPO {
+}
diff --git a/src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java b/src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java
new file mode 100644
index 0000000..5e315b4
--- /dev/null
+++ b/src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java
@@ -0,0 +1,32 @@
+package cn.stock.market.domain.basic.repository;
+
+import cn.qutaojing.common.domain.convert.IEntityPOConvert;
+import cn.qutaojing.common.domain.respostory.SimplePoConvertEntityRepository;
+import cn.stock.market.domain.basic.convert.OptionalStockConvert;
+import cn.stock.market.domain.basic.entity.OptionalStock;
+import cn.stock.market.infrastructure.db.po.OptionalStockPO;
+import cn.stock.market.infrastructure.db.repo.OptionalStockRepo;
+import com.rp.spring.jpa.GenericJpaRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@RequiredArgsConstructor(
+ onConstructor = @__(@Autowired)
+)
+public class OptionalStockRepository extends SimplePoConvertEntityRepository {
+ final OptionalStockRepo repo;
+
+ final OptionalStockConvert convert;
+
+ @Override
+ public GenericJpaRepository repo() {
+ return repo;
+ }
+
+ @Override
+ public IEntityPOConvert convert() {
+ return convert;
+ }
+}
diff --git a/src/main/java/cn/stock/market/dto/OptionalStockResponse.java b/src/main/java/cn/stock/market/dto/OptionalStockResponse.java
new file mode 100644
index 0000000..0a5cd89
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/OptionalStockResponse.java
@@ -0,0 +1,18 @@
+package cn.stock.market.dto;
+
+import lombok.Data;
+
+@Data
+public class OptionalStockResponse {
+ private String message;
+ private Integer code;
+ private DataResponse data = new DataResponse();
+
+ @Data
+ public static class DataResponse {
+ private String symbol;
+ private String company;
+ private Double pricecurrent;
+ private Float pricepercentchange;
+ }
+}
diff --git a/src/main/java/cn/stock/market/dto/query/StockChartDto.java b/src/main/java/cn/stock/market/dto/query/StockChartDto.java
new file mode 100644
index 0000000..08f2263
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/query/StockChartDto.java
@@ -0,0 +1,48 @@
+package cn.stock.market.dto.query;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class StockChartDto {
+ private Long timestamp;
+ private double open;
+ private double high;
+ private double low;
+ private double close;
+ private double volume;
+
+ public StockChartDto(Long date, double open, double high, double low, double close, double volume) {
+ this.timestamp = date;
+ this.open = open;
+ this.high = high;
+ this.low = low;
+ this.close = close;
+ this.volume = volume;
+ }
+
+ public double getVolume() {
+ return volume;
+ }
+
+ public Long getTimestamp() {
+ return timestamp;
+ }
+
+ public double getOpen() {
+ return open;
+ }
+
+ public double getHigh() {
+ return high;
+ }
+
+ public double getLow() {
+ return low;
+ }
+
+ public double getClose() {
+ return close;
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java
index 8e73f8e..959bdbe 100644
--- a/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java
+++ b/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java
@@ -60,6 +60,10 @@ public class MoneyStockPO {
* NSE India的id */
String nseIndiaId;
+ /**
+ * NSE India Chart的id */
+ String nseIndiaChartId;
+
/**
* 自有self_url */
String selfUrl;
diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java
new file mode 100644
index 0000000..6197e75
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java
@@ -0,0 +1,35 @@
+package cn.stock.market.infrastructure.db.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@SuperBuilder
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@DynamicInsert
+@DynamicUpdate
+@Table(
+ name = "optional_stock"
+)
+public class OptionalStockPO {
+ @Id
+ @GeneratedValue(
+ strategy = javax.persistence.GenerationType.IDENTITY
+ )
+ Integer id;
+
+ String symbol;
+
+ String company;
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java
new file mode 100644
index 0000000..d61d258
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java
@@ -0,0 +1,7 @@
+package cn.stock.market.infrastructure.db.repo;
+
+import cn.stock.market.infrastructure.db.po.OptionalStockPO;
+import com.rp.spring.jpa.GenericJpaRepository;
+
+public interface OptionalStockRepo extends GenericJpaRepository {
+}
diff --git a/src/main/java/cn/stock/market/utils/NseIndiaRequest.java b/src/main/java/cn/stock/market/utils/NseIndiaRequest.java
index 326eb18..9868600 100644
--- a/src/main/java/cn/stock/market/utils/NseIndiaRequest.java
+++ b/src/main/java/cn/stock/market/utils/NseIndiaRequest.java
@@ -1,17 +1,22 @@
package cn.stock.market.utils;
+import cn.stock.market.dto.StockHistoryRequest;
+import cn.stock.market.dto.StockHistoryResponse;
import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
+import org.apache.commons.lang.StringUtils;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class NseIndiaRequest {
private static final String NSE_INDIA_URL = "https://www.nseindia.com";
+ private static final String NSE_INDIA_CHART_URL = "https://charting.nseindia.com";
private static final OkHttpClient client;
+ private static final ObjectMapper objectMapper = new ObjectMapper();
static {
client = new OkHttpClient.Builder()
@@ -45,8 +50,8 @@ public class NseIndiaRequest {
return request;
}
- private static void initCookie() {
- Request request = createRequest(NSE_INDIA_URL);
+ private static void initCookie(String url) {
+ Request request = createRequest(url);
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to fetch initial cookies");
@@ -56,8 +61,35 @@ public class NseIndiaRequest {
}
}
+ private static Integer getCode(String symbol) {
+ Request request = createRequest(NSE_INDIA_CHART_URL + "//Charts/GetEQMasters").newBuilder()
+ .addHeader("referer", NSE_INDIA_CHART_URL)
+ .addHeader("origin", NSE_INDIA_CHART_URL)
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Failed to get EQ code");
+ }
+
+ String result = response.body().string();
+
+ String regex = "(\\d+)\\|" + symbol + "\\|.*";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(result);
+
+ if (matcher.find()) {
+ return Integer.valueOf(matcher.group(1));
+ }
+ throw new IOException("No data found");
+
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to get EQ code", e);
+ }
+ }
+
public static JSONObject stockByJYSFromHttp(String stockType, String symbol, String nseIndiaId) {
- initCookie();
+ initCookie(NSE_INDIA_URL);
String url = NSE_INDIA_URL + "/api/quote-equity?symbol=" + nseIndiaId;
Request request = createRequest(url).newBuilder()
@@ -93,4 +125,57 @@ public class NseIndiaRequest {
throw new RuntimeException("Failed to fetch data", e);
}
}
+
+ public static StockHistoryResponse stockKLineFromHttp(StockHistoryRequest stockHistoryRequest, String resolution) {
+ initCookie(NSE_INDIA_CHART_URL);
+
+ Integer code = getCode(stockHistoryRequest.getSymbol());
+
+ int interval = 1;
+ if (StringUtils.equals("H", resolution)) {
+ resolution = "I";
+ interval = 60;
+ }
+
+ Map body = new HashMap<>();
+ body.put("chartPeriod", resolution);
+ body.put("chartStart", 0);
+ body.put("exch", "N");
+ body.put("fromDate", 0);
+ body.put("instrType", "C");
+ body.put("scripCode", code);
+ body.put("timeInterval", interval);
+ body.put("toDate", stockHistoryRequest.getTo() + 18000);
+ body.put("ulToken", code);
+
+ String payload;
+ try {
+ payload = objectMapper.writeValueAsString(body);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to serialize body", e);
+ }
+
+ RequestBody requestBody = RequestBody.create(
+ MediaType.get("application/json; charset=utf-8"),
+ payload
+ );
+
+ Request request = createRequest(NSE_INDIA_CHART_URL + "//Charts/symbolhistoricaldata/").newBuilder()
+ .addHeader("referer", NSE_INDIA_CHART_URL)
+ .addHeader("origin", NSE_INDIA_CHART_URL)
+ .post(requestBody)
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Request failed with code: " + response.code());
+ }
+
+ StockHistoryResponse result = objectMapper.readValue(response.body().string(), StockHistoryResponse.class);
+
+ return result;
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to fetch data", e);
+ }
+ }
}
diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java
index 399db83..adfc339 100644
--- a/src/main/java/cn/stock/market/web/MoneyApiController.java
+++ b/src/main/java/cn/stock/market/web/MoneyApiController.java
@@ -3,14 +3,21 @@ package cn.stock.market.web;
import cn.hutool.core.date.DateUtil;
import cn.stock.market.MoneyStockSuggestDTO;
import cn.stock.market.domain.basic.entity.MoneyStock;
+import cn.stock.market.domain.basic.entity.OptionalStock;
import cn.stock.market.domain.basic.repository.MoneyStockRepository;
+import cn.stock.market.domain.basic.repository.OptionalStockRepository;
+import cn.stock.market.dto.OptionalStockResponse;
import cn.stock.market.dto.StockHistoryRequest;
import cn.stock.market.dto.StockHistoryResponse;
+import cn.stock.market.dto.query.StockChartDto;
import cn.stock.market.infrastructure.db.po.QMoneyStockPO;
+import cn.stock.market.utils.HttpRequest;
import cn.stock.market.utils.NseIndiaRequest;
import cn.stock.market.utils.ServerResponse;
import cn.stock.market.web.annotations.EncryptFilter;
import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
@@ -23,6 +30,12 @@ import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@@ -36,8 +49,16 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriUtils;
import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -58,7 +79,14 @@ public class MoneyApiController {
@Autowired
private MoneyStockRepository moneyStockRepository;
+ @Autowired
+ private OptionalStockRepository optionalStockRepository;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
private static final String EXTERNAL_API_URL = "https://priceapi.moneycontrol.com/techCharts/indianMarket/stock/history";
+ private static final String OPTIONAL_STOCK_MONEYCONTROL_URL = "https://priceapi.moneycontrol.com/pricefeed/notapplicable/inidicesindia/";
@ApiOperation(value = "股票详情信息", httpMethod = "GET")
@ApiImplicitParams({
@@ -690,26 +718,26 @@ public class MoneyApiController {
Long to = null;
Long from = null;
int countback = 5;
- if (StringUtils.equals("H", resolution)) {
- to = (long) (System.currentTimeMillis() / 1000);
- from = to - (60 * 60);
- countback = 60;
- request.setResolution("1");
- } else if (StringUtils.equals("D", resolution)) {
- to = (long) (System.currentTimeMillis() / 1000);
- from = to - (24 * 60 * 60);
- countback = 390;
- request.setResolution("1");
+ if(StringUtils.equals("H",resolution)){
+ to = (long) (System.currentTimeMillis() / 1000);
+ from = to - (10 * 60 * 60 );
+ countback = 328;
+ request.setResolution("60");
+ }else if(StringUtils.equals("D",resolution)){
+ to = (long) (System.currentTimeMillis() / 1000);
+ from = to - (2 * 30 * 24 * 60 * 60 );
+ countback = 730;
+ request.setResolution("1D");
} else if (StringUtils.equals("W", resolution)) {
to = (long) (System.currentTimeMillis() / 1000);
from = to - (7 * 24 * 60 * 60);
- countback = 471;
- request.setResolution("5");
+ countback = 730;
+ request.setResolution("1W");
} else if (StringUtils.equals("M", resolution)) {
- to = (long) (System.currentTimeMillis() / 1000);
- from = to - (35 * 24 * 60 * 60);
- countback = 328;
- request.setResolution("30");
+ to = (long) (System.currentTimeMillis() / 1000);
+ from = to - (15 * 30 * 24 * 60 * 60);
+ countback = 730;
+ request.setResolution("1D");
}
request.setFrom(from);
@@ -724,7 +752,81 @@ public class MoneyApiController {
while (response == null && retryCount < maxRetries) {
try {
- response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class);
+ if (StringUtils.equals("M", resolution)) {
+ CloseableHttpClient client = HttpClients.createDefault();
+ HttpGet req = new HttpGet(apiUrl);
+ HttpResponse resp = client.execute(req);
+ String jsonResponse = EntityUtils.toString(resp.getEntity(), "UTF-8");
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(jsonResponse);
+
+ JsonNode timeNode = rootNode.get("t");
+ JsonNode lowNode = rootNode.get("l");
+ JsonNode highNode = rootNode.get("h");
+ JsonNode openNode = rootNode.get("o");
+ JsonNode closeNode = rootNode.get("c");
+ JsonNode volumeNote = rootNode.get("v");
+
+ List stocks = new ArrayList<>();
+ for (int i = 0; i < timeNode.size(); i++) {
+ long timestamp = timeNode.get(i).asLong();
+ double closePrice = closeNode.get(i).asDouble();
+ double openPrice = openNode.get(i).asDouble();
+ double volume = volumeNote.get(i).asDouble();
+ double low = lowNode.get(i).asDouble();
+ double high = highNode.get(i).asDouble();
+
+ stocks.add(new StockChartDto(timestamp, openPrice, high, low, closePrice, volume));
+ }
+
+ Map> groupedByMonth = stocks.stream()
+ .collect(Collectors.groupingBy(
+ sp -> Instant.ofEpochSecond(sp.getTimestamp())
+ .atZone(ZoneId.systemDefault())
+ .toLocalDate()
+ .format(DateTimeFormatter.ofPattern("yyyy-MM")),
+ LinkedHashMap::new,
+ Collectors.toList()
+ ));
+
+ List timestamps = new ArrayList<>();
+ List opens = new ArrayList<>();
+ List closes = new ArrayList<>();
+ List highs = new ArrayList<>();
+ List lows = new ArrayList<>();
+ List volumes = new ArrayList<>();
+
+ response = new StockHistoryResponse();
+ groupedByMonth.forEach((month, prices) -> {
+ double open = prices.get(0).getOpen();
+ double close = prices.get(prices.size() - 1).getClose();
+ double high = prices.stream().mapToDouble(StockChartDto::getHigh).max().orElse(0);
+ double low = prices.stream().mapToDouble(StockChartDto::getLow).min().orElse(0);
+ double volume = prices.stream().mapToDouble(StockChartDto::getVolume).sum();
+
+ long timestamp = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")).atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() / 1000;
+ timestamps.add(timestamp);
+ opens.add(open);
+ closes.add(close);
+ highs.add(high);
+ lows.add(low);
+ volumes.add((long) volume);
+
+ System.out.println("Month: " + month);
+ System.out.println("Open: " + open + ", Close: " + close + ", High: " + high + ", Low: " + low);
+ });
+
+ response.setS("ok");
+ response.setT(timestamps);
+ response.setL(lows);
+ response.setH(highs);
+ response.setO(opens);
+ response.setC(closes);
+ response.setV(volumes);
+
+ } else {
+ response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class);
+ }
} catch (RestClientException e) {
// Log the exception or perform any other error handling
log.error("Error while making API request. Retrying... (Retry count: {})", retryCount + 1);
@@ -737,14 +839,34 @@ public class MoneyApiController {
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
+ } catch (ClientProtocolException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
- if (response != null) {
- setResponse(response, resolution);
+ if (response != null && !response.getS().equals("error")) {
+// setResponse(response, resolution);
// API request successful, return the response
return ResponseEntity.ok(response);
} else {
+ try {
+ MoneyStock moneyStock = moneyStockRepository.findOne((QMoneyStockPO.moneyStockPO.moneyScId.eq(symbol))
+ .and(QMoneyStockPO.moneyStockPO.isLock.eq(0))
+ .and(QMoneyStockPO.moneyStockPO.isShow.eq(0)))
+ .orElse(null);
+
+ if (moneyStock != null && moneyStock.getNseIndiaChartId() != null && !moneyStock.getNseIndiaChartId().isEmpty()) {
+ request.setSymbol(moneyStock.getNseIndiaChartId());
+ response = NseIndiaRequest.stockKLineFromHttp(request, resolution);
+ return ResponseEntity.ok(response);
+ }
+ } catch (Exception e) {
+ log.error("Failed to get data from nseindia.", e.getMessage());
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
+ }
+
// All retries failed, return an error response
log.error("Failed to get a successful response after {} retries.", maxRetries);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
@@ -752,6 +874,33 @@ public class MoneyApiController {
// 返回响应
}
+ @GetMapping({"/market/api/market/stock/optional", "/api/market/stock/optional"})
+ @ResponseBody
+ @EncryptFilter(decryptRequest = false)
+ public ResponseEntity> getOptionalStock() {
+ List optionalStocks = optionalStockRepository.findAll();
+ List