|
|
@@ -0,0 +1,113 @@
|
|
|
+package com.yingpai.gupiao.controller;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.yingpai.gupiao.domain.vo.Result;
|
|
|
+import com.yingpai.gupiao.domain.vo.StockInfoVO;
|
|
|
+import com.yingpai.gupiao.util.StockUtils;
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestParam;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.net.URI;
|
|
|
+import java.net.http.HttpClient;
|
|
|
+import java.net.http.HttpRequest;
|
|
|
+import java.net.http.HttpResponse;
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/stock")
|
|
|
+public class StockDataController {
|
|
|
+
|
|
|
+ // 东方财富通用行情接口
|
|
|
+ private static final String BASE_URL = "http://push2.eastmoney.com/api/qt/stock/get";
|
|
|
+
|
|
|
+ private final HttpClient httpClient = HttpClient.newBuilder()
|
|
|
+ .connectTimeout(Duration.ofSeconds(3))
|
|
|
+ .build();
|
|
|
+
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取股票数据
|
|
|
+ * 调用示例:
|
|
|
+ * - 单个股票: GET /api/stock/fetch?codes=600519
|
|
|
+ * - 多个股票: GET /api/stock/fetch?codes=600519,300059,000001
|
|
|
+ *
|
|
|
+ * @param codes 股票代码,多个用逗号分隔
|
|
|
+ * @return 股票数据列表
|
|
|
+ */
|
|
|
+ @GetMapping("/fetch")
|
|
|
+ public Result<List<StockInfoVO>> fetchStockData(@RequestParam("codes") String codes) {
|
|
|
+ List<StockInfoVO> dataList = new ArrayList<>();
|
|
|
+
|
|
|
+ String[] codeArray = codes.split(",");
|
|
|
+
|
|
|
+ for (String code : codeArray) {
|
|
|
+ code = code.trim();
|
|
|
+ if (code.isEmpty()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ StockInfoVO stockInfo = fetchAShareData(code);
|
|
|
+ if (stockInfo != null) {
|
|
|
+ dataList.add(stockInfo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result.success(dataList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单个A股数据
|
|
|
+ */
|
|
|
+ private StockInfoVO fetchAShareData(String code) {
|
|
|
+ // 1. 获取 secid (只允许 A 股)
|
|
|
+ String secId = StockUtils.getAShareSecId(code);
|
|
|
+
|
|
|
+ // 如果返回 null,说明不是沪深 A 股
|
|
|
+ if (secId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 构建 URL
|
|
|
+ // f58: 名称, f169: 涨跌额, f170: 涨跌幅
|
|
|
+ String params = String.format("?invt=2&fltt=2&fields=f58,f169,f170&secid=%s", secId);
|
|
|
+ String fullUrl = BASE_URL + params;
|
|
|
+
|
|
|
+ // 3. 发送请求
|
|
|
+ HttpRequest request = HttpRequest.newBuilder()
|
|
|
+ .uri(URI.create(fullUrl))
|
|
|
+ .GET()
|
|
|
+ .build();
|
|
|
+
|
|
|
+ try {
|
|
|
+ HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
|
+
|
|
|
+ if (response.statusCode() == 200) {
|
|
|
+ JsonNode root = objectMapper.readTree(response.body());
|
|
|
+ JsonNode data = root.get("data");
|
|
|
+
|
|
|
+ if (data != null && !data.isNull()) {
|
|
|
+ BigDecimal priceChange = data.has("f169") ? new BigDecimal(data.get("f169").asText()) : null;
|
|
|
+ BigDecimal changePercent = data.has("f170") ? new BigDecimal(data.get("f170").asText()) : null;
|
|
|
+
|
|
|
+ return StockInfoVO.builder()
|
|
|
+ .stockCode(code)
|
|
|
+ .stockName(data.has("f58") ? data.get("f58").asText() : null)
|
|
|
+ .priceChange(StockInfoVO.formatWithSign(priceChange))
|
|
|
+ .changePercent(StockInfoVO.formatPercent(changePercent))
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("获取股票数据异常 [" + code + "]: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|