diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QSiteSettingPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QSiteSettingPO.java index 5440bae..546e01c 100644 --- a/src/main/generated/cn/stock/market/infrastructure/db/po/QSiteSettingPO.java +++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QSiteSettingPO.java @@ -15,7 +15,7 @@ import com.querydsl.core.types.Path; @Generated("com.querydsl.codegen.EntitySerializer") public class QSiteSettingPO extends EntityPathBase { - private static final long serialVersionUID = -51441337L; + private static final long serialVersionUID = 926298165L; public static final QSiteSettingPO siteSettingPO = new QSiteSettingPO("siteSettingPO"); diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QStockPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QStockPO.java index 22c7369..90be42c 100644 --- a/src/main/generated/cn/stock/market/infrastructure/db/po/QStockPO.java +++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QStockPO.java @@ -15,7 +15,7 @@ import com.querydsl.core.types.Path; @Generated("com.querydsl.codegen.EntitySerializer") public class QStockPO extends EntityPathBase { - private static final long serialVersionUID = -1246401708L; + private static final long serialVersionUID = 2143409346L; public static final QStockPO stockPO = new QStockPO("stockPO"); @@ -33,6 +33,8 @@ public class QStockPO extends EntityPathBase { public final StringPath stockCode = createString("stockCode"); + public final NumberPath stockExchangeId = createNumber("stockExchangeId", Integer.class); + public final StringPath stockGid = createString("stockGid"); public final StringPath stockName = createString("stockName"); @@ -43,6 +45,8 @@ public class QStockPO extends EntityPathBase { public final NumberPath stockState = createNumber("stockState", Integer.class); + public final NumberPath stockSymbol = createNumber("stockSymbol", Integer.class); + public final StringPath stockType = createString("stockType"); public QStockPO(String variable) { diff --git a/src/main/java/cn/stock/market/application/CommonApis.java b/src/main/java/cn/stock/market/application/CommonApis.java index b20ffee..e8a240f 100644 --- a/src/main/java/cn/stock/market/application/CommonApis.java +++ b/src/main/java/cn/stock/market/application/CommonApis.java @@ -47,7 +47,7 @@ public class CommonApis { public Map stockDetailMap(List code) { Function> func = (source) -> { return code.stream() - .filter(val -> val != null && source == val.getSource()) +// .filter(val -> val != null && source == val.getSource()) .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(StockCode::getCode))), ArrayList::new)); }; 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 d6ea223..4059817 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 @@ -1,5 +1,6 @@ package cn.stock.market.domain.basic.service; +import java.io.IOException; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.*; @@ -74,41 +75,6 @@ public class StockService { String apikey = "VF7SHRYOFIYHCIKE"; - 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" - + ""; - - 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 ServerResponse getMarket() { String market_url = "https://hq.sinajs.cn/rn=1520407404627&list=s_sh000001,s_sz399001,s_sz399006,s_sz399300,s_sz399005,s_sz399673,s_sz399106,s_sz399004,s_sz399100"; String result = null; @@ -569,22 +535,6 @@ public class StockService { return SpringUtils.getBean(StockService.class); } - public ServerResponse getIndiaK(String code, Integer type) { - if (type == 0) { - return getYCTimeK(code); - } - if (type == 1) { - return getYQDayK(code); - } - if (type == 2) { - return getYQWeekK(code); - } - if (type == 3) { - return getYQMonthK(code); - } - return null; - } - /* 指数日线-K线 */ public ServerResponse getIndexDayK(String code) { return getIndexK(code, "day"); @@ -678,73 +628,6 @@ public class StockService { return null; } - //印度股票个股详情 英财 - public ServerResponse getYCStockInfo(String codeId) { - log.info("开始查询个股行情"); - String sina_result = ""; - - try { - INDStockInfo indStockInfo = new INDStockInfo(); - 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, codeId); - log.info("url: {}", url); - Builder builder = new Request.Builder().url(url).method("GET", null); - addHeader(builder, API_HEADER); - sina_result = httpClient().newCall(builder.build()).execute().body().string(); - JSONArray pairs_data = JSON.parseObject(sina_result) - .getJSONArray("data").getJSONObject(0) - .getJSONObject("screen_data").getJSONArray("pairs_data"); - - JSONArray overview_table = pairs_data.getJSONObject(0).getJSONArray("overview_table"); - JSONObject info_header = pairs_data.getJSONObject(0).getJSONObject("info_header"); - indStockInfo.setClose(find_overview_table_value_with_key(overview_table, "昨收")); - indStockInfo.setVolume(find_overview_table_value_with_key(overview_table, "成交量")); - indStockInfo.setOpen(find_overview_table_value_with_key(overview_table, "开盘")); - String range = find_overview_table_value_with_key(overview_table, "当日幅度"); //6.91 - 7.89 - if(StringUtils.isNotBlank(range)) { - String[] split = range.trim().split("-"); - indStockInfo.setLow(numberToString(split[0])); - indStockInfo.setHigh(numberToString(split[1])); - } - String last = info_header.getString("last"); //当前价 - indStockInfo.setPrice(numberToString(last)); - - String percent_tooltip_value = info_header.getString("percent_tooltip_value"); //涨幅 - indStockInfo.setPrice(percent_tooltip_value); - String change = info_header.getString("change"); //涨幅值 ("+0.65") - if(change.startsWith("+")) { - indStockInfo.setChange(numberToString(change.substring(1))); - } else { - indStockInfo.setChange(numberToString(change)); - } - - return ServerResponse.createBySuccess(indStockInfo); - - } catch (Exception e) { - log.error("获取出错,错误信息 = {}", e); - } - return null; - } - - //印度股票列表 英财 - public ServerResponse getINDStockList(Integer pageSize, Integer pageNum) { - String sina_result = ""; - try { - 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, pageSize); - Stopwatch stopwatch = Stopwatch.createStarted(); - log.info("url: {}", url); - Builder builder = new Request.Builder().url(url).method("GET", null); - String body = httpClient().newCall(builder.build()).execute().body().string(); - sina_result = JSON.parseObject(body).toString(); - return ServerResponse.createBySuccess(sina_result); - - } catch (Exception e) { - log.error("获取出错,错误信息 = {}", e); - } - return null; - } - //印度股票时线-K线 public ServerResponse getTimeK(String stockCode) { String sina_result = ""; @@ -760,21 +643,6 @@ public class StockService { return null; } - //印度股票时线-K线 英情 - public ServerResponse getYCTimeK(String codeId) { - String sina_result = ""; - - try { - sina_result = HttpClientRequest.http2Get("https://api.investing.com/api/financialdata/"+codeId+"/historical/chart/?interval=PT5M&pointscount=160"); - JSONObject json = JSONObject.parseObject(sina_result).getJSONObject("Time Series (Daily)"); - - return ServerResponse.createBySuccess(json); - } catch (Exception e) { - log.error("获取出错,错误信息 = {}", e); - } - return null; - } - //印度股票日线-K线 public ServerResponse getDayK(String stockCode) { String sina_result = ""; @@ -790,21 +658,6 @@ public class StockService { return null; } - //印度股票日线-K线 - public ServerResponse getYQDayK(String codeId) { - String sina_result = ""; - - try { - sina_result = HttpClientRequest.http2Get("https://api.investing.com/api/financialdata/"+codeId+"/historical/chart/?interval=P1D&pointscount=160"); - JSONObject json = JSONObject.parseObject(sina_result).getJSONObject("Time Series (Daily)"); - - return ServerResponse.createBySuccess(json); - } catch (Exception e) { - log.error("获取出错,错误信息 = {}", e); - } - return null; - } - //印度股票周线-K线 public ServerResponse getWeekK(String stockCode) { String sina_result = ""; @@ -821,21 +674,6 @@ public class StockService { } - //印度股票周线-K线 英情 - public ServerResponse getYQWeekK(String codeId) { - String sina_result = ""; - - try { - sina_result = HttpClientRequest.http2Get("https://api.investing.com/api/financialdata/"+codeId+"/historical/chart/?interval=P1W&pointscount=160"); - JSONObject json = JSONObject.parseObject(sina_result).getJSONObject("Time Series (Daily)"); - - return ServerResponse.createBySuccess(json); - } catch (Exception e) { - log.error("获取出错,错误信息 = {}", e); - } - return null; - } - //印度股票月线-K线 public ServerResponse getMonthK(String stockCode) { String sina_result = ""; @@ -851,21 +689,6 @@ public class StockService { return null; } - //印度股票月线-K线 英情 - public ServerResponse getYQMonthK(String codeId) { - String sina_result = ""; - - try { - sina_result = HttpClientRequest.http2Get("https://api.investing.com/api/financialdata/"+codeId+"/historical/chart/?interval=P1M&pointscount=160"); - JSONObject json = JSONObject.parseObject(sina_result).getJSONObject("Time Series (Daily)"); - - return ServerResponse.createBySuccess(json); - } catch (Exception e) { - log.error("获取出错,错误信息 = {}", e); - } - return null; - } - public ServerResponse getNewStockList() { try { JSONObject jsonParam = new JSONObject(); 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/infrastructure/api/investing/IndiaStockVO.java b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaStockVO.java new file mode 100644 index 0000000..c9f48f2 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/api/investing/IndiaStockVO.java @@ -0,0 +1,81 @@ +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); + 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..2358b71 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingApis.java @@ -0,0 +1,268 @@ +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); + + 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("找不到股票信息"); + } + String interval = 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"; + } + Date nowDate = new Date(); + JSONObject json = InvestingInvokerApis.of().__kline(code, interval); + 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..92a44ee --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/api/investing/InvestingInvokerApis.java @@ -0,0 +1,170 @@ +package cn.stock.market.infrastructure.api.investing; + +import java.io.IOException; +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, 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; + 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"; + } + + if(StringUtils.isBlank(interval)) { + interval = string; + } + String tmpl = "https://api.investing.com/api/financialdata/{}/historical/chart/?interval={}&pointscount=160"; + String 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().__market(StockCode.of("17988")); + System.out.println(__market); + } +} 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/web/StockApiController.java b/src/main/java/cn/stock/market/web/StockApiController.java index 09bada3..7f6fb9a 100644 --- a/src/main/java/cn/stock/market/web/StockApiController.java +++ b/src/main/java/cn/stock/market/web/StockApiController.java @@ -1,5 +1,6 @@ package cn.stock.market.web; +import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import com.ag.utils.DateUtils; import com.ag.utils.Jsons; +import com.ag.utils.param.ParamUtils; import com.alibaba.fastjson.JSONObject; import com.google.common.base.Joiner; import com.google.common.cache.Cache; @@ -40,6 +42,9 @@ import cn.stock.market.domain.basic.service.StockService; import cn.stock.market.dto.model.StockCode; import cn.stock.market.dto.model.StockVO; import cn.stock.market.infrastructure.api.EastmoneyApi; +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.qq.QqStockApi; import cn.stock.market.infrastructure.api.sina.SinaStockApi; import cn.stock.market.utils.RequestCacheUtils; @@ -119,22 +124,18 @@ public class StockApiController { @ApiOperation(value = "印度股票个股详情", httpMethod = "GET") @ResponseBody public ServerResponse getINDStockInfo(@RequestParam("stockCode") String stockCode) { - return this.stockService.getYCStockInfo(stockCode); - } - - //测试 - @RequestMapping({"test.do"}) - @ResponseBody - public ServerResponse test(@RequestParam("stockCode") String stockCode) { - return ServerResponse.createBySuccess(stockCode); + ParamUtils.verify("股票代码", stockCode, ParamUtils.STRING_NOT_EMPTY_VERIFY_AND_CONVERT_VALUE); + + IndiaStockVO market = InvestingApis.of().market(StockCode.of(stockCode)); + return ServerResponse.createBySuccess(market.toStockVo()); } //印度股票列表 英情 @RequestMapping({"getINDStockList.do"}) @ApiOperation(value = "印度股票列表", httpMethod = "GET") @ResponseBody - public ServerResponse getINDStockList(@RequestParam("pageSize") Integer pageSize, @RequestParam("pageNum") Integer pageNum) { - return this.stockService.getINDStockList(pageSize, pageNum); + public ServerResponse getINDStockList(@RequestParam("pageSize") Integer pageSize, @RequestParam("pageNum") Integer pageNum) throws IOException { + return ServerResponse.createBySuccess(InvestingInvokerApis.of().__page(pageNum, pageSize)); } //印度股票时线-K线 @@ -142,11 +143,15 @@ public class StockApiController { @ApiOperation(value = "印度股票K线", httpMethod = "GET") @ResponseBody @ApiImplicitParams({ - @ApiImplicitParam(name = "stockCode",value = "股票对应代码",dataType ="String",required = true), - @ApiImplicitParam(name = "type",value = "type 0-时分线 1-日线 2-周线 3-月线",dataType ="Integer",required = true), + @ApiImplicitParam(name = "stockCode",value = "股票对应代码",dataType ="String", paramType = "query", required = true), + @ApiImplicitParam(name = "type",value = "type min-时分线 day-日线 week-周线 month-月线",dataType ="String", paramType = "query", required = true), }) - public ServerResponse getINDTimeK(@RequestParam("stockCode") String stockCode, @RequestParam("type") Integer type) { - return this.stockService.getIndiaK(stockCode, type); + public ServerResponse getINDTimeK(@RequestParam("stockCode") String stockCode, @RequestParam("type") String type) throws IOException { + ParamUtils.verify("股票代码", stockCode, ParamUtils.STRING_NOT_EMPTY_VERIFY_AND_CONVERT_VALUE); + ParamUtils.verify("K线类型", stockCode, ParamUtils.STRING_NOT_EMPTY_VERIFY_AND_CONVERT_VALUE); + + List list = InvestingApis.of().kline(StockCode.of(stockCode), type); + return ServerResponse.createBySuccess(list); } //根据股票id查询 股票指数、大盘指数信息 diff --git a/src/main/java/cn/stock/market/web/config/ControlSchedulingConfiguration.java b/src/main/java/cn/stock/market/web/config/ControlSchedulingConfiguration.java index bb1bb33..6e772d4 100644 --- a/src/main/java/cn/stock/market/web/config/ControlSchedulingConfiguration.java +++ b/src/main/java/cn/stock/market/web/config/ControlSchedulingConfiguration.java @@ -6,7 +6,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling -@ConditionalOnProperty(prefix = "enable", name = "scheduled", havingValue = "true") +@ConditionalOnProperty(prefix = "enable", name = "scheduled", havingValue = "true", matchIfMissing = true) public class ControlSchedulingConfiguration { }