From 1f19b78818609bf6d0849f8e58b349132a800538 Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Mon, 01 Jun 2026 17:08:49 +0800
Subject: [PATCH] 1

---
 src/main/java/com/nq/utils/TradeFeeUtil.java                   |   29 +++++
 .idea/git_toolbox_prj.xml                                      |   15 +++
 src/main/resources/mapper/UserPositionMapper.xml               |   11 ++
 src/main/java/com/nq/controller/protol/UserController.java     |    7 
 src/main/java/com/nq/vo/pay/OcocnPayVO.java                    |   13 ++
 .idea/A-stock.iml                                              |    9 +
 src/main/java/com/nq/service/impl/UserPositionServiceImpl.java |   69 ++++++++-----
 src/main/java/com/nq/service/IUserPositionService.java         |    2 
 src/main/java/com/nq/utils/DateTimeUtil.java                   |    8 +
 src/main/java/com/nq/utils/pay/OcocnPayUtil.java               |   97 +++++++++++++++++++
 10 files changed, 229 insertions(+), 31 deletions(-)

diff --git a/.idea/A-stock.iml b/.idea/A-stock.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/A-stock.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000..02b915b
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GitToolBoxProjectSettings">
+    <option name="commitMessageIssueKeyValidationOverride">
+      <BoolValueOverride>
+        <option name="enabled" value="true" />
+      </BoolValueOverride>
+    </option>
+    <option name="commitMessageValidationEnabledOverride">
+      <BoolValueOverride>
+        <option name="enabled" value="true" />
+      </BoolValueOverride>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/nq/controller/protol/UserController.java b/src/main/java/com/nq/controller/protol/UserController.java
index bfa1b4b..86c31c3 100644
--- a/src/main/java/com/nq/controller/protol/UserController.java
+++ b/src/main/java/com/nq/controller/protol/UserController.java
@@ -92,8 +92,7 @@
     public ServerResponse buy(@RequestParam("stockId") Integer stockId, @RequestParam("buyNum") Integer buyNum, @RequestParam("buyType") Integer buyType, @RequestParam("lever") Integer lever,@RequestParam(value = "profitTarget",required = false) BigDecimal profitTarget,@RequestParam(value = "stopTarget",required = false) BigDecimal stopTarget, HttpServletRequest request) {
         ServerResponse serverResponse = null;
         try {
-//            serverResponse = this.iUserPositionService.buy(stockId, buyNum, buyType, lever,profitTarget,stopTarget, request);
-            serverResponse = this.iUserPositionService.pending(stockId, buyNum, buyType, lever,profitTarget,stopTarget, request);
+            serverResponse = this.iUserPositionService.buy(stockId, buyNum, buyType, lever, profitTarget, stopTarget, request);
         } catch (Exception e) {
             log.error("用户下单操作 = {}", e);
         }
@@ -103,10 +102,10 @@
     //用户下单买入股票
     @RequestMapping({"fee.do"})
     @ResponseBody
-    public ServerResponse fee(@RequestParam("buyNum") Integer buyNum,@RequestParam("nowPrice") BigDecimal nowPrice,HttpServletRequest request) {
+    public ServerResponse fee(@RequestParam("buyNum") Integer buyNum, @RequestParam("nowPrice") BigDecimal nowPrice, @RequestParam(value = "lever", required = false, defaultValue = "1") Integer lever, HttpServletRequest request) {
         ServerResponse serverResponse = null;
         try {
-            serverResponse = this.iUserPositionService.fee(buyNum,nowPrice);
+            serverResponse = this.iUserPositionService.fee(buyNum, nowPrice, lever);
         } catch (Exception e) {
             log.error("用户下单操作 = {}", e);
         }
diff --git a/src/main/java/com/nq/service/IUserPositionService.java b/src/main/java/com/nq/service/IUserPositionService.java
index ee57f2b..2ae7002 100644
--- a/src/main/java/com/nq/service/IUserPositionService.java
+++ b/src/main/java/com/nq/service/IUserPositionService.java
@@ -26,7 +26,7 @@
   
   ServerResponse lock(Integer paramInteger1, Integer paramInteger2, String paramString,Integer paramInteger3);
 
-  ServerResponse fee(Integer buyNum,BigDecimal nowPrice);
+  ServerResponse fee(Integer buyNum, BigDecimal nowPrice, Integer lever);
 
   ServerResponse del(Integer paramInteger);
   
diff --git a/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java b/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
index 5162932..2da1050 100644
--- a/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
@@ -299,10 +299,10 @@
         BigDecimal ztRate = chaPrice.multiply(new BigDecimal("100")).divide(zsPrice, 2, 4);
 
         log.info("当前涨跌幅 = {} % , 涨停幅度 = {} %", Double.valueOf(stock_crease), ztRate);
-        if ((new BigDecimal(String.valueOf(stock_crease))).compareTo(ztRate) == 0 && buyType
-                .intValue() == 0) {
-            return ServerResponse.createByErrorMsg("当前股票已涨停不能买涨");
-        }
+//        if ((new BigDecimal(String.valueOf(stock_crease))).compareTo(ztRate) == 0 && buyType
+//                .intValue() == 0) {
+//            return ServerResponse.createByErrorMsg("当前股票已涨停不能买涨");
+//        }
 
 
         if (stock.getStockPlate() == null || StringUtils.isEmpty(stock.getStockPlate())) {
@@ -388,7 +388,7 @@
         }
 
 
-        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt);
+        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual);
         BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
         int compareUserAmtInt = user_enable_amt.compareTo(buy_debit);
         log.info("用户可用金额 = {}  下单扣款(保证金+手续费) =  {}", user_enable_amt, buy_debit);
@@ -479,6 +479,7 @@
 
         userPosition.setOrderStayDays(Integer.valueOf(0));
         userPosition.setOrderStayFee(new BigDecimal("0"));
+        userPosition.setStatus(1);
 
         this.userPositionMapper.insert(userPosition);
         if (userPosition.getId() != null && userPosition.getId() > 0) {
@@ -496,9 +497,10 @@
 
 
     @Override
-    public ServerResponse fee(Integer buyNum, BigDecimal nowPrice) {
+    public ServerResponse fee(Integer buyNum, BigDecimal nowPrice, Integer lever) {
+        int leverValue = (lever == null || lever <= 0) ? 1 : lever;
         BigDecimal buy_amt = nowPrice.multiply(new BigDecimal(buyNum.intValue()));
-        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt).setScale(2, 4);
+        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFeeByNotional(buy_amt, leverValue).setScale(2, 4);
         return ServerResponse.createBySuccess(buy_fee_amt);
     }
 
@@ -788,7 +790,7 @@
                     .getBuyMaxAmtPercent().multiply(new BigDecimal("100")) + "%");
         }
 
