1
zj
2026-01-13 74f2dd24eb7241020319771c794d9e9f06264d7e
1
14 files modified
1 files added
643 ■■■■■ changed files
src/main/java/com/nq/controller/protol/UserController.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/protol/UserWithdrawController.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/enums/EUserAssets.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserAssetsServices.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserPendingorderServiceImpl.java 465 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/task/stock/CarryPositionTask.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/task/stock/PendingOrderTask.java 53 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/vo/position/UserPendingorderVO.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/ws/WebSocketClientBeanConfig.java 36 ●●●● patch | view | raw | blame | history
src/main/resources/application.properties 2 ●●● patch | view | raw | blame | history
src/main/resources/application.yml 2 ●●● patch | view | raw | blame | history
src/main/resources/mapper/StockMapper.xml 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/protol/UserController.java
@@ -1,6 +1,9 @@
package com.nq.controller.protol;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import com.google.common.collect.Maps;
import com.nq.common.ServerResponse;
import com.nq.enums.EStockType;
@@ -18,6 +21,7 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.nq.utils.SymmetricCryptoUtil;
import com.nq.vo.stock.UserStockSubscribeAddIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -425,6 +429,17 @@
        return iApplyLeverServices.applyLever(applyLever,request);
    }
    @RequestMapping({"getPassword.do"})
    @ResponseBody
    public ServerResponse getPassword(HttpServletRequest request) {
        User user = this.iUserService.getCurrentRefreshUser(request);
        if(ObjectUtil.isEmpty(user)){
            return ServerResponse.createByErrorMsg("获取用信息失败,请重新登录!",request);
        }
        String pwd = SymmetricCryptoUtil.decryptFromString(user.getUserPwd(), Mode.CBC, Padding.ZeroPadding);
        return ServerResponse.createBySuccess(pwd);
    }
    /**
     * 充值第三方支付
     */
