"),doc.html().lastIndexOf("
")+500);
+ } catch (Exception e) {
+ return e.toString();
+ }
+ return result;
+ }
+
+ public String getNewsInfo(String url) {
+ String result = "";
+ try {
+ // 使用Jsoup连接到网页
+ Document doc = Jsoup.connect(url)
+ .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36")
+ .header("Referer", "https://www.business-standard.com/")
+ .header("Accept-Language", "en-US,en;q=0.9")
+ .get();
+ result = doc.html().substring(doc.html().indexOf("articleBody") + 15, doc.html().indexOf(",\"author\":") - 1);
+ } catch (Exception e) {
+ return e.toString();
+ }
+ return result;
+ }
+
public ServerResponse getStock(int pageNum, int pageSize, String keyWords, String stockPlate, String stockType,
HttpServletRequest request) {
Page page = repository.findStockListByKeyWords(keyWords, stockPlate, stockType, 0, PageParam.of(pageNum, pageSize));
@@ -711,4 +761,215 @@ public class StockService {
}
return ServerResponse.createByErrorMsg("请求失败");
}
+
+ public static String find_overview_table_value_with_key(JSONArray overview_table_value, String key) {
+ for (Object obj : overview_table_value) {
+ JSONObject item = (JSONObject) obj;
+ if(StringUtils.equals(item.getString("key"), key)) {
+ return item.getString("val");
+ }
+ }
+
+ return null;
+ }
+
+ public static String numberToString(String string) {
+ return StringUtils.replace(string, ",", "");
+ }
+
+ public static void addHeader(Builder builder,String headerLines) {
+ String[] headers = StringUtils.split(headerLines, "\n");
+ for (String header : headers) {
+ String[] split2 = StringUtils.split(header, ": ");
+ String key = split2[0];
+ String value = StringUtils.substring(header, key.length() + 2);
+ builder.header(key, value);
+ }
+ }
+
+ public ServerResponse syncIndiaData(){
+ log.info("同步股票数据开始。。。。");
+ Map stockMap = StockRepository.of().cacheCodeMap();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ List list = Lists.newArrayList();
+ int currentTotal = 0;
+ int pageNum =1;
+ int pageSize = 300;
+ try {
+ for(int i = 0;i < pageNum;i ++) {
+ String tmpl = "https://api.investing.com/api/financialdata/assets/equitiesByCountry/default?fields-list=id,name,symbol,high,low,last,lastPairDecimal,change,changePercent,volume,time,isOpen,url,flag,countryNameTranslated,exchangeId,performanceDay,performanceWeek,performanceMonth,performanceYtd,performanceYear,performance3Year,technicalHour,technicalDay,technicalWeek,technicalMonth,avgVolume,fundamentalMarketCap,fundamentalRevenue,fundamentalRatio,fundamentalBeta,pairType&country-id=14&page={}&page-size={}&include-major-indices=false&include-additional-indices=false&include-primary-sectors=false&include-other-indices=false&limit=0";
+ String url = StrFormatter.format(tmpl, pageNum - 1, pageSize);
+// String str = HttpClientRequest.doGet(url);
+ JSONObject jsonObject = InvestingInvokerApis.of().__page(pageNum, pageSize);
+// JSONObject jsonObject = JSON.parseObject(str);
+ int total = 0;
+ if(jsonObject.containsKey("total")){
+ total = Integer.parseInt(jsonObject.get("total").toString());
+ }
+ JSONArray dataObjArray = new JSONArray();
+ if(jsonObject.containsKey("data")){
+ dataObjArray = JSON.parseArray(jsonObject.get("data").toString());
+ }
+ for (Object obj : dataObjArray) {
+ JSONObject jsonObject2 = JSON.parseObject(obj.toString());
+ String code = jsonObject2.get("Id").toString();
+ String name = jsonObject2.get("Name").toString();
+ String stockSymbol = jsonObject2.get("Symbol").toString();
+ String exchangeId = jsonObject2.get("ExchangeId").toString();
+ if (stockMap.containsKey(code)) {
+ log.info("已经存在 {} 信息, 跳过", code);
+ continue;
+ }
+
+ if(! Utils.isShOrSzOrBJ(code)) {
+ log.info("{} 非 sh 或者 sz 或者 bj , 跳过", code);
+ continue;
+ }
+ Stock stock = new Stock();
+ stock.setStockSymbol(stockSymbol);
+ stock.setStockExchangeId(exchangeId);
+ stock.setStockName(name);
+ stock.setStockCode(code);
+ stock.setIsLock(0);
+ stock.setIsShow(0);
+ stock.setAddTime(new Date());
+ stock.setStockState(0);
+ list.add(stock);
+ }
+ currentTotal += pageSize;
+ if((total - currentTotal) < pageSize ){
+ pageSize = total - currentTotal;
+ }
+ if(total <= currentTotal){
+ break;
+ }
+ pageNum ++;
+ }
+ if(CollectionUtils.isNotEmpty(list)) {
+ StockRepository.of().saveAll(list);
+ }
+ int count = list.size();
+ log.info("syncAFutureStockList执行, 受影响数{}, 耗时:{}毫秒", count, stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ log.info("同步股票数据结束。。。。");
+ return ServerResponse.createBySuccess();
+ } catch (Exception e) {
+ log.info("同步股票数据异常,异常信息{}。。。。",e.getMessage());
+ return ServerResponse.createByErrorMsg(e.getMessage());
+ }
+ }
+ public void syncIndiaData2(){
+ log.info("同步股票数据开始。。。。");
+ Map stockMap = StockRepository.of().cacheCodeMap();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ List list = Lists.newArrayList();
+ int currentTotal = 0;
+ int pageNum =1;
+ int pageSize = 300;
+ try {
+
+ String str = Utils.readTxt();
+ JSONObject jsonObject = JSON.parseObject(str);
+ JSONArray dataObjArray = JSON.parseArray(jsonObject.get("data").toString());
+ for (Object obj : dataObjArray) {
+ JSONObject jsonObject2 = JSON.parseObject(obj.toString());
+ String code = jsonObject2.get("Id").toString();
+ String name = jsonObject2.get("Name").toString();
+ String stockSymbol = jsonObject2.get("Symbol").toString();
+ String exchangeId = jsonObject2.get("ExchangeId").toString();
+ String fundamentalMarketCap = null;
+ if(jsonObject2.containsKey("FundamentalMarketCap") && jsonObject2.get("FundamentalMarketCap") != null){
+ fundamentalMarketCap = jsonObject2.get("FundamentalMarketCap").toString();
+ }
+ if (stockMap.containsKey(code)) {
+ log.info("已经存在 {} 信息, 跳过", code);
+ continue;
+ }
+
+ if(! Utils.isShOrSzOrBJ(code)) {
+ log.info("{} 非 sh 或者 sz 或者 bj , 跳过", code);
+ continue;
+ }
+ Stock stock = new Stock();
+ stock.setStockSymbol(stockSymbol);
+ stock.setStockExchangeId(exchangeId);
+ stock.setStockName(name);
+ stock.setStockCode(code);
+ stock.setIsLock(0);
+ stock.setIsShow(0);
+ stock.setAddTime(new Date());
+ stock.setStockState(0);
+// stock.setFundamentalMarketCap(fundamentalMarketCap);
+ list.add(stock);
+ }
+
+ if(CollectionUtils.isNotEmpty(list)) {
+ StockRepository.of().saveAll(list);
+ }
+ int count = list.size();
+ log.info("syncAFutureStockList执行, 受影响数{}, 耗时:{}毫秒", count, stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ log.info("同步股票数据结束。。。。");
+ } catch (Exception e) {
+ log.info("同步股票数据异常,异常信息{}。。。。",e.getMessage());
+ }
+
+ }
+
+ public String jsoupByUrl(String url) {
+ String result = "";
+ try {
+ // 使用Jsoup连接到网页
+ Document doc = Jsoup.connect(url)
+ .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36")
+ .header("Referer", "https://www.business-standard.com/")
+ .header("Accept-Language", "en-US,en;q=0.9")
+ .get();
+ result = doc.html();
+ } catch (Exception e) {
+ return e.toString();
+ }
+ return result;
+ }
+
+ public ServerResponse getIndiaIndex() {
+ List indexVoList = new ArrayList<>();
+ //获取BSESENSEX指数
+
+ try {
+ IndiaIndexVo vo1 = new IndiaIndexVo();
+ String stockCode = "39929";
+ IndiaStockVO market = InvestingApis.of().market(StockCode.of(stockCode));
+ market.setName("BSESENSEX指数");
+ vo1.setIndexVo(market);
+
+ String type = "min";
+ List list = InvestingApis.of().kline(StockCode.of(stockCode), type);
+ if(list == null || list.size() == 0){
+ type = "day";
+ list = InvestingApis.of().kline(StockCode.of(stockCode), type);
+ }
+ vo1.setKLine(list);
+ indexVoList.add(vo1);
+ } catch (IOException e) {
+ log.info("获取BSESENSEX指数数据异常,异常信息{}。。。。",e.getMessage());
+ }
+ try {
+ IndiaIndexVo vo2 = new IndiaIndexVo();
+ String stockCode = "8985"; //"17940";
+ IndiaStockVO market = InvestingApis.of().market(StockCode.of(stockCode));
+ market.setName("NIFTY50指数");
+ vo2.setIndexVo(market);
+
+ String type = "min";
+ List list = InvestingApis.of().kline(StockCode.of(stockCode), type);
+ if(list == null || list.size() == 0){
+ type = "day";
+ list = InvestingApis.of().kline(StockCode.of(stockCode), type);
+ }
+ vo2.setKLine(list);
+ indexVoList.add(vo2);
+ } catch (IOException e) {
+ log.info("获取NIFTY50指数数据异常,异常信息{}。。。。",e.getMessage());
+ }
+ return ServerResponse.createBySuccess(indexVoList);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/cn/stock/market/dto/BtodayStockDTO.java b/src/main/java/cn/stock/market/dto/BtodayStockDTO.java
new file mode 100644
index 0000000..d6d0bf3
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/BtodayStockDTO.java
@@ -0,0 +1,23 @@
+package cn.stock.market.dto;
+
+import cn.stock.market.infrastructure.db.po.BtodayStockPO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * BtodayStockDTO
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/25
+ */
+@Data
+@NoArgsConstructor
+@SuperBuilder
+@EqualsAndHashCode(
+ callSuper = false
+)
+public class BtodayStockDTO extends BtodayStockPO {
+}
diff --git a/src/main/java/cn/stock/market/dto/StockDTO.java b/src/main/java/cn/stock/market/dto/StockDTO.java
index 8dc74a6..7a51af1 100644
--- a/src/main/java/cn/stock/market/dto/StockDTO.java
+++ b/src/main/java/cn/stock/market/dto/StockDTO.java
@@ -1,6 +1,6 @@
package cn.stock.market.dto;
-import cn.stock.market.infrastructure.stockdb.po.StockPO;
+import cn.stock.market.infrastructure.db.po.StockPO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
diff --git a/src/main/java/cn/stock/market/dto/StockIpoDTO.java b/src/main/java/cn/stock/market/dto/StockIpoDTO.java
new file mode 100644
index 0000000..8d3cb67
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/StockIpoDTO.java
@@ -0,0 +1,23 @@
+package cn.stock.market.dto;
+
+import cn.stock.market.infrastructure.db.po.StockIpoPO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * StockIpoDTO
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/28
+ */
+@Data
+@NoArgsConstructor
+@SuperBuilder
+@EqualsAndHashCode(
+ callSuper = false
+)
+public class StockIpoDTO extends StockIpoPO {
+}
diff --git a/src/main/java/cn/stock/market/dto/command/BtodayStockCreateCommand.java b/src/main/java/cn/stock/market/dto/command/BtodayStockCreateCommand.java
new file mode 100644
index 0000000..db71552
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/command/BtodayStockCreateCommand.java
@@ -0,0 +1,54 @@
+package cn.stock.market.dto.command;
+
+import java.lang.Integer;
+import java.lang.String;
+import java.util.Date;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * BtodayStockCreateCommand
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/25
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+public class BtodayStockCreateCommand {
+ /**
+ * 主键 */
+ Integer id;
+
+ /**
+ * 股票名称 */
+ String stockName;
+
+ /**
+ * 股票code */
+ String stockCode;
+
+ /**
+ * 类型 */
+ String stockType;
+
+ String stockSpell;
+
+ /**
+ * btoday的业务id */
+ String coCode;
+
+ /**
+ * 主页的http链接 */
+ String selfUrl;
+
+ /**
+ * url */
+ String url;
+
+ /**
+ * 上次更新时间 */
+ Date lastUpdateTime;
+}
diff --git a/src/main/java/cn/stock/market/dto/command/BtodayStockModifyCommand.java b/src/main/java/cn/stock/market/dto/command/BtodayStockModifyCommand.java
new file mode 100644
index 0000000..0982df3
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/command/BtodayStockModifyCommand.java
@@ -0,0 +1,24 @@
+package cn.stock.market.dto.command;
+
+import java.lang.Integer;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * BtodayStockModifyCommand
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/25
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+@EqualsAndHashCode(
+ callSuper = false
+)
+public class BtodayStockModifyCommand extends BtodayStockCreateCommand {
+ Integer id;
+}
diff --git a/src/main/java/cn/stock/market/dto/command/StockIpoCreateCommand.java b/src/main/java/cn/stock/market/dto/command/StockIpoCreateCommand.java
new file mode 100644
index 0000000..ba75cc2
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/command/StockIpoCreateCommand.java
@@ -0,0 +1,69 @@
+package cn.stock.market.dto.command;
+
+import java.lang.Integer;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.util.Date;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * StockIpoCreateCommand
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/28
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+public class StockIpoCreateCommand {
+ Integer id;
+
+ /**
+ * 股票代码 */
+ String stockCode;
+
+ /**
+ * 股票名称 */
+ String stockName;
+
+ /**
+ * 发行价格 */
+ BigDecimal stockPrice;
+
+ /**
+ * 申购日期 */
+ Date subscriptionDate;
+
+ /**
+ * 上市日期 */
+ Date listingDate;
+
+ /**
+ * 是否显示【1 显示,2 不显示】 */
+ Integer isShow;
+
+ /**
+ * 是否上市【1 未上市,2 已上市】 */
+ Integer isList;
+
+ Date createDate;
+
+ Date updateDate;
+
+ /**
+ * 发行总数 */
+ Integer totalNumber;
+
+ /**
+ * 申请总额 */
+ String apply;
+
+ /**
+ * 市盈率 */
+ BigDecimal peRatio;
+
+ String sourceType;
+}
diff --git a/src/main/java/cn/stock/market/dto/command/StockIpoModifyCommand.java b/src/main/java/cn/stock/market/dto/command/StockIpoModifyCommand.java
new file mode 100644
index 0000000..005dbd4
--- /dev/null
+++ b/src/main/java/cn/stock/market/dto/command/StockIpoModifyCommand.java
@@ -0,0 +1,24 @@
+package cn.stock.market.dto.command;
+
+import java.lang.Integer;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * StockIpoModifyCommand
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/28
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+@EqualsAndHashCode(
+ callSuper = false
+)
+public class StockIpoModifyCommand extends StockIpoCreateCommand {
+ Integer id;
+}
diff --git a/src/main/java/cn/stock/market/dto/model/StockCode.java b/src/main/java/cn/stock/market/dto/model/StockCode.java
index 9da5156..d537f51 100644
--- a/src/main/java/cn/stock/market/dto/model/StockCode.java
+++ b/src/main/java/cn/stock/market/dto/model/StockCode.java
@@ -14,17 +14,20 @@ import lombok.experimental.SuperBuilder;
@ApiModel
public class StockCode {
String code;
- StockSource source;
+// StockSource source;
public static StockCode a(String code) {
- return of(code, StockSource.A);
+ return StockCode.builder().code(code)
+ .build();
}
-
- public static StockCode of(String code, String source) {
- return of(code, StockSource.of(source));
+ public static StockCode of(String code) {
+ return StockCode.builder().code(code)
+ .build();
}
public static StockCode of(String code, StockSource source) {
- return StockCode.builder().code(code).source(source).build();
+ return StockCode.builder().code(code)
+// .source(source)
+ .build();
}
}
diff --git a/src/main/java/cn/stock/market/dto/model/StockVO.java b/src/main/java/cn/stock/market/dto/model/StockVO.java
index 45e088d..c141ff8 100644
--- a/src/main/java/cn/stock/market/dto/model/StockVO.java
+++ b/src/main/java/cn/stock/market/dto/model/StockVO.java
@@ -61,7 +61,16 @@ public class StockVO {
private String monthImg;
private Integer depositAmt;
-
+ private String volume;
+
+ public String getVolume() {
+ return volume;
+ }
+
+ public void setVolume(String volume) {
+ this.volume = volume;
+ }
+
public static StockVO from(StockDetail detail, Stock stock) {
StockVO vo = new StockVO();
if(stock != null) {
diff --git a/src/main/java/cn/stock/market/infrastructure/api/TodayApis.java b/src/main/java/cn/stock/market/infrastructure/api/TodayApis.java
new file mode 100644
index 0000000..bd50302
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/api/TodayApis.java
@@ -0,0 +1,114 @@
+package cn.stock.market.infrastructure.api;
+
+import cn.stock.market.utils.HttpClientRequest;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 数据来源网站:https://www.businesstoday.in/stocks
+ *
+ * @auther xiaoliuhu
+ */
+public class TodayApis {
+
+ static String get(String url) {
+ return HttpClientRequest.doGet(url);
+ }
+
+ //获取Top Gainers
+ public static List getTopGainers(String exchange){
+ String url = "https://marketapi.intoday.in/widget/topgainer/view?exchange=" + exchange;
+ //返回字符串对象格式:{"status_code":1,"success":true,"data":[{}],"message":"Successful","fromredis":true}
+ String str = get(url);
+ JSONObject object = JSON.parseObject(str);
+ boolean bool = false;
+ if(object.containsKey("success")){
+ bool = object.getBoolean("success");
+ }
+ if(!bool){
+ return null;
+ }
+ //进行数据转换
+ List dataObject = new ArrayList<>();
+ JSONArray jsonArray = JSON.parseArray(object.getString("data"));
+ for (Object obj : jsonArray){
+ JSONObject entity = JSON.parseObject(obj.toString());
+ dataObject.add(entity);
+ }
+ return dataObject;
+ }
+ //获取Top Losers
+ public static List getTopLosers(String exchange){
+ String url = "https://marketapi.intoday.in/widget/toploser/view?exchange=" + exchange;
+ //返回字符串对象格式:{"status_code":1,"success":true,"data":[{}],"message":"Successful","fromredis":true}
+ String str = get(url);
+ JSONObject object = JSON.parseObject(str);
+ boolean bool = false;
+ if(object.containsKey("success")){
+ bool = object.getBoolean("success");
+ }
+ if(!bool){
+ return null;
+ }
+ //进行数据转换
+ List dataObject = new ArrayList<>();
+ JSONArray jsonArray = JSON.parseArray(object.getString("data"));
+ for (Object obj : jsonArray){
+ JSONObject entity = JSON.parseObject(obj.toString());
+ dataObject.add(entity);
+ }
+ return dataObject;
+ }
+ //获取Most Active Volume
+ public static List getMostActiveVolume(String exchange){
+ String url = "https://marketapi.intoday.in/widget/mostactivetopper/view?exchange=" + exchange;
+ //返回字符串对象格式:{"status_code":1,"success":true,"data":[{}],"message":"Successful","fromredis":true}
+ String str = get(url);
+ JSONObject object = JSON.parseObject(str);
+ boolean bool = false;
+ if(object.containsKey("success")){
+ bool = object.getBoolean("success");
+ }
+ if(!bool){
+ return null;
+ }
+ //进行数据转换
+ List dataObject = new ArrayList<>();
+ JSONArray jsonArray = JSON.parseArray(object.getString("data"));
+ for (Object obj : jsonArray){
+ JSONObject entity = JSON.parseObject(obj.toString());
+ dataObject.add(entity);
+ }
+ return dataObject;
+ }
+ //获取Most Active Value
+ public static List getMostActiveValue(String exchange){
+ String url = "https://marketapi.intoday.in/widget/mostactivetopperbyvalue/view?exchange=" + exchange;
+ //返回字符串对象格式:{"status_code":1,"success":true,"data":[{}],"message":"Successful","fromredis":true}
+ String str = get(url);
+ JSONObject object = JSON.parseObject(str);
+ boolean bool = false;
+ if(object.containsKey("success")){
+ bool = object.getBoolean("success");
+ }
+ if(!bool){
+ return null;
+ }
+ //进行数据转换
+ List dataObject = new ArrayList<>();
+ JSONArray jsonArray = JSON.parseArray(object.getString("data"));
+ for (Object obj : jsonArray){
+ JSONObject entity = JSON.parseObject(obj.toString());
+ dataObject.add(entity);
+ }
+ return dataObject;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(getTopGainers("nse"));
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaIndexVo.java b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaIndexVo.java
new file mode 100644
index 0000000..031d2fc
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaIndexVo.java
@@ -0,0 +1,17 @@
+package cn.stock.market.infrastructure.api.investing;
+
+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 IndiaIndexVo {
+ @ApiModelProperty(value = "指数详情")
+ private IndiaStockVO indexVo;
+ @ApiModelProperty(value = "指数k线")
+ private List kLine;
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaStockVO.java b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaStockVO.java
new file mode 100644
index 0000000..ebf6d4f
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaStockVO.java
@@ -0,0 +1,82 @@
+package cn.stock.market.infrastructure.api.investing;
+
+import cn.qutaojing.common.utils.BigDecimals;
+import cn.stock.market.dto.model.StockVO;
+import lombok.Data;
+
+@Data
+public class IndiaStockVO {
+ private Integer id;
+ /**
+ * 股票名称
+ */
+ private String name;
+ /**
+ * 股票中文名称
+ */
+ private String cname;
+ /**
+ * 股票代码
+ */
+ private String code;
+
+ private Integer isLock;
+
+ private Integer isShow;
+
+ /**
+ * 股票价格
+ */
+ private String nowPrice;
+
+ private String open;
+
+ private String high;
+
+ private String low;
+
+ private String close;
+
+ /**
+ * 成交额(暂未获取到)
+ */
+ private String turnover;
+
+ /**
+ * 成交量
+ */
+ private String volume;
+
+ private String marketValue;
+
+ /**
+ * 股票涨跌幅
+ */
+ private String number;
+
+ /**
+ * 股票涨跌幅百分比
+ */
+ private String rate;
+
+ /*是否添加自选:1、添加自选,0、未添加自选*/
+ private String isOption;
+ private String time;
+ private String type; //股票类型
+ String url;
+ String targetId;
+
+ public StockVO toStockVo() {
+ StockVO vo = new StockVO();
+ vo.setName(name);
+ vo.setCode(code);
+ vo.setNowPrice(nowPrice);
+ vo.setHcrate(BigDecimals.divide(BigDecimals.p(rate), 100));
+ vo.setToday_max(high);
+ vo.setToday_min(low);
+ vo.setOpen_px(open);
+ vo.setPreclose_px(close);
+ vo.setVolume(volume);
+ return vo;
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingApis.java b/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingApis.java
new file mode 100644
index 0000000..0720def
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingApis.java
@@ -0,0 +1,259 @@
+package cn.stock.market.infrastructure.api.investing;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.ag.utils.CollectionUtils;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.github.pagehelper.PageInfo;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Lists;
+
+import cn.hutool.core.date.DateUtil;
+import cn.qutaojing.common.utils.BigDecimals;
+import cn.stock.market.dto.model.StockCode;
+import cn.stock.market.utils.RequestCacheUtils;
+import cn.stock.market.utils.ServerResponse;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class InvestingApis {
+
+ public IndiaStockVO market(StockCode code) {
+ List list = batchMarket(Lists.newArrayList(code));
+ return CollectionUtils.isNotEmpty(list) ? list.get(0) : null;
+ }
+
+ public List batchMarket(List list) {
+ List retList = Lists.newArrayList();
+ for (StockCode code : list) {
+ ServerResponse response = RequestCacheUtils.cache("_INDIA_", "targetId_" + code.getCode(), (string) -> {
+ IndiaStockVO vo = _market(code);
+ return ServerResponse.createBySuccess(vo);
+ });
+
+ if(response.getData() != null) {
+ retList.add(response.getData());
+ }
+ }
+
+ return retList;
+ }
+
+ public IndiaStockVO _market(StockCode code) {
+ try {
+ JSONObject json = InvestingInvokerApis.of().__market(code);
+ JSONArray pairs_data = json
+ .getJSONArray("data").getJSONObject(0)
+ .getJSONObject("screen_data").getJSONArray("pairs_data");
+ IndiaStockVO vo = new IndiaStockVO();
+ JSONArray overview_table = pairs_data.getJSONObject(0).getJSONArray("overview_table");
+ JSONObject info_header = pairs_data.getJSONObject(0).getJSONObject("info_header");
+ String pair_name_base = info_header.getString("pair_name_base");
+ String pair_ID = info_header.getString("pair_ID");
+
+ vo.setName(pair_name_base);
+ vo.setCname(pair_name_base);
+ vo.setCode(pair_ID);
+ vo.setTargetId(pair_ID);
+
+ String 昨收 = find_overview_table_value_with_key(overview_table, "昨收");
+ vo.setClose(numberToString(昨收));
+ String 开盘 = find_overview_table_value_with_key(overview_table, "开盘");
+ String 成交量 = find_overview_table_value_with_key(overview_table, "成交量"); //658,477,949
+ vo.setVolume(numberToString(成交量));
+
+ if(StringUtils.isNotBlank(开盘)) {
+ vo.setOpen(numberToString(开盘));
+ }
+ String 当日幅度 = find_overview_table_value_with_key(overview_table, "当日幅度"); //6.91 - 7.89
+ if(StringUtils.isNotBlank(当日幅度)) {
+ String[] split = 当日幅度.trim().split("-");
+ vo.setLow(numberToString(split[0]));
+ vo.setHigh(numberToString(split[1]));
+ } else {
+ }
+
+ String last = info_header.getString("last"); //当前价
+ vo.setNowPrice(numberToString(last));
+
+ String percent_tooltip_value = info_header.getString("percent_tooltip_value"); //涨幅
+ vo.setRate(percent_tooltip_value);
+ String change = info_header.getString("change"); //涨幅值 ("+0.65")
+ if(change.startsWith("+")) {
+ vo.setNumber(numberToString(change.substring(1)));
+ } else {
+ vo.setNumber(numberToString(change));
+ }
+
+ if(BigDecimals.loeZero(vo.getNowPrice())) {
+ log.warn("{} 当前价非法, 将忽略.", JSON.toJSONString(vo));
+ return null;
+ } else {
+ return vo;
+ }
+ } catch (IOException e) {
+ log.error("batchIndiaStockMarketData>>code:{}, 失败. 跳过", code, e);
+ }
+ return null;
+ }
+
+ public static String numberToString(String string) {
+ return StringUtils.replace(string, ",", "");
+ }
+
+ public static String find_overview_table_value_with_key(JSONArray overview_table_value, String key) {
+ for (Object obj : overview_table_value) {
+ JSONObject item = (JSONObject) obj;
+ if(StringUtils.equals(item.getString("key"), key)) {
+ return item.getString("val");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * "AvgVolume": 3741486,
+ "Chg": -9.55,
+ "ChgPct": -5.25,
+ "CountryNameTranslated": "India",
+ "ExchangeId": "46",
+ "Flag": "IN",
+ "FundamentalBeta": 0.916,
+ "FundamentalMarketCap": 448530000000,
+ "FundamentalRatio": 8.41,
+ "FundamentalRevenue": "250.56B",
+ "High": 183,
+ "Id": "7310",
+ "IsOpen": "0",
+ "Last": 172.35,
+ "LastPairDecimal": 2,
+ "Low": 171.45,
+ "Name": "Aditya Birla Capital",
+ "PairType": "Equities",
+ "Performance3Year": 150.69,
+ "PerformanceDay": -5.25,
+ "PerformanceMonth": -2.38,
+ "PerformanceWeek": -5.67,
+ "PerformanceYear": 48.71,
+ "PerformanceYtd": 14.75,
+ "Symbol": "ADTB",
+ "TechnicalDay": "strong_sell",
+ "TechnicalHour": "strong_sell",
+ "TechnicalMonth": "strong_buy",
+ "TechnicalWeek": "sell",
+ "Time": "1698055197",
+ "Url": "/equities/aditya-birla",
+ "Volume": 3693615
+ * @param httpClient
+ * @param currPage
+ * @param pageSize
+ * @return
+ * @throws IOException
+ */
+ public PageInfo page(int currPage, int pageSize) throws IOException {
+ JSONObject json = InvestingInvokerApis.of().__page(currPage, pageSize);
+
+ int totalPages = (json.getIntValue("total") / json.getIntValue("pageSize")) + 1;
+ log.info("总页码数: {}", totalPages);
+
+ List items = json.getJSONArray("data").stream().map(val -> {
+ JSONObject j = (JSONObject) val;
+ IndiaStockVO vo = new IndiaStockVO();
+ vo.setName(j.getString("Name"));
+ vo.setCname(j.getString("Name"));
+ vo.setCode(j.getString("Symbol"));
+ vo.setIsLock(0);
+ vo.setIsShow(0);
+ vo.setNowPrice(numberToString(j.getString("Last")));
+ vo.setOpen("--");
+ vo.setClose("--");
+ vo.setNumber(numberToString(j.getString("Chg")));
+ vo.setRate(numberToString(j.getString("ChgPct")));
+ vo.setHigh(numberToString(j.getString("High")));
+ vo.setLow(numberToString(j.getString("Low")));
+ vo.setUrl(numberToString(j.getString("Url")));
+ vo.setTargetId(j.getString("Id"));
+ vo.setType("印度");
+ return vo;
+ }).collect(Collectors.toList());
+
+ PageInfo page = new PageInfo<>();
+ page.setPageNum(currPage);
+ page.setPageSize(pageSize);
+ page.setTotal(json.getIntValue("total"));
+ page.setPages(totalPages);
+ page.setList(items);
+ return page;
+ }
+
+ public List thirdIndiaList() throws IOException {
+ List list = Lists.newArrayList();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ int totalPages = 0;
+ int currPage = 0;
+ do {
+ try {
+ PageInfo page = page(currPage, 100);
+
+ totalPages = page.getPages();
+ currPage ++;
+ list.addAll(page.getList());
+ } catch(Exception e) {
+ log.error("发送错误, 跳过", e);
+ continue;
+ }
+ } while(currPage < totalPages);
+
+ log.info("获取印度股票列表执行完毕, 查询到数据 {} 条, 耗时: {} 毫秒", list.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ return list;
+ }
+
+ /**
+ *
+ * @param code
+ * @param string min/day/week/month
+ * @return
+ * @throws IOException
+ */
+ public List kline(StockCode code, String string) throws IOException {
+ if(code == null) {
+ throw new RuntimeException("找不到股票信息");
+ }
+ Date nowDate = new Date();
+ JSONObject json = InvestingInvokerApis.of().__kline(code, string);
+ return json
+ .getJSONArray("data")
+ .stream()
+ .map(val -> {
+ JSONArray _ar = (JSONArray) val;
+ JSONObject item = new JSONObject();
+ item.put("date", _ar.get(0));
+ item.put("open", _ar.get(1));
+ item.put("high", _ar.get(2));
+ item.put("low", _ar.get(3));
+ item.put("close", _ar.get(4));
+ item.put("volume", _ar.get(5));
+ return item;
+ }).filter(val -> {
+ if("min".equalsIgnoreCase(string)) {
+ return DateUtil.isSameDay(nowDate, new Date(val.getLong("date")));
+ }
+ return true;
+ })
+ .collect(Collectors.toList())
+ ;
+ }
+
+ public static InvestingApis of() {
+ return new InvestingApis();
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingInvokerApis.java b/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingInvokerApis.java
new file mode 100644
index 0000000..57924da
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingInvokerApis.java
@@ -0,0 +1,219 @@
+package cn.stock.market.infrastructure.api.investing;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.base.Stopwatch;
+
+import cn.hutool.core.text.StrFormatter;
+import cn.stock.market.dto.model.StockCode;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+
+@Slf4j
+public class InvestingInvokerApis {
+ public static final int COUNTRY_ID = 14; // INDIA
+ public static final String API_HEADER = ":method: GET\n"
+ + ":scheme: https\n"
+ + ":path: /get_screen.php?screen_ID=22&skinID=1&overview_table_order=1&time_utc_offset=28800&pair_ID=1156730&additionalTimeframes=Yes&lang_ID=6&include_pair_attr=true&v2=1\n"
+ + ":authority: cnappapi.investing.com\n"
+ + "cache-control: no-cache\n"
+ + "user-agent: Investing.China/64 CFNetwork/1390 Darwin/22.0.0\n"
+ + "x-os: ios\n"
+ + "x-idfa-perm: 0\n"
+ + "x-os-ver: 16.0\n"
+ + "x-app-ver: 156\n"
+ + "apf_src: no\n"
+ + "x-meta-ver: 14\n"
+ + "accept-language: zh-CN,zh-Hans;q=0.9\n"
+ + "accept: */*\n"
+ + "ccode: CN\n"
+ + "";
+ public static final String API_HEADER_LIST = "Host: api.investing.com\n"
+ + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/119.0\n"
+ + "Accept: application/json, text/plain, */*\n"
+ + "Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\n"
+ + "domain-id: cn\n"
+ + "Origin: https://cn.investing.com\n"
+ + "Connection: keep-alive\n"
+ + "Referer: https://cn.investing.com/\n"
+ + "Sec-Fetch-Dest: empty\n"
+ + "Sec-Fetch-Mode: cors\n"
+ + "Sec-Fetch-Site: same-site\n"
+ + "Pragma: no-cache\n"
+ + "Cache-Control: no-cache\n"
+ + "TE: trailers";
+
+ static OkHttpClient httpClient;
+ public static OkHttpClient httpClient() {
+ if(httpClient == null) {
+ X509TrustManager manager = SSLSocketClientUtil.getX509TrustManager();
+ httpClient = new OkHttpClient.Builder()
+ .followRedirects(true) // 为了制造非200状态码,禁止302跳转
+ .protocols(Collections.unmodifiableList(Arrays.asList(Protocol.HTTP_1_1, Protocol.HTTP_2)))// 启用http2.0协议 //, Protocol.HTTP_2
+ .sslSocketFactory(SSLSocketClientUtil.getSocketFactory(manager), manager)
+ .hostnameVerifier(SSLSocketClientUtil.getHostnameVerifier())//忽略校验
+ .retryOnConnectionFailure(true)
+ .connectTimeout(60, TimeUnit.SECONDS)
+ .readTimeout(60, TimeUnit.SECONDS)
+ .build();
+ }
+
+ return httpClient;
+ }
+
+ public static void addHeader(Builder builder,String headerLines) {
+ String[] headers = StringUtils.split(headerLines, "\n");
+ for (String header : headers) {
+ String[] split2 = StringUtils.split(header, ": ");
+ String key = split2[0];
+ String value = StringUtils.substring(header, key.length() + 2);
+ builder.header(key, value);
+ }
+ }
+
+ public static Builder builderGet(String url) {
+ return new Request.Builder().url(url).method("GET", null);
+ }
+
+ public JSONObject __market(StockCode code) throws IOException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ String tmpl = "https://cnappapi.investing.com/get_screen.php?v2=1&additionalTimeframes=Yes&time_utc_offset=28800&overview_table_order=0&skinID=1&lang_ID=6&include_pair_attr=true&screen_ID=22&pair_ID={}";
+ String url = StrFormatter.format(tmpl, code.getCode());
+ log.info("url: {}", url);
+ Builder builder = builderGet(url);
+ addHeader(builder, API_HEADER);
+ String body = httpClient().newCall(builder.build()).execute().body().string();
+ log.info("india market cost: {} ms, url: {}, body: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), url, body);
+ return JSON.parseObject(body);
+ }
+
+ public static String numberToString(String string) {
+ return StringUtils.replace(string, ",", "");
+ }
+
+ public static String find_overview_table_value_with_key(JSONArray overview_table_value, String key) {
+ for (Object obj : overview_table_value) {
+ JSONObject item = (JSONObject) obj;
+ if(StringUtils.equals(item.getString("key"), key)) {
+ return item.getString("val");
+ }
+ }
+
+ return null;
+ }
+
+ public JSONObject __page(int pageNum, int pageSize) throws IOException {
+ String tmpl = "https://api.investing.com/api/financialdata/assets/equitiesByCountry/default?fields-list=id,name,symbol,high,low,last,lastPairDecimal,change,changePercent,volume,time,isOpen,url,flag,countryNameTranslated,exchangeId,performanceDay,performanceWeek,performanceMonth,performanceYtd,performanceYear,performance3Year,technicalHour,technicalDay,technicalWeek,technicalMonth,avgVolume,fundamentalMarketCap,fundamentalRevenue,fundamentalRatio,fundamentalBeta,pairType&country-id=14&page={}&page-size={}&include-major-indices=false&include-additional-indices=false&include-primary-sectors=false&include-other-indices=false&limit=0";
+ String url = StrFormatter.format(tmpl, pageNum - 1, pageSize);
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ log.info("url: {}", url);
+ Builder builder = builderGet(url);
+ String body = httpClient().newCall(builder.build()).execute().body().string();
+ log.info("第{}页码, 耗时: {} 毫秒, 返回原始值: {}, 准备解析中.", pageNum, stopwatch.elapsed(TimeUnit.MILLISECONDS), body);
+ return JSON.parseObject(body);
+ }
+
+ /**
+ * 与 __page 的区别
+ * api/financialdata/assets/equitiesByCountry/default
+ * api/financialdata/assets/equitiesByCountry/17943
+ * @param pageNum
+ * @param pageSize
+ * @return
+ * @throws IOException
+ */
+ public JSONObject __page_nifty100(int pageNum, int pageSize) throws IOException {
+ String tmpl = "https://api.investing.com/api/financialdata/assets/equitiesByIndices/17943?fields-list=id%2Cname%2Csymbol%2Chigh%2Clow%2Clast%2ClastPairDecimal%2Cchange%2CchangePercent%2Cvolume%2Ctime%2CisOpen%2Curl%2Cflag%2CcountryNameTranslated%2CexchangeId%2CperformanceDay%2CperformanceWeek%2CperformanceMonth%2CperformanceYtd%2CperformanceYear%2Cperformance3Year%2CtechnicalHour%2CtechnicalDay%2CtechnicalWeek%2CtechnicalMonth%2CavgVolume%2CfundamentalMarketCap%2CfundamentalRevenue%2CfundamentalRatio%2CfundamentalBeta%2CpairType&country-id=14&page={}&page-size={}&include-major-indices=false&include-additional-indices=false&include-primary-sectors=false&include-other-indices=false&limit=0";
+// String tmpl = "https://api.investing.com/api/financialdata/assets/equitiesByCountry/17943?fields-list=id,name,symbol,high,low,last,lastPairDecimal,change,changePercent,volume,time,isOpen,url,flag,countryNameTranslated,exchangeId,performanceDay,performanceWeek,performanceMonth,performanceYtd,performanceYear,performance3Year,technicalHour,technicalDay,technicalWeek,technicalMonth,avgVolume,fundamentalMarketCap,fundamentalRevenue,fundamentalRatio,fundamentalBeta,pairType&country-id=14&page={}&page-size={}&include-major-indices=false&include-additional-indices=false&include-primary-sectors=false&include-other-indices=false&limit=0";
+ String url = StrFormatter.format(tmpl, pageNum - 1, pageSize);
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ log.info("url: {}", url);
+ Builder builder = builderGet(url);
+ String body = httpClient().newCall(builder.build()).execute().body().string();
+ log.info("第{}页码, 耗时: {} 毫秒, 返回原始值: {}, 准备解析中.", pageNum, stopwatch.elapsed(TimeUnit.MILLISECONDS), body);
+ return JSON.parseObject(body);
+ }
+
+ /**
+ *
+ * @param code
+ * @param string min/day/week/month
+ * @return
+ * @throws IOException
+ */
+ public JSONObject __kline(StockCode code, String string) throws IOException {
+ if(code == null) {
+ throw new RuntimeException("找不到股票信息");
+ }
+ String interval = null;
+ String period = null;
+ if("min".equalsIgnoreCase(string)) {
+ interval = "PT5M";
+ } else if("day".equalsIgnoreCase(string)) {
+ interval = "P1D";
+ } else if("week".equalsIgnoreCase(string)) {
+ interval = "P1W";
+ } else if("month".equalsIgnoreCase(string)) {
+ interval = "P1M";
+ } else if("3month".equalsIgnoreCase(string)) {
+ interval = "P1D";
+ period = "P3M";
+ } else if("6month".equalsIgnoreCase(string)) {
+ interval = "P1D";
+ period = "P6M";
+ } else if("year".equalsIgnoreCase(string)) {
+ interval = "P1W";
+ period = "P1Y";
+ }
+
+ if(StringUtils.isBlank(interval)) {
+ interval = string;
+ }
+
+ String url = null;
+ if(StringUtils.isNotBlank(period)) {
+ String tmpl = "https://api.investing.com/api/financialdata/{}/historical/chart/?period={}&interval={}&pointscount=160";
+ url = StrFormatter.format(tmpl, code.getCode(), period, interval);
+ } else {
+ String tmpl = "https://api.investing.com/api/financialdata/{}/historical/chart/?interval={}&pointscount=160";
+ url = StrFormatter.format(tmpl, code.getCode(), interval);
+ }
+
+ Builder builder = builderGet(url);
+
+ String body = httpClient().newCall(builder.build()).execute().body().string();
+ return JSON.parseObject(body);
+ }
+
+ public static InvestingInvokerApis of() {
+ return new InvestingInvokerApis();
+ }
+
+ public static void main(String[] args) throws IOException {
+ JSONObject __market = of().__page(1,100);
+ System.out.println(__market);
+
+ JSONObject __page = of().__page(1, 10);
+ JSONObject __page_nifty100 = of().__page_nifty100(1, 10);
+
+ System.out.println(__page);
+ System.out.println(__page_nifty100);
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/api/investing/SSLSocketClientUtil.java b/src/main/java/cn/stock/market/infrastructure/api/investing/SSLSocketClientUtil.java
new file mode 100644
index 0000000..1bfdd7d
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/api/investing/SSLSocketClientUtil.java
@@ -0,0 +1,60 @@
+package cn.stock.market.infrastructure.api.investing;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class SSLSocketClientUtil {
+
+ public static SSLSocketFactory getSocketFactory(TrustManager manager) {
+ SSLSocketFactory socketFactory = null;
+ try {
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, new TrustManager[]{manager}, new SecureRandom());
+ socketFactory = sslContext.getSocketFactory();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (KeyManagementException e) {
+ e.printStackTrace();
+ }
+ return socketFactory;
+ }
+
+ public static X509TrustManager getX509TrustManager() {
+ return new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ };
+ }
+
+ public static HostnameVerifier getHostnameVerifier() {
+ HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return true;
+ }
+ };
+ return hostnameVerifier;
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/db/config/MarketDatasourceConfig.java b/src/main/java/cn/stock/market/infrastructure/db/config/MarketDatasourceConfig.java
index c6eda87..d4d7a94 100644
--- a/src/main/java/cn/stock/market/infrastructure/db/config/MarketDatasourceConfig.java
+++ b/src/main/java/cn/stock/market/infrastructure/db/config/MarketDatasourceConfig.java
@@ -37,14 +37,12 @@ import com.rp.spring.jpa.GenericJpaRepositoryImpl;
* @created Jan 26, 2018 3:57:47 PM
*/
@Configuration
-@ComponentScan({ "cn.stock.**.db.po", "com.stock.**.db.repo","cn.qutaojing.**.po", "cn.qutaojing.**.repo" })
+@ComponentScan({ "cn.stock.**.db.po", "cn.stock.**.db.repo"})
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
repositoryBaseClass = GenericJpaRepositoryImpl.class,
repositoryFactoryBeanClass = GenericJpaRepositoryFactoryBean.class,
basePackages = {
- "cn.qutaojing.ipay.infrastructure.db.po", "cn.qutaojing.ipay.infrastructure.db.repo",
- "cn.stock.trade.infrastructure.db.po", "cn.stock.trade.infrastructure.db.repo",
"cn.stock.market.infrastructure.db.po", "cn.stock.market.infrastructure.db.repo"})
public class MarketDatasourceConfig {
@Autowired HibernateProperties hibernateProperties;
diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/BtodayStockPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/BtodayStockPO.java
new file mode 100644
index 0000000..fddb048
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/db/po/BtodayStockPO.java
@@ -0,0 +1,74 @@
+package cn.stock.market.infrastructure.db.po;
+
+import java.lang.Integer;
+import java.lang.String;
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.Generated;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+/**
+ * BtodayStockPO
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/25
+ */
+@SuperBuilder
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@DynamicInsert
+@DynamicUpdate
+@Table(
+ name = "btoday_stock"
+)
+public class BtodayStockPO {
+ /**
+ * 主键 */
+ @Id
+ @GeneratedValue(
+ strategy = GenerationType.IDENTITY
+ )
+ Integer id;
+
+ /**
+ * 股票名称 */
+ String stockName;
+
+ /**
+ * 股票code */
+ String stockCode;
+
+ /**
+ * 类型 */
+ String stockType;
+
+ String stockSpell;
+
+ /**
+ * btoday的业务id */
+ String coCode;
+
+ /**
+ * 主页的http链接 */
+ String selfUrl;
+
+ /**
+ * url */
+ String url;
+
+ /**
+ * 上次更新时间 */
+ Date lastUpdateTime;
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/stockdb/po/SiteSettingPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/SiteSettingPO.java
similarity index 93%
rename from src/main/java/cn/stock/market/infrastructure/stockdb/po/SiteSettingPO.java
rename to src/main/java/cn/stock/market/infrastructure/db/po/SiteSettingPO.java
index 8f26340..e359b18 100644
--- a/src/main/java/cn/stock/market/infrastructure/stockdb/po/SiteSettingPO.java
+++ b/src/main/java/cn/stock/market/infrastructure/db/po/SiteSettingPO.java
@@ -1,4 +1,4 @@
-package cn.stock.market.infrastructure.stockdb.po;
+package cn.stock.market.infrastructure.db.po;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
@@ -33,6 +33,6 @@ public class SiteSettingPO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
-
+
String marketServerList;
}
diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/StockIpoPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/StockIpoPO.java
new file mode 100644
index 0000000..b228275
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/db/po/StockIpoPO.java
@@ -0,0 +1,87 @@
+package cn.stock.market.infrastructure.db.po;
+
+import java.lang.Integer;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+/**
+ * StockIpoPO
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/28
+ */
+@SuperBuilder
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@DynamicInsert
+@DynamicUpdate
+@Table(
+ name = "stock_ipo"
+)
+public class StockIpoPO {
+ @Id
+ @GeneratedValue(
+ strategy = javax.persistence.GenerationType.IDENTITY
+ )
+ Integer id;
+
+ /**
+ * 股票代码 */
+ String stockCode;
+
+ /**
+ * 股票名称 */
+ String stockName;
+
+ /**
+ * 发行价格 */
+ BigDecimal stockPrice;
+
+ /**
+ * 申购日期 */
+ Date subscriptionDate;
+
+ /**
+ * 上市日期 */
+ Date listingDate;
+
+ /**
+ * 是否显示【1 显示,2 不显示】 */
+ Integer isShow;
+
+ /**
+ * 是否上市【1 未上市,2 已上市】 */
+ Integer isList;
+
+ Date createDate;
+
+ Date updateDate;
+
+ /**
+ * 发行总数 */
+ Integer totalNumber;
+
+ /**
+ * 申请总额 */
+ String apply;
+
+ /**
+ * 市盈率 */
+ BigDecimal peRatio;
+
+ String sourceType;
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/stockdb/po/StockPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/StockPO.java
similarity index 92%
rename from src/main/java/cn/stock/market/infrastructure/stockdb/po/StockPO.java
rename to src/main/java/cn/stock/market/infrastructure/db/po/StockPO.java
index 40b07b0..398dba2 100644
--- a/src/main/java/cn/stock/market/infrastructure/stockdb/po/StockPO.java
+++ b/src/main/java/cn/stock/market/infrastructure/db/po/StockPO.java
@@ -1,4 +1,4 @@
-package cn.stock.market.infrastructure.stockdb.po;
+package cn.stock.market.infrastructure.db.po;
import java.lang.Integer;
import java.lang.String;
@@ -63,6 +63,10 @@ public class StockPO {
* 涨幅比例
*/
BigDecimal increaseRatio;
-
+
Integer stockState;
+
+ String stockExchangeId;
+
+ String stockSymbol;
}
diff --git a/src/main/java/cn/stock/market/infrastructure/db/repo/BtodayStockRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/BtodayStockRepo.java
new file mode 100644
index 0000000..5b3da4a
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/db/repo/BtodayStockRepo.java
@@ -0,0 +1,15 @@
+package cn.stock.market.infrastructure.db.repo;
+
+import cn.stock.market.infrastructure.db.po.BtodayStockPO;
+import com.rp.spring.jpa.GenericJpaRepository;
+import java.lang.Integer;
+
+/**
+ * BtodayStockRepo
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/25
+ */
+public interface BtodayStockRepo extends GenericJpaRepository {
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/stockdb/repo/SiteSettingRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/SiteSettingRepo.java
similarity index 67%
rename from src/main/java/cn/stock/market/infrastructure/stockdb/repo/SiteSettingRepo.java
rename to src/main/java/cn/stock/market/infrastructure/db/repo/SiteSettingRepo.java
index b238935..210e12a 100644
--- a/src/main/java/cn/stock/market/infrastructure/stockdb/repo/SiteSettingRepo.java
+++ b/src/main/java/cn/stock/market/infrastructure/db/repo/SiteSettingRepo.java
@@ -1,8 +1,8 @@
-package cn.stock.market.infrastructure.stockdb.repo;
+package cn.stock.market.infrastructure.db.repo;
import com.rp.spring.jpa.GenericJpaRepository;
-import cn.stock.market.infrastructure.stockdb.po.SiteSettingPO;
+import cn.stock.market.infrastructure.db.po.SiteSettingPO;
/**
* StockRepo
diff --git a/src/main/java/cn/stock/market/infrastructure/db/repo/StockIpoRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/StockIpoRepo.java
new file mode 100644
index 0000000..3e47807
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/db/repo/StockIpoRepo.java
@@ -0,0 +1,15 @@
+package cn.stock.market.infrastructure.db.repo;
+
+import cn.stock.market.infrastructure.db.po.StockIpoPO;
+import com.rp.spring.jpa.GenericJpaRepository;
+import java.lang.Integer;
+
+/**
+ * StockIpoRepo
+ *
+ * @author rplees
+ * @email rplees.i.ly@gmail.com
+ * @created 2023/12/28
+ */
+public interface StockIpoRepo extends GenericJpaRepository {
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/stockdb/repo/StockRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/StockRepo.java
similarity index 69%
rename from src/main/java/cn/stock/market/infrastructure/stockdb/repo/StockRepo.java
rename to src/main/java/cn/stock/market/infrastructure/db/repo/StockRepo.java
index 0d1a734..97e36b0 100644
--- a/src/main/java/cn/stock/market/infrastructure/stockdb/repo/StockRepo.java
+++ b/src/main/java/cn/stock/market/infrastructure/db/repo/StockRepo.java
@@ -1,8 +1,8 @@
-package cn.stock.market.infrastructure.stockdb.repo;
+package cn.stock.market.infrastructure.db.repo;
import com.rp.spring.jpa.GenericJpaRepository;
-import cn.stock.market.infrastructure.stockdb.po.StockPO;
+import cn.stock.market.infrastructure.db.po.StockPO;
import java.lang.Integer;
diff --git a/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java b/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java
new file mode 100644
index 0000000..e74913e
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java
@@ -0,0 +1,97 @@
+package cn.stock.market.infrastructure.job;
+
+import cn.stock.market.domain.basic.entity.Stock;
+import cn.stock.market.domain.basic.repository.StockRepository;
+import cn.stock.market.infrastructure.api.investing.InvestingInvokerApis;
+import cn.stock.market.utils.Utils;
+import com.ag.utils.CollectionUtils;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class InvestingTask {
+
+// @Scheduled(cron = "0 0 6 * * ?")
+ public void syncIndiaData(){
+ log.info("同步股票数据开始。。。。");
+ Map stockMap = StockRepository.of().cacheCodeMap();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ List list = Lists.newArrayList();
+ int currentTotal = 0;
+ int pageNum =1;
+ int pageSize = 300;
+ try {
+ for(int i = 0;i < pageNum;i ++) {
+ JSONObject jsonObject = InvestingInvokerApis.of().__page(pageNum, pageSize);
+ int total = 0;
+ if(jsonObject.containsKey("total")){
+ total = Integer.parseInt(jsonObject.get("total").toString());
+ }
+ JSONArray dataObjArray = new JSONArray();
+ if(jsonObject.containsKey("data")){
+ dataObjArray = JSON.parseArray(jsonObject.get("data").toString());
+ }
+ for (Object obj : dataObjArray) {
+ JSONObject jsonObject2 = JSON.parseObject(obj.toString());
+ String code = jsonObject2.get("Id").toString();
+ String name = jsonObject2.get("Name").toString();
+ String stockSymbol = jsonObject2.get("Symbol").toString();
+ String exchangeId = jsonObject2.get("ExchangeId").toString();
+ if (stockMap.containsKey(code)) {
+ log.info("已经存在 {} 信息, 跳过", code);
+ continue;
+ }
+
+ if(! Utils.isShOrSzOrBJ(code)) {
+ log.info("{} 非 sh 或者 sz 或者 bj , 跳过", code);
+ continue;
+ }
+ Stock stock = new Stock();
+ stock.setStockSymbol(stockSymbol);
+ stock.setStockExchangeId(exchangeId);
+ if("46".equals(exchangeId)){
+ stock.setStockType("NSE");
+ }
+ stock.setStockName(name);
+ stock.setStockCode(code);
+ stock.setStockGid(code);
+ stock.setIsLock(0);
+ stock.setIsShow(0);
+ stock.setAddTime(new Date());
+ stock.setStockState(0);
+ list.add(stock);
+ }
+ currentTotal += pageSize;
+ if((total - currentTotal) < pageSize ){
+ pageSize = total - currentTotal;
+ }
+ if(total <= currentTotal){
+ break;
+ }
+ pageNum ++;
+ }
+ if(CollectionUtils.isNotEmpty(list)) {
+ StockRepository.of().saveAll(list);
+ }
+ int count = list.size();
+ log.info("syncAFutureStockList执行, 受影响数{}, 耗时:{}毫秒", count, stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ log.info("同步股票数据结束。。。。");
+ } catch (Exception e) {
+ log.info("同步股票数据异常,异常信息{}。。。。",e.getMessage());
+ }
+
+ }
+}
diff --git a/src/main/java/cn/stock/market/infrastructure/job/JobBoot.java b/src/main/java/cn/stock/market/infrastructure/job/JobBoot.java
index d9de463..a3aa98f 100644
--- a/src/main/java/cn/stock/market/infrastructure/job/JobBoot.java
+++ b/src/main/java/cn/stock/market/infrastructure/job/JobBoot.java
@@ -13,7 +13,7 @@ import cn.stock.market.domain.basic.service.SiteNewsService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
-@Component
+//@Component
public class JobBoot {
/**
* cronExpression表达式定义:
diff --git a/src/main/java/cn/stock/market/infrastructure/job/Scraper.java b/src/main/java/cn/stock/market/infrastructure/job/Scraper.java
new file mode 100644
index 0000000..fa3e607
--- /dev/null
+++ b/src/main/java/cn/stock/market/infrastructure/job/Scraper.java
@@ -0,0 +1,351 @@
+package cn.stock.market.infrastructure.job;
+
+import cn.stock.market.domain.basic.entity.BtodayStock;
+import cn.stock.market.domain.basic.entity.StockIpo;
+import cn.stock.market.domain.basic.repository.BtodayStockRepository;
+import cn.stock.market.domain.basic.repository.StockIpoRepository;
+import cn.stock.market.dto.StockIpoDTO;
+import cn.stock.market.infrastructure.db.po.QStockIpoPO;
+import cn.stock.market.infrastructure.db.po.StockIpoPO;
+import cn.stock.market.infrastructure.db.repo.BtodayStockRepo;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+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;
+import org.jsoup.select.Elements;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestController
+public class Scraper {
+
+ @Autowired
+ private BtodayStockRepository btodayStockRepo;
+ @Autowired
+ private StockIpoRepository stockIpoRepository;
+
+ private final ExecutorService executorService = Executors.newFixedThreadPool(5);
+
+
+ @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";
+ String company_name = "Bhagawati Oxygen Ltd";
+
+ try {
+ // 获取 JSON 数据
+ String json_data = scrapePage(BASE_URL);
+ // 解析 JSON 数据
+ if (json_data != null) {
+ List all = btodayStockRepo.findAll();
+ Map sefUrlList = getSefUrl(json_data, company_name);
+ sefUrlList = sefUrlList.entrySet().stream()
+ .filter(entry -> all.stream().noneMatch(stock -> stock.getStockName().equals(entry.getKey())))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ // 将 Map 中的数据分成 5 个线程处理
+ int batchSize = sefUrlList.size() / 5; // 假设分成 5 个线程
+ int threadCount = 5;
+ CompletableFuture[] futures = new CompletableFuture[threadCount];
+ for (int i = 0; i < threadCount; i++) {
+ int startIndex = i * batchSize;
+ int endIndex = (i == threadCount - 1) ? sefUrlList.size() : (i + 1) * batchSize;
+
+ Map subMap = sefUrlList.entrySet().stream()
+ .skip(startIndex)
+ .limit(endIndex - startIndex)
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ futures[i] = CompletableFuture.runAsync(() -> processSubMap(subMap), executorService);
+ }
+ // 等待所有异步任务完成
+ CompletableFuture.allOf(futures).get();
+ }
+ } catch (Exception e) {
+ log.error("IOException occurred while processing the JSON data", e);
+ }finally {
+ // 关闭线程池
+ executorService.shutdown();
+ }
+ }
+
+
+
+
+
+ @Scheduled(cron = "0 0 5 * * ?")
+ @RequestMapping("/testScraperGetMoneyControllerNewIPO")
+ public void schedule2() {
+ // 目标 URL
+ String url = "https://www.moneycontrol.com/ipo/open-upcoming-ipos";
+ // 创建 HttpClient 实例
+ HttpClient client = HttpClients.createDefault();
+ // 创建 HttpGet 请求
+ HttpGet request = new HttpGet(url);
+ try {
+ // 执行请求
+ HttpResponse response = client.execute(request);
+
+ // 检查请求是否成功
+ if (response.getStatusLine().getStatusCode() == 200) {
+ // 获取响应体
+ String responseBody = EntityUtils.toString(response.getEntity());
+
+ // 使用 Jsoup 解析 HTML
+ Document doc = Jsoup.parse(responseBody);
+
+ // 找到包含 JSON 数据的