From 5e57de9b12ee136e45ce5754c7fe2e7eb12af05a Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Fri, 12 Jun 2026 18:35:45 +0800
Subject: [PATCH] 1

---
 trading-order-bean/src/main/java/com/yami/trading/bean/future/dto/TFuturesOrderDTO.java                       |    3 
 trading-order-admin/src/main/java/com/yami/trading/admin/controller/item/AdminItemPreMarketController.java    |   56 ++
 trading-order-huobi/src/main/java/com/yami/trading/huobi/task/future/FuturesOrderCalculationJob.java          |    3 
 docs/db/V9__pre_market_sys_menu.sql                                                                           |   48 ++
 trading-order-huobi/src/main/java/com/yami/trading/huobi/task/contract/ContractApplyOrderHandleJob.java       |   52 +-
 docs/db/V10_1__us_futures_role_menu.sql                                                                       |   25 +
 trading-order-service/src/main/java/com/yami/trading/service/MarketOpenChecker.java                           |    4 
 docs/db/V8__pre_market_and_futures_manual_profit.sql                                                          |   22 +
 docs/db/V10_2__us_futures_menu_fix.sql                                                                        |   45 ++
 trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java                              |    8 
 trading-order-service/src/main/java/com/yami/trading/service/item/ItemPreMarketService.java                   |  260 ++++++++++++
 trading-order-admin/src/main/java/com/yami/trading/admin/controller/sys/SysMenuController.java                |    6 
 trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiFuturesOrderController.java              |   23 
 trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java          |   30 +
 trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigDTO.java                   |   17 
 trading-order-admin/src/main/java/com/yami/trading/admin/controller/future/AdminFuturesOrderController.java   |   13 
 trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java                        |    6 
 trading-order-service/src/main/java/com/yami/trading/service/exchange/job/ExchangeApplyOrderHandleJob.java    |   21 
 trading-order-bean/src/main/java/com/yami/trading/bean/future/domain/FuturesOrder.java                        |    3 
 trading-order-huobi/src/main/java/com/yami/trading/huobi/data/internal/DataServiceImpl.java                   |   23 
 trading-order-service/src/main/java/com/yami/trading/service/user/impl/SessionTokenServiceImpl.java           |   25 
 trading-order-service/src/main/java/com/yami/trading/dao/item/ItemPreMarketConfigMapper.java                  |    7 
 trading-order-service/src/main/java/com/yami/trading/service/future/FuturesOrderService.java                  |  212 ++++++++-
 trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java                      |    3 
 trading-order-bean/src/main/java/com/yami/trading/bean/item/domain/ItemPreMarketConfig.java                   |   29 +
 docs/db/V10__us_futures_menu.sql                                                                              |   71 +++
 trading-order-sys/src/main/java/com/yami/trading/sys/service/impl/SysMenuServiceImpl.java                     |   97 +--
 trading-order-huobi/src/main/java/com/yami/trading/huobi/task/RealtimePushJob.java                            |   11 
 trading-order-service/src/main/java/com/yami/trading/service/item/ItemService.java                            |    6 
 trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigSaveModel.java             |   27 +
 trading-order-sys/src/main/resources/mapper/SysMenuMapper.xml                                                 |   17 
 trading-order-service/src/main/java/com/yami/trading/service/exchange/impl/ExchangeApplyOrderServiceImpl.java |   56 ++
 32 files changed, 1,053 insertions(+), 176 deletions(-)

diff --git a/docs/db/V10_1__us_futures_role_menu.sql b/docs/db/V10_1__us_futures_role_menu.sql
new file mode 100644
index 0000000..d34ef97
--- /dev/null
+++ b/docs/db/V10_1__us_futures_role_menu.sql
@@ -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);
diff --git a/docs/db/V10_2__us_futures_menu_fix.sql b/docs/db/V10_2__us_futures_menu_fix.sql
new file mode 100644
index 0000000..a19f549
--- /dev/null
+++ b/docs/db/V10_2__us_futures_menu_fix.sql
@@ -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
+  );
diff --git a/docs/db/V10__us_futures_menu.sql b/docs/db/V10__us_futures_menu.sql
new file mode 100644
index 0000000..a4d684e
--- /dev/null
+++ b/docs/db/V10__us_futures_menu.sql
@@ -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);
diff --git a/docs/db/V8__pre_market_and_futures_manual_profit.sql b/docs/db/V8__pre_market_and_futures_manual_profit.sql
new file mode 100644
index 0000000..8f9dd9f
--- /dev/null
+++ b/docs/db/V8__pre_market_and_futures_manual_profit.sql
@@ -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 '后台设定的盈亏百分比,结算时优先使用';
diff --git a/docs/db/V9__pre_market_sys_menu.sql b/docs/db/V9__pre_market_sys_menu.sql
new file mode 100644
index 0000000..f162389
--- /dev/null
+++ b/docs/db/V9__pre_market_sys_menu.sql
@@ -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;
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/future/AdminFuturesOrderController.java b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/future/AdminFuturesOrderController.java
index 1fd714e..28271c7 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/future/AdminFuturesOrderController.java
+++ b/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("操作成功");
+	}
 }
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/item/AdminItemPreMarketController.java b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/item/AdminItemPreMarketController.java
new file mode 100644
index 0000000..c566d6e
--- /dev/null
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/item/AdminItemPreMarketController.java
@@ -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("删除成功");
+    }
+}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/sys/SysMenuController.java b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/sys/SysMenuController.java
index 0494a7a..03c402e 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/sys/SysMenuController.java
+++ b/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);
 				}
 			}
 		}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiFuturesOrderController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiFuturesOrderController.java
