1
zj
5 days ago 5e57de9b12ee136e45ce5754c7fe2e7eb12af05a
1
21 files modified
11 files added
1229 ■■■■ changed files
docs/db/V10_1__us_futures_role_menu.sql 25 ●●●●● patch | view | raw | blame | history
docs/db/V10_2__us_futures_menu_fix.sql 45 ●●●●● patch | view | raw | blame | history
docs/db/V10__us_futures_menu.sql 71 ●●●●● patch | view | raw | blame | history
docs/db/V8__pre_market_and_futures_manual_profit.sql 22 ●●●●● patch | view | raw | blame | history
docs/db/V9__pre_market_sys_menu.sql 48 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/admin/controller/future/AdminFuturesOrderController.java 13 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/admin/controller/item/AdminItemPreMarketController.java 56 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/admin/controller/sys/SysMenuController.java 6 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiFuturesOrderController.java 23 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java 3 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java 6 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java 8 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/future/domain/FuturesOrder.java 3 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/future/dto/TFuturesOrderDTO.java 3 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/item/domain/ItemPreMarketConfig.java 29 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigDTO.java 17 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigSaveModel.java 27 ●●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com/yami/trading/huobi/data/internal/DataServiceImpl.java 23 ●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com/yami/trading/huobi/task/RealtimePushJob.java 11 ●●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com/yami/trading/huobi/task/contract/ContractApplyOrderHandleJob.java 52 ●●●● patch | view | raw | blame | history
trading-order-huobi/src/main/java/com/yami/trading/huobi/task/future/FuturesOrderCalculationJob.java 3 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/dao/item/ItemPreMarketConfigMapper.java 7 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/MarketOpenChecker.java 4 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java 30 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/exchange/impl/ExchangeApplyOrderServiceImpl.java 56 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/exchange/job/ExchangeApplyOrderHandleJob.java 21 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/future/FuturesOrderService.java 212 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/item/ItemPreMarketService.java 260 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/item/ItemService.java 6 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/user/impl/SessionTokenServiceImpl.java 25 ●●●●● patch | view | raw | blame | history
trading-order-sys/src/main/java/com/yami/trading/sys/service/impl/SysMenuServiceImpl.java 97 ●●●●● patch | view | raw | blame | history
trading-order-sys/src/main/resources/mapper/SysMenuMapper.xml 17 ●●●● patch | view | raw | blame | history
docs/db/V10_1__us_futures_role_menu.sql
New file
@@ -0,0 +1,25 @@
-- 给角色补授权:交割合约单 + 场控设置 + 父目录 1864
-- 把 @role_id 换成你登录账号的角色 id(见下方查询)
-- 查当前用户角色
-- SELECT ur.user_id, u.username, ur.role_id, r.role_name
-- FROM tz_sys_user_role ur
-- JOIN tz_sys_user u ON u.user_id = ur.user_id
-- JOIN tz_sys_role r ON r.role_id = ur.role_id
-- WHERE u.username = '你的登录名';
SET @role_id = 1;
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT @role_id, m.menu_id
FROM tz_sys_menu m
WHERE m.menu_id IN (1864, 1981, 1982)
  AND NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x WHERE x.role_id = @role_id AND x.menu_id = m.menu_id
  );
-- 验证
-- SELECT rm.role_id, m.menu_id, m.name, m.url
-- FROM tz_sys_role_menu rm
-- JOIN tz_sys_menu m ON m.menu_id = rm.menu_id
-- WHERE rm.role_id = @role_id AND m.menu_id IN (1864, 1981, 1982);
docs/db/V10_2__us_futures_menu_fix.sql
New file
@@ -0,0 +1,45 @@
-- 菜单「有记录但前端不显示」常见修复
-- 请在【管理后台实际连接的数据库】执行(本地 zh-jys-admin 默认连 https://zhapi.bitget-jp-us.cyou 对应库)
-- 1) 诊断:菜单是否存在
SELECT menu_id, parent_id, name, url, type, app_type, order_num
FROM tz_sys_menu
WHERE menu_id IN (1864, 1981, 1982)
   OR url IN ('us-spots/us-pickAddr', 'us-spots/us-indexImg', 'us-spots/us-markets-config');
-- 2) 诊断:当前角色是否授权(把 @role_id 改成你的角色)
SET @role_id = 1;
SELECT rm.role_id, m.menu_id, m.name, m.url
FROM tz_sys_role_menu rm
JOIN tz_sys_menu m ON m.menu_id = rm.menu_id
WHERE rm.role_id = @role_id
  AND m.menu_id IN (1864, 1981, 1982);
-- 3) 同步 app_type:与父目录 1864 保持一致(避免 app_type 精确匹配查不到父节点)
UPDATE tz_sys_menu child
JOIN tz_sys_menu parent ON parent.menu_id = 1864
SET child.app_type = IFNULL(NULLIF(parent.app_type, ''), '1')
WHERE child.menu_id IN (1981, 1982);
-- 4) 确保 type=1(页面菜单),icon 不为空
UPDATE tz_sys_menu
SET type = 1, icon = IFNULL(NULLIF(icon, ''), 'sql')
WHERE menu_id IN (1981, 1982);
-- 5) 给角色补授权(1864 + 两个子菜单)
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT @role_id, m.menu_id
FROM tz_sys_menu m
WHERE m.menu_id IN (1864, 1981, 1982)
  AND NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x WHERE x.role_id = @role_id AND x.menu_id = m.menu_id
  );
-- 6) 若 1864 本身未授权,补全 1864 全部子菜单给该角色
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT @role_id, m.menu_id
FROM tz_sys_menu m
WHERE m.parent_id = 1864
  AND NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x WHERE x.role_id = @role_id AND x.menu_id = m.menu_id
  );
docs/db/V10__us_futures_menu.sql
New file
@@ -0,0 +1,71 @@
-- 美股交割合约单 + 交割场控设置 — 挂到「股票-显示-美股」(menu_id=1864) 下
-- 若你库中美股父菜单 id 不是 1864,先查:SELECT menu_id, name FROM tz_sys_menu WHERE name LIKE '%美股%';
-- 执行后退出后台重新登录
-- 【可选】查看 1864 下现有子菜单
-- SELECT menu_id, parent_id, name, url, order_num FROM tz_sys_menu WHERE parent_id = 1864;
-- 1) 交割合约单(US)
INSERT INTO tz_sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, app_type)
SELECT
    (SELECT IFNULL(MAX(s.menu_id), 0) + 1 FROM tz_sys_menu s),
    1864,
    '交割合约单(US)',
    'us-spots/us-pickAddr',
    '',
    1,
    'sql',
    IFNULL((SELECT MAX(c.order_num) FROM tz_sys_menu c WHERE c.parent_id = 1864), 0) + 2,
    '1'
WHERE NOT EXISTS (
    SELECT 1 FROM tz_sys_menu p WHERE p.url = 'us-spots/us-pickAddr'
);
-- 2) 交割场控设置(美股)
INSERT INTO tz_sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, app_type)
SELECT
    (SELECT IFNULL(MAX(s.menu_id), 0) + 1 FROM tz_sys_menu s),
    1864,
    '交割场控设置(美股)',
    'us-spots/us-indexImg',
    '',
    1,
    'sql',
    IFNULL((SELECT MAX(c.order_num) FROM tz_sys_menu c WHERE c.parent_id = 1864), 0) + 3,
    '1'
WHERE NOT EXISTS (
    SELECT 1 FROM tz_sys_menu p WHERE p.url = 'us-spots/us-indexImg'
);
-- 3) 角色授权:已有 1864 权限的角色
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT DISTINCT rm.role_id, m.menu_id
FROM tz_sys_role_menu rm
CROSS JOIN tz_sys_menu m
WHERE rm.menu_id = 1864
  AND m.url IN ('us-spots/us-pickAddr', 'us-spots/us-indexImg')
  AND NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x WHERE x.role_id = rm.role_id AND x.menu_id = m.menu_id
  );
-- 4) 角色授权:已有 1864 下任意子菜单权限的角色
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT DISTINCT rm.role_id, m.menu_id
FROM tz_sys_role_menu rm
INNER JOIN tz_sys_menu child ON rm.menu_id = child.menu_id AND child.parent_id = 1864
CROSS JOIN tz_sys_menu m
WHERE m.url IN ('us-spots/us-pickAddr', 'us-spots/us-indexImg')
  AND NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x WHERE x.role_id = rm.role_id AND x.menu_id = m.menu_id
  );
