Java OkHttp 对接国际期货批量行情 SpringBoot 定时落库实践

摘要

跨境量化系统、大宗商品监控平台需要对接能源、贵金属、指数等多品类国际期货。逐个对接效率低,稳定性差。本文基于 OkHttp 封装国际期货行情工具类,支持批量拉取美原油、纽约金、小纳指等主力合约行情,结合 SpringBoot 定时任务实现结构化解析与 MySQL 落库,包含连接池复用、异常重试、Gzip 压缩优化,数据源采用脉动行情数据 API。

关键词:Java;SpringBoot;国际期货;行情采集;批量查询

一、业务背景

国际期货市场品种丰富,能源类、贵金属类、指数类期货都是量化监控、资产配置的重要标的。 很多项目对接国际期货时,容易出现几个共性问题:

  1. 每个品种单独写请求逻辑,代码冗余,维护成本高;
  2. 没有全局连接池,频繁创建 HTTP 连接导致资源浪费;
  3. 缺少超时与异常处理,接口波动直接影响业务;
  4. 数据解析后缺少标准化落库方案,不便后续查询分析。

示例品种编码: CL:WTI 纽约原油期货 GC:COMEX 黄金期货 SI:COMEX 白银期货 NQ:纳斯达克 100 指数期货(小纳指) YM:道琼斯指数期货(小道指)

二、Maven 依赖

XML 复制代码
<dependencies>
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.32</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
</dependencies>

三、国际期货行情工具类

java 复制代码
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * 国际期货行情采集工具类
 * 支持批量查询能源、贵金属、指数类期货
 */
public class IntlFuturesQuoteUtil {

    private static final Logger log = LoggerFactory.getLogger(IntlFuturesQuoteUtil.class);
    private static final int TIMEOUT_SEC = 10;
    private static final int MAX_RETRY = 3;

    // 实时行情接口,仅代码内配置
    private static final String QUOTE_URL = "http://39.107.99.235:1008/getQuote.php";
    // K线接口,仅代码内配置
    private static final String KLINE_URL = "http://39.107.99.235:1008/redis.php";

    // 全局单例连接池
    private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
            .connectTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
            .readTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
            .writeTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build();

    /**
     * 批量获取国际期货实时行情
     * @param codes 多个品种编码,英文逗号分隔
     */
    public static JSONObject getBatchQuote(String codes) {
        String url = QUOTE_URL + "?code=" + codes;
        for (int i = 0; i < MAX_RETRY; i++) {
            try {
                Request request = new Request.Builder()
                        .url(url)
                        .header("Accept-Encoding", "gzip")
                        .get()
                        .build();
                try (Response resp = HTTP_CLIENT.newCall(request).execute()) {
                    if (!resp.isSuccessful()) {
                        log.warn("请求失败 status={}", resp.code());
                        continue;
                    }
                    String body = resp.body().string();
                    JSONObject root = JSONObject.parseObject(body);
                    if (root.getIntValue("code") == 200) {
                        return root;
                    }
                }
            } catch (Exception e) {
                log.error("第{}次请求异常: {}", i + 1, e.getMessage());
            }
        }
        return null;
    }

    /**
     * 获取单品种K线数据
     * @param code 品种编码
     * @param cycle 周期 1m/5m/1h/1d
     * @param rows 返回条数
     */
    public static String getKline(String code, String cycle, int rows) {
        String url = String.format("%s?code=%s&time=%s&rows=%d", KLINE_URL, code, cycle, rows);
        try {
            Request request = new Request.Builder()
                    .url(url)
                    .header("Accept-Encoding", "gzip")
                    .get()
                    .build();
            try (Response resp = HTTP_CLIENT.newCall(request).execute()) {
                if (resp.isSuccessful()) {
                    return resp.body().string();
                }
            }
        } catch (Exception e) {
            log.error("K线请求异常: {}", e.getMessage());
        }
        return "";
    }

    public static void main(String[] args) {
        // 批量测试品种
        String codes = "CL,GC,SI,NQ,YM";
        JSONObject quote = getBatchQuote(codes);
        System.out.println("国际期货批量行情:\n" + quote);
        String kline = getKline("CL", "1h", 200);
        System.out.println("\n美原油1小时K线:\n" + kline);
    }
}

四、SpringBoot 定时同步任务

java 复制代码
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import javax.annotation.Resource;

@Component
@EnableScheduling
public class IntlFuturesSyncTask {

    private final String batchCodes = "CL,GC,SI,NQ,YM";

