新版仿ok交易所-后端
c8d8e75810b7777f2e2768082b6a3e19c7c99cd2..7289903c3b78d4e6e141e3a5e976ddef52e9fc97
2026-05-29 zyy
1
728990 diff | tree
2026-05-29 zyy
1
038c7e diff | tree
2026-05-29 zyy
1
f23676 diff | tree
5 files modified
1 files added
597 ■■■■ changed files
trading-order-admin/src/main/java/com/yami/trading/admin/controller/AddressController.java 95 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/admin/model/channelBlockchain/ChannelBlockchainUpdateModel.java 35 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java 50 ●●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java 113 ●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java 198 ●●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java 106 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/admin/controller/AddressController.java
@@ -2,22 +2,28 @@
import com.yami.trading.admin.model.LoginModel;
import com.yami.trading.admin.model.UpdateAddressModel;
import com.yami.trading.admin.model.channelBlockchain.ChannelBlockchainUpdateModel;
import com.yami.trading.bean.model.ChannelBlockchain;
import com.yami.trading.common.constants.RedisKeys;
import com.yami.trading.common.domain.Result;
import com.yami.trading.common.exception.BusinessException;
import com.yami.trading.common.exception.YamiShopBindException;
import com.yami.trading.common.util.GoogleAuthenticator;
import com.yami.trading.common.util.IPHelper;
import com.yami.trading.common.util.StringUtils;
import com.yami.trading.security.common.bo.UserInfoInTokenBO;
import com.yami.trading.security.common.enums.CryptoCurrencyEnum;
import com.yami.trading.security.common.enums.SysTypeEnum;
import com.yami.trading.security.common.util.LocalKeyStorageAESUtil;
import com.yami.trading.security.common.util.SecurityUtils;
import com.yami.trading.security.common.vo.TokenInfoVO;
import com.yami.trading.service.ChannelBlockchainService;
import com.yami.trading.service.system.LogService;
import com.yami.trading.sys.model.SysUser;
import com.yami.trading.sys.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@@ -30,14 +36,17 @@
 * @description: 充值地址
 * @create: 2025-08-07 14:44
 **/
