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