index 29186e4..5368f4a 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiFuturesOrderController.java
+++ b/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);
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java
index 56c1fca..a781446 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java
+++ b/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);
     }
 
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
index e9e33e8..621ea34 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
+++ b/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));
     }
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java b/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java
index 885f601..754767d 100644
--- a/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java
+++ b/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();
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/future/domain/FuturesOrder.java b/trading-order-bean/src/main/java/com/yami/trading/bean/future/domain/FuturesOrder.java
index eac0d36..a1d16c0 100644
--- a/trading-order-bean/src/main/java/com/yami/trading/bean/future/domain/FuturesOrder.java
+++ b/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;
+
 }
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/future/dto/TFuturesOrderDTO.java b/trading-order-bean/src/main/java/com/yami/trading/bean/future/dto/TFuturesOrderDTO.java
index 4fcfa72..d05a928 100644
--- a/trading-order-bean/src/main/java/com/yami/trading/bean/future/dto/TFuturesOrderDTO.java
+++ b/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 "盈利";
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/item/domain/ItemPreMarketConfig.java b/trading-order-bean/src/main/java/com/yami/trading/bean/item/domain/ItemPreMarketConfig.java
new file mode 100644
index 0000000..acb7006
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/item/domain/ItemPreMarketConfig.java
@@ -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;
+}
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigDTO.java b/trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigDTO.java
new file mode 100644
index 0000000..87b4500
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigDTO.java
@@ -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;
+}
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigSaveModel.java b/trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigSaveModel.java
new file mode 100644
index 0000000..cebd6a3
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/item/dto/ItemPreMarketConfigSaveModel.java
@@ -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;
+}
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/internal/DataServiceImpl.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/internal/DataServiceImpl.java
index a058d41..fc7cbd3 100644
--- a/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/internal/DataServiceImpl.java
+++ b/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);
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/RealtimePushJob.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/RealtimePushJob.java
index e1553ca..2790de2 100644
--- a/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/RealtimePushJob.java
+++ b/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);
                 }
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/contract/ContractApplyOrderHandleJob.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/contract/ContractApplyOrderHandleJob.java
index 19d72dc..61b1d63 100644
--- a/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/contract/ContractApplyOrderHandleJob.java
+++ b/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) {
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/future/FuturesOrderCalculationJob.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/future/FuturesOrderCalculationJob.java
index 46ddb11..50cd49a 100644
--- a/trading-order-huobi/src/main/java/com/yami/trading/huobi/task/future/FuturesOrderCalculationJob.java
+++ b/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;
 							}
diff --git a/trading-order-service/src/main/java/com/yami/trading/dao/item/ItemPreMarketConfigMapper.java b/trading-order-service/src/main/java/com/yami/trading/dao/item/ItemPreMarketConfigMapper.java
new file mode 100644
index 0000000..4cbd1fd
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/dao/item/ItemPreMarketConfigMapper.java
@@ -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> {
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/MarketOpenChecker.java b/trading-order-service/src/main/java/com/yami/trading/service/MarketOpenChecker.java
index f064cbc..ce0edb5 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/MarketOpenChecker.java
+++ b/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;
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
index 086bcef..98de9a3 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
+++ b/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);
     }
 
     /**
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/exchange/impl/ExchangeApplyOrderServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/exchange/impl/ExchangeApplyOrderServiceImpl.java
index 0a16c09..91fecdc 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/exchange/impl/ExchangeApplyOrderServiceImpl.java
+++ b/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);
+        }
     }
     //================================================闪兑==============================================================
 
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/exchange/job/ExchangeApplyOrderHandleJob.java b/trading-order-service/src/main/java/com/yami/trading/service/exchange/job/ExchangeApplyOrderHandleJob.java
index c1264c3..7a50715 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/exchange/job/ExchangeApplyOrderHandleJob.java
+++ b/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) {
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/future/FuturesOrderService.java b/trading-order-service/src/main/java/com/yami/trading/service/future/FuturesOrderService.java
index 8bf43c5..c96ed88 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/future/FuturesOrderService.java
+++ b/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());
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/item/ItemPreMarketService.java b/trading-order-service/src/main/java/com/yami/trading/service/item/ItemPreMarketService.java
new file mode 100644
index 0000000..f70adbd
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/service/item/ItemPreMarketService.java
@@ -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);
+    }
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/item/ItemService.java b/trading-order-service/src/main/java/com/yami/trading/service/item/ItemService.java
index 7fc4997..01b1af2 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/item/ItemService.java
+++ b/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());
     }
 
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/user/impl/SessionTokenServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/user/impl/SessionTokenServiceImpl.java
index d41b845..2c53e52 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/user/impl/SessionTokenServiceImpl.java
+++ b/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);
     }
-
 }
diff --git a/trading-order-sys/src/main/java/com/yami/trading/sys/service/impl/SysMenuServiceImpl.java b/trading-order-sys/src/main/java/com/yami/trading/sys/service/impl/SysMenuServiceImpl.java
index 8162316..9f7c97a 100644
--- a/trading-order-sys/src/main/java/com/yami/trading/sys/service/impl/SysMenuServiceImpl.java
+++ b/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);
 	}
 
diff --git a/trading-order-sys/src/main/resources/mapper/SysMenuMapper.xml b/trading-order-sys/src/main/resources/mapper/SysMenuMapper.xml
index fe7597e..b995252 100644
--- a/trading-order-sys/src/main/resources/mapper/SysMenuMapper.xml
+++ b/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>

--
Gitblit v1.9.3