-- 【验证】角色是否已授权(把 YOUR_ROLE_ID 换成你的角色 id)
-- SELECT rm.role_id, m.menu_id, m.name, m.url FROM tz_sys_role_menu rm
-- JOIN tz_sys_menu m ON rm.menu_id = m.menu_id
-- WHERE m.menu_id IN (1864, 1981, 1982);
-- 【若菜单有、角色无】给管理员角色补授权(示例 role_id=1,请按实际修改)
-- INSERT INTO tz_sys_role_menu (role_id, menu_id)
-- SELECT 1, m.menu_id FROM tz_sys_menu m
-- WHERE m.menu_id IN (1864, 1981, 1982)
-- AND NOT EXISTS (SELECT 1 FROM tz_sys_role_menu x WHERE x.role_id = 1 AND x.menu_id = m.menu_id);
docs/db/V8__pre_market_and_futures_manual_profit.sql
New file
@@ -0,0 +1,22 @@
-- 美股盘前交易配置
CREATE TABLE IF NOT EXISTS t_item_pre_market_config (
    uuid            VARCHAR(64)  NOT NULL PRIMARY KEY COMMENT '主键',
    symbol          VARCHAR(64)  NOT NULL COMMENT '股票代码',
    start_time      VARCHAR(8)   NOT NULL COMMENT '盘前开始时间 HH:mm 24小时制',
    end_time        VARCHAR(8)   NOT NULL COMMENT '盘前结束时间 HH:mm 24小时制',
    pre_price       DECIMAL(20, 8) NOT NULL COMMENT '盘前价格',
    enabled         TINYINT(1)   NOT NULL DEFAULT 1 COMMENT '是否启用 1启用 0禁用',
    create_time     DATETIME     NULL COMMENT '创建时间',
    create_time_ts  BIGINT       NULL,
    create_by       VARCHAR(64)  NULL COMMENT '创建人',
    update_time     DATETIME     NULL COMMENT '更新时间',
    update_time_ts  BIGINT       NULL,
    update_by       VARCHAR(64)  NULL COMMENT '更新人',
    del_flag        INT          NOT NULL DEFAULT 0 COMMENT '逻辑删除 0正常',
    remarks         VARCHAR(255) NULL COMMENT '备注',
    UNIQUE KEY uk_symbol (symbol)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '美股盘前交易配置';
-- 交割合约订单手动盈亏百分比(如 -50 表示亏损50%,150 表示盈利150%)
ALTER TABLE t_futures_order
    ADD COLUMN manual_profit_percent DOUBLE NULL COMMENT '后台设定的盈亏百分比,结算时优先使用';
docs/db/V9__pre_market_sys_menu.sql
New file
@@ -0,0 +1,48 @@
-- 盘前配置菜单 — 挂到「股票-显示-美股」(menu_id=1864) 下
-- 执行后退出后台重新登录
-- 【可选】查看 1864 下现有子菜单
-- SELECT menu_id, parent_id, name, url, order_num FROM tz_sys_menu WHERE parent_id = 1864;
-- 1) 新增盘前菜单
INSERT INTO tz_sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, app_type)
SELECT
    (SELECT IFNULL(MAX(s.menu_id), 0) + 1 FROM tz_sys_menu s),
    1864,
    '盘前配置(US)',
    'us-spots/us-pre-market-config',
    '',
    1,
    'bianji',
    IFNULL((SELECT MAX(c.order_num) FROM tz_sys_menu c WHERE c.parent_id = 1864), 0) + 1,
    '1'
WHERE NOT EXISTS (
    SELECT 1 FROM tz_sys_menu p
    WHERE p.url = 'us-spots/us-pre-market-config'
       OR (p.parent_id = 1864 AND p.name = '盘前配置(US)')
);
-- 2) 角色授权:已有「股票-显示-美股」(1864) 权限的角色
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT DISTINCT rm.role_id, pre.menu_id
FROM tz_sys_role_menu rm
INNER JOIN tz_sys_menu pre ON pre.url = 'us-spots/us-pre-market-config'
WHERE rm.menu_id = 1864
  AND NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x
    WHERE x.role_id = rm.role_id AND x.menu_id = pre.menu_id
  );
-- 3) 角色授权:已有 1864 下任意子菜单权限的角色
INSERT INTO tz_sys_role_menu (role_id, menu_id)
SELECT DISTINCT rm.role_id, pre.menu_id
FROM tz_sys_role_menu rm
INNER JOIN tz_sys_menu child ON rm.menu_id = child.menu_id AND child.parent_id = 1864
INNER JOIN tz_sys_menu pre ON pre.url = 'us-spots/us-pre-market-config'
WHERE NOT EXISTS (
    SELECT 1 FROM tz_sys_role_menu x
    WHERE x.role_id = rm.role_id AND x.menu_id = pre.menu_id
);
-- 【验证】
-- SELECT menu_id, parent_id, name, url FROM tz_sys_menu WHERE parent_id = 1864;
trading-order-admin/src/main/java/com/yami/trading/admin/controller/future/AdminFuturesOrderController.java
@@ -67,4 +67,17 @@
        return Result.ok("操作成过");
    }
    @ApiOperation(value = "设定订单场控方向与盈亏百分比(同时生效)")
    @GetMapping("setOrderManualProfitPercent.action")
    public Result<String> setOrderManualProfitPercent(@RequestParam String orderNo,
                                                      @RequestParam Double manualProfitPercent,
                                                      @RequestParam(required = false) String profitLoss) {
        String msg = futuresOrderService.saveOrderManualProfitPercent(orderNo, manualProfitPercent, profitLoss,
                SecurityUtils.getSysUser().getUsername());
        if (msg != null && !msg.isEmpty()) {
            return Result.failed(msg);
        }
        return Result.ok("操作成功");
    }
}
trading-order-admin/src/main/java/com/yami/trading/admin/controller/item/AdminItemPreMarketController.java
New file
@@ -0,0 +1,56 @@
package com.yami.trading.admin.controller.item;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yami.trading.bean.item.domain.ItemPreMarketConfig;
import com.yami.trading.bean.item.dto.ItemPreMarketConfigDTO;
import com.yami.trading.bean.item.dto.ItemPreMarketConfigSaveModel;
import com.yami.trading.common.domain.Result;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Api(tags = "【管理后台】美股盘前配置")
@RestController
@CrossOrigin
@RequestMapping({"normal/adminItemPreMarketAction!", "normal/adminItemPreMarketAction!/"})
public class AdminItemPreMarketController {
    @Autowired
    private ItemPreMarketService itemPreMarketService;
    @Autowired
    private ItemService itemService;
    @ApiOperation("盘前配置列表")
    @GetMapping("list.action")
    public Result<IPage<ItemPreMarketConfigDTO>> list(@RequestParam(required = false) String symbol,
                                                      Page<ItemPreMarketConfig> page) {
        String cleanSymbol = itemService.getSymbolByKey(symbol);
        return Result.ok(itemPreMarketService.listRecord(page, cleanSymbol));
    }
    @ApiOperation("盘前配置详情")
    @GetMapping("get.action")
    public Result<ItemPreMarketConfigDTO> get(@RequestParam String uuid) {
        return Result.ok(itemPreMarketService.getDetail(uuid));
    }
    @ApiOperation("新增或修改盘前配置")
    @PostMapping({"save.action", "/save.action"})
    public Result<String> save(@RequestBody @Valid ItemPreMarketConfigSaveModel model) {
        itemPreMarketService.saveConfig(model);
        return Result.ok("保存成功");
    }
    @ApiOperation("删除盘前配置")
    @DeleteMapping("delete.action")
    public Result<String> delete(@RequestParam String uuid) {
        itemPreMarketService.deleteConfig(uuid);
        return Result.ok("删除成功");
    }
}
trading-order-admin/src/main/java/com/yami/trading/admin/controller/sys/SysMenuController.java
@@ -58,10 +58,8 @@
        List<SysMenu> newMenuList=new ArrayList<>();
        if (StrUtil.isNotBlank(appType)){
            for (SysMenu sysMenu:menuList){
                if (!StrUtil.isEmpty(sysMenu.getAppType())){
                    if (checkAppType(sysMenu.getAppType(),appType)){
                        newMenuList.add(sysMenu);
                    }
                if (StrUtil.isBlank(sysMenu.getAppType()) || checkAppType(sysMenu.getAppType(), appType)){
                    newMenuList.add(sysMenu);
                }
            }
        }
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiFuturesOrderController.java
@@ -18,7 +18,6 @@
import com.yami.trading.common.util.StringUtils;
import com.yami.trading.common.util.ThreadUtils;
import com.yami.trading.security.common.util.SecurityUtils;
import com.yami.trading.service.MarketOpenChecker;
import com.yami.trading.service.RealNameAuthRecordService;
import com.yami.trading.service.SessionTokenService;
import com.yami.trading.service.WalletService;
@@ -133,7 +132,7 @@
        } else {
            data.put("amount", 0);
        }
        data.put("open", MarketOpenChecker.isMarketOpenByItemCloseType(bySymbol.getOpenCloseType()));
        data.put("open", itemService.isOpen(bySymbol.getSymbol()));
        return Result.succeed(data);
    }