-        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt);
+        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual);
         BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
         if (user_enable_amt.compareTo(buy_debit) == -1) {
             return ServerResponse.createByErrorMsg("挂单失败,融资可用金额小于" + buy_debit + "元(含保证金及手续费)");
@@ -950,6 +952,20 @@
     }
 
 
+    private ServerResponse validateT1BeforeSell(UserPosition userPosition, int doType) {
+        if (doType == 0 || userPosition == null || userPosition.getBuyOrderTime() == null) {
+            return null;
+        }
+        String stockGid = userPosition.getStockGid();
+        if (stockGid != null && (stockGid.contains("us") || stockGid.contains("hk"))) {
+            return null;
+        }
+        if (!DateTimeUtil.canSellByT1(userPosition.getBuyOrderTime())) {
+            return ServerResponse.createByErrorMsg("T+1交易制度,当日买入的股票需下一交易日才能平仓");
+        }
+        return null;
+    }
+
     public ServerResponse sell(String positionSn, int doType) throws Exception {
         log.info("【用戶交易平倉】 positionSn = {} , dotype = {}", positionSn, Integer.valueOf(doType));
 
@@ -1028,10 +1044,9 @@
             return ServerResponse.createByErrorMsg("平仓失败,此订单已平仓");
         }
 
-        if (DateTimeUtil.isCanSellOneday(userPosition.getBuyOrderTime(), siteSetting.getCantSellTimes().intValue()) && siteProduct.getTranWithdrawDisplay()) {
-            // return ServerResponse.createByErrorMsg(siteSetting.getCantSellTimes() + "分鐘內不能平倉");
-
-            return ServerResponse.createByErrorMsg("当日成交不可平仓");
+        ServerResponse t1Check = validateT1BeforeSell(userPosition, doType);
+        if (t1Check != null) {
+            return t1Check;
         }
 
         if (1 == userPosition.getIsLock().intValue()) {
@@ -1110,10 +1125,10 @@
 
         ztRate = ztRate.negate();
         log.info("股票當前漲跌幅 = {} 跌停幅度 = {}", Double.valueOf(stock_crease), ztRate);
-        if ((new BigDecimal(String.valueOf(stock_crease))).compareTo(ztRate) == 0 && "買漲"
-                .equals(userPosition.getOrderDirection())) {
-            return ServerResponse.createByErrorMsg("当前股票已跌停不能卖出");
-        }
+//        if ((new BigDecimal(String.valueOf(stock_crease))).compareTo(ztRate) == 0 && "買漲"
+//                .equals(userPosition.getOrderDirection())) {
+//            return ServerResponse.createByErrorMsg("当前股票已跌停不能卖出");
+//        }
 
         Integer buy_num = userPosition.getOrderNum();
 
@@ -1204,7 +1219,7 @@
         ucd.setUserName(user.getRealName());
         ucd.setDeType("总盈亏");
         ucd.setDeAmt(all_profit);
-        ucd.setDeSummary("卖出股票," + userPosition.getStockCode() + "/" + userPosition.getStockName() + ",占用本金:" + freez_amt + ",总手续费:" + all_fee_amt + ",递延费:" + orderStayFee + ",印花稅:" + orderSpread + ",盈亏:" + profitLoss + ",总盈亏:" + all_profit);
+        ucd.setDeSummary("卖出股票," + userPosition.getStockCode() + "/" + userPosition.getStockName() + ",占用本金:" + freez_amt + ",总手续费:" + all_fee_amt + ",盈亏:" + profitLoss + ",总盈亏:" + all_profit);
 
         ucd.setAddTime(new Date());
         ucd.setIsRead(Integer.valueOf(0));
@@ -1299,8 +1314,9 @@
         if (1 == userPosition.getIsLock().intValue()) {
             return ServerResponse.createByErrorMsg("平仓失败 " + userPosition.getLockMsg());
         }
-        if (!DateTimeUtil.isCanSell(userPosition.getBuyOrderTime(), siteSetting.getCantSellTimes().intValue())) {
-            return ServerResponse.createByErrorMsg("当日成交不可平仓");
+        ServerResponse t1Check = validateT1BeforeSell(userPosition, 1);
+        if (t1Check != null) {
+            return t1Check;
         }
 
         BigDecimal now_price;
@@ -1364,7 +1380,9 @@
         BigDecimal user_enable_amt = user.getEnableAmt();
         log.info("用戶原本總資金 = {} , 可用 = {}", user_all_amt, user_enable_amt);
 
-        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(all_buy_amt);
+        BigDecimal partialMargin = all_buy_amt.divide(
+                new BigDecimal(userPosition.getOrderLever()), 2, RoundingMode.HALF_UP);
+        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(partialMargin);
         log.info("買入手續費 = {}", buy_fee_amt);
 
         BigDecimal orderSpread = all_buy_amt.multiply(siteSetting.getDutyFee()).setScale(2, 4);
@@ -1921,7 +1939,7 @@
         BigDecimal buy_amt_autual = buy_amt.divide(new BigDecimal(lever.intValue()), 2, 4);
 
 
-        BigDecimal buy_fee_amt_check = TradeFeeUtil.calcBuyFee(buy_amt);
+        BigDecimal buy_fee_amt_check = TradeFeeUtil.calcBuyFee(buy_amt_autual);
         BigDecimal buy_debit_check = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt_check);
         if (user_enable_amt.compareTo(buy_debit_check) < 0) {
             log.info("下单失败,用户可用金额小于{}元(含保证金及手续费)", buy_debit_check);
@@ -1982,8 +2000,8 @@
         userPosition.setOrderStayDays(1);
 
 
-        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt).setScale(2, 4);
-        log.info("创建模拟持仓 手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
+        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual).setScale(2, 4);
+        log.info("创建模拟持仓 手续费(保证金 * 百分比) = {}", buy_fee_amt);
         userPosition.setOrderFee(buy_fee_amt);
 
 
@@ -2026,6 +2044,7 @@
         userPosition.setOrderStayDays(Integer.valueOf(0));
         userPosition.setOrderStayFee(new BigDecimal("0"));
         userPosition.setSpreadRatePrice(new BigDecimal("0"));
+        userPosition.setStatus(1);
 
         this.userPositionMapper.insert(userPosition);
         if (userPosition.getId() == null || userPosition.getId() <= 0) {
@@ -2744,7 +2763,7 @@
         }
 
 
-        BigDecimal buy_fee_amt_dz = TradeFeeUtil.calcBuyFee(buy_amt);
+        BigDecimal buy_fee_amt_dz = TradeFeeUtil.calcBuyFee(buy_amt_autual);
         BigDecimal buy_debit_dz = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt_dz);
         if (user_enable_amt.compareTo(buy_debit_dz) == -1) {
             return ServerResponse.createByErrorMsg("下单失败,融资可用金额小于" + buy_debit_dz + "元(含保证金及手续费)");
@@ -3052,7 +3071,7 @@
         }
 
 
-        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt);
+        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual);
         BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
         if (user_enable_amt.compareTo(buy_debit) == -1) {
             return ServerResponse.createByErrorMsg("下單失敗,可用金額小於" + buy_debit + "元(含保证金及手续费)");
diff --git a/src/main/java/com/nq/utils/DateTimeUtil.java b/src/main/java/com/nq/utils/DateTimeUtil.java
index acd6263..6f77877 100644
--- a/src/main/java/com/nq/utils/DateTimeUtil.java
+++ b/src/main/java/com/nq/utils/DateTimeUtil.java
@@ -223,6 +223,14 @@
         }
     }
 