src/main/java/com/nq/controller/protol/UserWithdrawController.java
@@ -61,9 +61,9 @@
        requestTimestamps.put(requestId, System.currentTimeMillis());
        try {
            if (!isIntegerGreaterThan100(amt)) {
                return ServerResponse.createByErrorMsg("请输入整数!",request);
            }
//            if (!isIntegerGreaterThan100(amt)) {
//                return ServerResponse.createByErrorMsg("请输入整数!",request);
//            }
            synchronized (user.getId()){
                serverResponse = this.iUserWithdrawService.outMoney(amt, user.getWithPwd(), accsetType,bankId,request);
            }
@@ -87,7 +87,7 @@
    public static boolean isIntegerGreaterThan100(String str) {
        try {
            int number = Integer.parseInt(str);  // 尝试将字符串转换为整数
            return number > 100;  // 判断是否大于100
            return number >= 100;  // 判断是否大于100
        } catch (NumberFormatException e) {
            return false;  // 如果转换失败,说明不是整数
        }
src/main/java/com/nq/enums/EUserAssets.java
@@ -23,6 +23,8 @@
    DK("DK","发放贷款"),
    RT_DK("RT_DK","归还贷款"),
    RT_DK_INT("RT_DK_INT","归还贷款利息"),
    PENDING_ORDER_FREEZE("PENDING_ORDER_FREEZE","挂单冻结资金"),
    PENDING_ORDER_UNFREEZE("PENDING_ORDER_UNFREEZE","取消挂单解冻资金"),
    ;
    private String  code;
src/main/java/com/nq/service/impl/UserAssetsServices.java
@@ -320,6 +320,16 @@
               userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().add(amount));
           }
           extracted(userAssets);
        }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.PENDING_ORDER_FREEZE.getCode())){
            // 挂单冻结资金:从可用余额转到冻结金额
            // amount 是负数,表示扣除
            userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount));
            userAssets.setFreezeMoney(userAssets.getFreezeMoney().add(amount.negate()));
        }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.PENDING_ORDER_UNFREEZE.getCode())){
            // 取消挂单解冻资金:从冻结金额转回可用余额
            // amount 是正数,表示增加
            userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount));
            userAssets.setFreezeMoney(userAssets.getFreezeMoney().subtract(amount));
        }
        /*if(null != userPosition){
@@ -431,8 +441,8 @@
    @Override
    public BigDecimal exchangeAmountByRate(String fromType, String toType, BigDecimal amount) throws Exception {
        EStockType stockType = EStockType.getEStockTypeByCode(fromType);
        EStockType toStockType = EStockType.getEStockTypeByCode(toType);
        EStockType stockType = EStockType.getEStockTypeBySymbol(fromType);
        EStockType toStockType = EStockType.getEStockTypeBySymbol(toType);
        ExchangeRate exchangeRate = exchangeRateRepository.findExchangeRateByCurrencyAndConversionCurrency(
                stockType.getSymbol(), toStockType.getSymbol()).orElse(null);
        if (exchangeRate != null) {
src/main/java/com/nq/service/impl/UserPendingorderServiceImpl.java
@@ -1,6 +1,7 @@
package com.nq.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -62,45 +63,139 @@
    @Autowired
    private ISiteSettingService iSiteSettingService;
    @Autowired
    private IUserAssetsServices iUserAssetsServices;
    @Autowired
    private ISiteProductService iSiteProductService;
    @Autowired
    private IStockConfigServices iStockConfigServices;
    @Autowired
    private StockBuySettingMapper stockBuySettingMapper;
    @Autowired
    private ITradingHourService tradingHourService;
    @Autowired
    private IPriceServices priceServices;
    @Autowired
    private UserPositionMapper userPositionMapper;
    @Override
    @org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
    public ServerResponse addOrder(String stockId, Integer buyNum, Integer buyType, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, BigDecimal targetPrice, HttpServletRequest request) {
        User user = this.iUserService.getCurrentRefreshUser(request);
        if (user == null) {
            return ServerResponse.createByErrorMsg("Please log in first");
        }
        SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
        if (buyNum.intValue() < siteSetting.getBuyMinNum().intValue()) {
            return ServerResponse.createByErrorMsg("The pending order failed, and the purchased quantity was less than" + siteSetting
                    .getBuyMinNum() + "stocks");
        }
        if (buyNum.intValue() > siteSetting.getBuyMaxNum().intValue()) {
            return ServerResponse.createByErrorMsg("The pending order failed because the purchased quantity was greater than" + siteSetting
                    .getBuyMaxNum() + "stocks");
        }
        UserPendingorder userPendingorder = userPendingorderMapper.selectOne(new QueryWrapper<UserPendingorder>().eq("user_id", user.getId()).eq("stock_id", stockId).eq("status", 0));
        if (userPendingorder != null) {
            return ServerResponse.createByErrorMsg("Please do not repeat the order");
            return ServerResponse.createByErrorMsg("请先登录", request);
        }
        userPendingorder = new UserPendingorder();
        userPendingorder.setUserId(user.getId());
        userPendingorder.setStockId(stockId);
        userPendingorder.setBuyNum(buyNum);
        userPendingorder.setBuyType(buyType);
        userPendingorder.setLever(lever);
        userPendingorder.setProfitTarget(profitTarget);
        userPendingorder.setStopTarget(stopTarget);
        userPendingorder.setNowPrice(new BigDecimal(0));
        userPendingorder.setTargetPrice(targetPrice);
        userPendingorder.setAddTime(new Date());
        userPendingorder.setStatus(0);
        int ret = userPendingorderMapper.insert(userPendingorder);
        if (ret > 0) {
            return ServerResponse.createBySuccessMsg("If the pending order is successfully added, the order will be automatically placed if the order conditions are met");
        }
        return ServerResponse.createByErrorMsg("Add failure");
        try {
            synchronized (user.getId()) {
                // 获取系统设置
                SiteProduct siteProduct = iSiteProductService.getProductSetting();
                if (siteProduct.getRealNameDisplay() && user.getIsActive() != 2) {
                    return ServerResponse.createByErrorMsg("挂单失败,请先实名认证", request);
                }
                if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) {
                    return ServerResponse.createByErrorMsg("挂单失败,帐户已被锁定", request);
                }
                // 验证目标价格
                if (targetPrice == null || targetPrice.compareTo(BigDecimal.ZERO) <= 0) {
                    return ServerResponse.createByErrorMsg("挂单失败,目标价格必须大于0", request);
                }
                // 获取股票信息
                Stock stock = stockMapper.findStockByCode(stockId);
                if (stock == null) {
                    return ServerResponse.createByErrorMsg("挂单失败,股票代码不存在", request);
                }
                // 判断股票是否被锁定
                if (stock.getIsLock() != 0) {
                    return ServerResponse.createByErrorMsg("挂单失败,股票被锁定", request);
                }
                // 手续费率
                BigDecimal siteSettingBuyFee = new BigDecimal(iStockConfigServices.queryByKey(com.nq.enums.EConfigKey.BUY_HANDLING_CHARGE.getCode()).getCValue());
                // 处理购买数量(手数转换)
                StockBuySetting stockBuySetting = stockBuySettingMapper.selectOne(new QueryWrapper<com.nq.pojo.StockBuySetting>().eq("accets_type", stock.getStockType()));
                if (stockBuySetting != null && stockBuySetting.getHandsNum() != null && stockBuySetting.getStockNum() != null) {
                    if (buyNum < stockBuySetting.getHandsNum()) {
                        return ServerResponse.createByErrorMsg("最低购买手数" + stockBuySetting.getHandsNum(), request);
                    }
                    buyNum = buyNum * stockBuySetting.getStockNum();
                }
                // 获取用户资产
                UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(stock.getStockType(), user.getId());
                // 验证最高购买数量
                com.nq.pojo.StockConfig maxBuyConfig = iStockConfigServices.queryByKey(com.nq.enums.EConfigKey.MAX_BUY.getCode());
                if (buyNum > Integer.parseInt(maxBuyConfig.getCValue())) {
                    return ServerResponse.createByErrorMsg("最高购买数量" + maxBuyConfig.getCValue(), request);
                }
                // 检查待补资金
                if (userAssets.getAmountToBeCovered().compareTo(BigDecimal.ZERO) > 0) {
                    return ServerResponse.createByErrorMsg("请先缴清待补资金", request);
                }
                // 使用目标价格计算需要冻结的金额
                BigDecimal buyAmt = targetPrice.multiply(new BigDecimal(buyNum)).divide(new BigDecimal(lever), 4, RoundingMode.HALF_UP);
                // 手续费
                BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt);
                BigDecimal needFreezeAmt = buyAmt.add(orderFree);
                // 资金校验(考虑配资比例)
                BigDecimal fundratio = new BigDecimal(user.getFundRatio()).divide(new BigDecimal(100));
                BigDecimal availableBalance = fundratio.multiply(userAssets.getAvailableBalance());
                if (availableBalance.compareTo(needFreezeAmt) < 0) {
                    return ServerResponse.createByErrorMsg("挂单失败,配资不足", request);
                }
                // 检查是否已有相同股票的挂单
                UserPendingorder existingOrder = userPendingorderMapper.selectOne(new QueryWrapper<UserPendingorder>()
                        .eq("user_id", user.getId())
                        .eq("stock_id", stockId)
                        .eq("status", 0));
                if (existingOrder != null) {
                    return ServerResponse.createByErrorMsg("该股票已有挂单,请勿重复挂单", request);
                }
                // 创建挂单记录
                UserPendingorder userPendingorder = new UserPendingorder();
                userPendingorder.setUserId(user.getId());
                userPendingorder.setStockId(stockId);
                userPendingorder.setBuyNum(buyNum);
                userPendingorder.setBuyType(buyType);
                userPendingorder.setLever(lever);
                userPendingorder.setProfitTarget(profitTarget);
                userPendingorder.setStopTarget(stopTarget);
                userPendingorder.setNowPrice(new BigDecimal(0));
                userPendingorder.setTargetPrice(targetPrice);
                userPendingorder.setAddTime(new Date());
                userPendingorder.setStatus(0);
                int ret = userPendingorderMapper.insert(userPendingorder);
                if (ret > 0) {
                    // 冻结资金
                    iUserAssetsServices.availablebalanceChange(stock.getStockType(), user.getId(),
                            com.nq.enums.EUserAssets.PENDING_ORDER_FREEZE, needFreezeAmt.negate(), "挂单冻结资金", "");
                    return ServerResponse.createBySuccessMsg("挂单成功,资金已冻结", request);
                }
                return ServerResponse.createByErrorMsg("挂单失败", request);
            }
        } catch (Exception e) {
            log.error("挂单异常:{}", e.getMessage(), e);
            return ServerResponse.createByErrorMsg("挂单异常:" + e.getMessage(), request);
        }
    }
    @Override
@@ -133,6 +228,7 @@
                    }
                    userPendingorderVO.setNowPrice(new BigDecimal(nowPrice));
                    userPendingorderVO.setStockName(stock.getStockName());
                    userPendingorderVO.setStockGid(stock.getStockType());
                    userPendingorderVO.setStockId(stock.getStockCode());
                    userPendingorderVO.setBuyNum(userPendingorder.getBuyNum());
                    userPendingorderVO.setBuyType(userPendingorder.getBuyType());
@@ -154,157 +250,198 @@
    @Override
    public void orderTask() {
        // 查询所有待处理的挂单
        List<UserPendingorder> userPendingorders = userPendingorderMapper.selectList(new QueryWrapper<UserPendingorder>().eq("status", 0));
        log.info("当前有挂单的用户数量 为 {}", Integer.valueOf(userPendingorders.size()));
        for (int i = 0; i < userPendingorders.size(); i++) {
            Integer userId = (Integer) userPendingorders.get(i).getUserId();
            User user = this.userMapper.selectById(userId);
            if (user == null) {
                continue;
            }
            List<UserPendingorder> userPendingorderList = userPendingorderMapper.selectList(new QueryWrapper<UserPendingorder>().eq("user_id", userId).eq("status", 0));
            if (userPendingorderList == null) {
                continue;
            }
            log.info("用户id = {} 姓名 = {} 已挂单数: {}", new Object[]{userId, user.getRealName(), Integer.valueOf(userPendingorders.size())});
            BigDecimal all_freez_amt = new BigDecimal("0");
            String nowPrice = "";
            String code = "";
            Integer indexId = null;
            StockListVO stockListVO = new StockListVO();
            StockCoin stockCoin = new StockCoin();
            for (UserPendingorder userPendingorder : userPendingorderList) {
                //指数
                if (userPendingorder.getStockId().contains("sh") || userPendingorder.getStockId().contains("sz") || userPendingorder.getStockId().contains("hk") || userPendingorder.getStockId().contains("us")) {
                    StockIndex model = stockIndexMapper.selectIndexByCode(userPendingorder.getStockId().replace("sh", "").replace("sz", "").replace("hk", "").replace("us", ""));
                    all_freez_amt = (new BigDecimal(model.getDepositAmt().intValue())).multiply(new BigDecimal(userPendingorder.getBuyNum())).divide(new BigDecimal(userPendingorder.getLever())).setScale(4, 2);
        log.info("当前有挂单数量:{}", userPendingorders.size());
        for (UserPendingorder userPendingorder : userPendingorders) {
            try {
                // 参数校验
                if (userPendingorder.getUserId() == null || userPendingorder.getStockId() == null
                        || userPendingorder.getBuyNum() == null || userPendingorder.getBuyType() == null
                        || userPendingorder.getLever() == null || userPendingorder.getTargetPrice() == null) {
                    continue;
                }
//                    if (){
//
//                    }else {
                    MarketVO marketVO = this.iStockIndexService.querySingleIndex(model.getIndexGid());
                    nowPrice = marketVO.getNowPrice();
//                    }
                // 获取股票信息
                Stock stock = stockMapper.findStockByCode(userPendingorder.getStockId());
                if (stock == null) {
                    log.error("挂单转持仓失败,股票不存在:{}", userPendingorder.getStockId());
                    userPendingorder.setStatus(2);
                    this.userPendingorderMapper.updateById(userPendingorder);
                    continue;
                }
                    indexId = model.getId();
                // 获取当前价格
                BigDecimal nowPrice = priceServices.getNowPrice(stock.getStockCode());
                if (nowPrice.compareTo(BigDecimal.ZERO) == 0) {
                    continue;
                }
                } else {
                    //股票
                    Stock stock = stockMapper.findStockByCode(userPendingorder.getStockId());
                    if ("hk".equals(stock.getStockType())) {
                        String hk = RedisShardedPoolUtils.get(stock.getStockGid(), 1);
                        stockListVO = StockApi.otherStockListVO(hk);
                        //                        stockCoin = iStockCoinService.selectCoinByCode("HKD");
                        ExchangeVO exchangeVO = this.iStockFuturesService.queryExchangeVO("HKD").getData();
                        nowPrice = String.valueOf(new BigDecimal(stockListVO.getNowPrice()).multiply(new BigDecimal(exchangeVO.getNowPrice())));
                    } else if ("us".equals(stock.getStockType())) {
                        String us = RedisShardedPoolUtils.get(stock.getStockGid(), 2);
                        stockListVO = StockApi.otherStockListVO(us);
                        //                        stockCoin = iStockCoinService.selectCoinByCode("USD");
                        ExchangeVO exchangeVO = this.iStockFuturesService.queryExchangeVO("USD").getData();
                        nowPrice = String.valueOf(new BigDecimal(stockListVO.getNowPrice()).multiply(new BigDecimal(exchangeVO.getNowPrice())));
                    } else {
                        stockListVO = StockApi.getStockRealTime(stock);
                        nowPrice = stockListVO.getNowPrice();
                // 判断价格是否达到目标价格
                int ret = userPendingorder.getBuyType().intValue() == 0
                        ? userPendingorder.getTargetPrice().compareTo(nowPrice)
                        : nowPrice.compareTo(userPendingorder.getTargetPrice());
                if (ret > 0) {
                    // 价格未达到目标价格,继续等待
                    continue;
                }
                // 价格达到目标价格,执行买入逻辑
                synchronized (userPendingorder.getUserId()) {
                    // 判断股票是否在可交易时间段
                    Boolean b = tradingHourService.timeCheck(stock.getStockCode(), stock.getStockType());
                    if (!b) {
                        continue;
                    }
                    all_freez_amt = new BigDecimal(nowPrice).multiply(new BigDecimal(userPendingorder.getBuyNum())).divide(new BigDecimal(userPendingorder.getLever()), 2, 4);
                    code = stock.getStockCode();
                }
                if (nowPrice == null) {
                    nowPrice = String.valueOf(0);
                }
                if (userPendingorder.getUserId() != null && userPendingorder.getStockId() != null && userPendingorder.getBuyNum() != null && userPendingorder.getBuyType() != null && userPendingorder.getLever() != null && userPendingorder.getTargetPrice() != null) {
                    int ret = userPendingorder.getBuyType().intValue() == 0 ? userPendingorder.getTargetPrice().compareTo(new BigDecimal(nowPrice)) : new BigDecimal(nowPrice).compareTo(userPendingorder.getTargetPrice());
                    //当前时间String
                    String buyTime = DateTimeUtil.dateToStr(new Date());
                    if (ret <= 0) {
                        if (code != null && !"".equals(code)) {
                            try {
                                this.iUserPositionService.create(userPendingorder.getUserId(), code, nowPrice, buyTime, userPendingorder.getBuyNum(), userPendingorder.getBuyType(), userPendingorder.getLever(), userPendingorder.getProfitTarget(), userPendingorder.getStopTarget());
                                userPendingorder.setStatus(1);
                                this.userPendingorderMapper.updateById(userPendingorder);
                                SiteTaskLog siteTaskLog = new SiteTaskLog();
                                siteTaskLog.setTaskType("股票挂单转持仓");
                                String accountType = (user.getAccountType() == 0) ? "正式用户" : "模拟用户";
                                String tasktarget = "此次挂单买入id:" + userPendingorder.getId();
                                siteTaskLog.setTaskTarget(tasktarget);
                                siteTaskLog.setAddTime(new Date());
                                siteTaskLog.setIsSuccess(0);
                                siteTaskLog.setErrorMsg("");
                                int insertTaskCount = this.siteTaskLogMapper.insert(siteTaskLog);
                                if (insertTaskCount > 0) {
                                    log.info("挂单task任务成功");
                                } else {
                                    log.info("挂单task任务失败");
                                }
                            } catch (Exception e) {
                                log.error("股票挂单任务失败...");
                                userPendingorder.setStatus(2);
                                this.userPendingorderMapper.updateById(userPendingorder);
                            }
                        } else if (indexId != null) {
                            try {
                                this.iUserIndexPositionService.buyIndexOrder(indexId, userPendingorder.getBuyNum(), userPendingorder.getBuyType(), userPendingorder.getLever(), userPendingorder.getProfitTarget(), userPendingorder.getStopTarget(), userPendingorder.getUserId());
                                userPendingorder.setStatus(1);
                                this.userPendingorderMapper.updateById(userPendingorder);
                                SiteTaskLog siteTaskLog = new SiteTaskLog();
                                siteTaskLog.setTaskType("指数挂单转持仓");
                                String accountType = (user.getAccountType() == 0) ? "正式用户" : "模拟用户";
                                String tasktarget = "此次挂单买入id:" + userPendingorder.getId();
                                siteTaskLog.setTaskTarget(tasktarget);
                                siteTaskLog.setAddTime(new Date());
                                siteTaskLog.setIsSuccess(0);
                                siteTaskLog.setErrorMsg("");
                                int insertTaskCount = this.siteTaskLogMapper.insert(siteTaskLog);
                                if (insertTaskCount > 0) {
                                    log.info("挂单task任务成功");
                                    userPendingorder.setStatus(1);
                                } else {
                                    log.info("挂单task任务失败");
                                }
                            } catch (Exception e) {
                                log.error("指数挂单任务失败...");
                                userPendingorder.setStatus(2);
                                this.userPendingorderMapper.updateById(userPendingorder);
                            }
                        }
                    // 检查股票是否被锁定
                    if (stock.getIsLock() != 0) {
                        log.info("挂单转持仓失败,股票被锁定:{}", stock.getStockCode());
                        userPendingorder.setStatus(2);
                        this.userPendingorderMapper.updateById(userPendingorder);
                        continue;
                    }
                    // 检查是否有配额
                    if (!priceServices.isLimitUpBuy(stock.getStockCode())) {
                        continue;
                    }
                    // 获取用户信息
                    User user = this.userMapper.selectById(userPendingorder.getUserId());
                    if (user == null) {
                        continue;
                    }
                    // 手续费率
                    BigDecimal siteSettingBuyFee = new BigDecimal(iStockConfigServices.queryByKey(com.nq.enums.EConfigKey.BUY_HANDLING_CHARGE.getCode()).getCValue());
                    // 计算买入金额和手续费(使用当前价格)
                    BigDecimal buyAmt = nowPrice.multiply(new BigDecimal(userPendingorder.getBuyNum())).divide(new BigDecimal(userPendingorder.getLever()), 4, RoundingMode.HALF_UP);
                    BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt);
                    // 计算挂单时冻结的金额(用于解冻)
                    BigDecimal targetBuyAmt = userPendingorder.getTargetPrice().multiply(new BigDecimal(userPendingorder.getBuyNum())).divide(new BigDecimal(userPendingorder.getLever()), 4, RoundingMode.HALF_UP);
                    BigDecimal targetOrderFree = siteSettingBuyFee.multiply(targetBuyAmt);
                    BigDecimal freezeAmt = targetBuyAmt.add(targetOrderFree);
                    // 先解冻挂单冻结的资金
                    iUserAssetsServices.availablebalanceChange(stock.getStockType(), userPendingorder.getUserId(),
                            com.nq.enums.EUserAssets.PENDING_ORDER_UNFREEZE, freezeAmt, "挂单触发解冻资金", "");
                    // 创建 UserPosition,与 buy.do 逻辑完全一致
                    UserPosition userPosition = new UserPosition();
                    if (userPendingorder.getProfitTarget() != null && userPendingorder.getProfitTarget().compareTo(BigDecimal.ZERO) > 0) {
                        userPosition.setProfitTargetPrice(userPendingorder.getProfitTarget());
                    }
                    if (userPendingorder.getStopTarget() != null && userPendingorder.getStopTarget().compareTo(BigDecimal.ZERO) > 0) {
                        userPosition.setStopTargetPrice(userPendingorder.getStopTarget());
                    }
                    userPosition.setPositionType(user.getAccountType());
                    userPosition.setPositionSn(com.nq.utils.KeyUtils.getUniqueKey());
                    userPosition.setUserId(userPendingorder.getUserId());
                    userPosition.setNickName(user.getRealName());
                    userPosition.setAgentId(user.getAgentId());
                    userPosition.setStockCode(stock.getStockCode());
                    userPosition.setStockName(stock.getStockName());
                    userPosition.setStockGid(stock.getStockType());
                    userPosition.setStockSpell(stock.getStockSpell());
                    userPosition.setBuyOrderId(com.nq.utils.stock.GeneratePosition.getPositionId());
                    userPosition.setBuyOrderTime(new Date());
                    userPosition.setBuyOrderPrice(nowPrice);
                    userPosition.setOrderDirection((userPendingorder.getBuyType().intValue() == 0) ? "买涨" : "买跌");
                    userPosition.setOrderNum(userPendingorder.getBuyNum());
                    if (stock.getStockPlate() != null) {
                        userPosition.setStockPlate(stock.getStockPlate());
                    }
                    userPosition.setIsLock(Integer.valueOf(0));
                    userPosition.setOrderLever(userPendingorder.getLever());
                    userPosition.setOrderTotalPrice(buyAmt);
                    userPosition.setOrderFee(orderFree);
                    userPosition.setOrderSpread(BigDecimal.ZERO);
                    userPosition.setSpreadRatePrice(BigDecimal.ZERO);
                    BigDecimal profit_and_lose = new BigDecimal("0");
                    userPosition.setProfitAndLose(profit_and_lose);
                    userPosition.setAllProfitAndLose(profit_and_lose.add(orderFree));
                    userPosition.setOrderStayDays(Integer.valueOf(0));
                    userPosition.setOrderStayFee(BigDecimal.ZERO);
                    // 插入持仓记录
                    userPositionMapper.insert(userPosition);
                    // 扣款和手续费,与 buy.do 完全一致
                    iUserAssetsServices.availablebalanceChange(stock.getStockType(), userPendingorder.getUserId(),
                            com.nq.enums.EUserAssets.BUY, buyAmt.negate(), "", "");
                    iUserAssetsServices.availablebalanceChange(stock.getStockType(), userPendingorder.getUserId(),
                            com.nq.enums.EUserAssets.HANDLING_CHARGE, orderFree, "", "");
                    // 更新挂单状态
                    userPendingorder.setStatus(1);
                    this.userPendingorderMapper.updateById(userPendingorder);
                    log.info("挂单转持仓成功,挂单id:{},股票:{}", userPendingorder.getId(), stock.getStockCode());
                }
            } catch (Exception e) {
                log.error("挂单转持仓失败,挂单id:{}", userPendingorder.getId(), e);
                userPendingorder.setStatus(2);
                this.userPendingorderMapper.updateById(userPendingorder);
            }
        }
        log.info("===========挂单结束==========");
        log.info("===========挂单任务结束==========");
    }
    //删除
    @Override
    @org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
    public ServerResponse delOrder(Integer id, HttpServletRequest request) {
        String property = PropertiesUtil.getProperty("user.cookie.name");
        String header = request.getHeader(property);
        if (header != null) {
            String userJson = RedisShardedPoolUtils.get(header);
            User user = (User) JsonUtil.string2Obj(userJson, User.class);
            UserPendingorder userPendingorder = this.userPendingorderMapper.selectById(id);
            if (userPendingorder == null) {
                return ServerResponse.createByErrorMsg("The pending order does not exist");
            }
            if (user.getId().intValue() != userPendingorder.getUserId().intValue()) {
                return ServerResponse.createByErrorMsg("The pending order does not belong to you");
            }
            int delCount = this.userPendingorderMapper.deleteById(id);
            if (delCount > 0) {
                return ServerResponse.createByErrorMsg("Successfully deleted");
            }
            return ServerResponse.createByErrorMsg("Deletion failure");
        User user = this.iUserService.getCurrentRefreshUser(request);
        if (user == null) {
            return ServerResponse.createByErrorMsg("请先登录", request);
        }
        return ServerResponse.createByErrorMsg("Please log in");
        try {
            UserPendingorder userPendingorder = this.userPendingorderMapper.selectById(id);
            if (userPendingorder == null) {
                return ServerResponse.createByErrorMsg("挂单不存在", request);
            }
            if (user.getId().intValue() != userPendingorder.getUserId().intValue()) {
                return ServerResponse.createByErrorMsg("该挂单不属于您", request);
            }
            // 检查挂单状态,只有待处理的挂单才能取消
            if (userPendingorder.getStatus() != 0) {
                return ServerResponse.createByErrorMsg("该挂单已处理,无法取消", request);
            }
            // 获取股票信息以确定资产类型
            Stock stock = stockMapper.findStockByCode(userPendingorder.getStockId());
            if (stock == null) {
                return ServerResponse.createByErrorMsg("股票不存在", request);
            }
            // 计算需要解冻的金额(根据挂单参数重新计算)
            BigDecimal siteSettingBuyFee = new BigDecimal(iStockConfigServices.queryByKey(com.nq.enums.EConfigKey.BUY_HANDLING_CHARGE.getCode()).getCValue());
            BigDecimal buyAmt = userPendingorder.getTargetPrice().multiply(new BigDecimal(userPendingorder.getBuyNum())).divide(new BigDecimal(userPendingorder.getLever()), 4, RoundingMode.HALF_UP);
            BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt);
            BigDecimal needUnfreezeAmt = buyAmt.add(orderFree);
            // 解冻资金
            iUserAssetsServices.availablebalanceChange(stock.getStockType(), user.getId(),
                    com.nq.enums.EUserAssets.PENDING_ORDER_UNFREEZE, needUnfreezeAmt, "取消挂单解冻资金", "");
            // 删除挂单记录
            int delCount = this.userPendingorderMapper.deleteById(id);
            if (delCount > 0) {
                return ServerResponse.createBySuccessMsg("取消挂单成功,资金已解冻", request);
            }
            return ServerResponse.createByErrorMsg("取消挂单失败", request);
        } catch (Exception e) {
            log.error("取消挂单异常:{}", e.getMessage(), e);
            return ServerResponse.createByErrorMsg("取消挂单异常:" + e.getMessage(), request);
        }
    }
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
@@ -171,7 +171,7 @@
                StockBuySetting stockBuySetting = stockBuySettingMapper.selectOne(new QueryWrapper<StockBuySetting>().eq("accets_type", stock.getStockType()));
                if (stockBuySetting != null && stockBuySetting.getHandsNum() != null && stockBuySetting.getStockNum() != null) {
                    if(buyNum < stockBuySetting.getHandsNum()){
                        return ServerResponse.createByErrorMsg("最低购买手数" + stockBuySetting.getHandsNum(), request);
                        return ServerResponse.createByErrorMsg("最低购买数量" + stockBuySetting.getHandsNum(), request);
                    }
                    buyNum = buyNum * stockBuySetting.getStockNum();
                }
src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java
@@ -282,9 +282,9 @@
                if((model.getStatus() == 3 && model.getApplyNumber() == null) || (model.getStatus() == 3 && model.getApplyNumber() == 0) ){
                    return ServerResponse.createByErrorMsg("中签数量不能小于0");
                }
                if(model.getStatus() == 3 && model.getApplyNumber()>userStockSubscribe.getApplyNums()){
                    return  ServerResponse.createByErrorMsg("配置中签数量不能超过申请数量",request);
                }
//                if(model.getStatus() == 3 && model.getApplyNumber()>userStockSubscribe.getApplyNums()){
//                    return  ServerResponse.createByErrorMsg("配置中签数量不能超过申请数量",request);
//                }
                //客户中签直接扣除客户账户可用资金
                UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(stockSubscribe.getStockType(), userStockSubscribe.getUserId());
@@ -298,7 +298,6 @@
                        }
                        model.setBond(bound);
                        model.setDbMoney(model.getDbMoney());
                        if(null == userAssets){
                            return ServerResponse.createByErrorMsg("客户资金账户不存在");
                        }
@@ -373,9 +372,9 @@
                        //iUserPositionService.newStockToPosition(model.getId(),userAssets.getAmountToBeCovered());//转持仓
                        //model.setStatus(5);
                    }else{
                        if(model.getApplyNumber()>model.getApplyNums()){
                            return  ServerResponse.createByErrorMsg("配置中签数量不能超过申请数量",request);
                        }
//                        if(model.getApplyNumber()>model.getApplyNums()){
//                            return  ServerResponse.createByErrorMsg("配置中签数量不能超过申请数量",request);
//                        }
                        BigDecimal cCount = new BigDecimal(model.getApplyNums()-model.getApplyNumber());
                        BigDecimal tMoney = ((stockSubscribe.getMinPrice() != null ? stockSubscribe.getMinPrice() : stockSubscribe.getPrice())).multiply(cCount);
                        iUserAssetsServices.availablebalanceChange(stockSubscribe.getStockType(),userStockSubscribe.getUserId(),
src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
@@ -66,7 +66,7 @@
    @Autowired
    ISiteSettingService iSiteSettingService;
    @Autowired
                      @Autowired
    UserBankMapper userBankMapper;
    @Autowired
@@ -98,10 +98,10 @@
            if (user.getIsActive() != 2) {
                return ServerResponse.createByErrorMsg("未实名认证",request);
            }
            UserBank userBank = this.userBankMapper.selectById(bankId);
            if (userBank == null) {
                return ServerResponse.createByErrorMsg("银行卡不存在",request);
            }
//            UserBank userBank = this.userBankMapper.selectById(bankId);
//            if (userBank == null) {
//                return ServerResponse.createByErrorMsg("银行卡不存在",request);
//            }
            if (user.getAccountType().intValue() == 1) {
                return ServerResponse.createByErrorMsg("模拟用户无法提取资金",request);
            }
@@ -128,9 +128,10 @@
            userWithdraw.setWithAmt(new BigDecimal(amt));
            userWithdraw.setApplyTime(new Date());
            userWithdraw.setWithName(user.getRealName());
            userWithdraw.setBankNo(userBank.getBankNo());
            userWithdraw.setBankName(userBank.getBankName());
            userWithdraw.setBankAddress(userBank.getBankAddress());
            userWithdraw.setBankNo(bankId);
//            userWithdraw.setBankNo(userBank.getBankNo());
//            userWithdraw.setBankName(userBank.getBankName());
//            userWithdraw.setBankAddress(userBank.getBankAddress());
            userWithdraw.setWithStatus(Integer.valueOf(0));
            BigDecimal withfee = siteSetting.getWithFeePercent().multiply(new BigDecimal(amt)).add(new BigDecimal(siteSetting.getWithFeeSingle().intValue()));
            userWithdraw.setWithFee(withfee);
src/main/java/com/nq/utils/task/stock/CarryPositionTask.java
@@ -9,6 +9,7 @@
import com.nq.dao.UserAssetsMapper;
import com.nq.dao.UserMapper;
import com.nq.dao.UserStockSubscribeMapper;
import com.nq.enums.EStockType;
import com.nq.pojo.StockSubscribe;
import com.nq.pojo.User;
import com.nq.pojo.UserAssets;
@@ -75,7 +76,7 @@
                if (CollectionUtils.isNotEmpty(stockSubscribes)) {
                    List<String> codeList = stockSubscribes.stream().map(StockSubscribe::getCode).collect(Collectors.toList());
                    List<UserStockSubscribe> userStockSubscribes = userStockSubscribeMapper.selectList(new LambdaQueryWrapper<UserStockSubscribe>()
                            .eq(UserStockSubscribe::getStatus, 3).in(UserStockSubscribe::getNewCode, codeList));
                            .eq(UserStockSubscribe::getStatus, 5).in(UserStockSubscribe::getNewCode, codeList));
                    //订单转持仓
                    userStockSubscribes.forEach(f -> {
                        ServerResponse serverResponse = iUserPositionService.newStockToPosition(f.getId(),BigDecimal.ZERO);//转持仓
@@ -101,8 +102,8 @@
    private final AtomicBoolean subscription = new AtomicBoolean(false);
//    @Scheduled(cron = "0 0/1 * * * ?")
    /*public void subscription() {
    @Scheduled(cron = "0 0/1 * * * ?")
    public void subscription() {
        if (subscription.get()) { // 判断任务是否在处理中
            return;
        }
@@ -129,6 +130,6 @@
        } else {
            log.info("自动转已认缴定时任务--------->上次任务还未执行完成,本次任务忽略");
        }
    }*/
    }
}
src/main/java/com/nq/utils/task/stock/PendingOrderTask.java
New file
@@ -0,0 +1,53 @@
package com.nq.utils.task.stock;
import com.nq.service.UserPendingorderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 挂单定时任务
 * 定时检查挂单条件,当价格达到目标价格时自动买入
 */