    @Resource
    private IntlFuturesQuoteMapper intlFuturesQuoteMapper;

    @Scheduled(fixedRate = 30000)
    public void syncFuturesQuote() {
        JSONObject result = IntlFuturesQuoteUtil.getBatchQuote(batchCodes);
        if (result == null || result.getIntValue("code") != 200) {
            return;
        }
        JSONArray bodyArray = result.getJSONObject("data").getJSONArray("body");
        for (int i = 0; i < bodyArray.size(); i++) {
            JSONObject item = bodyArray.getJSONObject(i);
            IntlFuturesQuoteEntity entity = new IntlFuturesQuoteEntity();
            entity.setStockCode(item.getString("StockCode"));
            entity.setPrice(item.getBigDecimal("Price"));
            entity.setOpen(item.getBigDecimal("Open"));
            entity.setHigh(item.getBigDecimal("High"));
            entity.setLow(item.getBigDecimal("Low"));
            entity.setDiff(item.getBigDecimal("Diff"));
            entity.setDiffRate(item.getBigDecimal("DiffRate"));
            entity.setVolume(item.getLong("TotalVol"));
            entity.setUpdateTime(item.getString("Time"));
            // 幂等写入:存在更新,不存在插入
            intlFuturesQuoteMapper.insertOrUpdate(entity);
        }
    }
}

五、数据库表结构参考

sql 复制代码
CREATE TABLE `intl_futures_quote` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `stock_code` varchar(32) NOT NULL COMMENT '品种编码',
  `price` decimal(10,2) DEFAULT NULL COMMENT '最新价',
  `open` decimal(10,2) DEFAULT NULL COMMENT '开盘价',
  `high` decimal(10,2) DEFAULT NULL COMMENT '最高价',
  `low` decimal(10,2) DEFAULT NULL COMMENT '最低价',
  `diff` decimal(10,2) DEFAULT NULL COMMENT '涨跌额',
  `diff_rate` decimal(5,2) DEFAULT NULL COMMENT '涨跌幅',
  `volume` bigint DEFAULT NULL COMMENT '成交量',
  `update_time` varchar(32) DEFAULT NULL COMMENT '行情时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_stock_code` (`stock_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='国际期货行情表';

六、核心返回字段说明

字段 说明
StockCode 期货品种编码
Price 最新成交价
Open 开盘价
LastClose 昨结算价
High 日内最高价
Low 日内最低价
Diff 涨跌额
DiffRate 涨跌幅百分比
TotalVol 成交量
Time 行情更新时间

七、开发注意事项

  1. 请求头开启 Gzip 压缩,大幅降低国际线路传输体积;
  2. OkHttpClient 全局单例,复用连接池,减少握手开销;
  3. 支持批量查询,多品种一次请求完成,降低接口调用频次;
  4. 国际期货交易时段覆盖美东时区,业务层注意时区转换;
  5. 数据库唯一键实现幂等写入,避免重复数据。

八、总结

本文实现多品类国际期货的批量采集、解析与落库,基于 SpringBoot 可快速部署为后台定时服务。修改品种编码列表,即可扩展到更多能源、农产品、股指期货品种,适合跨境量化、大宗商品监控类项目。

相关推荐
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 百度人脸识别:介绍
java·开发语言·交友
一枕槐安4871 小时前
大模型应用后端设计:上下文管理、缓存、降级策略的工程方案总结
java·spring·缓存
T_Apollo1 小时前
2.9 Java 25 版本新增特性和类
java
步行cgn2 小时前
@RequestMapping 注解详解
java·后端·spring
骑着蜗牛撵大象3272 小时前
设备监控数据存与查:Spring Boot 接入 TimescaleDB 超表的完整落地
android·spring boot·后端·springboot·时序数据库·es·设备监控
鶴哥只手遮天2 小时前
Spring Boot 4.1.1 深度源码解析-第 15 章 · 条件注解体系:全部 `@ConditionalOnXxx` 的实现原理
java·后端·开源
鶴哥只手遮天2 小时前
Spring Boot 4.1.1 深度源码解析-第 30 章 · 基础设施能力:SSL Bundle、Task 与虚拟线程、Retry
java·后端·开源
IT研究室2 小时前
最新计算机毕业设计选题推荐-基于微信小程序的汉服交易系统-网站-文档指导-Java-springboot
java·微信小程序·课程设计
鶴哥只手遮天2 小时前
Spring Boot 4.1.1 深度源码解析-第 26 章 · 指标、观测与链路追踪:Micrometer 与 OpenTelemetry
java·后端·开源