+    /** T+1:买入当日不可卖出,下一交易日方可平仓 */
+    public static boolean canSellByT1(Date buyDate) {
+        if (buyDate == null) {
+            return true;
+        }
+        return !sameDate(getCurrentDate(), buyDate);
+    }
+
     /*日期年月日是否相同*/
     public static boolean sameDate(Date d1, Date d2) {
         SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
diff --git a/src/main/java/com/nq/utils/TradeFeeUtil.java b/src/main/java/com/nq/utils/TradeFeeUtil.java
new file mode 100644
index 0000000..195ed57
--- /dev/null
+++ b/src/main/java/com/nq/utils/TradeFeeUtil.java
@@ -0,0 +1,29 @@
+package com.nq.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+public class TradeFeeUtil {
+
+    public static final BigDecimal BUY_FEE_RATE = new BigDecimal("0.03");
+
+    private TradeFeeUtil() {
+    }
+
+    public static BigDecimal calcBuyFee(BigDecimal buyAmt) {
+        if (buyAmt == null) {
+            return BigDecimal.ZERO;
+        }
+        return buyAmt.multiply(BUY_FEE_RATE).setScale(2, RoundingMode.HALF_UP);
+    }
+
+    public static BigDecimal calcBuyFeeByNotional(BigDecimal notional, Integer lever) {
+        return calcBuyFee(notional);
+    }
+
+    public static BigDecimal calcBuyDebit(BigDecimal margin, BigDecimal fee) {
+        BigDecimal marginAmt = margin == null ? BigDecimal.ZERO : margin;
+        BigDecimal feeAmt = fee == null ? BigDecimal.ZERO : fee;
+        return marginAmt.add(feeAmt).setScale(2, RoundingMode.HALF_UP);
+    }
+}
diff --git a/src/main/java/com/nq/utils/pay/OcocnPayUtil.java b/src/main/java/com/nq/utils/pay/OcocnPayUtil.java
new file mode 100644
index 0000000..8d0519e
--- /dev/null
+++ b/src/main/java/com/nq/utils/pay/OcocnPayUtil.java
@@ -0,0 +1,97 @@
+package com.nq.utils.pay;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class OcocnPayUtil {
+
+    private OcocnPayUtil() {
+    }
+
+    public static String encode(String value) {
+        if (value == null) {
+            return "";
+        }
+        try {
+            return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
+        } catch (UnsupportedEncodingException e) {
+            return value;
+        }
+    }
+
+    public static String buildSubmitSign(String money, String productName, String notifyUrl,
+                                         String ordersn, String pid, String returnUrl,
+                                         String sitename, String payType, String key) {
+        Map<String, String> params = new TreeMap<>();
+        params.put("money", money);
+        params.put("name", productName);
+        params.put("notify_url", notifyUrl);
+        params.put("out_trade_no", ordersn);
+        params.put("pid", pid);
+        params.put("return_url", returnUrl);
+        if (StringUtils.isNotBlank(sitename)) {
+            params.put("sitename", sitename);
+        }
+        params.put("type", payType);
+        return md5Sign(params, key);
+    }
+
+    public static Map<String, String> parseRequestParams(HttpServletRequest request) {
+        Map<String, String> result = new HashMap<>();
+        if (request == null) {
+            return result;
+        }
+        Map<String, String[]> parameterMap = request.getParameterMap();
+        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
+            String[] values = entry.getValue();
+            if (values != null && values.length > 0) {
+                result.put(entry.getKey(), values[0]);
+            }
+        }
+        return result;
+    }
+
+    public static boolean verifyNotifySign(Map<String, String> params, String key) {
+        if (params == null || StringUtils.isBlank(key)) {
+            return false;
+        }
+        String remoteSign = params.get("sign");
+        if (StringUtils.isBlank(remoteSign)) {
+            return false;
+        }
+        Map<String, String> signParams = new TreeMap<>();
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            String name = entry.getKey();
+            String value = entry.getValue();
+            if ("sign".equalsIgnoreCase(name) || "sign_type".equalsIgnoreCase(name)) {
+                continue;
+            }
+            if (StringUtils.isBlank(value)) {
+                continue;
+            }
+            signParams.put(name, value);
+        }
+        String localSign = md5Sign(signParams, key);
+        return remoteSign.equalsIgnoreCase(localSign);
+    }
+
+    private static String md5Sign(Map<String, String> params, String key) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            if (sb.length() > 0) {
+                sb.append('&');
+            }
+            sb.append(entry.getKey()).append('=').append(entry.getValue());
+        }
+        sb.append(key);
+        return DigestUtils.md5Hex(sb.toString()).toLowerCase();
+    }
+}
diff --git a/src/main/java/com/nq/vo/pay/OcocnPayVO.java b/src/main/java/com/nq/vo/pay/OcocnPayVO.java
new file mode 100644
index 0000000..bf7c23a
--- /dev/null
+++ b/src/main/java/com/nq/vo/pay/OcocnPayVO.java
@@ -0,0 +1,13 @@
+package com.nq.vo.pay;
+
+public class OcocnPayVO {
+    private String payUrl;
+
+    public String getPayUrl() {
+        return payUrl;
+    }
+
+    public void setPayUrl(String payUrl) {
+        this.payUrl = payUrl;
+    }
+}
diff --git a/src/main/resources/mapper/UserPositionMapper.xml b/src/main/resources/mapper/UserPositionMapper.xml
index 6a99a68..86e2306 100644
--- a/src/main/resources/mapper/UserPositionMapper.xml
+++ b/src/main/resources/mapper/UserPositionMapper.xml
@@ -465,7 +465,16 @@
     FROM user_position
     <where>
       user_id = #{uid}
-      <if test="state != null">
+      <if test="state == 1">
+        and sell_order_id is null and (status = 1 or status is null)
+      </if>
+      <if test="state == 2">
+        and status = 2
+      </if>
+      <if test="state == 0">
+        and status = 0
+      </if>
+      <if test="state != null and state != 0 and state != 1 and state != 2">
         and status = #{state, jdbcType=INTEGER}
       </if>
       <if test="stockCode != null and stockCode != '' ">

--
Gitblit v1.9.3