diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3cf1e6a..1605a3b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,11 @@ stages: - dockerize - deploy +variables: + company_name: juyoutech + project_name: germany_stock + service_name: market + build: image: maven:3.6.3-openjdk-8 stage: build @@ -48,18 +53,22 @@ dockerize: - main - develop script: - - echo ">>>>>>Start Building Image<<<<<<" - - ls - - ls target - - echo $DOCKER_PASSWORD - - echo $CI_PIPELINE_ID - - docker build -t juyoutech/india_stock_market:latest . - - docker tag juyoutech/india_stock_market:latest juyoutech/india_stock_market:$CI_PIPELINE_ID - - docker tag juyoutech/india_stock_market:latest juyoutech/india_stock_market:$CI_COMMIT_BRANCH - - docker login -u juyoutech -p dckr_pat_8rg23IBA2kZMOCX4IJKApb8m5l8 - - docker push juyoutech/india_stock_market:latest - - docker push juyoutech/india_stock_market:$CI_PIPELINE_ID - - docker push juyoutech/india_stock_market:$CI_COMMIT_BRANCH + - echo ">>>>>>Start Building Docker Image<<<<<<" + - pwd + - ls -lah + - echo $DOCKER_PASSWORD | docker login -u ${company_name} --password-stdin + - docker build -t ${company_name}/${project_name}_${service_name} . + + - docker push ${company_name}/${project_name}_${service_name} + + - docker tag ${company_name}/${project_name}_${service_name} ${company_name}/${project_name}_${service_name}:$CI_PIPELINE_ID + - docker push ${company_name}/${project_name}_${service_name}:$CI_PIPELINE_ID + + - docker tag ${company_name}/${project_name}_${service_name} ${company_name}/${project_name}_${service_name}:$CI_COMMIT_BRANCH + - docker push ${company_name}/${project_name}_${service_name}:$CI_COMMIT_BRANCH + + - echo ${company_name}/${project_name}_${service_name}:$CI_COMMIT_BRANCH + deploy-dev: stage: deploy only: @@ -72,8 +81,8 @@ deploy-dev: - | curl -X POST \ -H "Content-Type: application/json" \ - -d '{"namespace":"yddev", "deployment_name":"india-stock-market"}' \ - https://updater-yddev.moneytj.com/restart-deployment + -d '{"namespace":"dgdev", "deployment_name":"germany-stock-market"}' \ + https://updater-dgdev.moneytj.com/restart-deployment - echo "Application successfully deployed." diff --git a/pom.xml b/pom.xml index bd70d8f..341677e 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,12 @@ 4.1.0 + + org.json + json + 20240303 + + io.springfox springfox-swagger2 diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QMoneyStockPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QMoneyStockPO.java index b719747..7e6ffb0 100644 --- a/src/main/generated/cn/stock/market/infrastructure/db/po/QMoneyStockPO.java +++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QMoneyStockPO.java @@ -29,6 +29,10 @@ public class QMoneyStockPO extends EntityPathBase { public final StringPath moneyScId = createString("moneyScId"); + public final StringPath nseIndiaChartId = createString("nseIndiaChartId"); + + public final StringPath nseIndiaId = createString("nseIndiaId"); + public final DateTimePath saveTime = createDateTime("saveTime", java.util.Date.class); public final StringPath selfDispId = createString("selfDispId"); @@ -39,6 +43,8 @@ public class QMoneyStockPO extends EntityPathBase { public final StringPath stockType = createString("stockType"); + public final BooleanPath useFromBseindia = createBoolean("useFromBseindia"); + public QMoneyStockPO(String variable) { super(MoneyStockPO.class, forVariable(variable)); } diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QStockIpoPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QStockIpoPO.java index 657c8d4..3a39af1 100644 --- a/src/main/generated/cn/stock/market/infrastructure/db/po/QStockIpoPO.java +++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QStockIpoPO.java @@ -23,6 +23,8 @@ public class QStockIpoPO extends EntityPathBase { public final DateTimePath createDate = createDateTime("createDate", java.util.Date.class); + public final StringPath exchangeType = createString("exchangeType"); + public final NumberPath id = createNumber("id", Integer.class); public final NumberPath isList = createNumber("isList", Integer.class); @@ -38,7 +40,6 @@ public class QStockIpoPO extends EntityPathBase { public final StringPath stockCode = createString("stockCode"); public final StringPath stockName = createString("stockName"); - public final StringPath exchangeType = createString("exchangeType"); public final NumberPath stockPrice = createNumber("stockPrice", java.math.BigDecimal.class); diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QStockTempPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QStockTempPO.java new file mode 100644 index 0000000..c4dd448 --- /dev/null +++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QStockTempPO.java @@ -0,0 +1,43 @@ +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; + + +/** + * QStockTempPO is a Querydsl query type for StockTempPO + */ +@Generated("com.querydsl.codegen.EntitySerializer") +public class QStockTempPO extends EntityPathBase { + + private static final long serialVersionUID = 1973569078L; + + public static final QStockTempPO stockTempPO = new QStockTempPO("stockTempPO"); + + public final NumberPath id = createNumber("id", Integer.class); + + public final StringPath stockGid = createString("stockGid"); + + public final StringPath stockLogo = createString("stockLogo"); + + public final StringPath stockType = createString("stockType"); + + public QStockTempPO(String variable) { + super(StockTempPO.class, forVariable(variable)); + } + + public QStockTempPO(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QStockTempPO(PathMetadata metadata) { + super(StockTempPO.class, metadata); + } + +} + diff --git a/src/main/java/cn/stock/market/domain/basic/convert/StockTempConvert.java b/src/main/java/cn/stock/market/domain/basic/convert/StockTempConvert.java new file mode 100644 index 0000000..8874d4a --- /dev/null +++ b/src/main/java/cn/stock/market/domain/basic/convert/StockTempConvert.java @@ -0,0 +1,25 @@ +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.Stock; +import cn.stock.market.domain.basic.entity.StockTemp; +import cn.stock.market.infrastructure.db.po.StockPO; +import cn.stock.market.infrastructure.db.po.StockTempPO; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +/** + * StockConvert + * + * @author rplees + * @email rplees.i.ly@gmail.com + * @created 2023/06/17 + */ +@Component +@Lazy +public class StockTempConvert extends SimpleEntityPOConvert { + public static StockTempConvert of() { + return SpringUtils.getBean(StockTempConvert.class); + } +} diff --git a/src/main/java/cn/stock/market/domain/basic/entity/StockTemp.java b/src/main/java/cn/stock/market/domain/basic/entity/StockTemp.java new file mode 100644 index 0000000..e81ad33 --- /dev/null +++ b/src/main/java/cn/stock/market/domain/basic/entity/StockTemp.java @@ -0,0 +1,26 @@ +package cn.stock.market.domain.basic.entity; + +import cn.qutaojing.common.utils.Beans; +import cn.stock.market.dto.command.StockCreateCommand; +import cn.stock.market.infrastructure.db.po.StockPO; +import cn.stock.market.infrastructure.db.po.StockTempPO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Stock + * + * @author rplees + * @email rplees.i.ly@gmail.com + * @created 2023/06/17 + */ +@Data +@NoArgsConstructor +@SuperBuilder +@EqualsAndHashCode( + callSuper = false +) +public class StockTemp extends StockTempPO { +} diff --git a/src/main/java/cn/stock/market/domain/basic/repository/StockRepository.java b/src/main/java/cn/stock/market/domain/basic/repository/StockRepository.java index bd02858..39ed937 100644 --- a/src/main/java/cn/stock/market/domain/basic/repository/StockRepository.java +++ b/src/main/java/cn/stock/market/domain/basic/repository/StockRepository.java @@ -54,6 +54,11 @@ public class StockRepository extends LocalCacheRepository cacheGidMap() { + Map map = cacheList().stream().collect(Collectors.toMap(Stock::getStockGid, val -> val, (u, v) -> u)); + return map; + } public Map cacheNameMap() { Map map = cacheList() diff --git a/src/main/java/cn/stock/market/domain/basic/repository/StockTempRepository.java b/src/main/java/cn/stock/market/domain/basic/repository/StockTempRepository.java new file mode 100644 index 0000000..1742f51 --- /dev/null +++ b/src/main/java/cn/stock/market/domain/basic/repository/StockTempRepository.java @@ -0,0 +1,63 @@ +package cn.stock.market.domain.basic.repository; + +import cn.qutaojing.common.domain.convert.IEntityPOConvert; +import cn.qutaojing.common.domain.respostory.LocalCacheBean; +import cn.qutaojing.common.domain.respostory.LocalCacheRepository; +import cn.qutaojing.common.jpa.ConditionBuilder; +import cn.qutaojing.common.utils.SpringUtils; +import cn.stock.market.domain.basic.convert.StockConvert; +import cn.stock.market.domain.basic.convert.StockTempConvert; +import cn.stock.market.domain.basic.entity.Stock; +import cn.stock.market.domain.basic.entity.StockTemp; +import cn.stock.market.infrastructure.db.po.QStockPO; +import cn.stock.market.infrastructure.db.po.QStockTempPO; +import cn.stock.market.infrastructure.db.po.StockPO; +import cn.stock.market.infrastructure.db.po.StockTempPO; +import cn.stock.market.infrastructure.db.repo.StockRepo; +import cn.stock.market.infrastructure.db.repo.StockTempRepo; +import com.google.common.cache.CacheBuilder; +import com.rp.spring.jpa.GenericJpaRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * StockRepository + * + * @author rplees + * @email rplees.i.ly@gmail.com + * @created 2023/06/17 + */ +@Repository +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class StockTempRepository extends LocalCacheRepository { + final StockTempRepo repo; + final StockTempConvert convert; + final static QStockTempPO q = QStockTempPO.stockTempPO; + + public List findAllForCheck() { + return findAll(); + } + + @Override + public GenericJpaRepository repo() { + return repo; + } + + @Override + public IEntityPOConvert convert() { + return convert; + } + + public static StockTempRepository of() { + return SpringUtils.getBean(StockTempRepository.class); + } +} diff --git a/src/main/java/cn/stock/market/domain/basic/service/StockService.java b/src/main/java/cn/stock/market/domain/basic/service/StockService.java index b0ad5c3..84ab368 100644 --- a/src/main/java/cn/stock/market/domain/basic/service/StockService.java +++ b/src/main/java/cn/stock/market/domain/basic/service/StockService.java @@ -13,11 +13,9 @@ import cn.hutool.core.text.StrFormatter; import cn.stock.market.dto.model.*; import cn.stock.market.infrastructure.api.EttechchartsApis; import cn.stock.market.infrastructure.api.GrowwInApis; +import cn.stock.market.infrastructure.api.HomeApiIndex; import cn.stock.market.infrastructure.api.TodayApis; -import cn.stock.market.infrastructure.api.investing.IndiaIndexVo; -import cn.stock.market.infrastructure.api.investing.IndiaStockVO; -import cn.stock.market.infrastructure.api.investing.InvestingApis; -import cn.stock.market.infrastructure.api.investing.InvestingInvokerApis; +import cn.stock.market.infrastructure.api.investing.*; import cn.stock.market.infrastructure.api.sina.vo.HotSearchVO; import cn.stock.market.utils.*; import com.ag.utils.CollectionUtils; @@ -1048,70 +1046,33 @@ public class StockService { return ServerResponse.createBySuccess(indexVoList); } - public ServerResponse getIndexByBtoday(){ - List indexVoList = new ArrayList<>(); - try { - String exchange = "bse"; - IndiaIndexVo vo1 = new IndiaIndexVo(); - String coCode = "20558"; - JSONObject object = TodayApis.getStockDetail("in%3BSEN", coCode); - IndiaStockVO market = objToVo(object); - market.setName("BSESENSEX指数"); + public ServerResponse getIndexByBtoday() throws Exception { + List list = HomeApiIndex.fetchStockIndices(); + + List indexVoList = new ArrayList<>(); + + for (StockIndex stockIndex : list) { + IndiaIndexNewVo vo1 = new IndiaIndexNewVo(); + IndiaStockVO market = new IndiaStockVO(); + market.setClose(String.valueOf(stockIndex.getClose())); + market.setHigh(String.valueOf(stockIndex.getHigh())); + market.setLow(String.valueOf(stockIndex.getLow())); + market.setName(stockIndex.getName()); + market.setNowPrice(String.valueOf(stockIndex.getClose())); + market.setOpen(String.valueOf(stockIndex.getOpen())); + market.setRate(String.valueOf(stockIndex.getPercentChange())); vo1.setIndexVo(market); - //获取k线图 1D 当天的数据 - String format = "S"; - String durationType = "D"; - String duration = "1"; - List kine = TodayApis.getStockKline(exchange,coCode,format,durationType,duration); - vo1.setKLine(kine); + List kLines = HomeApiIndex.fetchChartData(stockIndex.getId(), 419); +// List kline = HomeApiIndex.convertToJsonList(kLines); + vo1.setKLine(kLines); indexVoList.add(vo1); - }catch (Exception e){ - log.error("BToday获取BSESENSEX指数数据异常,异常信息。。。。", e); - try { - GrowwInApis.requestSenSexData(indexVoList); - } catch (Exception e1) { - log.error("GrowwIn获取BSESENSEX指数数据异常,异常信息。。。。", e1); - try{ - EttechchartsApis.requestSensexData(indexVoList); - } catch (Exception e2) { - log.error("Ettechcharts获取BSESENSEX指数数据异常,异常信息。。。。", e2); - } - } } - try { - String exchange = "nse"; - IndiaIndexVo vo1 = new IndiaIndexVo(); - String coCode = "20559"; - JSONObject object = TodayApis.getStockDetail("in%3BNSX", coCode); - IndiaStockVO market = objToVo(object); - market.setName("NIFTY50指数"); - vo1.setIndexVo(market); - - //获取k线图 1D 当天的数据 - String format = "S"; - String durationType = "D"; - String duration = "1"; - List kine = TodayApis.getStockKline(exchange,coCode,format,durationType,duration); - vo1.setKLine(kine); - indexVoList.add(vo1); - }catch (Exception e){ - log.error("BToday获取NIFTY50指数数据异常,异常信息。。。。", e); - try { - GrowwInApis.requestNifty50Data(indexVoList); - } catch (Exception e1) { - log.error("GrowwIn获取NIFTY50指数数据异常,异常信息。。。。", e1); - try{ - EttechchartsApis.requestNifty50Data(indexVoList); - } catch (Exception e2) { - log.error("Ettechcharts获取NIFTY50指数数据异常,异常信息。。。。", e2); - } - } - } return ServerResponse.createBySuccess(indexVoList); } + private IndiaStockVO objToVo(JSONObject object){ IndiaStockVO market = new IndiaStockVO(); if(object.containsKey("priceprevclose")){ diff --git a/src/main/java/cn/stock/market/dto/RawStockApiResponse.java b/src/main/java/cn/stock/market/dto/RawStockApiResponse.java new file mode 100644 index 0000000..9f8a8a0 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/RawStockApiResponse.java @@ -0,0 +1,10 @@ +package cn.stock.market.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class RawStockApiResponse { + private List data; +} diff --git a/src/main/java/cn/stock/market/dto/StockDataDto.java b/src/main/java/cn/stock/market/dto/StockDataDto.java new file mode 100644 index 0000000..4e9c9d6 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/StockDataDto.java @@ -0,0 +1,13 @@ +package cn.stock.market.dto; + +import lombok.Data; + +@Data +public class StockDataDto { + private long time; + private double open; + private double close; + private double max; + private double min; + private long volume; +} \ No newline at end of file diff --git a/src/main/java/cn/stock/market/dto/StockQuoteData.java b/src/main/java/cn/stock/market/dto/StockQuoteData.java new file mode 100644 index 0000000..098ce04 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/StockQuoteData.java @@ -0,0 +1,31 @@ +package cn.stock.market.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class StockQuoteData { + private String id; + private String symbol; + private String name; + private String exchange; + private String mic_code; + private String datetime; + private long timestamp; + private double open; + private double high; + private double low; + private double close; + private long volume; + private double previous_close; + private double change; + private double percent_change; + private double average_volume; + @JsonProperty("is_market_open") + private boolean market_open; + private Long market_cap; + private Object fifty_two_week; + private int icon; +} diff --git a/src/main/java/cn/stock/market/dto/StockQuoteResponse.java b/src/main/java/cn/stock/market/dto/StockQuoteResponse.java new file mode 100644 index 0000000..e7fea88 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/StockQuoteResponse.java @@ -0,0 +1,13 @@ +package cn.stock.market.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class StockQuoteResponse { + private List data; + private boolean success; + private String message; + private int status; +} diff --git a/src/main/java/cn/stock/market/dto/TradingViewData.java b/src/main/java/cn/stock/market/dto/TradingViewData.java new file mode 100644 index 0000000..e27c201 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/TradingViewData.java @@ -0,0 +1,13 @@ +package cn.stock.market.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TradingViewData { + private String s; // symbol + private List d; // data array +} \ No newline at end of file diff --git a/src/main/java/cn/stock/market/dto/TradingViewResponse.java b/src/main/java/cn/stock/market/dto/TradingViewResponse.java new file mode 100644 index 0000000..0742dc6 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/TradingViewResponse.java @@ -0,0 +1,13 @@ +package cn.stock.market.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TradingViewResponse { + private int totalCount; + private List data; +} \ No newline at end of file diff --git a/src/main/java/cn/stock/market/dto/model/ChartCandle.java b/src/main/java/cn/stock/market/dto/model/ChartCandle.java new file mode 100644 index 0000000..ef77fb8 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/model/ChartCandle.java @@ -0,0 +1,11 @@ +package cn.stock.market.dto.model; + +import lombok.Data; + +@Data +public class ChartCandle { + private String upd_date; + private Double price; + + // Getters & Setters (hoặc @Data nếu dùng Lombok) +} diff --git a/src/main/java/cn/stock/market/dto/model/StockIndex.java b/src/main/java/cn/stock/market/dto/model/StockIndex.java new file mode 100644 index 0000000..9dd8a84 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/model/StockIndex.java @@ -0,0 +1,31 @@ +package cn.stock.market.dto.model; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class StockIndex { + private String id; + private String symbol; + private String name; + private String exchange; + private String micCode; + private String datetime; + private long timestamp; + private Double open; + private Double high; + private Double low; + private Double close; + private Long volume; + private Double previousClose; + private Double change; + private Double percentChange; + private Long averageVolume; + private Boolean isMarketOpen; + private Long marketCap; + private String fiftyTwoWeek; + private Integer icon; + + // Getters & setters (hoặc dùng @Data nếu có Lombok) +} \ No newline at end of file diff --git a/src/main/java/cn/stock/market/dto/model/Symbol.java b/src/main/java/cn/stock/market/dto/model/Symbol.java new file mode 100644 index 0000000..16990c4 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/model/Symbol.java @@ -0,0 +1,28 @@ +package cn.stock.market.dto.model; + +import lombok.Data; + +import java.util.List; + +@Data +public class Symbol { + private String symbol; + private String description; + private String stockGid; + private String type; + private String exchange; + private String currencyCode; + private String currencyLogoid; + private String logoid; + private String providerId; + private String sourceLogoid; + private String sourceId; + private String country; + private boolean isPrimaryListing; + private List typespecs; + + // Getters and setters + // Bạn có thể dùng Lombok nếu muốn ngắn gọn hơn + // Hoặc generate getter/setter trong IDE + +} diff --git a/src/main/java/cn/stock/market/infrastructure/api/HomeApiIndex.java b/src/main/java/cn/stock/market/infrastructure/api/HomeApiIndex.java new file mode 100644 index 0000000..72f1f40 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/api/HomeApiIndex.java @@ -0,0 +1,150 @@ +package cn.stock.market.infrastructure.api; +import cn.qutaojing.common.utils.SpringUtils; +import cn.stock.market.dto.model.ChartCandle; +import cn.stock.market.dto.model.StockIndex; +import cn.stock.market.web.config.Config; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class HomeApiIndex { + private static final OkHttpClient client = new OkHttpClient(); + static Config config = SpringUtils.getBean(Config.class); + private static final String API_URL = config.getStockUrlPrefix() + "/api/ger-market/stocks/query-list?symbols=DAX,MDAX,SDXP,HDAX"; + private static final String BASE_URL = config.getStockUrlPrefix() + "/api/ger-market/chart"; + + public static List fetchStockIndices() throws Exception { + List result = new ArrayList<>(); + + Request request = new Request.Builder() + .url(API_URL) + .addHeader("User-Agent", "Mozilla/5.0") + .addHeader("Accept", "application/json") + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) { + throw new RuntimeException("HTTP error code: " + response.code()); + } + + String body = response.body().string(); + JSONObject json = new JSONObject(body); + JSONArray data = json.getJSONArray("data"); + + for (int i = 0; i < data.length(); i++) { + JSONObject obj = data.getJSONObject(i); + StockIndex index = new StockIndex(); + + index.setId(obj.optString("id")); + index.setSymbol(obj.optString("symbol")); + if (index.getSymbol().equals("DAX")) { + index.setName("DAX Index"); + }else if (index.getSymbol().equals("MDAX")) { + index.setName("MDAX Index"); + }else if (index.getSymbol().equals("HDAX")) { + index.setName("HDAX PERFORMANCE-INDEX"); + } else if (index.getSymbol().equals("SDXP")) { + index.setName("SDAX Index"); + } + index.setExchange(obj.optString("exchange")); + index.setMicCode(obj.optString("mic_code")); + index.setDatetime(obj.optString("datetime")); + index.setTimestamp(obj.optLong("timestamp")); + index.setOpen(getDoubleOrNull(obj, "open")); + index.setHigh(getDoubleOrNull(obj, "high")); + index.setLow(getDoubleOrNull(obj, "low")); + index.setClose(getDoubleOrNull(obj, "close")); + index.setVolume(getLongOrNull(obj, "volume")); + index.setPreviousClose(getDoubleOrNull(obj, "previous_close")); + index.setChange(getDoubleOrNull(obj, "change")); + index.setPercentChange(getDoubleOrNull(obj, "percent_change")); + index.setAverageVolume(getLongOrNull(obj, "average_volume")); + index.setIsMarketOpen(obj.optBoolean("is_market_open")); + index.setMarketCap(getLongOrNull(obj, "market_cap")); + index.setFiftyTwoWeek(obj.optString("fifty_two_week", null)); + index.setIcon(obj.optInt("icon", 0)); + + result.add(index); + } + + return result; + } + + public static List fetchChartData(String symbol, int amount) throws Exception { + List result = new ArrayList<>(); + + String url = BASE_URL + "?symbol=" + symbol + "&interval=D&amount=" + amount; + + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0") + .addHeader("Accept", "application/json") + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) { + throw new RuntimeException("HTTP error code: " + response.code()); + } + + String body = response.body().string(); + JSONObject json = new JSONObject(body); + JSONArray data = json.getJSONArray("data"); + + for (int i = 0; i < data.length(); i++) { + JSONObject obj = data.getJSONObject(i); + ChartCandle candle = new ChartCandle(); + + long ts = obj.optLong("time"); + String formattedTime = convertToGermanTime(ts); + + candle.setUpd_date(formattedTime); + candle.setPrice(getDoubleOrNull(obj, "close")); + + result.add(candle); + } + + return result; + } + +// public static List convertToJsonList(List candles) { +// List result = new ArrayList<>(); +// +// for (ChartCandle c : candles) { +// JSONObject obj = new JSONObject(); +// String formattedTime = convertToGermanTime(c.getTime()); +// obj.put("upd_date", formattedTime); +// obj.put("price", c.getClose()); +// result.add(obj); +// } +// +// return result; +// } + + public static String convertToGermanTime(long epochSeconds) { + ZoneId germanyZone = ZoneId.of("Europe/Berlin"); + Instant instant = Instant.ofEpochSecond(epochSeconds); + ZonedDateTime zonedDateTime = instant.atZone(germanyZone); + DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + return formatter.format(zonedDateTime); + } + + // Helper to safely parse nullable numbers + private static Double getDoubleOrNull(JSONObject obj, String key) { + return obj.isNull(key) ? null : obj.optDouble(key); + } + + private static Long getLongOrNull(JSONObject obj, String key) { + return obj.isNull(key) ? null : obj.optLong(key); + } +} diff --git a/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaIndexNewVo.java b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaIndexNewVo.java new file mode 100644 index 0000000..2eefa48 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaIndexNewVo.java @@ -0,0 +1,18 @@ +package cn.stock.market.infrastructure.api.investing; + +import cn.stock.market.dto.model.ChartCandle; +import com.alibaba.fastjson.JSONObject; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "指数信息") +public class IndiaIndexNewVo { + @ApiModelProperty(value = "指数详情") + private IndiaStockVO indexVo; + @ApiModelProperty(value = "指数k线") + private List kLine; +} diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/StockTempPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/StockTempPO.java new file mode 100644 index 0000000..6801bc8 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/db/po/StockTempPO.java @@ -0,0 +1,39 @@ +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.*; +import java.math.BigDecimal; +import java.util.Date; + +/** + * StockPO + * + * @author rplees + * @email rplees.i.ly@gmail.com + * @created 2023/06/17 + */ +@SuperBuilder +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@DynamicInsert +@DynamicUpdate +@Table(name = "stockTemp") +public class StockTempPO { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Integer id; + + String stockType; + + String stockGid; + + String stockLogo; +} diff --git a/src/main/java/cn/stock/market/infrastructure/db/repo/StockTempRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/StockTempRepo.java new file mode 100644 index 0000000..6ef0ad4 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/db/repo/StockTempRepo.java @@ -0,0 +1,15 @@ +package cn.stock.market.infrastructure.db.repo; + +import cn.stock.market.infrastructure.db.po.StockPO; +import cn.stock.market.infrastructure.db.po.StockTempPO; +import com.rp.spring.jpa.GenericJpaRepository; + +/** + * StockRepo + * + * @author rplees + * @email rplees.i.ly@gmail.com + * @created 2023/06/17 + */ +public interface StockTempRepo extends GenericJpaRepository { +} diff --git a/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java b/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java index 52d9c69..d807405 100644 --- a/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java +++ b/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java @@ -16,27 +16,44 @@ import com.alibaba.fastjson.JSONObject; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import javax.annotation.PostConstruct; import java.io.IOException; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @Slf4j @Component +@RestController +@RequestMapping("/api/market/investing") public class InvestingTask { @Autowired StockService stockService; @Autowired SiteNewsRepository newsRepository; + @Autowired + RestTemplate restTemplate; // @Scheduled(cron = "0 0 6 * * ?") public void syncIndiaData(){ @@ -110,7 +127,7 @@ public class InvestingTask { } /*新闻接口*/ - @Scheduled(cron = "0 0 0/3 * * ?") +// @Scheduled(cron = "0 0 0/3 * * ?") public void saveStockNews() { log.info("英文股票新闻数据同步开始"); try { @@ -169,4 +186,220 @@ public class InvestingTask { return null; } + + /*德国新闻接口*/ +// @Scheduled(cron = "0 0 0/3 * * ?") + public void saveGerNews() { + log.info("德国股票新闻数据同步开始"); + int savedCount = 0; + int totalCount = 0; + try { + // API URL for getting news list + String newsListUrl = "https://api.boerse-frankfurt.de/v1/data/category_news?newsType=ALL&lang=de&offset=0&limit=50"; + + // Headers for the API request + HttpHeaders headers = new HttpHeaders(); + headers.add("accept", "application/json, text/plain, */*"); + headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8,ug;q=0.7,fr;q=0.6"); + headers.add("origin", "https://www.boerse-frankfurt.de"); + headers.add("priority", "u=1, i"); + headers.add("referer", "https://www.boerse-frankfurt.de/"); + headers.add("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"); + + HttpEntity entity = new HttpEntity<>(headers); + + // Get news list + ResponseEntity response = restTemplate.exchange( + newsListUrl, + HttpMethod.GET, + entity, + String.class + ); + + if (response.getStatusCode().value() == 200 && response.getBody() != null) { + JSONObject newsListResponse = JSON.parseObject(response.getBody()); + JSONArray newsData = newsListResponse.getJSONArray("data"); + + if (newsData != null && newsData.size() > 0) { + totalCount = newsData.size(); + log.info("Found {} German news items to process", totalCount); + + for (int i = 0; i < newsData.size(); i++) { + try { + JSONObject newsItem = newsData.getJSONObject(i); + String newsId = newsItem.getString("id"); + String headline = newsItem.getString("headline"); + String time = newsItem.getString("time"); + String source = newsItem.getString("source"); + String teaserText = newsItem.getString("teaserText"); + String teaserImageUrl = newsItem.getString("teaserImageUrl"); + + // Check if news already exists + List existingNews = newsRepository.findAll(QSiteNewsPO.siteNewsPO.sourceId.eq(newsId)); + if (existingNews.size() == 0) { + // Get news detail + String newsDetailUrl = "https://api.boerse-frankfurt.de/v1/data/news?id=" + newsId + "&lang=de"; + HttpEntity detailEntity = new HttpEntity<>(headers); + + ResponseEntity detailResponse = restTemplate.exchange( + newsDetailUrl, + HttpMethod.GET, + detailEntity, + String.class + ); + + if (detailResponse.getStatusCode().value() == 200 && detailResponse.getBody() != null) { + JSONObject newsDetail = JSON.parseObject(detailResponse.getBody()); + String body = newsDetail.getString("body"); + + // Create SiteNews entity + SiteNews siteNews = new SiteNews(); + siteNews.setAddTime(new Date()); + siteNews.setSourceId(newsId); + siteNews.setTitle(headline); + siteNews.setSourceName(source); + siteNews.setDescription(teaserText != null ? teaserText : ""); + siteNews.setImgurl(teaserImageUrl); + siteNews.setContent(body != null ? body : ""); + siteNews.setStatus(1); + siteNews.setType(1); // Set as financial news type + siteNews.setViews(0); + + // Parse and set show time + if (time != null && !time.isEmpty()) { + try { + // Parse ISO 8601 format: "2025-06-19T08:37:58+02:00" + // Remove timezone offset and convert to standard format + String timeStr = time.replace("+02:00", "").replace("T", " "); + siteNews.setShowTime(DateTimeUtil.strToDate(timeStr, "yyyy-MM-dd HH:mm:ss")); + } catch (Exception e) { + log.warn("Failed to parse time for news {}: {}", newsId, time); + siteNews.setShowTime(new Date()); + } + } else { + siteNews.setShowTime(new Date()); + } + + try { + newsRepository.save(siteNews); + savedCount++; + log.info("Saved German news [{}/{}]: {}", savedCount, totalCount, headline); + } catch (Exception e) { + log.warn("Failed to save German news {}: {}", newsId, e.getMessage()); + } + } else { + log.warn("Failed to get news detail for {}: HTTP {}", newsId, detailResponse.getStatusCode()); + } + } else { + log.debug("News {} already exists, skipping", newsId); + } + } catch (Exception e) { + log.warn("Error processing news item {}: {}", i, e.getMessage()); + } + } + } else { + log.warn("No news data found in API response"); + } + } else { + log.error("Failed to get news list: HTTP {}", response.getStatusCode()); + } + log.info("德国股票新闻数据同步完成,处理了 {} 条新闻,保存了 {} 条新闻", totalCount, savedCount); + } catch (Exception e) { + log.error("德国新闻数据同步异常,异常信息: {}", e.getMessage(), e); + } + } + + @Scheduled(cron = "0 0 0/3 * * ?") +// @PostConstruct + public void getBoerseNews(){ + String url_request = "https://www.boerse-online.de"; + + try { + List results = new ArrayList<>(); + + String listUrl = url_request + "/nachrichten/1"; + Document doc = Jsoup.connect(listUrl) + .userAgent("Mozilla/5.0") + .get(); + + Elements articles = doc.select("article.article-list-item"); + + for (Element article : articles) { + Element aTag = article.selectFirst("h2 a"); + String title = aTag != null ? aTag.text().trim() : null; + String link = aTag != null ? url_request + aTag.attr("href") : null; + + Element imgTag = article.selectFirst("figure a picture img"); + String image = imgTag != null ? imgTag.attr("src") : null; + + Element timeTag = article.selectFirst("small.article-info time"); + Date publishedDate = null; + + if (timeTag != null) { + String datetimeAttr = timeTag.attr("datetime"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(datetimeAttr, formatter); + ZoneId berlinZone = ZoneId.of("Europe/Berlin"); + publishedDate = Date.from(dateTime.atZone(berlinZone).toInstant()); + } + + Element authorTag = article.selectFirst("small.article-info strong"); + String author = authorTag != null ? authorTag.text().trim() : null; + + // Fetch article detail page + String htmlContent = ""; + if (link != null) { + try { + Document detailPage = Jsoup.connect(link) + .userAgent("Mozilla/5.0") + .get(); + + Element body = detailPage.selectFirst("div.article-body"); + if (body != null) { + htmlContent = body.html(); // ✅ inner HTML only + } + + } catch (Exception e) { + System.err.println("Error fetching article detail: " + link); + e.printStackTrace(); + } + } + SiteNews siteNews = new SiteNews(); + siteNews.setAddTime(new Date()); + siteNews.setSourceId(link); + siteNews.setTitle(title); + siteNews.setSourceName("BOERSE"); + siteNews.setDescription(title); + siteNews.setImgurl(image); + siteNews.setContent(htmlContent); + siteNews.setStatus(1); + siteNews.setType(1); // Set as financial news type + siteNews.setViews(0); + siteNews.setShowTime(publishedDate); + try { + newsRepository.save(siteNews); + log.info("Saved German news : {}", title); + } catch (Exception e) { + log.warn("Failed to save German news {}: {}", link, e.getMessage()); + } + } + }catch (Exception e){ + log.error("Error fetching article detail: {}", e.getMessage()); + e.printStackTrace(); + } + + + } + + /** + * Test method to manually trigger German news sync + * This can be called via REST API or scheduled task + */ + @GetMapping("/test-ger-news") + public String testSaveGerNews() { + log.info("Testing German news sync..."); + saveGerNews(); + log.info("German news sync test completed"); + return "German news sync test completed. Check logs for details."; + } } diff --git a/src/main/java/cn/stock/market/infrastructure/job/RealTimeTask.java b/src/main/java/cn/stock/market/infrastructure/job/RealTimeTask.java index 3108955..c1adec1 100644 --- a/src/main/java/cn/stock/market/infrastructure/job/RealTimeTask.java +++ b/src/main/java/cn/stock/market/infrastructure/job/RealTimeTask.java @@ -16,7 +16,7 @@ public class RealTimeTask { /*每天9点定时删除股票k线数据*/ @Transactional - @Scheduled(cron = "0 00 9 * * MON-FRI") +// @Scheduled(cron = "0 00 9 * * MON-FRI") public void deleteStockCode() { log.info("每天9点定时删除股票k线数据"); repo.deleteStockCode(); @@ -24,7 +24,7 @@ public class RealTimeTask { /*每天0点定时删除期货k线数据*/ @Transactional - @Scheduled(cron = "0 00 0 * * MON-FRI") +// @Scheduled(cron = "0 00 0 * * MON-FRI") public void deleteStockFuturesCode() { log.info("每天0点定时删除期货k线数据"); repo.deleteStockFuturesCode(); diff --git a/src/main/java/cn/stock/market/infrastructure/job/Scraper.java b/src/main/java/cn/stock/market/infrastructure/job/Scraper.java index c2d80af..a688f0e 100644 --- a/src/main/java/cn/stock/market/infrastructure/job/Scraper.java +++ b/src/main/java/cn/stock/market/infrastructure/job/Scraper.java @@ -63,7 +63,7 @@ public class Scraper { private boolean times = false; - @Scheduled(cron = "0 0 1 */2 * ?") +// @Scheduled(cron = "0 0 1 */2 * ?") @RequestMapping("/testScraperGetBusinessToday") public void schedule() { String BASE_URL = "https://akm-img-a-in.tosshub.com/businesstoday/resource/market-widgets/prod/company-master-23-01-2023.json"; @@ -110,7 +110,7 @@ public class Scraper { - @Scheduled(cron = "0 0 18 * * ?") +// @Scheduled(cron = "0 0 18 * * ?") // @PostConstruct @RequestMapping("/testScraperGetMoneyControllerNewIPO") public void getMoneyControllerNewIPOSchedule() { @@ -303,7 +303,7 @@ public class Scraper { } - @Scheduled(cron = "0 0 19 * * ?") +// @Scheduled(cron = "0 0 19 * * ?") @PostConstruct @RequestMapping("/addMoneyStock") public void addMoneyStock() { diff --git a/src/main/java/cn/stock/market/infrastructure/job/StockNewTask.java b/src/main/java/cn/stock/market/infrastructure/job/StockNewTask.java new file mode 100644 index 0000000..5e07022 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/job/StockNewTask.java @@ -0,0 +1,116 @@ +package cn.stock.market.infrastructure.job; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; +import java.util.stream.Collectors; + +import cn.stock.market.domain.basic.entity.Stock; +import cn.stock.market.domain.basic.entity.StockTemp; +import cn.stock.market.domain.basic.repository.StockRepository; +import cn.stock.market.domain.basic.repository.StockTempRepository; +import cn.stock.market.dto.model.Symbol; +import cn.stock.market.infrastructure.db.po.StockPO; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +@Component +@Slf4j +public class StockNewTask { + private static final OkHttpClient client = new OkHttpClient(); + private static final String BASE_URL = "https://symbol-search.tradingview.com/symbol_search/v3/"; + private static final String PARAMS = "?start={start}&hl=1&country=DE&lang=en&search_type=stocks&domain=production&sort_by_country=US&promo=true&exchange={exchange}"; + + @Autowired + StockRepository stockRepository; + + +// @PostConstruct +// @Scheduled(cron = "0 01 22 * * ?") + public void syncStock() throws Exception { + try { + int limit = 20000; + List exchanges = Arrays.asList("BER", "DUS", "HAM", "HAN", "MUN", "SWB", "FWB", "XETR"); + Map stockGidMap = stockRepository.cacheGidMap(); + for (String exchange : exchanges) { + List newStocks = new ArrayList<>(); + int start = 0; + int symbolsRemaining; + do { + int finalStart = start; + if (start > 0) { + finalStart = start + 1; + } + String url = BASE_URL + PARAMS.replace("{start}", String.valueOf(finalStart)).replace("{exchange}", exchange); + Request request = new Request.Builder() + .url(url) + .addHeader("Origin", "https://www.tradingview.com") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)") + .addHeader("Accept", "application/json, text/plain, */*") + .addHeader("Referer", "https://www.tradingview.com/") + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) { + throw new RuntimeException("Unexpected response code: " + response.code()); + } + + String responseBody = response.body().string(); + JSONObject json = new JSONObject(responseBody); + JSONArray symbols = json.getJSONArray("symbols"); + symbolsRemaining = json.getInt("symbols_remaining"); + + for (int i = 0; i < symbols.length(); i++) { + JSONObject s = symbols.getJSONObject(i); + if (s.optString("type").equals("stock") && !stockGidMap.containsKey(s.optString("exchange") + ":" + s.getString("symbol"))) { + Stock stock = new Stock(); + stock.setStockGid(s.optString("exchange") + ":" + s.getString("symbol")); + stock.setStockCode(stock.getStockGid()); + stock.setStockSpell(s.optString("symbol")); + stock.setStockName(s.optString("description")); + stock.setStockSymbol(s.optString("symbol")); + stock.setStockType(s.optString("exchange")); + stock.setIsLock(0); + stock.setIsShow(0); + stock.setAddTime(new Date()); + stock.setStockState(0); + stock.setStockPlate("https://s3-symbol-logo.tradingview.com/" + s.optString("source_logoid") + "--big.svg"); + newStocks.add(stock); + stockGidMap.put(s.optString("exchange") + ":" + s.getString("symbol"), stock); + } + } + Thread.sleep(500); + + start += symbols.length(); + System.out.println("Fetched: " + symbols.length() + ", Remaining: " + symbolsRemaining); + try { + if (!newStocks.isEmpty()) { + stockRepository.saveAll(newStocks); + } + } catch (Exception e) { + log.error("Insert stock failed: {}", e.getMessage()); + } + + if (start >= limit) { + break; + } + + } while (symbolsRemaining > 0); + } + }catch (Exception e) { + log.error("Insert stock failed: {}", e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/cn/stock/market/infrastructure/job/StockTask.java b/src/main/java/cn/stock/market/infrastructure/job/StockTask.java index eec6087..de7afda 100644 --- a/src/main/java/cn/stock/market/infrastructure/job/StockTask.java +++ b/src/main/java/cn/stock/market/infrastructure/job/StockTask.java @@ -112,7 +112,7 @@ public class StockTask { } /*同步股票列表*/ - @Scheduled(cron = "0 0 6 * * ?") +// @Scheduled(cron = "0 0 6 * * ?") public void syncAFutureStockList() { PageInfo info = EastmoneyApi.ipoApplyDate(1, 300); Map stockMap = StockRepository.of().cacheCodeMap(); @@ -154,7 +154,7 @@ public class StockTask { } /*同步股票列表*/ - @Scheduled(cron = "0 0 6 * * ?") +// @Scheduled(cron = "0 0 6 * * ?") public void syncAStockList() { MdcUtil.setTraceIdIfAbsent(); Config config = SpringUtils.getBean(Config.class); diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index e905a43..300ef11 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -4,15 +4,20 @@ 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.entity.Stock; import cn.stock.market.domain.basic.repository.MoneyStockRepository; import cn.stock.market.domain.basic.repository.OptionalStockRepository; +import cn.stock.market.domain.basic.repository.StockRepository; +import cn.stock.market.domain.basic.service.StockService; import cn.stock.market.dto.*; import cn.stock.market.dto.query.StockChartDto; import cn.stock.market.infrastructure.db.po.QMoneyStockPO; +import cn.stock.market.infrastructure.db.po.QStockPO; 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 cn.stock.market.web.service.MoneyApiService; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -87,8 +92,14 @@ public class MoneyApiController { @Autowired private ObjectMapper objectMapper; + @Autowired + private MoneyApiService moneyApiService; + 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/"; + @Autowired + private StockRepository stockRepository; + @ApiOperation(value = "股票详情信息", httpMethod = "GET") @ApiImplicitParams({ @@ -197,136 +208,13 @@ public class MoneyApiController { @ResponseBody @EncryptFilter(decryptRequest = false) public ServerResponse getStockDetail(@RequestParam String stockType, @RequestParam String symbol) { - MoneyStock moneyStock = moneyStockRepository.findOne(QMoneyStockPO.moneyStockPO.stockType.eq(stockType) - .and(QMoneyStockPO.moneyStockPO.moneyScId.eq(symbol)) - .and(QMoneyStockPO.moneyStockPO.isLock.eq(0)) - .and(QMoneyStockPO.moneyStockPO.isShow.eq(0))) - .orElse(null); - /* if(moneyStock==null){ - return ServerResponse.createByErrorMsg("没有找到该股票"); - }*/ - // 设置重试次数 - if ("ANI".equals(symbol)) { - JSONObject json1 = new JSONObject(); - json1.put("company", "Archit Nuwood Industries Ltd"); - json1.put("pricepercentchange", "Archit Nuwood Industries Ltd"); - json1.put("stockType", stockType); - json1.put("pricecurrent", "386"); - json1.put("dataSourceType", "3"); - json1.put("symbol", "ANI"); - json1.put("BSEID", "ANI"); - json1.put("NSEID", "ANI"); - return ServerResponse.createBySuccess(json1); + Stock stock = stockRepository.findStockByCode(symbol); + if (stock == null) { + return ServerResponse.createByErrorMsg("Stock is not exist!"); } - - String url = String.format("https://priceapi.moneycontrol.com/pricefeed/%s/equitycash/%s", stockType.toLowerCase(), symbol); - int maxRetries = 3; - if (moneyStock.getUseFromBseindia()) { - String bseUrl = "https://api.bseindia.com/BseIndiaAPI/api/getScripHeaderData/w?Debtflag=&scripcode=" + moneyStock.getNseIndiaId() + "&seriesid="; - HttpHeaders headers = new HttpHeaders(); - headers.add("accept", "application/json, text/plain, */*"); - headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8"); - headers.add("origin", "https://www.bseindia.com"); - headers.add("referer", "https://www.bseindia.com/"); - headers.add("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\""); - headers.add("sec-ch-ua-mobile", "?0"); - headers.add("sec-ch-ua-platform", "\"Windows\""); - headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"); - - HttpEntity entity = new HttpEntity<>(headers); - - try { - ResponseEntity responseEntity = restTemplate.exchange(bseUrl, HttpMethod.GET, entity, String.class); - if (responseEntity.getStatusCode().value() == 200 && responseEntity.getBody() != null) { - JSONObject bseData = JSONObject.parseObject(responseEntity.getBody()); - JSONObject json1 = new JSONObject(); - json1.put("company", bseData.getJSONObject("Cmpname").getString("FullN")); - json1.put("pricepercentchange", bseData.getJSONObject("CurrRate").getString("PcChg")); - json1.put("stockType", stockType); - json1.put("pricechange", bseData.getJSONObject("CurrRate").getString("Chg")); - json1.put("pricecurrent", bseData.getJSONObject("CurrRate").getString("LTP")); - json1.put("priceprevclose", bseData.getJSONObject("Header").getString("PrevClose")); - json1.put("PREVDATE", ""); - - json1.put("dataSourceType", "3"); - json1.put("symbol", symbol); - json1.put("BSEID", moneyStock.getNseIndiaId()); - json1.put("NSEID", moneyStock.getNseIndiaId()); - json1.put("LTH", bseData.getJSONObject("Header").getString("High")); - json1.put("LTL", bseData.getJSONObject("Header").getString("Low")); - json1.put("OPN", bseData.getJSONObject("Header").getString("Open")); - - - if (moneyStock != null) { - json1.put("id", moneyStock.getId()); - } - - json1.put("VOL", this.getVolume(moneyStock.getNseIndiaId())); - return ServerResponse.createBySuccess(json1); - } - } catch (Exception e) { - System.err.println("Error fetching data from BSE India: " + e.getMessage()); - } - } - - for (int retry = 1; retry <= maxRetries; retry++) { - try { - ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, String.class); - JSONObject json1 = new JSONObject(); - if (responseEntity.getStatusCode().value() == 200 && responseEntity.getBody() != null) { - JSONObject data = JSONObject.parseObject(responseEntity.getBody()).getJSONObject("data"); - if (data != null) { - json1.put("company", data.getString("SC_FULLNM")); - json1.put("pricepercentchange", data.getString("pricepercentchange")); - json1.put("stockType", stockType); - json1.put("pricechange", data.getString("pricechange")); - json1.put("pricecurrent", data.getString("pricecurrent")); - json1.put("priceprevclose", data.getString("priceprevclose")); - json1.put("PREVDATE", data.getString("PREVDATE")); - json1.put("VOL", data.getString("VOL")); - json1.put("dataSourceType", "3"); - json1.put("symbol", data.getString("symbol")); - json1.put("BSEID", data.getString("BSEID")); - json1.put("NSEID", data.getString("NSEID")); - json1.put("LTH", data.getString("HP")); - json1.put("LTL", data.getString("LP")); - json1.put("OPN", data.getString("OPN")); - if (null != moneyStock) { - json1.put("id", moneyStock.getId()); - } - if (StringUtils.equals(data.getString("pricecurrent"), "0.00") - && (!StringUtils.equals(data.getString("priceprevclose"), "0.00"))) { - json1.put("pricecurrent", data.getString("priceprevclose")); - } - } - if (json1.size() > 0) - return ServerResponse.createBySuccess(json1); - } - } catch (Exception e) { - } - // 如果不是最后一次重试,则等待一段时间再进行下一次重试 - if (retry < maxRetries) { - try { - // 1秒钟 - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - if (moneyStock != null && moneyStock.getNseIndiaId() != null && !moneyStock.getNseIndiaId().isEmpty()) { - try { - // Get data from nseindia - JSONObject json = NseIndiaRequest.stockByJYSFromHttp(stockType, symbol, moneyStock.getNseIndiaId()); - json.put("id", moneyStock.getId()); - json.put("company",moneyStock.getStockName()); - return ServerResponse.createBySuccess(json); - - } catch (Exception e) { - return null; - } - } - return null; + StockQuoteData data = moneyApiService.getSingleStockQuote(symbol); + data.setName(stock.getStockName()); + return ServerResponse.createBySuccess(data); } private String getVolume(String scripcode) { @@ -651,7 +539,7 @@ public class MoneyApiController { @ApiOperation(value = "股票推荐TopGainer", httpMethod = "GET") @ApiImplicitParams({ - @ApiImplicitParam(name = "stockType", value = "BSE或者NSE"), + @ApiImplicitParam(name = "stockType", value = "BSE或者NSE或者germany"), }) @ApiResponses(value = { @ApiResponse(code = 200, message = "" + @@ -661,42 +549,41 @@ public class MoneyApiController { @ResponseBody @EncryptFilter(decryptRequest = false) - public List getTopGainer(@RequestParam String stockType) { + public List getTopGainer(@RequestParam(required = false, defaultValue = "germany") String stockType) { List moneyStockSuggestDTOS = null; // 尝试从缓存中获取结果 - moneyStockSuggestDTOS = gainerStockSuggestCache.getIfPresent(stockType); + // Use TradingView API for German stocks + List stockQuoteDataList = moneyApiService.getTopGainersFromTradingView(); + moneyStockSuggestDTOS = convertStockQuoteDataToMoneyStockSuggestDTO(stockQuoteDataList); + - if (moneyStockSuggestDTOS == null) { - // 缓存未命中,执行业务查询 - if (StringUtils.equals(stockType, "nse")) { - moneyStockSuggestDTOS = nseGainer(); - } else if (StringUtils.equals(stockType, "bse")) { - moneyStockSuggestDTOS = bseGainer(); - } - Map map = new HashMap<>(); - moneyStockSuggestDTOS = moneyStockSuggestDTOS.stream() - .filter(f -> StringUtils.isNotBlank(f.getStockName())) - .filter(i -> map.putIfAbsent(i.getStockName(), Boolean.TRUE) == null).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(moneyStockSuggestDTOS)) { - List selfUlrList = moneyStockSuggestDTOS.stream().map(MoneyStockSuggestDTO::getStockName).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(selfUlrList)) { - List all = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.stockName.in(selfUlrList)); - if (CollectionUtils.isNotEmpty(all)) { - moneyStockSuggestDTOS.stream().filter(f -> all.stream().anyMatch(s -> s.getStockName().equals(f.getStockName()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getStockName().equals(f.getStockName())).findFirst().orElse(null).getMoneyScId())); - } - } - gainerStockSuggestCache.put(stockType, moneyStockSuggestDTOS); - } - // 将结果放入缓存 - } return moneyStockSuggestDTOS; } + private List convertStockQuoteDataToMoneyStockSuggestDTO(List stockQuoteDataList) { + List result = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(stockQuoteDataList)) { + for (StockQuoteData stockQuoteData : stockQuoteDataList) { + MoneyStockSuggestDTO dto = new MoneyStockSuggestDTO(); + dto.setStockName(stockQuoteData.getName()); + dto.setStockType("XETRA"); + dto.setLastPrice(String.valueOf(stockQuoteData.getClose())); + dto.setChange(String.valueOf(stockQuoteData.getChange())); + dto.setChangePercent(String.valueOf(stockQuoteData.getPercent_change())); + dto.setHighPrice(String.valueOf(stockQuoteData.getHigh())); + dto.setLowPrice(String.valueOf(stockQuoteData.getLow())); + dto.setPrevClosePrice(String.valueOf(stockQuoteData.getPrevious_close())); + dto.setScId(String.valueOf(stockQuoteData.getSymbol())); + dto.setDispId(String.valueOf(stockQuoteData.getSymbol())); + result.add(dto); + } + } + return result; + } @ApiOperation(value = "股票推荐TopLoser", httpMethod = "GET") @ApiImplicitParams({ - @ApiImplicitParam(name = "stockType", value = "BSE或者NSE"), + @ApiImplicitParam(name = "stockType", value = "BSE或者NSE或者germany"), }) @ApiResponses(value = { @ApiResponse(code = 200, message = "" + @@ -706,41 +593,11 @@ public class MoneyApiController { @ResponseBody @EncryptFilter(decryptRequest = false) - public List getTopLoser(@RequestParam String stockType) { + public List getTopLoser(@RequestParam(required = false) String stockType) { List moneyStockSuggestDTOS = null; - moneyStockSuggestDTOS = loserStockSuggestCache.getIfPresent(stockType); - if (null == moneyStockSuggestDTOS) { - if (StringUtils.equals(stockType, "nse")) { - moneyStockSuggestDTOS = nseTopLoser(); - } else if (StringUtils.equals(stockType, "bse")) { - moneyStockSuggestDTOS = bseTopLoser(); - } - Map map = new HashMap<>(); - moneyStockSuggestDTOS = moneyStockSuggestDTOS.stream() - .filter(f -> StringUtils.isNotBlank(f.getStockName())) - .filter(i -> map.putIfAbsent(i.getStockName(), Boolean.TRUE) == null).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(moneyStockSuggestDTOS)) { - moneyStockSuggestDTOS.stream().forEach(f -> f.setDispId(extractLastSegment(f.getStockUrl()))); - List selfUlrList = moneyStockSuggestDTOS.stream().map(MoneyStockSuggestDTO::getStockName).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(selfUlrList)) { - List all = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.stockName.in(selfUlrList)); - if (CollectionUtils.isNotEmpty(all)) { - moneyStockSuggestDTOS.stream().filter(f -> all.stream().anyMatch(s -> s.getStockName().equals(f.getStockName()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getStockName().equals(f.getStockName())).findFirst().orElse(null).getMoneyScId())); - } - List noScIdList = moneyStockSuggestDTOS.stream().filter(f -> StringUtils.isBlank(f.getScId())).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(noScIdList)) { - List dispIdList = noScIdList.stream().map(MoneyStockSuggestDTO::getDispId).collect(Collectors.toList()); - List all1 = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.selfDispId.in(dispIdList)); - if (CollectionUtils.isNotEmpty(all1)) { - moneyStockSuggestDTOS.stream().filter(f -> all1.stream().anyMatch(s -> s.getSelfDispId().equals(f.getDispId()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getSelfDispId().equals(f.getDispId())).findFirst().orElse(null).getMoneyScId())); - } - } - } - loserStockSuggestCache.put(stockType, moneyStockSuggestDTOS); - } - } + List stockQuoteDataList = moneyApiService.getTopLosersFromTradingView(); + moneyStockSuggestDTOS = convertStockQuoteDataToMoneyStockSuggestDTO(stockQuoteDataList); + return moneyStockSuggestDTOS; } @@ -757,43 +614,18 @@ public class MoneyApiController { @ResponseBody @EncryptFilter(decryptRequest = false) - public List getTopActive(@RequestParam String stockType) { - List moneyStockSuggestDTOS = null; - moneyStockSuggestDTOS = activesStockSuggestCache.getIfPresent(stockType); - if (moneyStockSuggestDTOS == null) { - if (StringUtils.equals(stockType, "nse")) { - moneyStockSuggestDTOS = nseActives(); - } else if (StringUtils.equals(stockType, "bse")) { - moneyStockSuggestDTOS = bseActives(); - } - Map map = new HashMap<>(); - moneyStockSuggestDTOS = moneyStockSuggestDTOS.stream() - .filter(f -> StringUtils.isNotBlank(f.getStockName())) - .filter(i -> map.putIfAbsent(i.getStockName(), Boolean.TRUE) == null).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(moneyStockSuggestDTOS)) { - moneyStockSuggestDTOS.stream().forEach(f -> f.setDispId(extractLastSegment(f.getStockUrl()))); - List selfUlrList = moneyStockSuggestDTOS.stream().map(MoneyStockSuggestDTO::getStockName).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(selfUlrList)) { - List all = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.stockName.in(selfUlrList)); - if (CollectionUtils.isNotEmpty(all)) { - moneyStockSuggestDTOS.stream().filter(f -> all.stream().anyMatch(s -> s.getStockName().equals(f.getStockName()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getStockName().equals(f.getStockName())).findFirst().orElse(null).getMoneyScId())); - } - List noScIdList = moneyStockSuggestDTOS.stream().filter(f -> StringUtils.isBlank(f.getScId())).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(noScIdList)) { - List dispIdList = noScIdList.stream().map(MoneyStockSuggestDTO::getDispId).collect(Collectors.toList()); - List all1 = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.selfDispId.in(dispIdList)); - if (CollectionUtils.isNotEmpty(all1)) { - moneyStockSuggestDTOS.stream().filter(f -> all1.stream().anyMatch(s -> s.getSelfDispId().equals(f.getDispId()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getSelfDispId().equals(f.getDispId())).findFirst().orElse(null).getMoneyScId())); - } - } - } - activesStockSuggestCache.put(stockType, moneyStockSuggestDTOS); + public List getTopActive(@RequestParam String stockType) { + List topActiveCode = Arrays.asList("SAP","LIN","SIE","DTE","RHM","MUV2","SHL","DB1","MRK", + "BMW", "VOW3", "DHL","ENI","BAS","HEI","ADS","CBK","TLX", "BAYN", "RWE"); + List stocks = stockRepository.findAll(QStockPO.stockPO.stockCode.in(topActiveCode)); + List stockQuoteDatas = moneyApiService.getStocksQuote(stocks); + for (StockQuoteData stockQuoteData : stockQuoteDatas) { + Stock name = stocks.stream().filter(e->e.getStockCode().equals(stockQuoteData.getSymbol())).findFirst().orElse(null); + if (name != null) { + stockQuoteData.setName(name.getStockName()); } } - - return moneyStockSuggestDTOS; + return stockQuoteDatas; } private static void addToListDouble(List list, String value) { @@ -834,343 +666,15 @@ public class MoneyApiController { } } - - @GetMapping({"/market/api/market/money/history/kLine", "/api/market/money/history/kLine"}) @ApiOperation(value = "获取kline的money数据源", notes = "获取kline的money数据源", response = StockHistoryResponse.class) - @ApiImplicitParams({ - @ApiImplicitParam(name = "symbol", value = "Stock symbol 对应的是NSEID 或者是BSEID", required = true, dataType = "String", paramType = "query"), - @ApiImplicitParam(name = "resolution", value = "单位:60 1D 1W 1D 对应H,D,W,Y", required = true, dataType = "String", paramType = "query"), - @ApiImplicitParam(name = "from", value = "Start timestamp", required = true, dataType = "long", paramType = "query"), - @ApiImplicitParam(name = "to", value = "End timestamp", required = true, dataType = "long", paramType = "query"), - @ApiImplicitParam(name = "countback", value = "开始时间和结束时间区间的计划展示的数量", required = true, dataType = "int", paramType = "query"), - @ApiImplicitParam(name = "currencyCode", value = "INR 不变", required = true, dataType = "String", paramType = "query") - }) @ResponseBody @EncryptFilter(decryptRequest = false) - public ResponseEntity getStockHistory(@RequestParam String symbol, - @RequestParam String resolution + public ResponseEntity getStockChart(@RequestParam String symbol, + @RequestParam String interval, + @RequestParam(required = false) Integer amount ) { - MoneyStock moneyStock1 = moneyStockRepository.findOne( - QMoneyStockPO.moneyStockPO.nseIndiaId.eq(symbol) - .and(QMoneyStockPO.moneyStockPO.isLock.eq(0)) - .and(QMoneyStockPO.moneyStockPO.isShow.eq(0))) - .orElse(null); - - if (moneyStock1 != null && moneyStock1.getUseFromBseindia()) { - - String json = this.getChartData(moneyStock1.getNseIndiaId(), resolution); - - Gson gson = new Gson(); - - JsonObject outerJson = gson.fromJson(json, JsonObject.class); - - String innerJsonString; - - if (resolution.equals("H")) { - innerJsonString = outerJson.get("getDatIResult").getAsString(); - } else { - innerJsonString = outerJson.get("getDatResult").getAsString(); - } - - JsonObject innerJson = gson.fromJson(innerJsonString, JsonObject.class); - - JsonArray dataInputValues = innerJson.getAsJsonArray("DataInputValues"); - - List openList = new ArrayList<>(); - List highList = new ArrayList<>(); - List lowList = new ArrayList<>(); - List closeList = new ArrayList<>(); - List volumeList = new ArrayList<>(); - List dateList = new ArrayList<>(); - - Map latestDataMap = new LinkedHashMap<>(); - - if (dataInputValues.size() > 0) { - JsonObject dataObject = dataInputValues.get(0).getAsJsonObject(); - - JsonArray openArray = dataObject.getAsJsonArray("OpenData"); - JsonArray highArray = dataObject.getAsJsonArray("HighData"); - JsonArray lowArray = dataObject.getAsJsonArray("LowData"); - JsonArray closeArray = dataObject.getAsJsonArray("CloseData"); - JsonArray volumeArray = dataObject.getAsJsonArray("VolumeData"); - JsonArray dateArray = dataObject.getAsJsonArray("DateData"); - - if (resolution.equals("H")) { - openArray.forEach(o -> addToListDouble(openList, o.getAsJsonObject().get("Open").getAsString())); - highArray.forEach(h -> addToListDouble(highList, h.getAsJsonObject().get("High").getAsString())); - lowArray.forEach(l -> addToListDouble(lowList, l.getAsJsonObject().get("Low").getAsString())); - closeArray.forEach(c -> addToListDouble(closeList, c.getAsJsonObject().get("Close").getAsString())); - volumeArray.forEach(v -> addToList(volumeList, v.getAsJsonObject().get("Volume").getAsString())); - dateArray.forEach(d -> addDatesToList(dateList, d.getAsJsonObject().get("Date").getAsString())); - } else { - for (int i = 0; i < openArray.size(); i++) { - String open = openArray.get(i).getAsJsonObject().get("Open").getAsString(); - String high = highArray.get(i).getAsJsonObject().get("High").getAsString(); - String low = lowArray.get(i).getAsJsonObject().get("Low").getAsString(); - String close = closeArray.get(i).getAsJsonObject().get("Close").getAsString(); - String volume = volumeArray.get(i).getAsJsonObject().get("Volume").getAsString(); - String dateStr = dateArray.get(i).getAsJsonObject().get("Date").getAsString(); - - Long timestamp = convertToTimestamp(dateStr); - - String dateTimeKey = dateStr; - - JsonObject dataJson = new JsonObject(); - dataJson.addProperty("Open", open); - dataJson.addProperty("High", high); - dataJson.addProperty("Low", low); - dataJson.addProperty("Close", close); - dataJson.addProperty("Volume", volume); - dataJson.addProperty("Date", dateStr); - - latestDataMap.put(dateTimeKey, dataJson); - } - - latestDataMap.forEach((key, value) -> { - // Lấy giá trị các trường Open, High, Low, Close, Volume, Date từ dữ liệu - String openData = value.get("Open").getAsString(); - String highData = value.get("High").getAsString(); - String lowData = value.get("Low").getAsString(); - String closeData = value.get("Close").getAsString(); - String volumeData = value.get("Volume").getAsString(); - String dateData = value.get("Date").getAsString(); - - // Tách các giá trị trong chuỗi bằng dấu phẩy - String[] openValues = openData.split(","); - String[] highValues = highData.split(","); - String[] lowValues = lowData.split(","); - String[] closeValues = closeData.split(","); - String[] volumeValues = volumeData.split(","); - String[] dateValues = dateData.split(","); - - for (int i = 0; i < openValues.length; i++) { - double open = Double.parseDouble(openValues[i]); - double high = Double.parseDouble(highValues[i]); - double low = Double.parseDouble(lowValues[i]); - double close = Double.parseDouble(closeValues[i]); - long volume = Math.round(Double.parseDouble(volumeValues[i])); - long timestamp = convertToTimestamp(dateValues[i]); - - int index = dateList.indexOf(timestamp); - if (index == -1) { - dateList.add(timestamp); - openList.add(open); - highList.add(high); - lowList.add(low); - closeList.add(close); - volumeList.add(volume); - } else { - openList.set(index, open); - highList.set(index, high); - lowList.set(index, low); - closeList.set(index, close); - volumeList.set(index, volume); - } - } - }); - } - - StockHistoryResponse response = new StockHistoryResponse(); - response.setS("ok"); - response.setT(dateList); - response.setO(openList); - response.setH(highList); - response.setL(lowList); - response.setC(closeList); - response.setV(volumeList); - -// if (resolution.equals("H")) { -// Set desiredTimes = new HashSet<>(Arrays.asList("09:30", "10:30", "11:30", "12:30", "13:30", "14:30")); -// -// // Filter data based on timestamps -// SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); -// List indicesToKeep = new ArrayList<>(); -// for (int i = 0; i < dateList.size(); i++) { -// String time = sdf.format(new Date(dateList.get(i) * 1000)); -// if (desiredTimes.contains(time)) { -// indicesToKeep.add(i); -// } -// } -// -// // Filter corresponding data -// List filteredT = indicesToKeep.stream().map(dateList::get).collect(Collectors.toList()); -// List filteredO = indicesToKeep.stream().map(openList::get).collect(Collectors.toList()); -// List filteredH = indicesToKeep.stream().map(highList::get).collect(Collectors.toList()); -// List filteredL = indicesToKeep.stream().map(lowList::get).collect(Collectors.toList()); -// List filteredC = indicesToKeep.stream().map(closeList::get).collect(Collectors.toList()); -// List filteredV = indicesToKeep.stream().map(volumeList::get).collect(Collectors.toList()); -// response.setT(filteredT); -// response.setO(filteredO); -// response.setH(filteredH); -// response.setL(filteredL); -// response.setC(filteredC); -// response.setV(filteredV); -// } - return ResponseEntity.ok(response); - } - } - - // 向外部API发起请求,并获取响应 - StockHistoryRequest request = new StockHistoryRequest(); - request.setSymbol(symbol); - Long to = null; - Long from = null; - int countback = 5; - 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 = 730; - request.setResolution("1W"); - } else if (StringUtils.equals("M", resolution)) { - to = (long) (System.currentTimeMillis() / 1000); - from = to - (15 * 30 * 24 * 60 * 60); - countback = 730; - request.setResolution("1D"); - } - - request.setFrom(from); - request.setTo(to); - request.setCountback(countback); - request.setCurrencyCode("INR"); - String apiUrl = buildApiUrl(request); - log.info("request url:" + apiUrl); - StockHistoryResponse response = null; - int maxRetries = 3; - int retryCount = 0; - - while (response == null && retryCount < maxRetries) { - try { - 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); - - // Increment the retry count - retryCount++; - // Add some delay before the next retry (you can adjust this as needed) - try { - Thread.sleep(300); // 1 second delay - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } catch (ClientProtocolException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - 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(); - } - // 返回响应 + return ResponseEntity.ok(moneyApiService.getStockHistory(symbol, interval, amount)); } @GetMapping({"/market/api/market/stock/optional", "/api/market/stock/optional"}) diff --git a/src/main/java/cn/stock/market/web/StockApiController.java b/src/main/java/cn/stock/market/web/StockApiController.java index 3d25e95..4126cff 100644 --- a/src/main/java/cn/stock/market/web/StockApiController.java +++ b/src/main/java/cn/stock/market/web/StockApiController.java @@ -167,7 +167,7 @@ public class StockApiController { @ApiOperation(value = "印度新闻列表", httpMethod = "GET") @ResponseBody public ServerResponse getINDNews(@RequestParam("pageSize") Integer pageSize, @RequestParam("pageNum") Integer pageNum) { - return ServerResponse.createBySuccess(newsRepository.findAll(ConditionBuilder.builder().build(), PageParam.of(pageNum, pageSize), QSiteNewsPO.siteNewsPO.id.desc())); + return ServerResponse.createBySuccess(newsRepository.findAll(ConditionBuilder.builder().build(), PageParam.of(pageNum, pageSize), QSiteNewsPO.siteNewsPO.showTime.desc())); } @RequestMapping({"test.do"}) @@ -249,8 +249,12 @@ public class StockApiController { public ServerResponse getIndiaIndexByToday() { String INDEX_CODE = "TODAY_INDEX"; return RequestCacheUtils.cache("getIndiaIndexByToday.do", INDEX_CODE,6000, (string) -> { - return this.stockService.getIndexByBtoday(); - }); + try { + return this.stockService.getIndexByBtoday(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @RequestMapping({"getIndiaIndexByEttech.do"}) @ApiOperation(value = "印度--获取指定指数信息-Ettech", httpMethod = "GET") diff --git a/src/main/java/cn/stock/market/web/config/Config.java b/src/main/java/cn/stock/market/web/config/Config.java index 7349203..cd18242 100644 --- a/src/main/java/cn/stock/market/web/config/Config.java +++ b/src/main/java/cn/stock/market/web/config/Config.java @@ -39,4 +39,5 @@ public class Config { String aliyunAccessKeyId; String aliyunAccessKeySecret; String aliyunAppCode; + String stockUrlPrefix; } diff --git a/src/main/java/cn/stock/market/web/service/MoneyApiService.java b/src/main/java/cn/stock/market/web/service/MoneyApiService.java new file mode 100644 index 0000000..3bc78d6 --- /dev/null +++ b/src/main/java/cn/stock/market/web/service/MoneyApiService.java @@ -0,0 +1,264 @@ +package cn.stock.market.web.service; + +import cn.qutaojing.common.utils.SpringUtils; +import cn.stock.market.domain.basic.entity.Stock; +import cn.stock.market.domain.basic.repository.StockRepository; +import cn.stock.market.dto.*; +import cn.stock.market.infrastructure.db.po.QStockPO; +import cn.stock.market.web.config.Config; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +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.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class MoneyApiService { + @Autowired + private ObjectMapper objectMapper; + @Autowired + private StockRepository stockRepository; + + public StockHistoryResponse getStockHistory(String symbol, String interval, Integer amount) { + Stock stock = stockRepository.findStockByCode(symbol); + if (stock != null) { + RawStockApiResponse rawData = fetchChartData(symbol, interval, amount); + return convertToHistoryResponse(rawData); + } else { + StockHistoryResponse stockHistoryResponse = new StockHistoryResponse(); + stockHistoryResponse.setS("fail!"); + return stockHistoryResponse; + } + } + + private RawStockApiResponse fetchChartData(String symbol, String interval, Integer amount) { + Config config = SpringUtils.getBean(Config.class); + String url = config.getStockUrlPrefix() + "/api/ger-market/chart?symbol=" + symbol + "&interval=" + interval; + if (amount != null) { + url += "&amount=" + amount; + } + + HttpHeaders headers = new HttpHeaders(); + headers.add("accept", "application/json, text/plain, */*"); + headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8"); + headers.add("origin", "https://moneytj.com"); + headers.add("referer", "https://moneytj.com/"); + headers.add("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\""); + headers.add("sec-ch-ua-mobile", "?0"); + headers.add("sec-ch-ua-platform", "\"Windows\""); + headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"); + + HttpEntity entity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + + try { + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(response.getBody(), RawStockApiResponse.class); + } catch (Exception e) { + e.printStackTrace(); + return new RawStockApiResponse(); + } + } + + public StockQuoteData getSingleStockQuote(String symbol) { + Config config = SpringUtils.getBean(Config.class); + String url = config.getStockUrlPrefix() + "/api/ger-market/stocks/query-list?symbols=" + symbol; + + HttpHeaders headers = new HttpHeaders(); + headers.add("accept", "application/json, text/plain, */*"); + headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8"); + headers.add("origin", "https://moneytj.com"); + headers.add("referer", "https://moneytj.com/"); + headers.add("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\""); + headers.add("sec-ch-ua-mobile", "?0"); + headers.add("sec-ch-ua-platform", "\"Windows\""); + headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"); + + HttpEntity entity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + + try { + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + ObjectMapper mapper = new ObjectMapper(); + StockQuoteResponse quoteResponse = mapper.readValue(response.getBody(), StockQuoteResponse.class); + if (quoteResponse != null && quoteResponse.getData() != null && !quoteResponse.getData().isEmpty()) { + return quoteResponse.getData().get(0); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + public List getStocksQuote(List stocks) { + Config config = SpringUtils.getBean(Config.class); + String codes = stocks.stream().map(e->e.getStockCode()).collect(Collectors.joining(",")); + + String url = config.getStockUrlPrefix() + "/api/ger-market/stocks/query-list?symbols=" + codes; + + HttpHeaders headers = new HttpHeaders(); + headers.add("accept", "application/json, text/plain, */*"); + headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8"); + headers.add("origin", "https://moneytj.com"); + headers.add("referer", "https://moneytj.com/"); + headers.add("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\""); + headers.add("sec-ch-ua-mobile", "?0"); + headers.add("sec-ch-ua-platform", "\"Windows\""); + headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"); + + HttpEntity entity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + + try { + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + ObjectMapper mapper = new ObjectMapper(); + StockQuoteResponse quoteResponse = mapper.readValue(response.getBody(), StockQuoteResponse.class); + if (quoteResponse != null && quoteResponse.getData() != null && !quoteResponse.getData().isEmpty()) { + return quoteResponse.getData(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + private StockHistoryResponse convertToHistoryResponse(RawStockApiResponse apiResponse) { + StockHistoryResponse response = new StockHistoryResponse(); + response.setS("ok"); + response.setT(new ArrayList<>()); + response.setO(new ArrayList<>()); + response.setH(new ArrayList<>()); + response.setL(new ArrayList<>()); + response.setC(new ArrayList<>()); + response.setV(new ArrayList<>()); + + List data = apiResponse.getData(); + for (int i = data.size() - 1; i >= 0; i--) { + StockDataDto item = data.get(i); + response.getT().add(item.getTime()); + response.getO().add(item.getOpen()); + response.getH().add(item.getMax()); + response.getL().add(item.getMin()); + response.getC().add(item.getClose()); + response.getV().add(item.getVolume()); + } + + return response; + } + + public List getTopGainersFromTradingView() { + List result = getTopStocksFromTradingView("desc"); + // Sort by percent_change in descending order for gainers + return result.stream() + .sorted((a, b) -> Double.compare(b.getPercent_change(), a.getPercent_change())) + .collect(Collectors.toList()); + } + + public List getTopLosersFromTradingView() { + List result = getTopStocksFromTradingView("asc"); + // Sort by percent_change in ascending order for losers + return result.stream() + .sorted((a, b) -> Double.compare(a.getPercent_change(), b.getPercent_change())) + .collect(Collectors.toList()); + } + + public List getTopStocksFromTradingView(String sortOrder) { + String url = "https://scanner.tradingview.com/germany/scan"; + + // Determine preset based on sort order + String preset = "desc".equals(sortOrder) ? "gainers" : "losers"; + + // Prepare request body + String requestBody = "{\n" + + " \"columns\": [\n" + + " \"name\",\n" + + " \"description\",\n" + + " \"logoid\",\n" + + " \"update_mode\",\n" + + " \"type\",\n" + + " \"currency\",\n" + + " \"change\",\n" + + " \"volume\",\n" + + " \"exchange\"\n" + + " ],\n" + + " \"filter\": [\n" + + " {\n" + + " \"left\": \"is_primary\",\n" + + " \"operation\": \"equal\",\n" + + " \"right\": true\n" + + " }\n" + + " ],\n" + + " \"options\": {\n" + + " \"lang\": \"en\"\n" + + " },\n" + + " \"preset\": \"" + preset + "\",\n" + + " \"range\": [\n" + + " 0,\n" + + " 30\n" + + " ],\n" + + " \"sort\": {\n" + + " \"sortBy\": \"change\",\n" + + " \"sortOrder\": \"" + sortOrder + "\"\n" + + " },\n" + + " \"markets\": [\n" + + " \"germany\"\n" + + " ]\n" + + "}"; + + HttpHeaders headers = new HttpHeaders(); + headers.add("accept", "application/json"); + headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8,ug;q=0.7,fr;q=0.6"); + headers.add("origin", "https://www.tradingview.com"); + headers.add("referer", "https://www.tradingview.com/"); + headers.add("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"); + headers.add("Content-Type", "application/json"); + + HttpEntity entity = new HttpEntity<>(requestBody, headers); + RestTemplate restTemplate = new RestTemplate(); + + try { + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + ObjectMapper mapper = new ObjectMapper(); + TradingViewResponse tradingViewResponse = mapper.readValue(response.getBody(), TradingViewResponse.class); + + if (tradingViewResponse != null && tradingViewResponse.getData() != null) { + // Extract symbols from TradingView response + List symbols = tradingViewResponse.getData().stream() + .filter(item -> item.getS() != null && item.getS().startsWith("XETR:")) + .map(item -> item.getS().substring(5)) // Remove "XETR:" prefix + .limit(30) + .collect(Collectors.toList()); + + if (!symbols.isEmpty()) { + // Find stocks in database + List stocks = stockRepository.findAll(QStockPO.stockPO.stockCode.in(symbols)); + + if (!stocks.isEmpty()) { + List stockQuoteDatas = getStocksQuote(stocks); + for (StockQuoteData stockQuoteData : stockQuoteDatas) { + Stock name = stocks.stream().filter(e->e.getStockCode().equals(stockQuoteData.getSymbol())).findFirst().orElse(null); + if (name != null) { + stockQuoteData.setName(name.getStockName()); + } + } + return stockQuoteDatas; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return new ArrayList<>(); + } +} diff --git a/src/main/resources/application-base-alpha.yml b/src/main/resources/application-base-alpha.yml index 9b896bb..b2e9501 100644 --- a/src/main/resources/application-base-alpha.yml +++ b/src/main/resources/application-base-alpha.yml @@ -3,9 +3,9 @@ spring: show-sql: true # Redis配置 redis: - host: 43.153.174.179 + host: 43.153.142.41 password: a5v8b86P4mVzFlUqJV - port: 30001 + port: 30041 database: 1 lettuce: pool: @@ -17,7 +17,7 @@ spring: datasource: stock-market: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://43.153.174.179:30000/india_stock?useUnicode=true&characterEncoding=utf-8 + url: jdbc:mysql://43.153.142.41:30040/germany_stock?useUnicode=true&characterEncoding=utf-8 username: root password: uNejHIFQGJOUtYTmE maxActive: 500 @@ -28,7 +28,7 @@ spring: stock: driver-class-name: com.mysql.cj.jdbc.Driver #url: jdbc:mysql://129.226.172.67:3306/vip_huananyong_c?useUnicode=true&characterEncoding=utf-8&useSSL=true&verifyServerCertificate=true&requireSSL=true&clientCertificateKeyStoreUrl=classpath:keystoremysql&clientCertificateKeyStorePassword=abs1234567890&trustCertificateKeyStoreUrl=classpath:truststoremysql&trustCertificateKeyStorePassword=abs1234567890 - url: jdbc:mysql://124.156.133.209:33306/vip_huananyong_c?useUnicode=true&characterEncoding=utf-8&useSSL=true + url: jdbc:mysql://43.153.142.41:33306/vip_huananyong_c?useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: 33BsUUcnXRYgwt maxActive: 500 @@ -42,6 +42,8 @@ gugudataAppKey: K5LZKV8KAYM4 aliyunAccessKeyId: LTAI5tJi2z8cegG8fTW7BSQu aliyunAccessKeySecret: fnCI9LUcqLuH7D6nkhvSHQMob1JSm8 aliyunAppCode: 75ef2615da614eaaa71e2e2058fc53b0 + +stockUrlPrefix: https://apinode-dgdev.moneytj.com # 具体看类:CloudStorageConfig oss: type: 2 # 类型 1:七牛 2:阿里云 3:腾讯云