| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- package com.yingpai.stock.task;
- import com.fasterxml.jackson.databind.JsonNode;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.yingpai.stock.domain.StockInfo;
- import com.yingpai.stock.mapper.StockInfoMapper;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.stereotype.Component;
- import cn.hutool.http.HttpUtil;
- import java.time.LocalDateTime;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * 股票信息同步定时任务
- * 从东方财富接口同步全部A股列表到 stock_info 表
- */
- @Slf4j
- @Component
- @RequiredArgsConstructor
- public class StockSyncTask {
- private final StockInfoMapper stockInfoMapper;
- private final ObjectMapper objectMapper = new ObjectMapper();
- // 东方财富全部A股列表接口
- // 沪市:m:1+t:2 主板, m:1+t:23 科创板
- // 深市:m:0+t:6 主板, m:0+t:80 创业板
- private static final String SH_STOCK_URL = "http://80.push2.eastmoney.com/api/qt/clist/get?pn=%d&pz=500&fs=m:1+t:2,m:1+t:23&fields=f12,f14";
- private static final String SZ_STOCK_URL = "http://80.push2.eastmoney.com/api/qt/clist/get?pn=%d&pz=500&fs=m:0+t:6,m:0+t:80&fields=f12,f14";
- /**
- * 每天凌晨3点执行同步
- */
- @Scheduled(cron = "0 0 3 * * ?")
- public void syncAllStocks() {
- log.info("[股票同步] 开始同步全部A股信息...");
- try {
- List<StockInfo> allStocks = new ArrayList<>();
- // 同步沪市股票
- List<StockInfo> shStocks = fetchStockList(SH_STOCK_URL, "SH");
- allStocks.addAll(shStocks);
- log.info("[股票同步] 沪市股票数量: {}", shStocks.size());
- // 同步深市股票
- List<StockInfo> szStocks = fetchStockList(SZ_STOCK_URL, "SZ");
- allStocks.addAll(szStocks);
- log.info("[股票同步] 深市股票数量: {}", szStocks.size());
- // 批量保存或更新
- if (!allStocks.isEmpty()) {
- batchSaveOrUpdate(allStocks);
- log.info("[股票同步] 同步完成,共 {} 只股票", allStocks.size());
- }
- } catch (Exception e) {
- log.error("[股票同步] 同步失败: {}", e.getMessage(), e);
- }
- }
- /**
- * 手动触发同步(供管理员调用)
- */
- public void manualSync() {
- syncAllStocks();
- }
- /**
- * 分页获取股票列表
- */
- private List<StockInfo> fetchStockList(String urlTemplate, String market) {
- List<StockInfo> stocks = new ArrayList<>();
- int page = 1;
- while (true) {
- try {
- String url = String.format(urlTemplate, page);
- String responseBody = HttpUtil.createGet(url)
- .header("User-Agent", "Mozilla/5.0")
- .timeout(30000)
- .execute()
- .body();
- if (responseBody != null && !responseBody.isEmpty()) {
- JsonNode root = objectMapper.readTree(responseBody);
- JsonNode data = root.path("data").path("diff");
- if (data == null || data.isNull() || !data.isArray() || data.isEmpty()) {
- break;
- }
- for (JsonNode item : data) {
- String code = item.path("f12").asText();
- String name = item.path("f14").asText();
- if (code != null && !code.isEmpty() && name != null && !name.isEmpty()) {
- StockInfo stockInfo = new StockInfo();
- stockInfo.setStockCode(code);
- stockInfo.setStockName(name);
- stockInfo.setMarket(market);
- stocks.add(stockInfo);
- }
- }
- // 如果返回数量少于500,说明已经是最后一页
- if (data.size() < 500) {
- break;
- }
- page++;
- // 防止请求过快
- Thread.sleep(200);
- } else {
- log.warn("[股票同步] 请求失败,响应为空");
- break;
- }
- } catch (Exception e) {
- log.error("[股票同步] 获取第{}页数据失败: {}", page, e.getMessage());
- break;
- }
- }
- return stocks;
- }
- /**
- * 批量保存或更新
- */
- private void batchSaveOrUpdate(List<StockInfo> list) {
- if (list == null || list.isEmpty()) {
- return;
- }
- LocalDateTime now = LocalDateTime.now();
- // 分批处理,每批500条
- int batchSize = 500;
- for (int i = 0; i < list.size(); i += batchSize) {
- int end = Math.min(i + batchSize, list.size());
- List<StockInfo> batch = list.subList(i, end);
- for (StockInfo stock : batch) {
- // 查询是否存在
- StockInfo existing = stockInfoMapper.selectOne(
- new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<StockInfo>()
- .eq(StockInfo::getStockCode, stock.getStockCode())
- );
- if (existing != null) {
- // 更新
- existing.setStockName(stock.getStockName());
- existing.setMarket(stock.getMarket());
- existing.setUpdateTime(now);
- stockInfoMapper.updateById(existing);
- } else {
- // 新增
- stock.setCreateTime(now);
- stock.setUpdateTime(now);
- stockInfoMapper.insert(stock);
- }
- }
- }
- log.info("[股票信息] 批量保存完成,共 {} 条", list.size());
- }
- }
|