@@ -151,8 +150,7 @@
        if (bySymbol == null) {
            throw new YamiShopBindException("当前币对不存在");
        }
        boolean isOpen = MarketOpenChecker.isMarketOpenByItemCloseType(bySymbol.getOpenCloseType());
        if (!isOpen) {
        if (!itemService.isOpen(bySymbol.getSymbol())) {
            throw new YamiShopBindException("当前已经休市");
        }
@@ -167,15 +165,20 @@
            lock = true;
            String session_token = futureOpenAction.getSession_token();
            Object object = this.sessionTokenService.cacheGet(session_token);
            String cachedPartyId = this.sessionTokenService.cacheGet(session_token);
            this.sessionTokenService.del(session_token);
            User party = this.partyService.findUserByUserCode(partyId);
            User party = this.partyService.getById(partyId);
            if (party == null) {
                throw new YamiShopBindException("用户不存在");
            }
            if (!party.isEnabled()) {
                throw new YamiShopBindException("用户已锁定");
            }
            realNameAuthRecordService.requireApproved(party, true);
            if (null == object || !party.getUserId().equals((String) object)) {
                throw new BusinessException("请稍后再试");
            if (cachedPartyId == null || !partyId.equals(cachedPartyId)) {
                log.warn("交割开仓 session_token 无效, partyId={}, token={}, cachedPartyId={}",
                        partyId, session_token, cachedPartyId);
                throw new BusinessException("操作已失效,请刷新页面后重试");
            }
            Syspara syspara = sysparaService.find("stop_user_internet");
            String stopUserInternet = syspara.getSvalue();
@@ -201,9 +204,11 @@
            data.put("order_no", order.getOrderNo());
            data.put("open_price", order.getTradeAvgPrice().toString());
            return Result.succeed(data);
        } catch (BusinessException | YamiShopBindException e) {
            throw e;
        } catch (Exception e) {
            log.error("开仓异常", e);
            throw new YamiShopBindException(e.getMessage());
            throw new YamiShopBindException(e.getMessage() != null ? e.getMessage() : "请稍后再试");
        } finally {
            if (lock) {
                ThreadUtils.sleep(100);
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java
@@ -28,7 +28,6 @@
import com.yami.trading.common.util.RandomUtil;
import com.yami.trading.common.util.StringUtils;
import com.yami.trading.security.common.util.SecurityUtils;
import com.yami.trading.service.MarketOpenChecker;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.item.ItemUserOptionalItemService;
@@ -403,7 +402,7 @@
    @GetMapping(ITEM + "queryBySymbol.action")
    public Result<Item> queryBySymbol(String symbol) {
        Item item = itemService.findBySymbol(symbol);
        item.setOpen(MarketOpenChecker.isMarketOpenByItemCloseType(item.getOpenCloseType()));
        item.setOpen(itemService.isOpen(symbol));
        return Result.succeed(item);
    }
trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
@@ -10,6 +10,7 @@
import com.yami.trading.huobi.data.TimeZoneConverterService;
import com.yami.trading.huobi.tradingview.service.TradingViewService;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -37,6 +38,8 @@
public class KlineController {
    @Autowired
    private ItemService itemService;
    @Autowired
    private ItemPreMarketService itemPreMarketService;
    @Autowired
    @Qualifier("dataService")
    private DataService dataService;
@@ -69,6 +72,7 @@
            // Fetch Kline data from service (for cryptos)
            List<Kline> data = this.dataService.kline(symbol, line);
            itemPreMarketService.applyPreMarketToLatestKline(symbol, data);
            // Return an empty response if no data is found
            if (Objects.isNull(data)) {
@@ -109,6 +113,7 @@
        if (cachedData != null && lastUpdateTime != null) {
            long currentTime = System.currentTimeMillis();
            if ((currentTime - lastUpdateTime) <= TimeUnit.MINUTES.toMillis(5)) {
                itemPreMarketService.applyPreMarketToLatestKline(symbol, cachedData);
                return Result.succeed(this.build(cachedData, twForLine, symbol));
            }
        }
@@ -122,6 +127,7 @@
        // Format Kline data timestamps
        formatKlineTimestamps(data, line);
        itemPreMarketService.applyPreMarketToLatestKline(symbol, data);
        return Result.succeed(this.build(data, line, symbol));
    }
trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java
@@ -317,6 +317,14 @@
    @TableField(exist = false)
    private int tableIndex;
    @TableField(exist = false)
    @ApiModelProperty("是否处于盘前时段")
    private transient Boolean preMarketActive;
    @TableField(exist = false)
    @ApiModelProperty("盘前配置价格")
    private transient Double preMarketPrice;
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
trading-order-bean/src/main/java/com/yami/trading/bean/future/domain/FuturesOrder.java
@@ -110,4 +110,7 @@
    @ApiModelProperty("t_futures_para")
    private String futuresParaId;
    @ApiModelProperty("后台设定的盈亏百分比,结算时优先使用,如-50表示亏损50%,150表示盈利150%")
    private Double manualProfitPercent;
}
trading-order-bean/src/main/java/com/yami/trading/bean/future/dto/TFuturesOrderDTO.java
@@ -182,6 +182,9 @@
    @ApiModelProperty("订单盈亏控制情况(优先级高于交割场控设置)")
    private String profitLosssStr;
    @ApiModelProperty("后台设定盈亏百分比")
    private Double manualProfitPercent;
    public String getProfitLosssStr() {
        if("profit".equalsIgnoreCase(profitLoss)){
            return "盈利";
trading-order-bean/src/main/java/com/yami/trading/bean/item/domain/ItemPreMarketConfig.java
New file
@@ -0,0 +1,29 @@
package com.yami.trading.bean.item.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yami.trading.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_item_pre_market_config")
public class ItemPreMarketConfig extends BaseEntity {
    private static final long serialVersionUID = 1L;
    private String symbol;
    /** 盘前开始 HH:mm */
    private String startTime;
    /** 盘前结束 HH:mm */
    private String endTime;
    private BigDecimal prePrice;
    /** 1启用 0禁用 */
    private Integer enabled;
}
trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigDTO.java
New file
@@ -0,0 +1,17 @@
package com.yami.trading.bean.item.dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ItemPreMarketConfigDTO {
    private String uuid;
    private String symbol;
    private String symbolName;
    private String startTime;
    private String endTime;
    private BigDecimal prePrice;
    private Integer enabled;
}
trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigSaveModel.java
New file
@@ -0,0 +1,27 @@
package com.yami.trading.bean.item.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
public class ItemPreMarketConfigSaveModel {
    private String uuid;
    @NotBlank(message = "股票代码不能为空")
    private String symbol;
    @NotBlank(message = "盘前开始时间不能为空")
    private String startTime;
    @NotBlank(message = "盘前结束时间不能为空")
    private String endTime;
    @NotNull(message = "盘前价格不能为空")
    private BigDecimal prePrice;
    private Integer enabled = 1;
}
trading-order-huobi/src/main/java/com/yami/trading/huobi/data/internal/DataServiceImpl.java
@@ -5,6 +5,7 @@
import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.huobi.data.DataCache;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.syspara.SysparaService;
import lombok.extern.slf4j.Slf4j;
@@ -25,6 +26,8 @@
    ItemService itemService;
    @Autowired
    KlineService klineService;
    @Autowired
    ItemPreMarketService itemPreMarketService;
    /**
     * 根据币种分类 获取实时价格数据
@@ -52,7 +55,7 @@
        if (!StrUtil.isEmpty(symbols) && !symbols.contains(",")) {
            Realtime realtime = DataCache.getRealtime(symbols);
            if (realtime != null) {
                list.add(realtime);
                list.add(applyPreMarket(realtime));
            }
        }
        // 如果不传参数 返回全部币种行情
@@ -61,7 +64,7 @@
            for (Item item : items) {
                Realtime realtime = DataCache.getRealtime(item.getSymbol());
                if (realtime != null) {
                    list.add(realtime);
                    list.add(applyPreMarket(realtime));
                }
            }
        }
@@ -71,13 +74,26 @@
            for (String oneSymbol : symbolArr) {
                Realtime realtime = DataCache.getRealtime(oneSymbol);
                if (realtime != null) {
                    list.add(realtime);
                    list.add(applyPreMarket(realtime));
                } else {
                    log.error("realtime is null; 币种->{}", oneSymbol);
                }
            }
        }
        return list;
    }
    private Realtime applyPreMarket(Realtime realtime) {
        if (realtime == null || itemPreMarketService == null) {
            return realtime;
        }
        try {
            Realtime copy = (Realtime) realtime.clone();
            return itemPreMarketService.applyPreMarketPrice(copy);
        } catch (CloneNotSupportedException e) {
            log.warn("clone realtime failed, symbol={}", realtime.getSymbol(), e);
            return realtime;
        }
    }
    /**
@@ -109,6 +125,7 @@
        Realtime realtime = DataCache.getLatestRealTime(symbol);
        if (realtime != null) {
            realtime = applyPreMarket(realtime);
            Kline kline = null;
            if (KlineConstant.PERIOD_1MIN.equals(line)) {
                kline = klineService.bulidKline1Minute(realtime, KlineConstant.PERIOD_1MIN);
trading-order-huobi/src/main/java/com/yami/trading/huobi/task/RealtimePushJob.java
@@ -12,6 +12,7 @@
import com.yami.trading.huobi.data.TimeZoneConverterService;
import com.yami.trading.huobi.websocket.WebSocketServer;
import com.yami.trading.huobi.websocket.WebSocketSession;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +35,9 @@
public class RealtimePushJob implements Runnable {
    @Autowired
    private ItemService itemService;
    @Autowired
    private ItemPreMarketService itemPreMarketService;
    @Autowired
    private TimeZoneConverterService timeZoneConverterService;
@@ -76,6 +80,13 @@
                    Realtime realtimeData = DataCache.getRealtime(symbol);
                    if (realtimeData == null) {
                        log.error("realtimeHandle 获取{} 数据为空", symbol);
                    } else if (itemPreMarketService != null) {
                        try {
                            Realtime copy = (Realtime) realtimeData.clone();
                            realtimeData = itemPreMarketService.applyPreMarketPrice(copy);
                        } catch (CloneNotSupportedException e) {
                            log.warn("clone realtime failed, symbol={}", symbol, e);
                        }
                    }
                    this.realtimeRevise(realtimeResultMap, realtimeData, symbol);
                }
trading-order-huobi/src/main/java/com/yami/trading/huobi/task/contract/ContractApplyOrderHandleJob.java
@@ -7,13 +7,13 @@
import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.common.constants.RedisKeys;
import com.yami.trading.common.constants.RedisLockKeyConstants;
import com.yami.trading.service.MarketOpenChecker;
import com.yami.trading.common.util.RedisUtil;
import com.yami.trading.common.util.ThreadUtils;
import com.yami.trading.service.contract.ContractApplyOrderService;
import com.yami.trading.service.contract.ContractLock;
import com.yami.trading.service.contract.ContractOrderService;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
@@ -44,6 +44,9 @@
    @Autowired
    private ItemService itemService;
    @Autowired(required = false)
    private ItemPreMarketService itemPreMarketService;
    @Autowired
    private RedissonClient redissonClient;
@@ -78,50 +81,45 @@
                    continue;
                }
                List<Realtime> realtime_list = this.dataService.realtime(order.getSymbol());
                Realtime realtime = null;
                if (realtime_list.size() > 0) {
                    realtime = realtime_list.get(0);
                    //log.info("---> ContractApplyOrderHandleJob.run symbol:{} 的 realTime 值为:{}", order.getSymbol(), realtime);
                } else {
                    log.warn("---> ContractApplyOrderHandleJob.run symbol:{} 的 realTime 值为空", order.getSymbol());
                    continue;
                }
                // 休市不做撮合
                Item bySymbol = itemService.findBySymbol(order.getSymbol());
                if (bySymbol == null) {
                    continue;
                }
                boolean isOpen = MarketOpenChecker.isMarketOpenByItemCloseType(bySymbol.getOpenCloseType());
                boolean preMarketActive = itemPreMarketService != null
                        && itemPreMarketService.isPreMarketTradingActive(order.getSymbol());
                boolean isOpen = itemService.isOpen(order.getSymbol());
                if (!isOpen) {
                    //log.warn("---> ContractApplyOrderHandleJob.run symbol:{} 未open", order.getSymbol());
                    continue;
                }
                List<Realtime> realtime_list = this.dataService.realtime(order.getSymbol());
                Realtime realtime = null;
                if (realtime_list.size() > 0) {
                    realtime = realtime_list.get(0);
                } else {
                    log.warn("---> ContractApplyOrderHandleJob.run symbol:{} 的 realTime 值为空", order.getSymbol());
                    continue;
                }
                if (preMarketActive && itemPreMarketService != null) {
                    Double prePrice = itemPreMarketService.getActivePreMarketPrice(order.getSymbol());
                    if (prePrice != null) {
                        realtime.setClose(prePrice);
                    }
                }
                if ("limit".equals(order.getOrderPriceType())) {
                    /**
                     * 限价单
                     */
                    if ("buy".equals(order.getDirection())) {
                        /**
                         * 买涨
                         */
                    if (preMarketActive) {
                        processSuccess = this.handle(order, realtime);
                    } else if ("buy".equals(order.getDirection())) {
                        if (BigDecimal.valueOf(realtime.getClose()).compareTo(order.getPrice()) <= 0) {
                            processSuccess = this.handle(order, realtime);
                        }
                    } else {
                        /**
                         * 买跌
                         */
                        if (BigDecimal.valueOf(realtime.getClose()).compareTo(order.getPrice()) >= 0) {
                            processSuccess = this.handle(order, realtime);
                        }
                    }
                } else {
                    /**
                     * 非限制,直接进入市场
                     */
                    processSuccess = this.handle(order, realtime);
                }
            } catch (Exception e) {
trading-order-huobi/src/main/java/com/yami/trading/huobi/task/future/FuturesOrderCalculationJob.java
@@ -3,7 +3,6 @@
import com.yami.trading.bean.future.domain.FuturesLock;
import com.yami.trading.bean.future.domain.FuturesOrder;
import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.service.MarketOpenChecker;
import com.yami.trading.common.util.ThreadUtils;
import com.yami.trading.service.future.FuturesOrderService;
import com.yami.trading.service.item.ItemService;
@@ -44,7 +43,7 @@
                            if(bySymbol == null){
                                continue;
                            }
                            boolean isOpen = MarketOpenChecker.isMarketOpenByItemCloseType(bySymbol.getOpenCloseType());
                            boolean isOpen = itemService.isOpen(order.getSymbol());
                            if(!isOpen){
                                continue;
                            }
trading-order-service/src/main/java/com/yami/trading/dao/item/ItemPreMarketConfigMapper.java
New file
@@ -0,0 +1,7 @@
package com.yami.trading.dao.item;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yami.trading.bean.item.domain.ItemPreMarketConfig;
public interface ItemPreMarketConfigMapper extends BaseMapper<ItemPreMarketConfig> {
}
trading-order-service/src/main/java/com/yami/trading/service/MarketOpenChecker.java
@@ -124,10 +124,6 @@
     * @return
     */
    public static boolean isMarketOpenByItemCloseType(String closeType) {
        // 放开美股测试
//        if(closeType.equalsIgnoreCase(US_STOCKS)){
//            return true;
//        }
        List<String> stocksType = Lists.newArrayList(A_STOCKS, HK_STOCKS,MY_STOCKS, US_STOCKS, TW_STOCKS, JP_STOCKS, INDIA_STOCKS, UK_STOCKS, DE_STOCKS, BZ_STOCKS);
        if (StrUtil.isBlank(closeType)) {
            return true;
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
@@ -30,6 +30,7 @@
import com.yami.trading.service.WalletService;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.item.ItemLeverageService;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.rate.ExchangeRateService;
import com.yami.trading.service.syspara.SysparaService;
@@ -70,6 +71,8 @@
    private UserService partyService;
    @Autowired
    private ItemService itemService;
    @Autowired(required = false)
    private ItemPreMarketService itemPreMarketService;
    @Qualifier("dataService")
    @Autowired
    private DataService dataService;
@@ -194,10 +197,11 @@
        //rose 校验如果是数字货币,则7*24
        String type = item.getType();
        if(!type.equalsIgnoreCase(Item.cryptos)){
            //外汇交易后台接口
        if (!type.equalsIgnoreCase(Item.cryptos)) {
            boolean preMarketActive = itemPreMarketService != null
                    && itemPreMarketService.isPreMarketTradingActive(order.getSymbol());
            boolean orderOpen = this.sysparaService.find("order_open").getBoolean();
            if (!orderOpen) {
            if (!orderOpen && !preMarketActive && !itemService.isOpen(order.getSymbol())) {
                throw new YamiShopBindException("不在交易时段");
            }
        }
@@ -334,6 +338,26 @@
                }
            }
        }
        tryImmediatePreMarketFill(order);
    }
    /** 盘前时段:市价/限价委托立即按盘前价成交,避免停留在委托队列 */
    private void tryImmediatePreMarketFill(ContractApplyOrder order) {
        if (itemPreMarketService == null || !itemPreMarketService.isPreMarketTradingActive(order.getSymbol())) {
            return;
        }
        if (!ContractApplyOrder.OFFSET_OPEN.equals(order.getOffset())) {
            return;
        }
        Double prePrice = itemPreMarketService.getActivePreMarketPrice(order.getSymbol());
        if (prePrice == null) {
            log.warn("盘前已激活但未取到盘前价 symbol={} orderNo={}", order.getSymbol(), order.getOrderNo());
            return;
        }
        Realtime realtime = new Realtime();
        realtime.setSymbol(order.getSymbol());
        realtime.setClose(prePrice);
        contractOrderService.saveOpen(order, realtime);
    }
    /**
trading-order-service/src/main/java/com/yami/trading/service/exchange/impl/ExchangeApplyOrderServiceImpl.java
@@ -1,6 +1,7 @@
package com.yami.trading.service.exchange.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -26,17 +27,18 @@
import com.yami.trading.common.util.StringUtils;
import com.yami.trading.common.util.UTCDateUtils;
import com.yami.trading.dao.exchange.ExchangeApplyOrderMapper;
import com.yami.trading.service.MarketOpenChecker;
import com.yami.trading.service.MoneyLogService;
import com.yami.trading.service.WalletService;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.exchange.ExchangeApplyOrderService;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.rate.ExchangeRateService;
import com.yami.trading.service.syspara.SysparaService;
import com.yami.trading.service.user.UserDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -61,6 +63,8 @@
    @Autowired
    ItemService itemService;
    @Autowired
    ItemPreMarketService itemPreMarketService;
    @Autowired
    DataService dataService;
    @Autowired
    WalletService walletService;
@@ -72,6 +76,9 @@
    ExchangeApplyOrderMapper exchangeApplyOrderMapper;
    @Autowired
    private ExchangeRateService exchangeRateService;
    @Autowired
    @Lazy
    private ExchangeApplyOrderService self;
    @Override
    public List<ExchangeApplyOrder> findSubmitted() {
@@ -623,12 +630,17 @@
     * 现货交易买入
     */
    @Override
    @Transactional
    public void saveSpotTradOpen(ExchangeApplyOrder order) {
        order.setOrderNo(DateUtil.getToday("yyMMddHHmmss") + RandomUtil.getRandomNum(8));
        double fee = Arith.mul(order.getVolume(), sysparaService.find("exchange_apply_order_buy_fee").getDouble());
        Wallet wallet = this.walletService.saveWalletByPartyId(order.getPartyId());
        String symbol = order.getSymbol();
        Item item = itemService.findBySymbol(symbol);
        if (item != null && StrUtil.isNotBlank(item.getSymbol())) {
            symbol = item.getSymbol();
            order.setSymbol(symbol);
        }
        double amountBefore = wallet.getMoney().doubleValue();
        double realAmount = order.getVolume() + fee;
        String type = item.getType();
@@ -638,8 +650,10 @@
            throw new YamiShopBindException("余额不足");
        }
        // 休市期间,下市价订单, 叫 "竞价单",竞价单订单号长度为21,其他订单号长度为20位
        boolean preMarketActive = itemPreMarketService != null
                && itemPreMarketService.isPreMarketTradingActive(symbol);
        if (ExchangeApplyOrder.ORDER_PRICE_TYPE_OPPONENT.equals(order.getOrderPriceType())) {
            boolean isOpen = MarketOpenChecker.isMarketOpenByItemCloseType(item.getOpenCloseType());
            boolean isOpen = preMarketActive || itemService.isOpen(symbol);
            if (!isOpen) {
                order.setOrderNo(DateUtil.getToday("yyMMddHHmmss") + RandomUtil.getRandomNum(9));
            }
@@ -664,6 +678,7 @@
        order.setCreateTime(new Date());
        save(order);
        userDataService.saveBuy(order);
        tryImmediatePreMarketFill(order, symbol, preMarketActive);
    }
    /**
@@ -676,14 +691,20 @@
        order.setCreateTime(new Date());
        order.setFee(Arith.mul(order.getVolume(), sysparaService.find("exchange_apply_order_sell_fee").getDouble()));
        Item item = itemService.findBySymbol(order.getSymbol());
        // 休市期间,不让下市价卖出单
        String symbol = order.getSymbol();
        if (item != null && StrUtil.isNotBlank(item.getSymbol())) {
            symbol = item.getSymbol();
            order.setSymbol(symbol);
        }
        boolean preMarketActive = itemPreMarketService != null
                && itemPreMarketService.isPreMarketTradingActive(symbol);
        // 休市期间,不让下市价卖出单(盘前除外)
        if (ExchangeApplyOrder.ORDER_PRICE_TYPE_OPPONENT.equals(order.getOrderPriceType())) {
            boolean isOpen = MarketOpenChecker.isMarketOpenByItemCloseType(item.getOpenCloseType());
            if (!isOpen) {
            if (!preMarketActive && !itemService.isOpen(symbol)) {
                throw new YamiShopBindException("The current stock market is closed");
            }
        }
        WalletExtend walletExtend = walletService.saveExtendByPara(order.getPartyId(), order.getSymbol());
        WalletExtend walletExtend = walletService.saveExtendByPara(order.getPartyId(), symbol);
        double amount_before = walletExtend.getAmount();
        walletService.updateExtend(walletExtend.getPartyId(), walletExtend.getWallettype(), Arith.sub(0, order.getVolume()));
        String type = item.getType();
@@ -696,14 +717,33 @@
        moneylog.setAmountAfter(BigDecimal.valueOf(Arith.sub(amount_before, order.getVolume())));
        moneylog.setLog(name + type2 + "现货交易卖出,订单号[" + order.getOrderNo() + "]");
        moneylog.setUserId(order.getPartyId());
        moneylog.setWalletType(order.getSymbol());
        moneylog.setSymbol(order.getSymbol());
        moneylog.setWalletType(symbol);
        moneylog.setSymbol(symbol);
        moneylog.setContentType(type2 + Constants.MONEYLOG_CONTENT_CLOSE);
        moneylog.setCreateTime(new Date());
        moneylog.setUpdateTime(new Date());
        moneyLogService.save(moneylog);
        save(order);
        userDataService.saveSell(order);
        tryImmediatePreMarketFill(order, symbol, preMarketActive);
    }
    /** 盘前时段:限价/市价均立即按盘前价成交,避免进入委托队列 */
    private void tryImmediatePreMarketFill(ExchangeApplyOrder order, String symbol, boolean preMarketActive) {
        if (!preMarketActive || itemPreMarketService == null) {
            return;
        }
        Double prePrice = itemPreMarketService.getActivePreMarketPrice(symbol);
        if (prePrice == null) {
            log.warn("盘前已激活但未取到盘前价 symbol={} orderNo={}", symbol, order.getOrderNo());
            return;
        }
        order.setPrice(prePrice);
        if (ExchangeApplyOrder.OFFSET_OPEN.equals(order.getOffset())) {
            self.saveSpotTradOpenCreated(order, prePrice);
        } else if (ExchangeApplyOrder.OFFSET_CLOSE.equals(order.getOffset())) {
            self.saveSpotTradCloseCreated(order, prePrice);
        }
    }
    //================================================闪兑==============================================================
trading-order-service/src/main/java/com/yami/trading/service/exchange/job/ExchangeApplyOrderHandleJob.java
@@ -6,9 +6,9 @@
import com.yami.trading.bean.purchasing.dto.ExchangeLock;
import com.yami.trading.common.exception.YamiShopBindException;
import com.yami.trading.common.util.ThreadUtils;
import com.yami.trading.service.MarketOpenChecker;
import com.yami.trading.service.data.DataService;
import com.yami.trading.service.exchange.ExchangeApplyOrderService;
import com.yami.trading.service.item.ItemPreMarketService;
import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.syspara.SysparaService;
import lombok.extern.slf4j.Slf4j;
@@ -31,6 +31,8 @@
    @Autowired
    ItemService itemService;
    @Autowired
    ItemPreMarketService itemPreMarketService;
    @Autowired
    SysparaService sysparaService;
    public void start() {
@@ -51,14 +53,25 @@
                    if (bySymbol == null) {
                        throw new YamiShopBindException("当前币对不存在");
                    }
                    boolean isOpen = MarketOpenChecker.isMarketOpenByItemCloseType(bySymbol.getOpenCloseType());
                    boolean isOpen = itemService.isOpen(order.getSymbol());
                    if (!isOpen) {
                        continue;
                    }
                    Realtime realtime = this.dataService.realtime(order.getSymbol()).get(0);
                    Double fillPrice = realtime.getClose();
                    boolean preMarketActive = itemPreMarketService != null
                            && itemPreMarketService.isPreMarketTradingActive(order.getSymbol());
                    if (preMarketActive) {
                        Double prePrice = itemPreMarketService.getActivePreMarketPrice(order.getSymbol());
                        if (prePrice != null) {
                            fillPrice = prePrice;
                        }
                    }
                    // 限价单
                    if (ExchangeApplyOrder.ORDER_PRICE_TYPE_LIMIT.equals(order.getOrderPriceType())) {
                        if (ExchangeApplyOrder.OFFSET_OPEN.equals(order.getOffset())) {
                        if (preMarketActive) {
                            this.handle(order, fillPrice);
                        } else if (ExchangeApplyOrder.OFFSET_OPEN.equals(order.getOffset())) {
                            if (realtime.getClose() <= order.getPrice()) {
                                this.handle(order, realtime.getClose());
                            }
@@ -70,7 +83,7 @@
                    }
                    // 市价单
                    else {
                        this.handle(order, realtime.getClose());
                        this.handle(order, fillPrice);
                    }
                }
            } catch (Exception e) {
trading-order-service/src/main/java/com/yami/trading/service/future/FuturesOrderService.java
@@ -208,6 +208,87 @@
     * @param operaName
     * @return
     */
    public String saveOrderManualProfitPercent(String orderNo, Double manualProfitPercent, String profitLoss, String operaName) {
        if (manualProfitPercent == null) {
            return "盈亏百分比不能为空";
        }
        double absPercent = Math.abs(manualProfitPercent);
        if (absPercent <= 0) {
            return "盈亏百分比必须大于0";
        }
        double signedPercent;
        String resolvedProfitLoss;
        if (StrUtil.isNotBlank(profitLoss)) {
            resolvedProfitLoss = profitLoss.trim().toLowerCase();
            if ("loss".equals(resolvedProfitLoss)) {
                signedPercent = -absPercent;
                resolvedProfitLoss = "loss";
            } else if ("profit".equals(resolvedProfitLoss)) {
                signedPercent = absPercent;
                resolvedProfitLoss = "profit";
            } else {
                return "场控方向无效";
            }
        } else {
            signedPercent = manualProfitPercent;
            resolvedProfitLoss = signedPercent >= 0 ? "profit" : "loss";
        }
        String message = "";
        boolean lock = false;
        while (true) {
            try {
                if (!FuturesLock.add(orderNo)) {
                    continue;
                }
                lock = true;
                FuturesOrder futuresOrder = (FuturesOrder) RedisUtil.get(FuturesRedisKeys.FUTURES_SUBMITTED_ORDERNO + orderNo);
                if (futuresOrder == null) {
                    futuresOrder = cache.get(orderNo);
                }
                if (futuresOrder == null || FuturesOrder.STATE_CREATED.equals(futuresOrder.getState())) {
                    message = "订单已结算或不存在";
                    break;
                }
                Double oldPercent = futuresOrder.getManualProfitPercent();
                String oldProfitLoss = futuresOrder.getProfitLoss();
                futuresOrder.setProfitLoss(resolvedProfitLoss);
                futuresOrder.setManualProfitPercent(signedPercent);
                applyManualProfitPercentToOrder(futuresOrder);
                Double close = futuresOrder.getCloseAvgPrice();
                if (close == null) {
                    close = futuresOrder.getTradeAvgPrice();
                }
                refreshCache(futuresOrder, close);
                RedisUtil.set(FuturesRedisKeys.FUTURES_SUBMITTED_ORDERNO + futuresOrder.getOrderNo(), futuresOrder);
                cache.put(futuresOrder.getOrderNo(), futuresOrder);
                updateById(futuresOrder);
                User party = userService.getById(futuresOrder.getPartyId());
                Log log = new Log();
                log.setCategory(Constants.LOG_CATEGORY_OPERATION);
                log.setOperator(operaName);
                log.setUsername(party.getUserName());
                log.setUserId(party.getUserId());
                log.setCreateTime(new Date());
                log.setLog("设定交割订单场控与盈亏比例。订单号[" + futuresOrder.getOrderNo() + "],场控["
                        + Constants.PROFIT_LOSS_TYPE.get(oldProfitLoss) + "→"
                        + Constants.PROFIT_LOSS_TYPE.get(resolvedProfitLoss) + "],比例["
                        + oldPercent + "→" + signedPercent + "%].");
                this.logService.save(log);
                ThreadUtils.sleep(100);
            } catch (Throwable e) {
                log.error("error:", e);
                message = "修改错误";
            } finally {
                if (lock) {
                    FuturesLock.remove(orderNo);
                    break;
                }
            }
        }
        return message;
    }
    public String saveOrderPorfitOrLoss(String orderNo, String porfitOrLoss, String operaName) {
        String message = "";
        boolean lock = false;
@@ -229,6 +310,7 @@
                Map<String, Double> futuresAssetsOld = this.walletService.getMoneyFuturesByOrder(futuresOrder);
                String oldProfitLoss = futuresOrder.getProfitLoss();
                futuresOrder.setManualProfitPercent(null);
                futuresOrder.setProfitLoss(porfitOrLoss);
                RedisUtil.set(FuturesRedisKeys.FUTURES_SUBMITTED_ORDERNO + futuresOrder.getOrderNo(), futuresOrder);
@@ -330,7 +412,7 @@
            realtime = realtime_list.get(0);
        }
        if (null == realtime) {
            throw new BusinessException(1, "请稍后再试");
            throw new BusinessException(1, "行情获取失败,请稍后再试");
        }
        Double volume = futuresOrder.getVolume();
@@ -454,41 +536,45 @@
        order.setRemainTime(fomatTime(remain_time));
        order.setCloseAvgPrice(close);
        double futures_loss_part = Double.valueOf(this.sysparaService.find("futures_loss_part").getSvalue());
        double closeAvgPrice = order.getCloseAvgPrice().doubleValue();
        double tradeAvgPrice = order.getTradeAvgPrice().doubleValue();
        if (FuturesOrder.DIRECTION_BUY.equals(order.getDirection())) {
            /*
             * 0 买涨
             */
            if (closeAvgPrice >= tradeAvgPrice) {
                DecimalFormat df = new DecimalFormat("#.##");
                order.setProfit(Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio()))));
            }
            if (closeAvgPrice <= tradeAvgPrice) {
                if (futures_loss_part == 2) {
                    DecimalFormat df = new DecimalFormat("#.##");
                    order.setProfit(Arith.sub(0, Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio())))));
                } else {
                    order.setProfit(Arith.sub(0, order.getVolume()));
                }
            }
        if (order.getManualProfitPercent() != null) {
            applyManualProfitPercentToOrder(order);
        } else {
            /*
             * 1 买跌
             */
            if (closeAvgPrice <= tradeAvgPrice) {
                DecimalFormat df = new DecimalFormat("#.##");
                order.setProfit(Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio()))));
            }
            if (closeAvgPrice >= tradeAvgPrice) {
                if (futures_loss_part == 2) {
            double futures_loss_part = Double.valueOf(this.sysparaService.find("futures_loss_part").getSvalue());
            double closeAvgPrice = order.getCloseAvgPrice().doubleValue();
            double tradeAvgPrice = order.getTradeAvgPrice().doubleValue();
            if (FuturesOrder.DIRECTION_BUY.equals(order.getDirection())) {
                /*
                 * 0 买涨
                 */
                if (closeAvgPrice >= tradeAvgPrice) {
                    DecimalFormat df = new DecimalFormat("#.##");
                    order.setProfit(Arith.sub(0, Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio())))));
                } else {
                    order.setProfit(Arith.sub(0, order.getVolume()));
                    order.setProfit(Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio()))));
                }
                if (closeAvgPrice <= tradeAvgPrice) {
                    if (futures_loss_part == 2) {
                        DecimalFormat df = new DecimalFormat("#.##");
                        order.setProfit(Arith.sub(0, Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio())))));
                    } else {
                        order.setProfit(Arith.sub(0, order.getVolume()));
                    }
                }
            } else {
                /*
                 * 1 买跌
                 */
                if (closeAvgPrice <= tradeAvgPrice) {
                    DecimalFormat df = new DecimalFormat("#.##");
                    order.setProfit(Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio()))));
                }
                if (closeAvgPrice >= tradeAvgPrice) {
                    if (futures_loss_part == 2) {
                        DecimalFormat df = new DecimalFormat("#.##");
                        order.setProfit(Arith.sub(0, Double.valueOf(df.format(Arith.mul(order.getVolume(), order.getProfitRatio())))));
                    } else {
                        order.setProfit(Arith.sub(0, order.getVolume()));
                    }
                }
            }
        }