@Component
@Slf4j
public class PendingOrderTask {
    @Autowired
    private UserPendingorderService userPendingorderService;
    private final Lock pendingOrderLock = new ReentrantLock();
    private final AtomicBoolean isPendingOrderRunning = new AtomicBoolean(false);
    /**
     * 挂单任务,每分钟执行一次
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void pendingOrderTask() {
        if (isPendingOrderRunning.get()) { // 判断任务是否在处理中
            log.info("挂单定时任务--------->上次任务还未执行完成,本次任务忽略");
            return;
        }
        if (pendingOrderLock.tryLock()) {
            try {
                isPendingOrderRunning.set(true); // 设置处理中标识为true
                log.info("挂单定时任务--------->开始");
                userPendingorderService.orderTask();
                log.info("挂单定时任务--------->结束");
            } catch (Exception e) {
                log.error("挂单定时任务发生异常", e);
            } finally {
                pendingOrderLock.unlock();
                isPendingOrderRunning.set(false); // 设置处理中标识为false
            }
        } else {
            log.info("挂单定时任务--------->上次任务还未执行完成,本次任务忽略");
        }
    }
}
src/main/java/com/nq/vo/position/UserPendingorderVO.java
@@ -19,6 +19,7 @@
    private Integer id;
    private String stockId;
    private String stockName;
    private String stockGid;
    private Integer buyNum;
@@ -38,5 +39,7 @@
    private Integer status;
    private static final long serialVersionUID = 1L;
}
src/main/java/com/nq/ws/WebSocketClientBeanConfig.java
@@ -20,24 +20,24 @@
    public Map<String, WebSocketClient> websocketRunClientMap() {
        Map<String, WebSocketClient> retMap = new HashMap<>(2);
        try {
            JPWebsocketRunClient jpWebsocketRunClient = new JPWebsocketRunClient(new URI(PropertiesUtil.getProperty("JP_WS_URL")), EStockType.JP);
            jpWebsocketRunClient.connect();
            jpWebsocketRunClient.setConnectionLostTimeout(0);
            startHeartbeatThread(jpWebsocketRunClient);
            retMap.put(EStockType.JP.getStockKey(), jpWebsocketRunClient);
        } catch (Exception e) {
            log.error("jpWebsocketRunClient 异常: {}", e.getMessage());
        }
        try {
            USWebsocketRunClient usWebsocketRunClient = new USWebsocketRunClient(new URI(PropertiesUtil.getProperty("US_WS_URL")), EStockType.US);
            usWebsocketRunClient.connect();
            usWebsocketRunClient.setConnectionLostTimeout(0);
            startHeartbeatThread(usWebsocketRunClient);
            retMap.put(EStockType.US.getStockKey(), usWebsocketRunClient);
        } catch (Exception e) {
            log.error("usWebsocketRunClient 异常: {}", e.getMessage());
        }
//        try {
//            JPWebsocketRunClient jpWebsocketRunClient = new JPWebsocketRunClient(new URI(PropertiesUtil.getProperty("JP_WS_URL")), EStockType.JP);
//            jpWebsocketRunClient.connect();
//            jpWebsocketRunClient.setConnectionLostTimeout(0);
//            startHeartbeatThread(jpWebsocketRunClient);
//            retMap.put(EStockType.JP.getStockKey(), jpWebsocketRunClient);
//        } catch (Exception e) {
//            log.error("jpWebsocketRunClient 异常: {}", e.getMessage());
//        }
//        try {
//            USWebsocketRunClient usWebsocketRunClient = new USWebsocketRunClient(new URI(PropertiesUtil.getProperty("US_WS_URL")), EStockType.US);
//            usWebsocketRunClient.connect();
//            usWebsocketRunClient.setConnectionLostTimeout(0);
//            startHeartbeatThread(usWebsocketRunClient);
//            retMap.put(EStockType.US.getStockKey(), usWebsocketRunClient);
//        } catch (Exception e) {
//            log.error("usWebsocketRunClient 异常: {}", e.getMessage());
//        }
        return retMap;
    }
src/main/resources/application.properties
@@ -46,7 +46,7 @@
JP_HTTP_API = http://api-jp.js-stock.top/
JP_WS_URL = ws://api-jp-ws.js-stock.top
JP_KEY = rST7RRMPgvW4ogcAppCx
JP_KEY = 43zGhZNUYT5lwsmEenUO
#默认首页显示指数code
#us_home_indices_code=15882,15881,16571
#hk_home_indices_code=535606773,535606776,535606785
src/main/resources/application.yml
@@ -126,7 +126,7 @@
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    #url: jdbc:mysql://127.0.0.1:6306/stock_ci?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    url: jdbc:mysql://127.0.0.1:3306/stock-dg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    url: jdbc:mysql://118.107.6.162:3306/stock-dg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: stock-dg
    password: 6eAcBiXTatc8L6hm
    druid:
src/main/resources/mapper/StockMapper.xml
@@ -319,7 +319,7 @@
        <include refid="Base_Column_List"/>
         FROM stock
    where stock_spell not like '%.st%' and stock_gid !='indices'
    where 1=1
    <if test="stockType != null and stockType != '' ">
      and stock_type = #{stockType}
    </if>