trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
@@ -2,6 +2,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yami.trading.bean.data.domain.Kline; import com.yami.trading.bean.data.dto.BeforeClose; import com.yami.trading.bean.ico.domain.Ico; import com.yami.trading.bean.item.domain.Item; import com.yami.trading.common.domain.Result; @@ -9,6 +10,7 @@ import com.yami.trading.common.util.DateUtils; import com.yami.trading.common.web.ResultObject; import com.yami.trading.huobi.data.AdjustmentValueCache; import com.yami.trading.huobi.data.internal.DataDBService; import com.yami.trading.huobi.data.internal.KlineService; import com.yami.trading.service.data.DataService; import com.yami.trading.service.etf.MarketService; @@ -47,6 +49,9 @@ private DataService dataService; @Autowired private IcoService icoService; @Autowired private DataDBService dataDBService; @ApiOperation(value = "行情") @GetMapping(HOBI + "getKline.action") @@ -173,6 +178,16 @@ map.put("high", high.setScale(decimal, RoundingMode.HALF_UP)); map.put("low", low.setScale(decimal, RoundingMode.HALF_UP)); map.put("volume", kline.getVolume()); if (line.equalsIgnoreCase(Kline.PERIOD_15MIN) || line.equalsIgnoreCase(Kline.PERIOD_30MIN) || line.equalsIgnoreCase(Kline.PERIOD_60MIN)) { if (i == data.size() - 1) { BeforeClose beforeClose = dataDBService.getBeforeClose(kline.getSymbol(), line, ts); if (beforeClose != null) { map.put("high", beforeClose.getMaxClose().setScale(decimal, RoundingMode.HALF_UP)); map.put("low", beforeClose.getMinClose().setScale(decimal, RoundingMode.HALF_UP)); } } } list.add(map); } return list; trading-order-bean/src/main/java/com/yami/trading/bean/data/dto/BeforeClose.java
New file @@ -0,0 +1,20 @@ package com.yami.trading.bean.data.dto; import lombok.Data; import lombok.EqualsAndHashCode; import java.math.BigDecimal; @Data @EqualsAndHashCode(callSuper = false) public class BeforeClose { private BigDecimal maxClose = BigDecimal.ZERO; private BigDecimal minClose = BigDecimal.ZERO; } trading-order-common/src/main/java/com/yami/trading/common/constants/RedisKeys.java
@@ -281,4 +281,6 @@ public final static String SYMBOL_AMOUNT_VOLUME = "SYMBOL_AMOUNT_VOLUME_"; public final static String REAL_TIME_BEFORE = "REAL_TIME_BEFORE"; public final static String REAL_TIME_BEFORE_CLOSE = "REAL_TIME_BEFORE_CLOSE"; } trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBService.java
@@ -2,9 +2,11 @@ import com.yami.trading.bean.data.domain.Realtime; import com.yami.trading.bean.data.dto.BeforeClose; import java.math.BigDecimal; import java.util.List; import java.util.Map; public interface DataDBService { /** @@ -23,6 +25,11 @@ public Realtime getBefore(String symbol); /** * 数据库根据时间查询 */ public BeforeClose getBeforeClose(String symbol, String line, Long ts); /** * 缓存数据库前24小时最高最低价格 */ public void cacheBefore24Hour(String symbol); trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java
@@ -2,8 +2,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yami.trading.bean.data.domain.Kline; import com.yami.trading.bean.data.domain.Realtime; import com.yami.trading.bean.data.dto.BeforeClose; import com.yami.trading.bean.item.domain.Item; import com.yami.trading.common.config.RequestDataHelper; import com.yami.trading.common.constants.Constants; @@ -26,6 +28,7 @@ import java.time.*; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service @@ -139,6 +142,50 @@ return realtime; } public BeforeClose getBeforeClose(String symbol, String line, Long ts) { BeforeClose beforeClose = (BeforeClose) redisTemplate.opsForValue().get(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line); if (beforeClose == null) { // 直接获取当前时间的毫秒级时间戳(系统默认时区,但值是全球统一的) long currentTimeStamp = System.currentTimeMillis(); // 如果需要严格基于东京时区的当前时间戳(结果和上面一致,因为时间戳是UTC绝对时间) //long currentTokyoTimeStamp = Instant.now().atZone(ZoneId.of("Asia/Tokyo")).toInstant().toEpochMilli(); RequestDataHelper.set("symbol", symbol); QueryWrapper<Realtime> queryWrapper = new QueryWrapper<Realtime>() .eq("symbol", symbol) // 直接写数据库字段名(需和表字段一致) .ge("ts", ts) .le("ts", currentTimeStamp) .select("MAX(close) as maxClose", "MIN(close) as minClose"); // 4. 执行聚合查询,用selectMap接收结果(键值对:maxClose/minClose -> 对应值) Map<String, Object> resultMap = realtimeService.getMap(queryWrapper); RequestDataHelper.clear(); beforeClose = new BeforeClose(); if (resultMap == null || resultMap.isEmpty()) { return beforeClose; } beforeClose.setMaxClose(convertToBigDecimal(resultMap.get("maxClose"))); beforeClose.setMinClose(convertToBigDecimal(resultMap.get("minClose"))); redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose , 5 , TimeUnit.MINUTES); } return beforeClose; } // 辅助方法:统一转换为BigDecimal,避免类型错误 private BigDecimal convertToBigDecimal(Object value) { if (value == null) { return BigDecimal.ZERO; } if (value instanceof BigDecimal) { return (BigDecimal) value; } try { return new BigDecimal(value.toString()); } catch (NumberFormatException e) { log.error("转换数值为BigDecimal失败:value={}", value, e); return BigDecimal.ZERO; } } @Override public void cacheBefore24Hour(String symbol) { // 计算“24小时前”的时间戳(毫秒级)