@@ -641,6 +727,11 @@
        order.setCloseTime(System.currentTimeMillis() / 1000);
        order.setState(FuturesOrder.STATE_CREATED);
        if (order.getManualProfitPercent() != null) {
            settleByManualProfitPercent(order);
            return;
        }
        String ProfitLoss = null;
        /**
@@ -883,6 +974,59 @@
         tipService.deleteTip(order.getUuid());
    }
    private void settleByManualProfitPercent(FuturesOrder order) {
        applyManualProfitPercentToOrder(order);
        if (order.getProfit() >= 0) {
            order.setProfitLoss("profit");
        } else {
            order.setProfitLoss("loss");
        }
        double profit = order.getProfit();
        logger.warn("---> FuturesOrderService.settleByManualProfitPercent 订单:{} 使用手动盈亏百分比:{}%, profit:{}",
                order.getOrderNo(), order.getManualProfitPercent(), profit);
        if (profit > 0) {
            BigDecimal amount = BigDecimal.valueOf(order.getProfit()).add(BigDecimal.valueOf(order.getVolume()));
            walletService.updateMoney(order.getSymbol(), order.getPartyId(), amount, BigDecimal.ZERO,
                    Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET, Constants.DELIVERY_MONEYLOG_CONTENT_CONTRACT_CLOSE,
                    "交割合约盈利(手动盈亏%),订单号[" + order.getOrderNo() + "]");
            String future_profit_bonus_parameters = sysparaService.find("future_profit_bonus_parameters").getSvalue();
            if (StringUtils.isNotEmpty(future_profit_bonus_parameters)) {
                saveParentFeeProfit(order, future_profit_bonus_parameters);
            }
        } else {
            BigDecimal amount = BigDecimal.valueOf(order.getVolume()).add(BigDecimal.valueOf(order.getProfit()));
            walletService.updateMoney(order.getSymbol(), order.getPartyId(), amount, BigDecimal.ZERO,
                    Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET, Constants.DELIVERY_MONEYLOG_CONTENT_CONTRACT_CLOSE,
                    "交割合约亏损退还(手动盈亏%),订单号[" + order.getOrderNo() + "]");
        }
        updateById(order);
        cache.remove(order.getOrderNo());
        FuturesOrder futuresOld = (FuturesOrder) RedisUtil.get(FuturesRedisKeys.FUTURES_SUBMITTED_ORDERNO + order.getOrderNo());
        RedisUtil.del(FuturesRedisKeys.FUTURES_SUBMITTED_ORDERNO + order.getOrderNo());
        Double futuresAssets = (Double) RedisUtil.get(FuturesRedisKeys.FUTURES_ASSETS_PARTY_ID + order.getPartyId().toString());
        Double futuresAssetsProfit = (Double) RedisUtil.get(FuturesRedisKeys.FUTURES_ASSETS_PROFIT_PARTY_ID + order.getPartyId().toString());
        if (null != futuresOld) {
            Map<String, Double> futuresAssetsOld = this.walletService.getMoneyFuturesByOrder(futuresOld);
            RedisUtil.set(FuturesRedisKeys.FUTURES_ASSETS_PARTY_ID + order.getPartyId().toString(),
                    Arith.add(null == futuresAssets ? 0.000D : futuresAssets, 0 - futuresAssetsOld.get("money_futures")));
            RedisUtil.set(FuturesRedisKeys.FUTURES_ASSETS_PROFIT_PARTY_ID + order.getPartyId().toString(),
                    Arith.add(null == futuresAssetsProfit ? 0.000D : futuresAssetsProfit, 0 - futuresAssetsOld.get("money_futures_profit")));
        }
        this.userDataService.saveFuturesClose(order);
        User party = userService.getById(order.getPartyId());
        party.setWithdrawLimitNowAmount(new BigDecimal(Arith.add(party.getWithdrawLimitNowAmount().doubleValue(), order.getVolume())));
        userService.updateById(party);
        tipService.deleteTip(order.getUuid());
    }
    /** 按后台设定的 manualProfitPercent 计算盈亏,与买卖方向、现价无关 */
    private void applyManualProfitPercentToOrder(FuturesOrder order) {
        double profit = Arith.mul(order.getVolume(), Arith.div(order.getManualProfitPercent(), 100.0));
        DecimalFormat df = new DecimalFormat("#.##");
        order.setProfit(Double.valueOf(df.format(profit)));
    }
    public List<FuturesOrder> findByHourAndSate(String state, String rolename) {
        return baseMapper.findByHourAndSate(state, rolename, new Date());
trading-order-service/src/main/java/com/yami/trading/service/item/ItemPreMarketService.java
New file
@@ -0,0 +1,260 @@
package com.yami.trading.service.item;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.trading.bean.data.domain.Kline;
import com.yami.trading.bean.data.domain.Realtime;
import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.bean.item.domain.ItemPreMarketConfig;
import com.yami.trading.bean.item.dto.ItemPreMarketConfigDTO;
import com.yami.trading.bean.item.dto.ItemPreMarketConfigSaveModel;
import com.yami.trading.common.exception.YamiShopBindException;
import com.yami.trading.dao.item.ItemPreMarketConfigMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
@Transactional
public class ItemPreMarketService extends ServiceImpl<ItemPreMarketConfigMapper, ItemPreMarketConfig> {
    private static final ZoneId US_EASTERN = ZoneId.of("America/New_York");
    private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("H:mm");
    private final Map<String, ItemPreMarketConfig> cache = new ConcurrentHashMap<>();
    @Autowired
    private ItemService itemService;
    @PostConstruct
    public void loadCache() {
        refreshCache();
    }
    public void refreshCache() {
        cache.clear();
        List<ItemPreMarketConfig> list = list(new LambdaQueryWrapper<ItemPreMarketConfig>()
                .eq(ItemPreMarketConfig::getDelFlag, 0)
                .eq(ItemPreMarketConfig::getEnabled, 1));
        for (ItemPreMarketConfig config : list) {
            if (StrUtil.isBlank(config.getSymbol())) {
                continue;
            }
            cache.put(config.getSymbol(), config);
            Item item = itemService.findBySymbol(config.getSymbol());
            if (item != null && StrUtil.isNotBlank(item.getSymbol())
                    && !item.getSymbol().equals(config.getSymbol())) {
                cache.put(item.getSymbol(), config);
            }
        }
    }
    public IPage<ItemPreMarketConfigDTO> listRecord(Page<ItemPreMarketConfig> page, String symbol) {
        LambdaQueryWrapper<ItemPreMarketConfig> wrapper = new LambdaQueryWrapper<ItemPreMarketConfig>()
                .eq(ItemPreMarketConfig::getDelFlag, 0)
                .orderByDesc(ItemPreMarketConfig::getCreateTime);
        if (StrUtil.isNotBlank(symbol)) {
            wrapper.like(ItemPreMarketConfig::getSymbol, symbol);
        }
        IPage<ItemPreMarketConfig> entityPage = page(page, wrapper);
        Page<ItemPreMarketConfigDTO> dtoPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
        dtoPage.setRecords(entityPage.getRecords().stream().map(this::toDto).collect(Collectors.toList()));
        return dtoPage;
    }
    public ItemPreMarketConfigDTO getDetail(String uuid) {
        ItemPreMarketConfig config = getById(uuid);
        if (config == null || config.getDelFlag() != null && config.getDelFlag() != 0) {
            throw new YamiShopBindException("盘前配置不存在");
        }
        return toDto(config);
    }
    public void saveConfig(ItemPreMarketConfigSaveModel model) {
        Item item = itemService.findBySymbol(model.getSymbol());
        if (item == null) {
            throw new YamiShopBindException("股票不存在");
        }
        if (!Item.US_STOCKS.equalsIgnoreCase(item.getType())
                && !Item.US_STOCKS.equalsIgnoreCase(item.getOpenCloseType())) {
            throw new YamiShopBindException("仅支持美股配置盘前");
        }
        if (model.getPrePrice() == null || model.getPrePrice().compareTo(BigDecimal.ZERO) <= 0) {
            throw new YamiShopBindException("盘前价格必须大于0");
        }
        parseTime(model.getStartTime());
        parseTime(model.getEndTime());
        if (StrUtil.isBlank(model.getUuid())) {
            long count = count(new LambdaQueryWrapper<ItemPreMarketConfig>()
                    .eq(ItemPreMarketConfig::getSymbol, model.getSymbol())
                    .eq(ItemPreMarketConfig::getDelFlag, 0));
            if (count > 0) {
                throw new YamiShopBindException("该股票已存在盘前配置");
            }
            ItemPreMarketConfig config = new ItemPreMarketConfig();
            fillConfig(config, model);
            save(config);
        } else {
            ItemPreMarketConfig config = getById(model.getUuid());
            if (config == null) {
                throw new YamiShopBindException("盘前配置不存在");
            }
            fillConfig(config, model);
            updateById(config);
        }
        refreshCache();
    }
    public void deleteConfig(String uuid) {
        ItemPreMarketConfig config = getById(uuid);
        if (config == null) {
            throw new YamiShopBindException("盘前配置不存在");
        }
        removeById(uuid);
        refreshCache();
    }
    public boolean isPreMarketTradingActive(String symbol) {
        ItemPreMarketConfig config = resolveConfig(symbol);
        return config != null && isInWindow(config);
    }
    public Realtime applyPreMarketPrice(Realtime realtime) {
        if (realtime == null || StrUtil.isBlank(realtime.getSymbol())) {
            return realtime;
        }
        ItemPreMarketConfig config = resolveConfig(realtime.getSymbol());
        if (config != null && isInWindow(config)) {
            double price = config.getPrePrice().doubleValue();
            realtime.setPreMarketActive(true);
            realtime.setPreMarketPrice(price);
            // 仅覆盖当前价;open/high/low/涨跌幅等保持原始行情数据
            realtime.setClose(price);
        } else {
            realtime.setPreMarketActive(false);
            realtime.setPreMarketPrice(null);
        }
        return realtime;
    }
    public Double getActivePreMarketPrice(String symbol) {
        ItemPreMarketConfig config = resolveConfig(symbol);
        if (config != null && isInWindow(config)) {
            return config.getPrePrice().doubleValue();
        }
        return null;
    }
    /** 按 item 标准 symbol / remarks / 大小写 匹配盘前配置 */
    private ItemPreMarketConfig resolveConfig(String symbol) {
        if (StrUtil.isBlank(symbol)) {
            return null;
        }
        ItemPreMarketConfig config = cache.get(symbol);
        if (config != null) {
            return config;
        }
        Item item = itemService.findBySymbol(symbol);
        if (item != null && StrUtil.isNotBlank(item.getSymbol())) {
            config = cache.get(item.getSymbol());
            if (config != null) {
                return config;
            }
        }
        for (Map.Entry<String, ItemPreMarketConfig> entry : cache.entrySet()) {
            if (entry.getKey().equalsIgnoreCase(symbol)) {
                return entry.getValue();
            }
        }
        return null;
    }
    /** 盘前时段仅覆盖 K 线最后一根的最新价 */
    public void applyPreMarketToLatestKline(String symbol, List<Kline> klines) {
        if (klines == null || klines.isEmpty()) {
            return;
        }
        Double price = getActivePreMarketPrice(symbol);
        if (price == null) {
            return;
        }
        Kline last = klines.get(klines.size() - 1);
        last.setClose(price);
        if (price > last.getHigh()) {
            last.setHigh(price);
        }
        if (price < last.getLow()) {
            last.setLow(price);
        }
    }
    private void fillConfig(ItemPreMarketConfig config, ItemPreMarketConfigSaveModel model) {
        config.setSymbol(model.getSymbol());
        config.setStartTime(normalizeTime(model.getStartTime()));
        config.setEndTime(normalizeTime(model.getEndTime()));
        config.setPrePrice(model.getPrePrice());
        config.setEnabled(model.getEnabled() == null ? 1 : model.getEnabled());
    }
    private ItemPreMarketConfigDTO toDto(ItemPreMarketConfig config) {
        ItemPreMarketConfigDTO dto = new ItemPreMarketConfigDTO();
        dto.setUuid(config.getUuid());
        dto.setSymbol(config.getSymbol());
        dto.setStartTime(config.getStartTime());
        dto.setEndTime(config.getEndTime());
        dto.setPrePrice(config.getPrePrice());
        dto.setEnabled(config.getEnabled());
        try {
            Item item = itemService.findBySymbol(config.getSymbol());
            if (item != null) {
                dto.setSymbolName(item.getName());
            }
        } catch (Exception ignored) {
        }
        return dto;
    }
    private boolean isInWindow(ItemPreMarketConfig config) {
        LocalTime start = parseTime(config.getStartTime());
        LocalTime end = parseTime(config.getEndTime());
        LocalTime now = ZonedDateTime.now(US_EASTERN).toLocalTime();
        if (start.equals(end)) {
            return true;
        }
        if (start.isBefore(end)) {
            return !now.isBefore(start) && now.isBefore(end);
        }
        return !now.isBefore(start) || now.isBefore(end);
    }
    private LocalTime parseTime(String time) {
        if (StrUtil.isBlank(time)) {
            throw new YamiShopBindException("时间格式错误");
        }
        try {
            return LocalTime.parse(time.trim(), TIME_FMT);
        } catch (DateTimeParseException e) {
            throw new YamiShopBindException("时间格式错误,请使用 HH:mm,如 9:30");
        }
    }
    private String normalizeTime(String time) {
        return parseTime(time).format(TIME_FMT);
    }
}
trading-order-service/src/main/java/com/yami/trading/service/item/ItemService.java
@@ -44,6 +44,9 @@
    @Autowired
    @Lazy
    private ItemService proxyItemService;
    @Autowired
    @Lazy
    private ItemPreMarketService itemPreMarketService;
    // 做成全局模式,减少动态创建对象的次数
    private Map<String, Integer> symbolDecimal = Maps.newHashMap();
@@ -425,6 +428,9 @@
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public boolean isOpen(String symbol) {
        Item bySymbol = findBySymbol(symbol);
        if (itemPreMarketService != null && itemPreMarketService.isPreMarketTradingActive(symbol)) {
            return true;
        }
        return MarketOpenChecker.isMarketOpenByItemCloseType(bySymbol.getOpenCloseType());
    }
trading-order-service/src/main/java/com/yami/trading/service/user/impl/SessionTokenServiceImpl.java
@@ -3,27 +3,35 @@
import com.yami.trading.common.util.StringUtils;
import com.yami.trading.common.util.UUIDGenerator;
import com.yami.trading.service.SessionTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Service
public class SessionTokenServiceImpl implements SessionTokenService {
    private volatile Map<String, String> cache = new ConcurrentHashMap<String, String>();
    private static final String KEY_PREFIX = "session_token:";
    private static final long TTL_MINUTES = 10;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public String savePut(String partyId) {
        String session_token = UUIDGenerator.getUUID();
        cache.put(session_token, partyId);
        return session_token;
        String sessionToken = UUIDGenerator.getUUID();
        redisTemplate.opsForValue().set(KEY_PREFIX + sessionToken, partyId, TTL_MINUTES, TimeUnit.MINUTES);
        return sessionToken;
    }
    @Override
    public String cacheGet(String session_token) {
        if (StringUtils.isNullOrEmpty(session_token)) {
            return null;
        }
        return cache.get(session_token);
        Object value = redisTemplate.opsForValue().get(KEY_PREFIX + session_token);
        return value == null ? null : String.valueOf(value);
    }
    @Override
@@ -31,7 +39,6 @@
        if (StringUtils.isNullOrEmpty(session_token)) {
            return;
        }
        cache.remove(session_token);
        redisTemplate.delete(KEY_PREFIX + session_token);
    }
}
trading-order-sys/src/main/java/com/yami/trading/sys/service/impl/SysMenuServiceImpl.java
@@ -19,7 +19,6 @@
import com.yami.trading.sys.model.SysMenu;
import com.yami.trading.sys.service.SysMenuService;
import lombok.AllArgsConstructor;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;
import java.util.*;
@@ -38,15 +37,12 @@
    @Override
    public List<SysMenu> listMenuByUserId(Long userId,String appType) {
        // 用户的所有菜单信息
        List<SysMenu> sysMenus ;
        //系统管理员,拥有最高权限
        if(userId == Constant.SUPER_ADMIN_ID||userId == Constant.SUPER_ROOT_ID){
            sysMenus = sysMenuMapper.listMenu(appType);
        }else {
            sysMenus = sysMenuMapper.listMenuByUserId(userId,appType);
            //记录全勾选
            Map<Long,SysMenu> mapSysMenu = new HashMap<>();
            for (SysMenu sysMenu : sysMenus) {
                mapSysMenu.put(sysMenu.getMenuId(), sysMenu);
@@ -54,7 +50,6 @@
            List<Long> bigIntArrays = new Vector<>();
            //权限
            List<SysMenu> sysMenus3 = sysMenuMapper.queryAllPerms2(userId);
            for (SysMenu sysMenu : sysMenus3) {
                if (!mapSysMenu.containsKey(sysMenu.getParentId())
@@ -64,88 +59,72 @@
                }
            }
            //补充
            if(!bigIntArrays.isEmpty()){
                List<SysMenu> sysMenus2 = sysMenuMapper.listMenuByUserId2(bigIntArrays,appType);
                for (SysMenu sysMenu : sysMenus2) {
                    mapSysMenu.put(sysMenu.getMenuId(), sysMenu);
                    //权限按钮的父节点
                    if (!mapSysMenu.containsKey(sysMenu.getParentId())
                            && !bigIntArrays.contains(sysMenu.getParentId())
                            && sysMenu.getParentId() != 0) {
                        bigIntArrays.add(sysMenu.getParentId());
                    }
                }
                sysMenus.addAll(sysMenus2);
            }
            bigIntArrays.clear();
            //子菜单
            for (SysMenu sysMenu : sysMenus) {
                if (!mapSysMenu.containsKey(sysMenu.getParentId())
                        && !bigIntArrays.contains(sysMenu.getParentId())
                        && sysMenu.getParentId() != 0) {
                    bigIntArrays.add(sysMenu.getParentId());
                }
            }
            //补充
            if(!bigIntArrays.isEmpty()){
                List<SysMenu> sysMenus2 = sysMenuMapper.listMenuByUserId2(bigIntArrays,appType);
//                for (SysMenu sysMenu : sysMenus2) {
//                    mapSysMenu.put(sysMenu.getMenuId(), sysMenu);
//                    //权限按钮的父节点
//                    if (!mapSysMenu.containsKey(sysMenu.getParentId())
//                            && !bigIntArrays.contains(sysMenu.getParentId())
//                            && sysMenu.getParentId() != 0) {
//                        bigIntArrays.add(sysMenu.getParentId());
//                    }
//                }
                sysMenus.addAll(sysMenus2);
            }
            appendMissingMenuAncestors(sysMenus, mapSysMenu, appType);
        }
        Map<Long, List<SysMenu>> sysMenuLevelMap = sysMenus.stream()
                .sorted(Comparator.comparing(SysMenu::getOrderNum))
                .collect(Collectors.groupingBy(SysMenu::getParentId));
        //System.out.println("sys = " +sysMenuLevelMap.values());
        // 一级菜单
        List<SysMenu> rootMenu = sysMenuLevelMap.get(0L);
        if (CollectionUtil.isEmpty(rootMenu)) {
            return Collections.emptyList();
        }
        // 二级菜单
        for (SysMenu sysMenu : rootMenu) {
            sysMenu.setList(sysMenuLevelMap.get(sysMenu.getMenuId()));
        }
        // 三级菜单
        for (SysMenu sysMenu : rootMenu) {
            List list = sysMenu.getList();
            if(list != null){
                for (Object object : list) {
                    SysMenu sysMenu2 = (SysMenu)object;
                    sysMenu2.setList(sysMenuLevelMap.get(sysMenu2.getMenuId()));
                }
            }
            attachMenuChildren(sysMenu, sysMenuLevelMap);
        }
        return rootMenu;
    }
    private void appendMissingMenuAncestors(List<SysMenu> sysMenus, Map<Long, SysMenu> mapSysMenu, String appType) {
        while (true) {
            List<Long> missingParents = new ArrayList<>();
            for (SysMenu sysMenu : sysMenus) {
                Long parentId = sysMenu.getParentId();
                if (parentId != null && parentId != 0
                        && !mapSysMenu.containsKey(parentId)
                        && !missingParents.contains(parentId)) {
                    missingParents.add(parentId);
                }
            }
            if (missingParents.isEmpty()) {
                return;
            }
            List<SysMenu> parents = sysMenuMapper.listMenuByUserId2(missingParents, appType);
            if (CollectionUtil.isEmpty(parents)) {
                return;
            }
            for (SysMenu parent : parents) {
                mapSysMenu.put(parent.getMenuId(), parent);
            }
            sysMenus.addAll(parents);
        }
    }
    private void attachMenuChildren(SysMenu menu, Map<Long, List<SysMenu>> sysMenuLevelMap) {
        List<SysMenu> children = sysMenuLevelMap.get(menu.getMenuId());
        menu.setList(children);
        if (CollectionUtil.isEmpty(children)) {
            return;
        }
        for (SysMenu child : children) {
            attachMenuChildren(child, sysMenuLevelMap);
        }
    }
    @Override
    public void deleteMenuAndRoleMenu(Long menuId){
        //删除菜单
        this.removeById(menuId);
        //删除菜单与角色关联
        sysRoleMenuMapper.deleteByMenuId(menuId);
    }
trading-order-sys/src/main/resources/mapper/SysMenuMapper.xml
@@ -5,7 +5,8 @@
    <select id="listMenuIdByRoleId" resultType="Long">
            SELECT sm.menu_id  FROM tz_sys_role_menu rm LEFT JOIN
  tz_sys_menu sm  ON  rm.menu_id=sm.menu_id   WHERE sm.app_type=#{appType} AND rm.role_id=#{roleId}
  tz_sys_menu sm  ON  rm.menu_id=sm.menu_id   WHERE rm.role_id=#{roleId}
  AND (sm.app_type = #{appType} OR FIND_IN_SET(#{appType}, REPLACE(sm.app_type, ' ', '')) OR sm.app_type IS NULL OR sm.app_type = '')
    </select>
    <!-- 查询用户的所有菜单 -->
@@ -15,7 +16,7 @@
        WHERE  ur.user_id = #{userId} and m.type != 2
        
        <if test="appType!=null and appType!='' ">
            AND  app_type=#{appType}
            AND (m.app_type = #{appType} OR FIND_IN_SET(#{appType}, REPLACE(m.app_type, ' ', '')) OR m.app_type IS NULL OR m.app_type = '')
        </if>
        order by order_num
@@ -24,7 +25,7 @@
    <select id="listMenu" resultType="com.yami.trading.sys.model.SysMenu">
        select * from tz_sys_menu where `type` != 2
        <if test="appType!=null and appType!='' ">
            AND  app_type=#{appType}
            AND (app_type = #{appType} OR FIND_IN_SET(#{appType}, REPLACE(app_type, ' ', '')) OR app_type IS NULL OR app_type = '')
        </if>
        order by order_num
    </select>
@@ -33,7 +34,7 @@
        select menu_id ,parent_id ,`name` from tz_sys_menu where `type` != 2
        <if test="appType!=null and appType!='' ">
            AND  app_type=#{appType}
            AND (app_type = #{appType} OR FIND_IN_SET(#{appType}, REPLACE(app_type, ' ', '')) OR app_type IS NULL OR app_type = '')
        </if>
        order by order_num
@@ -50,16 +51,16 @@
    <select id="listMenuAndBtn" resultType="com.yami.trading.sys.model.SysMenu">
       select * from tz_sys_menu where
        1=1
         <if test="appType!=null and appType!='' ">
             AND  app_type=#{appType}
         </if>
        <if test="appType!=null and appType!='' ">
            AND (app_type = #{appType} OR FIND_IN_SET(#{appType}, REPLACE(app_type, ' ', '')) OR app_type IS NULL OR app_type = '')
        </if>
        order by order_num
    </select>
    <!-- 查询用户的所有菜单 -->
    <select id="listMenuByUserId2" resultType="com.yami.trading.sys.model.SysMenu">
        SELECT DISTINCT m.menu_id AS menu_id,m.parent_id,m.app_type,m.name,url,m.type,m.icon,m.order_num FROM tz_sys_menu m where 1=1
        <if test="appType!=null and appType!='' ">AND app_type=#{appType}</if>
        <if test="appType!=null and appType!='' ">AND (app_type = #{appType} OR FIND_IN_SET(#{appType}, REPLACE(app_type, ' ', '')) OR app_type IS NULL OR app_type = '')</if>
        <if test="children != null and children.size()>0">and m.menu_id in
            <foreach collection="children" item="item" index="index" open="(" close=")" separator=",">#{item}</foreach>
        </if>