|
@@ -36,7 +36,8 @@ public class StockQuoteServiceImpl implements IStockQuoteService {
|
|
|
.build();
|
|
.build();
|
|
|
|
|
|
|
|
private static final int BATCH_SIZE = 50;
|
|
private static final int BATCH_SIZE = 50;
|
|
|
- private static final String BATCH_QUOTE_URL = "http://push2.eastmoney.com/api/qt/ulist.np/get?fltt=2&fields=f12,f14,f2,f3,f4,f5,f6,f8,f18&secids=%s";
|
|
|
|
|
|
|
+ // 腾讯股票行情接口
|
|
|
|
|
+ private static final String BATCH_QUOTE_URL = "http://qt.gtimg.cn/q=%s";
|
|
|
|
|
|
|
|
public StockQuoteServiceImpl(@Qualifier("stockQuoteExecutor") ThreadPoolTaskExecutor executor) {
|
|
public StockQuoteServiceImpl(@Qualifier("stockQuoteExecutor") ThreadPoolTaskExecutor executor) {
|
|
|
this.executor = executor;
|
|
this.executor = executor;
|
|
@@ -94,45 +95,57 @@ public class StockQuoteServiceImpl implements IStockQuoteService {
|
|
|
private Map<String, StockQuoteData> fetchBatchQuotes(List<String> batch) {
|
|
private Map<String, StockQuoteData> fetchBatchQuotes(List<String> batch) {
|
|
|
Map<String, StockQuoteData> result = new HashMap<>();
|
|
Map<String, StockQuoteData> result = new HashMap<>();
|
|
|
try {
|
|
try {
|
|
|
- String secids = batch.stream()
|
|
|
|
|
- .map(this::getSecId)
|
|
|
|
|
|
|
+ // 构建腾讯API格式的股票代码列表:sh600519,sz000001
|
|
|
|
|
+ String codes = batch.stream()
|
|
|
|
|
+ .map(this::getTencentCode)
|
|
|
.filter(Objects::nonNull)
|
|
.filter(Objects::nonNull)
|
|
|
.collect(Collectors.joining(","));
|
|
.collect(Collectors.joining(","));
|
|
|
|
|
|
|
|
- if (secids.isEmpty()) return result;
|
|
|
|
|
|
|
+ if (codes.isEmpty()) return result;
|
|
|
|
|
|
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|
|
- .uri(URI.create(String.format(BATCH_QUOTE_URL, secids)))
|
|
|
|
|
- .header("User-Agent", "Mozilla/5.0")
|
|
|
|
|
|
|
+ .uri(URI.create(String.format(BATCH_QUOTE_URL, codes)))
|
|
|
.GET()
|
|
.GET()
|
|
|
.build();
|
|
.build();
|
|
|
|
|
|
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
|
|
|
|
|
|
if (response.statusCode() == 200) {
|
|
if (response.statusCode() == 200) {
|
|
|
- JsonNode root = objectMapper.readTree(response.body());
|
|
|
|
|
- JsonNode data = root.path("data").path("diff");
|
|
|
|
|
-
|
|
|
|
|
- if (data != null && data.isArray()) {
|
|
|
|
|
- for (JsonNode item : data) {
|
|
|
|
|
- String code = item.path("f12").asText();
|
|
|
|
|
- if (code == null || code.isEmpty()) continue;
|
|
|
|
|
-
|
|
|
|
|
- StockQuoteData quote = StockQuoteData.builder()
|
|
|
|
|
- .stockCode(code)
|
|
|
|
|
- .currentPrice(parseBigDecimal(item, "f2"))
|
|
|
|
|
- .yesterdayClose(parseBigDecimal(item, "f18"))
|
|
|
|
|
- .changeAmount(parseBigDecimal(item, "f4"))
|
|
|
|
|
- .changePercent(parseBigDecimal(item, "f3"))
|
|
|
|
|
- .turnoverRate(parseBigDecimal(item, "f8"))
|
|
|
|
|
- .build();
|
|
|
|
|
-
|
|
|
|
|
- BigDecimal tradeAmount = parseBigDecimal(item, "f6");
|
|
|
|
|
- if (tradeAmount != null) {
|
|
|
|
|
- quote.setTradeAmount(tradeAmount.divide(new BigDecimal("100000000"), 2, RoundingMode.HALF_UP));
|
|
|
|
|
|
|
+ String body = response.body();
|
|
|
|
|
+ String[] lines = body.split(";");
|
|
|
|
|
+
|
|
|
|
|
+ for (String line : lines) {
|
|
|
|
|
+ line = line.trim();
|
|
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 解析腾讯API返回格式: v_sh600519="1~贵州茅台~600519~2076.91~..."
|
|
|
|
|
+ int startIndex = line.indexOf("=\"");
|
|
|
|
|
+ int endIndex = line.lastIndexOf("\"");
|
|
|
|
|
+
|
|
|
|
|
+ if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
|
|
|
|
|
+ String data = line.substring(startIndex + 2, endIndex);
|
|
|
|
|
+ String[] fields = data.split("~");
|
|
|
|
|
+
|
|
|
|
|
+ if (fields.length > 38) {
|
|
|
|
|
+ String code = fields[2]; // 股票代码
|
|
|
|
|
+
|
|
|
|
|
+ StockQuoteData quote = StockQuoteData.builder()
|
|
|
|
|
+ .stockCode(code)
|
|
|
|
|
+ .currentPrice(parseBigDecimal(fields[3])) // 当前价格 [3]
|
|
|
|
|
+ .yesterdayClose(parseBigDecimal(fields[4])) // 昨收 [4]
|
|
|
|
|
+ .changeAmount(parseBigDecimal(fields[31])) // 涨跌额 [31]
|
|
|
|
|
+ .changePercent(parseBigDecimal(fields[32])) // 涨跌幅 [32]
|
|
|
|
|
+ .turnoverRate(parseBigDecimal(fields[38])) // 换手率 [38]
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+ // 成交额(万元)转换为亿
|
|
|
|
|
+ BigDecimal amount = parseBigDecimal(fields[37]);
|
|
|
|
|
+ if (amount != null) {
|
|
|
|
|
+ quote.setTradeAmount(amount.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result.put(code, quote);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- result.put(code, quote);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -154,24 +167,22 @@ public class StockQuoteServiceImpl implements IStockQuoteService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 获取secId(东方财富格式)
|
|
|
|
|
|
|
+ * 获取腾讯API格式的股票代码(sh600519, sz000001)
|
|
|
*/
|
|
*/
|
|
|
- private String getSecId(String code) {
|
|
|
|
|
|
|
+ private String getTencentCode(String code) {
|
|
|
if (code == null || code.length() != 6) return null;
|
|
if (code == null || code.length() != 6) return null;
|
|
|
- if (code.startsWith("6")) return "1." + code;
|
|
|
|
|
- if (code.startsWith("0") || code.startsWith("3")) return "0." + code;
|
|
|
|
|
|
|
+ if (code.startsWith("6")) return "sh" + code;
|
|
|
|
|
+ if (code.startsWith("0") || code.startsWith("3")) return "sz" + code;
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 解析BigDecimal
|
|
|
|
|
|
|
+ * 解析BigDecimal(从字符串)
|
|
|
*/
|
|
*/
|
|
|
- private BigDecimal parseBigDecimal(JsonNode node, String field) {
|
|
|
|
|
- if (!node.has(field)) return null;
|
|
|
|
|
- JsonNode value = node.get(field);
|
|
|
|
|
- if (value == null || value.isNull() || "-".equals(value.asText())) return null;
|
|
|
|
|
|
|
+ private BigDecimal parseBigDecimal(String value) {
|
|
|
|
|
+ if (value == null || value.isEmpty() || "-".equals(value)) return null;
|
|
|
try {
|
|
try {
|
|
|
- return new BigDecimal(value.asText());
|
|
|
|
|
|
|
+ return new BigDecimal(value);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|