@Slf4j
@RestController
@RequestMapping("address")
@Api(tags = "充值地址")
public class AddressController {
    @Autowired
    RedisTemplate redisTemplate;
    ChannelBlockchainService channelBlockchainService;
    @Autowired
    SysUserService sysUserService;
    /**
     * 地址列表
@@ -45,18 +54,11 @@
     */
    @PostMapping("/list")
    public Result<?> list() {
        List<CryptoCurrencyEnum> currencyEnums = CryptoCurrencyEnum.getAll();
        List<ChannelBlockchain> data = new ArrayList<>();
        currencyEnums.forEach(f->{
        //List<CryptoCurrencyEnum> currencyEnums = CryptoCurrencyEnum.getAll();
        List<ChannelBlockchain> data = channelBlockchainService.list();
        data.forEach(f->{
            try {
                String address = LocalKeyStorageAESUtil.loadAndDecrypt(f.getName());
                ChannelBlockchain blockchain = new ChannelBlockchain();
                blockchain.setBlockchain_name(f.getChain());
                blockchain.setAddress(address);
                blockchain.setCoin(f.getCoin());
                blockchain.setAuto(false);
                blockchain.setImg(null);
                data.add(blockchain);
                f.setBlockchain_name(f.getBlockchainName());
            } catch (Exception e) {
                e.getMessage();
            }
@@ -64,7 +66,7 @@
        return Result.succeed(data);
    }
    @PostMapping("/update")
    /*@PostMapping("/update")
    public Result<?> list(@RequestBody UpdateAddressModel model) {
        String name = model.getCoin().toLowerCase()+"_"+model.getChain().toLowerCase();
        try {
@@ -76,34 +78,41 @@
            e.getMessage();
        }
        return Result.succeed();
    }*/
    @ApiOperation("修改 区块链充值地址")
    @PostMapping("update")
    public Result update(@RequestBody @Valid ChannelBlockchainUpdateModel model) {
        String id = model.getId();
        String blockchain_name = model.getBlockchainName();
        String coin = model.getCoin();
        String address = model.getAddress();
        ChannelBlockchain channelBlockchain = this.channelBlockchainService.getById(id);
        /*String error = this.verif(blockchain_name, coin, address, null);
        if (!StringUtils.isNullOrEmpty(error)) {
            throw new BusinessException(error);
        }*/
        if (!blockchain_name.isEmpty()) {
            channelBlockchain.setBlockchain_name(blockchain_name);
        }
        if (!coin.isEmpty()) {
            channelBlockchain.setCoin(coin);
        }
        if (!address.isEmpty()) {
            channelBlockchain.setAddress(address);
        }
        channelBlockchainService.updateById(channelBlockchain);
        return Result.succeed();
    }
    /**
     * u盾地址列表
     * @return
     */
    @GetMapping("/getUDList")
    public Result<?> getUDList(String partyId) {
        if (partyId == null || partyId.isEmpty()) {
            return Result.failed("请选择用户");
        }
        List<CryptoCurrencyEnum> currencyEnums = CryptoCurrencyEnum.getAll();
        List<ChannelBlockchain> data = new ArrayList<>();
        currencyEnums.forEach(currencyEnum->{
            try {
                String address = (String)redisTemplate.opsForValue().get(RedisKeys.BLOCKCHAIN_ADDRESS + partyId + currencyEnum.getName());
                ChannelBlockchain blockchain = new ChannelBlockchain();
                blockchain.setBlockchain_name(currencyEnum.getChain());
                blockchain.setAddress(address);
                blockchain.setCoin(currencyEnum.getCoin());
                blockchain.setAuto(false);
                blockchain.setImg(null);
                data.add(blockchain);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        });
        return Result.succeed(data);
    private String verif(String blockchain_name, String coin, String address, String img) {
        if (StringUtils.isEmptyString(blockchain_name))
            return "请输入链名称";
        if (StringUtils.isEmptyString(coin))
            return "请输入币种";
        if (StringUtils.isEmptyString(address))
            return "请输入地址";
        return null;
    }
}
trading-order-admin/src/main/java/com/yami/trading/admin/model/channelBlockchain/ChannelBlockchainUpdateModel.java
New file
@@ -0,0 +1,35 @@
package com.yami.trading.admin.model.channelBlockchain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel
public class ChannelBlockchainUpdateModel {
    private  String id;
    /**
     * 币种名称 BTC ETH USDT
     */
    @ApiModelProperty("币种名称")
    private String coin;
    /**
     * 链名称
     */
    @ApiModelProperty("链名称")
    private String blockchainName;
    @ApiModelProperty("地址")
    private  String address;
/*    @ApiModelProperty("资金密码")
    private  String  safeword;*/
//
//    @ApiModelProperty("超级谷歌验证码")
//    private  String  superGoogleAuthCode;
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
@@ -123,30 +123,30 @@
            return Result.succeed(this.build(data, line, symbol));
        } catch (Exception e) {
            logger.error("getKline error", e);
            throw new YamiShopBindException("k线图获取失败");
            throw new YamiShopBindException("");
        }
    }
    private List<Map<String, Object>> build(List<Kline> data, String line, String symbol) {
        Collections.sort(data);
        // 相同时间戳保留最新一条(进行中K线会覆盖缓存中的旧数据)
        Map<Long, Kline> latestByTs = new LinkedHashMap<>();
        for (Kline kline : data) {
            latestByTs.put(kline.getTs(), kline);
        }
        data = new ArrayList<>(latestByTs.values());
        Collections.sort(data);
        int len = data.size();
        for (int i = 1; i < len; i++) {
            data.get(i).setOpen(data.get(i - 1).getClose());
        }
        Set<Long> tsSet = new HashSet<Long>();
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        Item bySymbol = itemService.findBySymbol(symbol);
        for (int i = 0; i < data.size(); i++) {
            Kline kline = data.get(i);
            Long ts = kline.getTs();
            if (tsSet.contains(ts)) {
                continue;
            } else {
                tsSet.add(ts);
            }
            String fake = bySymbol.getFake();
            //    if("1".equalsIgnoreCase(fake)){
            //        klineService.smoothlyKline(kline, 0.99);
@@ -181,20 +181,32 @@
            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) {
                    //获取当前价格
                    Realtime realtime = DataCache.getLatestRealTime(symbol);
                    if (realtime != null) {
                        map.put("close", realtime.getClose().setScale(decimal, RoundingMode.HALF_UP));
            if (i == data.size() - 1) {
                Realtime realtime = DataCache.getLatestRealTime(symbol);
                if (realtime != null && realtime.getClose() != null) {
                    close = realtime.getClose();
                    map.put("close", close.setScale(decimal, RoundingMode.HALF_UP));
                    if (close.compareTo(high) > 0) {
                        high = close;
                    }
                    BeforeClose beforeClose = dataDBService.getBeforeClose(kline.getSymbol(), line, ts, realtime);
                    if (beforeClose != null) {
                        map.put("high", beforeClose.getMaxClose().setScale(decimal, RoundingMode.HALF_UP));
                        map.put("low", beforeClose.getMinClose().setScale(decimal, RoundingMode.HALF_UP));
                    if (close.compareTo(low) < 0) {
                        low = close;
                    }
                }
            //}
                BeforeClose beforeClose = dataDBService.getBeforeClose(kline.getSymbol(), line, ts, realtime);
                if (beforeClose != null) {
                    if (beforeClose.getMaxClose() != null
                            && beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0) {
                        high = beforeClose.getMaxClose();
                    }
                    if (beforeClose.getMinClose() != null
                            && beforeClose.getMinClose().compareTo(BigDecimal.ZERO) > 0) {
                        low = beforeClose.getMinClose();
                    }
                }
                map.put("high", high.setScale(decimal, RoundingMode.HALF_UP));
                map.put("low", low.setScale(decimal, RoundingMode.HALF_UP));
            }
            list.add(map);
        }
        return list;
trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java
@@ -146,43 +146,96 @@
        BeforeClose beforeClose = (BeforeClose) redisTemplate.opsForValue().get(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line);
        //超出时间重新计算
        if (beforeClose == null || ts > beforeClose.getTs()) {
            // 直接获取当前时间的毫秒级时间戳(系统默认时区,但值是全球统一的)
            long currentTimeStamp = System.currentTimeMillis();
            // 如果需要严格基于东京时区的当前时间戳(结果和上面一致,因为时间戳是UTC绝对时间)
            //long currentTokyoTimeStamp = Instant.now().atZone(ZoneId.of("America/New_York")).toInstant().toEpochMilli();
            RequestDataHelper.set("symbol", symbol);
            QueryWrapper<Realtime> queryWrapper = new QueryWrapper<Realtime>()
                    .eq("symbol", symbol) // 直接写数据库字段名(需和表字段一致)
                    .ge("ts", ts)
                    .le("ts", currentTimeStamp)
                    .select("MAX(CAST(close AS DECIMAL(10,4))) as maxClose",
                            "MIN(CAST(close AS DECIMAL(10,4))) 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 = calcBeforeClose(symbol, line, ts);
            if (beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0) {
                redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
            }
            beforeClose.setMaxClose(convertToBigDecimal(resultMap.get("maxClose")));
            beforeClose.setMinClose(convertToBigDecimal(resultMap.get("minClose")));
            beforeClose.setTs(ts);
        }
        if (realtime != null && realtime.getClose() != null) {
            if (beforeClose.getMaxClose() == null || beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) <= 0) {
                beforeClose.setMaxClose(realtime.getClose());
            } else if (realtime.getClose().compareTo(beforeClose.getMaxClose()) > 0) {
                beforeClose.setMaxClose(realtime.getClose());
            }
            if (beforeClose.getMinClose() == null || beforeClose.getMinClose().compareTo(BigDecimal.ZERO) <= 0) {
                beforeClose.setMinClose(realtime.getClose());
            } else if (realtime.getClose().compareTo(beforeClose.getMinClose()) < 0) {
                beforeClose.setMinClose(realtime.getClose());
            }
            redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
        }
        return beforeClose;
    }
        if (realtime != null) {
            if (realtime.getClose().compareTo(beforeClose.getMaxClose()) > 0) {
                beforeClose.setMaxClose(realtime.getClose());
                redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
    private BeforeClose calcBeforeClose(String symbol, String line, Long ts) {
        BeforeClose beforeClose = new BeforeClose();
        beforeClose.setTs(ts);
        int interval = sysparaService.find("data_interval").getInteger() / 1000;
        if (interval <= 0) {
            interval = 1;
        }
        HighLow highLow = null;
        switch (line) {
            case Kline.PERIOD_1MIN:
                highLow = HighLowHandle.get(symbol, 60 / interval, interval);
                break;
            case Kline.PERIOD_5MIN:
                highLow = HighLowHandle.get(symbol, (60 * 5) / interval, interval);
                break;
            case Kline.PERIOD_15MIN:
                highLow = HighLowHandle.get(symbol, (60 * 15) / interval, interval);
                break;
            case Kline.PERIOD_30MIN:
                highLow = HighLowHandle.get(symbol, (60 * 30) / interval, interval);
                break;
            case Kline.PERIOD_60MIN:
                highLow = HighLowHandle.get(symbol, (60 * 60) / interval, interval);
                break;
            case Kline.PERIOD_4HOUR:
                highLow = HighLowHandle.get(symbol, (60 * 60 * 4) / interval, interval);
                break;
            case Kline.PERIOD_1DAY:
                highLow = HighLowHandle.get(symbol, (60 * 60 * 24) / interval, interval);
                break;
            case Kline.PERIOD_1WEEK:
                highLow = HighLowHandle.getByDay(symbol, 7);
                break;
            case Kline.PERIOD_1MON:
                highLow = HighLowHandle.getByDay(symbol, 30);
                break;
            default:
                break;
        }
        if (highLow != null && highLow.getHigh() != null) {
            beforeClose.setMaxClose(highLow.getHigh());
        }
        if (highLow != null && highLow.getLow() != null) {
            beforeClose.setMinClose(highLow.getLow());
        }
        if (beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0) {
            return beforeClose;
        }
        long currentTimeStamp = System.currentTimeMillis();
        RequestDataHelper.set("symbol", symbol);
        QueryWrapper<Realtime> queryWrapper = new QueryWrapper<Realtime>()
                .eq("symbol", symbol)
                .ge("ts", ts)
                .le("ts", currentTimeStamp)
                .select("MAX(CAST(close AS DECIMAL(10,4))) as maxClose",
                        "MIN(CAST(close AS DECIMAL(10,4))) as minClose");
        Map<String, Object> resultMap = realtimeService.getMap(queryWrapper);
        RequestDataHelper.clear();
        if (resultMap != null && !resultMap.isEmpty()) {
            BigDecimal maxClose = convertToBigDecimal(resultMap.get("maxClose"));
            BigDecimal minClose = convertToBigDecimal(resultMap.get("minClose"));
            if (maxClose.compareTo(BigDecimal.ZERO) > 0) {
                beforeClose.setMaxClose(maxClose);
            }
            if (realtime.getClose().compareTo(beforeClose.getMinClose()) < 0) {
                beforeClose.setMinClose(realtime.getClose());
                redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
            if (minClose.compareTo(BigDecimal.ZERO) > 0) {
                beforeClose.setMinClose(minClose);
            }
        }
        System.out.println("realtime.getClose():" + realtime.getClose() + "==" + beforeClose);
        return beforeClose;
    }
trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java
@@ -29,6 +29,9 @@
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -180,6 +183,165 @@
    public Kline buildKline(String symbol, String line, String smallLevelLine, int nums) {
        if (Kline.PERIOD_5DAY.equals(line)) {
            return buildKlineLegacy(symbol, line, smallLevelLine, nums);
        }
        return buildInProgressKline(symbol, line, smallLevelLine);
    }
    /**
     * 按当前周期起点拼接进行中的K线(ts 对齐到周期开盘时间,如 5 分钟线的 9:35)
     */
    private Kline buildInProgressKline(String symbol, String line, String smallLevelLine) {
        try {
            KlineTimeObject timeObject = DataCache.getKline(symbol, line);
            if (timeObject == null) {
                return null;
            }
            List<Kline> klineList = timeObject.getKline();
            Item item = itemService.findBySymbol(symbol);
            Kline latestSameLineKline = null;
            if (klineList != null && !klineList.isEmpty()) {
                latestSameLineKline = klineList.get(klineList.size() - 1);
            } else if (item.getFake().equalsIgnoreCase("0")) {
                return null;
            }
            Realtime latestRealtime = DataCache.getLatestRealTime(symbol);
            if (latestRealtime == null) {
                latestRealtime = DataCache.getRealtime(symbol);
            }
            if (latestRealtime == null || latestRealtime.getClose() == null) {
                return null;
            }
            long currentPeriodTs = alignPeriodStartTs(line, latestRealtime.getTs());
            if (latestSameLineKline != null && latestSameLineKline.getTs() != null
                    && latestSameLineKline.getTs() > currentPeriodTs) {
                return null;
            }
            KlineTimeObject smallObject = DataCache.getKline(symbol, smallLevelLine);
            List<Kline> periodBars = new ArrayList<>();
            if (smallObject != null && smallObject.getKline() != null) {
                periodBars = smallObject.getKline().stream()
                        .filter(k -> k.getTs() != null && k.getTs() >= currentPeriodTs)
                        .collect(Collectors.toList());
            }
            Kline kline = new Kline();
            kline.setSymbol(symbol);
            kline.setPeriod(line);
            kline.setTs(currentPeriodTs);
            if (latestSameLineKline != null && latestSameLineKline.getTs() != null
                    && latestSameLineKline.getTs() < currentPeriodTs) {
                kline.setOpen(latestSameLineKline.getClose());
            } else if (!periodBars.isEmpty() && periodBars.get(0).getOpen() != null) {
                kline.setOpen(periodBars.get(0).getOpen());
            } else if (latestRealtime.getOpen() != null) {
                kline.setOpen(latestRealtime.getOpen());
            } else {
                kline.setOpen(latestRealtime.getClose());
            }
            if (!periodBars.isEmpty()) {
                Double high = null;
                Double low = null;
                for (Kline bar : periodBars) {
                    if (bar.getHigh() != null) {
                        if (high == null || high <= bar.getHigh().doubleValue()) {
                            high = bar.getHigh().doubleValue();
                        }
                    }
                    if (bar.getLow() != null) {
                        if (low == null || low >= bar.getLow().doubleValue()) {
                            low = bar.getLow().doubleValue();
                        }
                    }
                }
                kline.setHigh(high == null ? latestRealtime.getClose() : new BigDecimal(high));
                kline.setLow(low == null ? latestRealtime.getClose() : new BigDecimal(low));
                kline.setClose(periodBars.get(periodBars.size() - 1).getClose());
                kline.setVolume(periodBars.stream()
                        .map(Kline::getVolume)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add));
                kline.setAmount(periodBars.stream()
                        .map(Kline::getAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add));
            } else {
                kline.setHigh(latestRealtime.getClose());
                kline.setLow(latestRealtime.getClose());
                kline.setClose(latestRealtime.getClose());
                kline.setVolume(latestRealtime.getVolume());
                kline.setAmount(latestRealtime.getAmount());
            }
            kline.setClose(latestRealtime.getClose());
            if (latestRealtime.getClose().compareTo(kline.getHigh()) > 0) {
                kline.setHigh(latestRealtime.getClose());
            }
            if (latestRealtime.getClose().compareTo(kline.getLow()) < 0) {
                kline.setLow(latestRealtime.getClose());
            }
            repairKline(kline);
            if (kline.getOpen().compareTo(BigDecimal.ZERO) == 0 || kline.getClose().compareTo(BigDecimal.ZERO) == 0) {
                return null;
            }
            return kline;
        } catch (Exception e) {
            logger.error("buildInProgressKline error: {}, {}", symbol, line, e);
        }
        return null;
    }
    /**
     * 将时间戳对齐到K线周期起点(UTC,与火币 id 字段一致)
     * 1week:周一 00:00 UTC;1mon:每月 1 日 00:00 UTC
     */
    private long alignPeriodStartTs(String line, long tsMillis) {
        ZonedDateTime zdt = Instant.ofEpochMilli(tsMillis).atZone(ZoneOffset.UTC);
        switch (line) {
            case Kline.PERIOD_1MIN:
                return zdt.withSecond(0).withNano(0).toInstant().toEpochMilli();
            case Kline.PERIOD_5MIN: {
                int minute = zdt.getMinute();
                return zdt.withMinute(minute - minute % 5).withSecond(0).withNano(0).toInstant().toEpochMilli();
            }
            case Kline.PERIOD_15MIN: {
                int minute = zdt.getMinute();
                return zdt.withMinute(minute - minute % 15).withSecond(0).withNano(0).toInstant().toEpochMilli();
            }
            case Kline.PERIOD_30MIN: {
                int minute = zdt.getMinute();
                return zdt.withMinute(minute - minute % 30).withSecond(0).withNano(0).toInstant().toEpochMilli();
            }
            case Kline.PERIOD_60MIN:
                return zdt.withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
            case Kline.PERIOD_1DAY:
                return zdt.withHour(0).withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
            case Kline.PERIOD_1WEEK: {
                zdt = zdt.withHour(0).withMinute(0).withSecond(0).withNano(0);
                int dayOfWeek = zdt.getDayOfWeek().getValue();
                return zdt.minusDays(dayOfWeek - 1L).toInstant().toEpochMilli();
            }
            case Kline.PERIOD_1MON:
                return zdt.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
            case Kline.PERIOD_2HOUR:
                return zdt.withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
            case Kline.PERIOD_4HOUR: {
                int hour = zdt.getHour();
                return zdt.withHour(hour - hour % 4).withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
            }
            default:
                return zdt.withSecond(0).withNano(0).toInstant().toEpochMilli();
        }
    }
    private Kline buildKlineLegacy(String symbol, String line, String smallLevelLine, int nums) {
        try {
            // 取5分钟K线全部数据集合
            KlineTimeObject timeObject = DataCache.getKline(symbol, line);
@@ -211,14 +373,27 @@
            if (realtimeKline == null) {
                return null;
            }
            if (latestSameLineKline != null && latestSameLineKline.getTs() >= realtimeKline.getTs()) {
            if (latestSameLineKline != null && latestSameLineKline.getTs() > realtimeKline.getTs()) {
                return null;
            }
            if (latestSameLineKline != null) {
                long latestSameLineKlineTs = latestSameLineKline.getTs();
                klineOneTop5 = klineOneTop5.stream().filter(r -> r.getTs() > latestSameLineKlineTs).collect(Collectors.toList());
                if (latestSameLineKlineTs == realtimeKline.getTs()) {
                    klineOneTop5 = klineOne.stream()
                            .filter(r -> r.getTs() >= latestSameLineKlineTs)
                            .collect(Collectors.toList());
                    if (klineOneTop5.size() > nums) {
                        klineOneTop5 = new ArrayList<>(klineOneTop5.subList(klineOneTop5.size() - nums, klineOneTop5.size()));
                    }
                } else {
                    klineOneTop5 = klineOneTop5.stream()
                            .filter(r -> r.getTs() > latestSameLineKlineTs)
                            .collect(Collectors.toList());
                }
            }
            if (klineOneTop5.isEmpty()) {
                return null;
            }
            Double high = null;
            Double low = null;
@@ -673,11 +848,16 @@
                latestKilne = klineList.get(klineList.size() - 1);
            }
            Realtime realtime = realTimeList.get(realTimeList.size() - 1);
            if (latestKilne != null && latestKilne.getTs() >= realtime.getTs()) {
            long currentMinuteTs = alignPeriodStartTs(Kline.PERIOD_1MIN, realtime.getTs());
            if (latestKilne != null && latestKilne.getTs() != null && latestKilne.getTs() > currentMinuteTs) {
                return null;
            }
            long lastKlineTs = latestKilne.getTs();
            realTimeList = realTimeList.stream().filter(r -> r.getTs() > lastKlineTs).collect(Collectors.toList());
            realTimeList = realTimeList.stream()
                    .filter(r -> r.getTs() != null && r.getTs() >= currentMinuteTs)
                    .collect(Collectors.toList());
            if (realTimeList.isEmpty()) {
                realTimeList = Collections.singletonList(realtime);
            }
            Double high = null;
            Double low = null;
            for (Realtime realTime : realTimeList) {
@@ -692,11 +872,11 @@
            // 保存K线到数据库
            Kline kline = new Kline();
            kline.setSymbol(symbol);
            kline.setTs(realtime.getTs());
            if (latestKilne != null) {
            kline.setTs(currentMinuteTs);
            if (latestKilne != null && latestKilne.getTs() != null && latestKilne.getTs() < currentMinuteTs) {
                kline.setOpen(latestKilne.getClose());
            } else {
                kline.setOpen(realTimeList.get(0).getOpen());
                kline.setOpen(realTimeList.get(0).getOpen() != null ? realTimeList.get(0).getOpen() : realtime.getClose());
            }
            kline.setHigh(new BigDecimal(high));
            kline.setLow(new BigDecimal(low));
trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java
@@ -73,10 +73,6 @@
    @Override
    public List<Kline> kline(String symbol, String line) {
        Item bySymbol = itemService.findBySymbol(symbol);
        if(Item.cryptos.equals(bySymbol.getType())){
            return klineCryptos(symbol, line);
        }
        KlineTimeObject timeObject = DataCache.getKline(symbol, line);
        List<Kline> list = new ArrayList<Kline>();
        if (timeObject != null) {
@@ -85,7 +81,7 @@
        List<Kline> list_clone = new ArrayList<Kline>();
        try {
            for (int i = 0; i < list.size(); i++) {
                if(list.get(i) == null){
                if (list.get(i) == null) {
                    continue;
                }
                Kline kline = (Kline) list.get(i).clone();
@@ -96,71 +92,61 @@
        }
        Realtime realtime = DataCache.getLatestRealTime(symbol);
        if (realtime != null) {
            Kline kline = null;
            if (KlineConstant.PERIOD_1MIN.equals(line)) {
                kline = klineService.bulidKline1Minute(realtime, KlineConstant.PERIOD_1MIN);
            } else if (KlineConstant.PERIOD_5MIN.equals(line)) {
                kline = klineService.bulidKline5Minute(realtime, KlineConstant.PERIOD_5MIN);
            } else if (KlineConstant.PERIOD_15MIN.equals(line)) {
                kline = klineService.bulidKline15Minute(realtime, KlineConstant.PERIOD_15MIN);
            } else if (KlineConstant.PERIOD_30MIN.equals(line)) {
                kline = klineService.bulidKline30Minute(realtime, KlineConstant.PERIOD_30MIN);
            } else if (KlineConstant.PERIOD_60MIN.equals(line)) {
                kline = klineService.bulidKline60Minute(realtime, KlineConstant.PERIOD_60MIN);
            } else if (KlineConstant.PERIOD_4HOUR.equals(line)) {
                kline = klineService.bulidKline4Hour(realtime, KlineConstant.PERIOD_4HOUR);
            } else if (KlineConstant.PERIOD_1DAY.equals(line)) {
                kline = klineService.bulidKline1Day(realtime, KlineConstant.PERIOD_1DAY);
            } else if (KlineConstant.PERIOD_5DAY.equals(line)) {
                kline = klineService.bulidKline5Day(realtime, KlineConstant.PERIOD_5DAY);
            } else if (KlineConstant.PERIOD_1WEEK.equals(line)) {
                kline = klineService.bulidKline1Week(realtime, KlineConstant.PERIOD_1WEEK);
            } else if (KlineConstant.PERIOD_1MON.equals(line)) {
                kline = klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_1MON);
            } else if (KlineConstant.PERIOD_QUARTER.equals(line)) {
                kline = klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_QUARTER);
            } else if (KlineConstant.PERIOD_YEAR.equals(line)) {
                kline = klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_YEAR);
            }
            if (null != kline) {
                list_clone.add(kline);
            }
        if (realtime == null) {
            realtime = DataCache.getRealtime(symbol);
        }
        // 按时间升序
        if (realtime != null) {
            appendOrReplaceKline(list_clone, buildCurrentKline(realtime, line));
        }
        Collections.sort(list_clone);
        return list_clone;
    }
    private Kline buildCurrentKline(Realtime realtime, String line) {
        if (KlineConstant.PERIOD_1MIN.equals(line)) {
            return klineService.bulidKline1Minute(realtime, KlineConstant.PERIOD_1MIN);
        } else if (KlineConstant.PERIOD_5MIN.equals(line)) {
            return klineService.bulidKline5Minute(realtime, KlineConstant.PERIOD_5MIN);
        } else if (KlineConstant.PERIOD_15MIN.equals(line)) {
            return klineService.bulidKline15Minute(realtime, KlineConstant.PERIOD_15MIN);
        } else if (KlineConstant.PERIOD_30MIN.equals(line)) {
            return klineService.bulidKline30Minute(realtime, KlineConstant.PERIOD_30MIN);
        } else if (KlineConstant.PERIOD_60MIN.equals(line)) {
            return klineService.bulidKline60Minute(realtime, KlineConstant.PERIOD_60MIN);
        } else if (KlineConstant.PERIOD_4HOUR.equals(line)) {
            return klineService.bulidKline4Hour(realtime, KlineConstant.PERIOD_4HOUR);
        } else if (KlineConstant.PERIOD_1DAY.equals(line)) {
            return klineService.bulidKline1Day(realtime, KlineConstant.PERIOD_1DAY);
        } else if (KlineConstant.PERIOD_5DAY.equals(line)) {
            return klineService.bulidKline5Day(realtime, KlineConstant.PERIOD_5DAY);
        } else if (KlineConstant.PERIOD_1WEEK.equals(line)) {
            return klineService.bulidKline1Week(realtime, KlineConstant.PERIOD_1WEEK);
        } else if (KlineConstant.PERIOD_1MON.equals(line)) {
            return klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_1MON);
        } else if (KlineConstant.PERIOD_QUARTER.equals(line)) {
            return klineService.bulidKlineQuarter(realtime, KlineConstant.PERIOD_QUARTER);
        } else if (KlineConstant.PERIOD_YEAR.equals(line)) {
            return klineService.bulidKlineYear(realtime, KlineConstant.PERIOD_YEAR);
        }
        return null;
    }
    public List<Kline> klineCryptos(String symbol, String line) {
        KlineTimeObject timeObject = DataCache.getKline(symbol, line);
        List<Kline> list = new ArrayList<Kline>();
        if (timeObject != null) {
            list = timeObject.getKline();
        return kline(symbol, line);
    }
    private void appendOrReplaceKline(List<Kline> list, Kline kline) {
        if (kline == null) {
            return;
        }
        List<Kline> list_clone = new ArrayList<Kline>();
        try {
            for (int i = 0; i < list.size(); i++) {
                Kline kline = (Kline) list.get(i).clone();
                list_clone.add(kline);
        for (int i = list.size() - 1; i >= 0; i--) {
            Kline existing = list.get(i);
            if (existing.getTs() != null && existing.getTs().equals(kline.getTs())) {
                list.set(i, kline);
                return;
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        Realtime realtime = DataCache.getRealtime(symbol);
        Kline hobiOne = DataCache.getKline_hobi().get(symbol + "_" + line);
        Kline lastOne = null;
        if (list != null && list.size() > 0) {
            lastOne = list.get(list.size() - 1);
        }
        if (realtime != null && hobiOne != null && lastOne != null) {
            list_clone.add(this.klineService.bulidKline(realtime, lastOne, hobiOne, line));
        }
        Collections.sort(list_clone); // 按时间升序
        return list_clone;
        list.add(kline);
    }
    @Override