1
dd
9 days ago 64bc81d5f7bd99b470422b329aaca2182b79531c
1
16 files modified
830 ■■■■■ changed files
src/main/java/com/nq/controller/PayApiController.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/protol/UserController.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/protol/UserPayController.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IPayService.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IUserPositionService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IUserService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/PayServiceImpl.java 136 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/StockOptionServiceImpl.java 41 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/StockServiceImpl.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java 359 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserServiceImpl.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/stock/sina/SinaStockApi.java 102 ●●●● patch | view | raw | blame | history
src/main/resources/application.properties 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/PayApiController.java
@@ -29,6 +29,8 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@@ -162,4 +164,29 @@
            log.error("fly notify error Msg = {}", serverResponse.getMsg());
        }
    }
    @RequestMapping(value = {"ococnReturn.do"}, method = {RequestMethod.GET, RequestMethod.POST})
    public void ococnReturn(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String redirectUrl = this.iPayService.ococnReturn(request);
        response.setContentType("text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        String safeUrl = redirectUrl.replace("\\", "\\\\").replace("'", "\\'");
        response.getWriter().write(
                "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>跳转中</title>"
                        + "<script>window.location.replace('" + safeUrl + "');</script>"
                        + "</head><body></body></html>"
        );
    }
    @RequestMapping(value = {"ococnNotify.do"}, method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void ococnNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServerResponse serverResponse = this.iPayService.ococnNotify(request);
        if (serverResponse.isSuccess()) {
            response.getWriter().write("success");
            log.info("ococn 支付渠道异步通知处理成功");
        } else {
            log.error("ococn notify error Msg = {}", serverResponse.getMsg());
        }
    }
}
src/main/java/com/nq/controller/protol/UserController.java
@@ -125,6 +125,20 @@
        }
        return serverResponse;
    }
    //撤销委托单
    @RequestMapping({"cancelOrder.do"})
    @ResponseBody
    public ServerResponse cancelOrder(HttpServletRequest request, @RequestParam("positionSn") String positionSn) {
        ServerResponse serverResponse = null;
        try {
            serverResponse = this.iUserPositionService.cancelOrder(positionSn, request);
        } catch (Exception e) {
            log.error("用户撤单操作 = {}", e);
            serverResponse = ServerResponse.createByErrorMsg("撤单失败");
        }
        return serverResponse;
    }
    //用户平仓操作
    @RequestMapping({"sell.do"})
    @ResponseBody
src/main/java/com/nq/controller/protol/UserPayController.java
@@ -49,4 +49,11 @@
        return this.iPayService.flyPay(payType, payAmt, currency, request);
    }
    @RequestMapping({"ococnPay.do"})
    @ResponseBody
    public ServerResponse ococnPay(@RequestParam("payType") String payType, @RequestParam("payAmt") String payAmt, HttpServletRequest request) {
        log.info("发起 ococn 线上支付 payType = {} payAmt = {}", payType, payAmt);
        return this.iPayService.ococnPay(payType, payAmt, request);
    }
}
src/main/java/com/nq/service/IPayService.java
@@ -20,4 +20,10 @@
  ServerResponse flyPay(String paramString1, String paramString2, String paramString3, HttpServletRequest paramHttpServletRequest);
  
  ServerResponse flyNotify(HttpServletRequest paramHttpServletRequest);
  ServerResponse ococnPay(String payType, String payAmt, HttpServletRequest request);
  ServerResponse ococnNotify(HttpServletRequest request);
  String ococnReturn(HttpServletRequest request);
}
src/main/java/com/nq/service/IUserPositionService.java
@@ -16,6 +16,8 @@
  ServerResponse pending(Integer paramInteger1, Integer paramInteger2, Integer paramInteger3, Integer paramInteger4,BigDecimal paramInteger5,BigDecimal paramInteger6, HttpServletRequest paramHttpServletRequest) throws Exception;
  ServerResponse cancelOrder(String positionSn, HttpServletRequest request) throws Exception;
  ServerResponse sell(String paramString, int paramInt) throws Exception;
  ServerResponse calendar(String paramString,HttpServletRequest request) throws Exception;
src/main/java/com/nq/service/IUserService.java
@@ -92,6 +92,7 @@
  void updateUserAmt(Double amt, Integer user_id);
  /** 将数据库中最新的用户资金写回登录缓存 */
  void syncUserCache(HttpServletRequest request);
}
src/main/java/com/nq/service/impl/PayServiceImpl.java
@@ -12,8 +12,10 @@
import com.nq.utils.PropertiesUtil;
import com.nq.utils.pay.CmcPayOuterRequestUtil;
import com.nq.utils.pay.CmcPayTool;
import com.nq.utils.pay.OcocnPayUtil;
import com.nq.vo.pay.FlyPayVO;
import com.nq.vo.pay.GuoPayVO;
import com.nq.vo.pay.OcocnPayVO;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
@@ -22,6 +24,7 @@
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
@@ -375,6 +378,139 @@
    }
    @Override
    public ServerResponse ococnPay(String payType, String payAmt, HttpServletRequest request) {
        if (StringUtils.isBlank(payType) || StringUtils.isBlank(payAmt)) {
            return ServerResponse.createByErrorMsg("参数不能为空");
        }
        BigDecimal payAmtBig = new BigDecimal(payAmt);
        if (payAmtBig.compareTo(BigDecimal.ZERO) <= 0) {
            return ServerResponse.createByErrorMsg("支付金额必须大于0");
        }
        User user = this.iUserService.getCurrentRefreshUser(request);
        if (user == null) {
            return ServerResponse.createByErrorMsg("请先登录");
        }
        String ordersn = KeyUtils.getRechargeOrderSn();
        String money = payAmtBig.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString();
        String pid = PropertiesUtil.getProperty("ococn.pay.pid");
        String key = PropertiesUtil.getProperty("ococn.pay.key");
        String submitUrl = PropertiesUtil.getProperty("ococn.pay.url");
        String apiDomain = trimTrailingSlash(PropertiesUtil.getProperty("website.domain.url"));
        if (StringUtils.isAnyBlank(pid, key, submitUrl, apiDomain)) {
            return ServerResponse.createByErrorMsg("支付配置不完整,请联系管理员");
        }
        String notifyUrl = apiDomain + "/api/pay/ococnNotify.do";
        String returnUrl = apiDomain + "/api/pay/ococnReturn.do";
        String sitename = StringUtils.defaultString(PropertiesUtil.getProperty("ococn.pay.sitename", ""), "");
        String productName = PropertiesUtil.getProperty("ococn.pay.name", "账户充值");
        UserRecharge userRecharge = new UserRecharge();
        userRecharge.setUserId(user.getId());
        userRecharge.setNickName(user.getRealName());
        userRecharge.setAgentId(user.getAgentId());
        userRecharge.setOrderSn(ordersn);
        userRecharge.setPayChannel(getOcocnChannelName(payType));
        userRecharge.setPayAmt(payAmtBig);
        userRecharge.setOrderStatus(Integer.valueOf(0));
        userRecharge.setAddTime(new Date());
        int insertCount = this.userRechargeMapper.insert(userRecharge);
        if (insertCount <= 0) {
            return ServerResponse.createByErrorMsg("创建支付订单失败");
        }
        String sign = OcocnPayUtil.buildSubmitSign(money, productName, notifyUrl, ordersn, pid, returnUrl, sitename, payType, key);
        log.info("ococn支付 notifyUrl={} returnUrl={}", notifyUrl, returnUrl);
        StringBuilder payUrlBuilder = new StringBuilder(submitUrl).append("?");
        payUrlBuilder.append("pid=").append(pid)
                .append("&type=").append(payType)
                .append("&out_trade_no=").append(ordersn)
                .append("&notify_url=").append(OcocnPayUtil.encode(notifyUrl))
                .append("&return_url=").append(OcocnPayUtil.encode(returnUrl))
                .append("&name=").append(OcocnPayUtil.encode(productName))
                .append("&money=").append(money);
        if (StringUtils.isNotBlank(sitename)) {
            payUrlBuilder.append("&sitename=").append(OcocnPayUtil.encode(sitename));
        }
        payUrlBuilder.append("&sign=").append(sign).append("&sign_type=MD5");
        String payUrl = payUrlBuilder.toString();
        OcocnPayVO ococnPayVO = new OcocnPayVO();
        ococnPayVO.setPayUrl(payUrl);
        log.info("ococn支付,创建订单成功 orderSn={}", ordersn);
        return ServerResponse.createBySuccess(ococnPayVO);
    }
    @Override
    public ServerResponse ococnNotify(HttpServletRequest request) {
        Map<String, String> params = OcocnPayUtil.parseRequestParams(request);
        log.info("ococn支付通知参数: {}", params);
        String outTradeNo = params.get("out_trade_no");
        String tradeNo = params.get("trade_no");
        String money = params.get("money");
        String tradeStatus = params.get("trade_status");
        String sign = params.get("sign");
        if (StringUtils.isAnyBlank(outTradeNo, tradeNo, money, tradeStatus, sign)) {
            return ServerResponse.createByErrorMsg("回调参数不完整");
        }
        String key = PropertiesUtil.getProperty("ococn.pay.key");
        if (!OcocnPayUtil.verifyNotifySign(params, key)) {
            log.error("ococn支付通知签名校验失败, remoteSign={}, params={}", sign, params);
            return ServerResponse.createByErrorMsg("签名校验失败");
        }
        if (!"TRADE_SUCCESS".equals(tradeStatus)) {
            return ServerResponse.createByErrorMsg("支付未成功");
        }
        UserRecharge existing = this.userRechargeMapper.findUserRechargeByOrderSn(outTradeNo);
        if (existing != null && existing.getOrderStatus().intValue() != 0) {
            return ServerResponse.createBySuccessMsg("订单已处理");
        }
        return doSuccess(outTradeNo, money);
    }
    @Override
    public String ococnReturn(HttpServletRequest request) {
        Map<String, String> params = OcocnPayUtil.parseRequestParams(request);
        log.info("ococn支付同步跳转: {}", params);
        String frontendUrl = StringUtils.defaultIfBlank(
                PropertiesUtil.getProperty("ococn.pay.frontend_redirect"),
                trimTrailingSlash(PropertiesUtil.getProperty("frontend.domain.url", "")) + "/#/user"
        );
        return frontendUrl;
    }
    private String getOcocnChannelName(String payType) {
        if ("alipay".equals(payType)) {
            return "支付宝-线上";
        }
        if ("wxpay".equals(payType)) {
            return "微信-线上";
        }
        if ("qqpay".equals(payType)) {
            return "QQ钱包-线上";
        }
        if ("tenpay".equals(payType)) {
            return "财付通-线上";
        }
        return "线上支付";
    }
    private String trimTrailingSlash(String url) {
        if (url == null) {
            return null;
        }
        return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
    }
    @Override
    public ServerResponse juhenewpayNotify(HttpServletRequest request) throws UnsupportedEncodingException {
        LinkedMap map = new LinkedMap();
        String out_trade_no = request.getParameter("out_trade_no");
src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java
@@ -9,6 +9,7 @@
import com.nq.pojo.SiteSetting;
import com.nq.service.ISiteSettingService;
import com.nq.utils.TradeFeeUtil;
import java.util.List;
@@ -34,6 +35,7 @@
        if (list.size() > 0) {
            siteSetting = (SiteSetting) list.get(0);
            siteSetting.setBuyFee(TradeFeeUtil.BUY_FEE_RATE);
        }
        return siteSetting;
@@ -48,6 +50,7 @@
        if (siteSetting == null) {
            return ServerResponse.createByErrorMsg("查不到设置记录");
        }
        setting.setBuyFee(TradeFeeUtil.BUY_FEE_RATE);
        int updateCount = this.siteSettingMapper.updateByPrimaryKeySelective(setting);
src/main/java/com/nq/service/impl/StockOptionServiceImpl.java
@@ -71,9 +71,14 @@
     List<StockOptionListVO> stockOptionListVOS = Lists.newArrayList();
     for (StockOption option : stockOptions) {
       StockOptionListVO stockOptionListVO = assembleStockOptionListVO(option);
       stockOptionListVO.setIsOption("1");
       stockOptionListVOS.add(stockOptionListVO);
       try {
         StockOptionListVO stockOptionListVO = assembleStockOptionListVO(option);
         stockOptionListVO.setIsOption("1");
         stockOptionListVOS.add(stockOptionListVO);
       } catch (Exception e) {
         log.error("自选列表单条行情组装失败, gid={}, code={}", option.getStockGid(), option.getStockCode(), e);
         stockOptionListVOS.add(buildFallbackOptionVO(option));
       }
     }
     PageInfo pageInfo = new PageInfo(stockOptions);
@@ -133,24 +138,42 @@
                 stockVO = SinaStockApi.assembleStockVO(SinaStockApi.getSinaStock(option.getStockGid()));
             }
         }
         stockOptionListVO.setNowPrice(stockVO.getNowPrice());
         if (stockVO == null) {
             stockVO = new StockVO();
         }
         stockOptionListVO.setNowPrice(stockVO.getNowPrice() == null ? "0" : stockVO.getNowPrice());
         stockOptionListVO.setHcrate(stockVO.getHcrate().toString());
         stockOptionListVO.setHcrate(stockVO.getHcrate() == null ? "0" : stockVO.getHcrate().toString());
         stockOptionListVO.setPreclose_px(stockVO.getPreclose_px());
         stockOptionListVO.setPreclose_px(stockVO.getPreclose_px() == null ? "0" : stockVO.getPreclose_px());
         stockOptionListVO.setOpen_px(stockVO.getOpen_px());
         stockOptionListVO.setOpen_px(stockVO.getOpen_px() == null ? "0" : stockVO.getOpen_px());
         stockOptionListVO.setType(stockVO.getType());
         Stock stock = this.stockMapper.selectByPrimaryKey(option.getStockId());
       stockOptionListVO.setStock_plate(stock.getStockPlate()==null?"":stock.getStockPlate());
       if (stock != null) {
         stockOptionListVO.setStock_plate(stock.getStockPlate()==null?"":stock.getStockPlate());
         stockOptionListVO.setStock_type(stock.getStockType());
       }
         return stockOptionListVO;
     }
   private StockOptionListVO buildFallbackOptionVO(StockOption option) {
     StockOptionListVO vo = new StockOptionListVO();
     vo.setId(option.getId().intValue());
     vo.setStockName(option.getStockName());
     vo.setStockCode(option.getStockCode());
     vo.setStockGid(option.getStockGid());
     vo.setNowPrice("0");
     vo.setHcrate("0");
     vo.setPreclose_px("0");
     vo.setOpen_px("0");
     vo.setIsOption("1");
     return vo;
   }
 }
src/main/java/com/nq/service/impl/StockServiceImpl.java
@@ -116,34 +116,49 @@
    PageHelper.startPage(pageNum, pageSize);
    User user = iUserService.getCurrentUser(request);
    List<Stock> stockList = this.stockMapper.findStockListByKeyWords(keyWords, stockPlate, stockType, Integer.valueOf(0));
    boolean quickSearch = org.apache.commons.lang3.StringUtils.isNotBlank(keyWords);
    List<StockListVO> stockListVOS = Lists.newArrayList();
    if (stockList.size() > 0)
      for (Stock stock : stockList) {
        StockListVO stockListVO = new StockListVO();
        stockListVO=SinaStockApi.assembleLideStockListVO(LiDeDataUtils.getStock(stock.getStockCode()));
        if(ObjectUtils.isEmpty(stockListVO)){
          stockListVO = SinaStockApi.assembleStockListVO(SinaStockApi.getSinaStock(stock.getStockGid()));
        }
        stockListVO.setCode(stock.getStockCode());
        stockListVO.setSpell(stock.getStockSpell());
        stockListVO.setGid(stock.getStockGid());
        BigDecimal day3Rate = (BigDecimal)selectRateByDaysAndStockCode(stock.getStockCode(), 3).getData();
        stockListVO.setDay3Rate(day3Rate);
        stockListVO.setStock_plate(stock.getStockPlate());
        stockListVO.setStock_type(stock.getStockType());
        //是否添加自选
        if(user == null){
          stockListVO.setIsOption("0");
        } else {
          stockListVO.setIsOption(iStockOptionService.isMyOption(user.getId(), stock.getStockCode()));
        }
        stockListVOS.add(stockListVO);
      }
    if (stockList.size() > 0) {
      Integer userId = user != null ? user.getId() : null;
      stockListVOS = stockList.parallelStream().map(stock -> assembleStockListItem(stock, userId, quickSearch))
              .collect(java.util.stream.Collectors.toList());
    }
    PageInfo pageInfo = new PageInfo(stockList);
    pageInfo.setList(stockListVOS);
    return ServerResponse.createBySuccess(pageInfo);
  }
  /** 搜索列表组装行情:关键词搜索走轻量路径,跳过三日涨幅等慢查询 */
  private StockListVO assembleStockListItem(Stock stock, Integer userId, boolean quickSearch) {
    StockListVO stockListVO = SinaStockApi.assembleLideStockListVO(LiDeDataUtils.getStock(stock.getStockCode()));
    if (ObjectUtils.isEmpty(stockListVO)) {
      stockListVO = SinaStockApi.assembleStockListVO(SinaStockApi.getSinaStock(stock.getStockGid()));
    }
    if (stockListVO == null) {
      stockListVO = new StockListVO();
      stockListVO.setName(stock.getStockName());
      stockListVO.setNowPrice("0");
      stockListVO.setHcrate(java.math.BigDecimal.ZERO);
    }
    stockListVO.setCode(stock.getStockCode());
    stockListVO.setSpell(stock.getStockSpell());
    stockListVO.setGid(stock.getStockGid());
    if (!quickSearch) {
      BigDecimal day3Rate = (BigDecimal) selectRateByDaysAndStockCode(stock.getStockCode(), 3).getData();
      stockListVO.setDay3Rate(day3Rate);
    }
    stockListVO.setStock_plate(stock.getStockPlate());
    stockListVO.setStock_type(stock.getStockType());
    if (userId == null) {
      stockListVO.setIsOption("0");
    } else if (quickSearch) {
      stockListVO.setIsOption("0");
    } else {
      stockListVO.setIsOption(iStockOptionService.isMyOption(userId, stock.getStockCode()));
    }
    return stockListVO;
  }
  public void z1() {
    this.stockPoll.z1();
  }
src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java
@@ -341,7 +341,7 @@
        userPosition.setIsLock(Integer.valueOf(0));
        userPosition.setOrderLever(lever);
        userPosition.setOrderTotalPrice(buy_amt);
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        BigDecimal buy_fee_amt = com.nq.utils.TradeFeeUtil.calcBuyFee(buy_amt);
        log.info("用户购买手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
        userPosition.setOrderFee(buy_fee_amt);
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
@@ -11,6 +11,8 @@
import com.google.common.collect.Lists;
import com.nq.common.ServerResponse;
import com.nq.utils.*;
import com.nq.utils.TradeFeeUtil;
import com.nq.utils.redis.JsonUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
import com.nq.utils.stock.BuyAndSellUtils;
import com.nq.utils.stock.GeneratePosition;
@@ -111,6 +113,7 @@
    StockDzMapper stockDzMapper;
    @Override
    @Transactional
    public ServerResponse buy(Integer stockId, Integer buyNum, Integer buyType, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, HttpServletRequest request) throws Exception {
@@ -385,11 +388,12 @@
        }
        int compareUserAmtInt = user_enable_amt.compareTo(buy_amt_autual);
        log.info("用户可用金额 = {}  实际购买金额 =  {}", user_enable_amt, buy_amt_autual);
        log.info("比较 用户金额 和 实际 购买金额 =  {}", Integer.valueOf(compareUserAmtInt));
        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt);
        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);
        if (compareUserAmtInt == -1) {
            return ServerResponse.createByErrorMsg("下单失败,融资可用金额小于" + buy_amt_autual + "元");
            return ServerResponse.createByErrorMsg("下单失败,融资可用金额小于" + buy_debit + "元(含保证金及手续费)");
        }
        if (user.getUserIndexAmt().compareTo(new BigDecimal("0")) == -1) {
@@ -445,7 +449,6 @@
        userPosition.setOrderStayDays(1);
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        log.info("用户购买手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
        userPosition.setOrderFee(buy_fee_amt);
@@ -477,33 +480,11 @@
        userPosition.setOrderStayDays(Integer.valueOf(0));
        userPosition.setOrderStayFee(new BigDecimal("0"));
        int insertPositionCount = 0;
        this.userPositionMapper.insert(userPosition);
        insertPositionCount = userPosition.getId();
        if (insertPositionCount > 0) {
            //修改用户可用余额= 当前余额-下单金额-买入手续费-印花税-点差费
            //BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual).subtract(buy_fee_amt).subtract(buy_yhs_amt).subtract(spread_rate_amt);
            //修改用户可用余额= 当前余额-下单总金额
            BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual);
            //修改用户可取余额=当前可取余额-下单总金额
            int compareUserWithdrawAmtInt = user_enable_withdraw_amt.compareTo(buy_amt_autual);
            if (compareUserWithdrawAmtInt == -1) {
                //若可取余额小于下单总额,但是可用余额充足,令可取余额为0
                user.setEnaleWithdrawAmt(BigDecimal.ZERO);
            } else {
                user_enable_withdraw_amt = user_enable_withdraw_amt.subtract(buy_amt_autual);
                user.setEnaleWithdrawAmt(user_enable_withdraw_amt);
            }
            user.setEnableAmt(reckon_enable);
            int updateUserCount = this.userMapper.updateByPrimaryKeySelective(user);
            if (updateUserCount > 0) {
                log.info("【用户交易下单】修改用户金额成功");
            } else {
                log.error("用户交易下单】修改用户金额出错");
                throw new Exception("用户交易下单】修改用户金额出错");
            }
            //核算代理收入-入仓手续费
        if (userPosition.getId() != null && userPosition.getId() > 0) {
            deductUserEnableOnBuy(user, buy_debit, buy_amt_autual, buy_fee_amt, userPosition);
            iAgentAgencyFeeService.AgencyFeeIncome(1, userPosition.getPositionSn());
            syncUserCacheAfterTrade(request);
            log.info("【用户交易下单】保存持仓记录成功");
        } else {
            log.error("用户交易下单】保存持仓记录出错");
@@ -514,12 +495,111 @@
    }
    public ServerResponse fee(Integer buyNum,BigDecimal nowPrice){
    @Override
    public ServerResponse fee(Integer buyNum, BigDecimal nowPrice) {
        BigDecimal buy_amt = nowPrice.multiply(new BigDecimal(buyNum.intValue()));
        SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt).setScale(2, 4);
        return ServerResponse.createBySuccess(buy_fee_amt);
    }
    /** 下单从两融可用资金扣除:保证金 + 买入手续费 */
    private void deductUserEnableOnBuy(User user, BigDecimal buyDebit, BigDecimal margin, BigDecimal fee,
                                       UserPosition position) throws Exception {
        User fresh = this.userMapper.selectByPrimaryKey(user.getId());
        if (fresh == null) {
            throw new Exception("用户不存在");
        }
        BigDecimal enableAmt = fresh.getEnableAmt() == null ? BigDecimal.ZERO : fresh.getEnableAmt();
        if (enableAmt.compareTo(buyDebit) < 0) {
            throw new Exception("扣除可用资金失败(保证金+手续费=" + buyDebit + "元),余额不足");
        }
        fresh.setEnableAmt(enableAmt.subtract(buyDebit));
        BigDecimal withdrawAmt = fresh.getEnaleWithdrawAmt() == null ? BigDecimal.ZERO : fresh.getEnaleWithdrawAmt();
        if (withdrawAmt.compareTo(buyDebit) < 0) {
            fresh.setEnaleWithdrawAmt(BigDecimal.ZERO);
        } else {
            fresh.setEnaleWithdrawAmt(withdrawAmt.subtract(buyDebit));
        }
        BigDecimal userAmt = fresh.getUserAmt() == null ? BigDecimal.ZERO : fresh.getUserAmt();
        fresh.setUserAmt(userAmt.subtract(buyDebit));
        int rows = this.userMapper.updateByPrimaryKeySelective(fresh);
        if (rows <= 0) {
            throw new Exception("扣除可用资金失败(保证金+手续费=" + buyDebit + "元)");
        }
        saveBuyDebitCashDetail(user, position, margin, fee, buyDebit);
        log.info("【用户交易下单】扣款成功,用户={},保证金={},手续费={},合计={}", user.getId(), margin, fee, buyDebit);
    }
    private void refundUserEnableOnCancel(User user, BigDecimal refundAmt, UserPosition position) throws Exception {
        User fresh = this.userMapper.selectByPrimaryKey(user.getId());
        if (fresh == null) {
            throw new Exception("用户不存在");
        }
        BigDecimal enableAmt = fresh.getEnableAmt() == null ? BigDecimal.ZERO : fresh.getEnableAmt();
        BigDecimal withdrawAmt = fresh.getEnaleWithdrawAmt() == null ? BigDecimal.ZERO : fresh.getEnaleWithdrawAmt();
        BigDecimal userAmt = fresh.getUserAmt() == null ? BigDecimal.ZERO : fresh.getUserAmt();
        fresh.setEnableAmt(enableAmt.add(refundAmt));
        fresh.setEnaleWithdrawAmt(withdrawAmt.add(refundAmt));
        fresh.setUserAmt(userAmt.add(refundAmt));
        int rows = this.userMapper.updateByPrimaryKeySelective(fresh);
        if (rows <= 0) {
            throw new Exception("撤单退款失败");
        }
        UserCashDetail ucd = new UserCashDetail();
        ucd.setPositionId(position.getId());
        ucd.setAgentId(user.getAgentId());
        ucd.setAgentName(user.getAgentName());
        ucd.setUserId(user.getId());
        ucd.setUserName(user.getRealName());
        ucd.setDeType("撤单退款");
        ucd.setDeAmt(refundAmt);
        ucd.setDeSummary("撤销委托," + position.getStockCode() + "/" + position.getStockName()
                + ",退还保证金+手续费:" + refundAmt);
        ucd.setAddTime(new Date());
        ucd.setIsRead(Integer.valueOf(0));
        this.userCashDetailMapper.insert(ucd);
        log.info("【用户撤单】退款成功,用户={},金额={}", user.getId(), refundAmt);
    }
    private void saveBuyDebitCashDetail(User user, UserPosition position, BigDecimal margin, BigDecimal fee,
                                        BigDecimal buyDebit) {
        UserCashDetail ucd = new UserCashDetail();
        ucd.setPositionId(position.getId());
        ucd.setAgentId(user.getAgentId());
        ucd.setAgentName(user.getAgentName());
        ucd.setUserId(user.getId());
        ucd.setUserName(user.getRealName());
        ucd.setDeType("买入扣款");
        ucd.setDeAmt(buyDebit.negate());
        ucd.setDeSummary("委托买入," + position.getStockCode() + "/" + position.getStockName()
                + ",保证金:" + margin + ",手续费:" + fee + ",合计扣款:" + buyDebit);
        ucd.setAddTime(new Date());
        ucd.setIsRead(Integer.valueOf(0));
        this.userCashDetailMapper.insert(ucd);
    }
    /** 下单/撤单后刷新 Redis 中的用户资金,避免页面仍显示旧可用余额 */
    private void syncUserCacheAfterTrade(HttpServletRequest request) {
        if (request == null) {
            return;
        }
        String cookieName = PropertiesUtil.getProperty("user.cookie.name");
        String loginToken = request.getHeader(cookieName);
        if (StringUtils.isBlank(loginToken)) {
            return;
        }
        String userJson = RedisShardedPoolUtils.get(loginToken);
        User cached = (User) JsonUtil.string2Obj(userJson, User.class);
        if (cached == null || cached.getId() == null) {
            return;
        }
        User dbUser = this.userMapper.selectByPrimaryKey(cached.getId());
        if (dbUser != null) {
            RedisShardedPoolUtils.setEx(loginToken, JsonUtil.obj2String(dbUser), 9999);
        }
    }
    @Override
    @Transactional
    public ServerResponse pending(Integer stockId, Integer buyNum, Integer buyType, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, HttpServletRequest request) throws Exception {
@@ -708,11 +788,10 @@
                    .getBuyMaxAmtPercent().multiply(new BigDecimal("100")) + "%");
        }
        int compareUserAmtInt = user_enable_amt.compareTo(buy_amt_autual);
        log.info("用户可用金额 = {}  实际购买金额 =  {}", user_enable_amt, buy_amt_autual);
        log.info("比较 用户金额 和 实际 购买金额 =  {}", Integer.valueOf(compareUserAmtInt));
        if (compareUserAmtInt == -1) {
            return ServerResponse.createByErrorMsg("挂单失败,融资可用金额小于" + buy_amt_autual + "元");
        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt);
        BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
        if (user_enable_amt.compareTo(buy_debit) == -1) {
            return ServerResponse.createByErrorMsg("挂单失败,融资可用金额小于" + buy_debit + "元(含保证金及手续费)");
        }
        if (user.getUserIndexAmt().compareTo(new BigDecimal("0")) == -1) {
            return ServerResponse.createByErrorMsg("失败,指数总资金小于0");
@@ -751,7 +830,6 @@
        BigDecimal allStayFee = stayFee.multiply(new BigDecimal(1));
        userPosition.setOrderStayFee(allStayFee);
        userPosition.setOrderStayDays(1);
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        log.info("用户购买手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
        userPosition.setOrderFee(buy_fee_amt);
        BigDecimal buy_yhs_amt = buy_amt.multiply(siteSetting.getDutyFee()).setScale(2, 4);
@@ -772,40 +850,60 @@
        userPosition.setAllProfitAndLose(all_profit_and_lose);
        userPosition.setOrderStayDays(Integer.valueOf(0));
        userPosition.setOrderStayFee(new BigDecimal("0"));
        int insertPositionCount = 0;
        this.userPositionMapper.insert(userPosition);
        insertPositionCount = userPosition.getId();
        if (insertPositionCount > 0) {
            //修改用户可用余额= 当前余额-下单金额-买入手续费-印花税-点差费
            //BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual).subtract(buy_fee_amt).subtract(buy_yhs_amt).subtract(spread_rate_amt);
            //修改用户可用余额= 当前余额-下单总金额
            BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual);
            //修改用户可取余额=当前可取余额-下单总金额
            int compareUserWithdrawAmtInt = user_enable_withdraw_amt.compareTo(buy_amt_autual);
            if (compareUserWithdrawAmtInt == -1) {
                //若可取余额小于下单总额,但是可用余额充足,令可取余额为0
                user.setEnaleWithdrawAmt(BigDecimal.ZERO);
            } else {
                user_enable_withdraw_amt = user_enable_withdraw_amt.subtract(buy_amt_autual);
                user.setEnaleWithdrawAmt(user_enable_withdraw_amt);
            }
            user.setEnableAmt(reckon_enable);
//            user.setDjzj(user.getDjzj().subtract(buy_amt_autual));
            int updateUserCount = this.userMapper.updateByPrimaryKeySelective(user);
            if (updateUserCount > 0) {
                log.info("【用户交易下单】修改用户金额成功");
            } else {
                log.error("用户交易下单】修改用户金额出错");
                throw new Exception("用户交易下单】修改用户金额出错");
            }
            //核算代理收入-入仓手续费
//            iAgentAgencyFeeService.AgencyFeeIncome(1, userPosition.getPositionSn());
        if (userPosition.getId() != null && userPosition.getId() > 0) {
            deductUserEnableOnBuy(user, buy_debit, buy_amt_autual, buy_fee_amt, userPosition);
            syncUserCacheAfterTrade(request);
            log.info("【用户交易下单】保存持仓记录成功");
        } else {
            log.error("用户交易下单】保存持仓记录出错");
            throw new Exception("用户交易下单】保存持仓记录出错");
        }
        return ServerResponse.createBySuccess("挂单成功");
    }
    /**
     * 撤销委托单(status=0),退还保证金+买入手续费
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ServerResponse cancelOrder(String positionSn, HttpServletRequest request) throws Exception {
        if (StringUtils.isBlank(positionSn)) {
            return ServerResponse.createByErrorMsg("参数错误");
        }
        User user = this.iUserService.getCurrentRefreshUser(request);
        if (user == null) {
            return ServerResponse.createByErrorMsg("请先登录");
        }
        UserPosition userPosition = this.userPositionMapper.findPositionBySn(positionSn);
        if (userPosition == null) {
            return ServerResponse.createByErrorMsg("委托不存在");
        }
        if (!user.getId().equals(userPosition.getUserId())) {
            return ServerResponse.createByErrorMsg("无权操作该委托");
        }
        if (userPosition.getStatus() == null || userPosition.getStatus().intValue() != 0) {
            return ServerResponse.createByErrorMsg("当前订单不可撤单");
        }
        if (userPosition.getSellOrderId() != null) {
            return ServerResponse.createByErrorMsg("当前订单不可撤单");
        }
        BigDecimal buyAmtActual = userPosition.getOrderTotalPrice()
                .divide(new BigDecimal(userPosition.getOrderLever()), 2, 4);
        BigDecimal buyFee = userPosition.getOrderFee() != null ? userPosition.getOrderFee() : BigDecimal.ZERO;
        BigDecimal refundAmt = TradeFeeUtil.calcBuyDebit(buyAmtActual, buyFee);
        User freshUser = this.userMapper.selectByPrimaryKey(user.getId());
        if (freshUser == null) {
            throw new Exception("用户不存在");
        }
        refundUserEnableOnCancel(freshUser, refundAmt, userPosition);
        int delCount = this.userPositionMapper.deleteByPrimaryKey(userPosition.getId());
        if (delCount <= 0) {
            throw new Exception("撤单失败,删除委托记录失败");
        }
        syncUserCacheAfterTrade(request);
        log.info("【用户撤单】positionSn={} 退还保证金+手续费={}", positionSn, refundAmt);
        return ServerResponse.createBySuccessMsg("撤单成功");
    }
@@ -1055,8 +1153,8 @@
        BigDecimal sell_fee_amt = all_sell_amt.multiply(siteSetting.getSellFee()).setScale(2, 4);
        log.info("卖出手续费 = {}", sell_fee_amt);
        //總手續費= 買入手續費+賣出手續費+印花稅+遞延費+點差費
        BigDecimal all_fee_amt = buy_fee_amt.add(sell_fee_amt).add(orderSpread).add(orderStayFee).add(spreadRatePrice);
        // 买入手续费已在下单时扣除,平仓只结算卖出侧费用
        BigDecimal all_fee_amt = sell_fee_amt.add(orderSpread).add(orderStayFee).add(spreadRatePrice);
        log.info("总的手续费费用 = {}", all_fee_amt);
        userPosition.setSellOrderId(GeneratePosition.getPositionId());
@@ -1266,7 +1364,7 @@
        BigDecimal user_enable_amt = user.getEnableAmt();
        log.info("用戶原本總資金 = {} , 可用 = {}", user_all_amt, user_enable_amt);
        BigDecimal buy_fee_amt = all_buy_amt.multiply(siteSetting.getBuyFee()).setScale(2,4);
        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(all_buy_amt);
        log.info("買入手續費 = {}", buy_fee_amt);
        BigDecimal orderSpread = all_buy_amt.multiply(siteSetting.getDutyFee()).setScale(2, 4);
@@ -1281,9 +1379,8 @@
        BigDecimal sell_fee_amt = all_sell_amt.multiply(siteSetting.getSellFee()).setScale(2, 4);
        log.info("賣出手續費 = {}", sell_fee_amt);
        //總手續費= 買入手續費+賣出手續費+印花稅+遞延費+點差費
//        BigDecimal all_fee_amt = buy_fee_amt.add(sell_fee_amt).add(orderSpread).add(orderStayFee).add(spreadRatePrice);
        BigDecimal all_fee_amt = buy_fee_amt.add(sell_fee_amt).add(orderSpread);
        // 买入手续费已在下单时扣除
        BigDecimal all_fee_amt = sell_fee_amt.add(orderSpread);
        log.info("總的手續費費用 = {}", all_fee_amt);
        //复制一条新订单
        UserPosition userPositionNew = new UserPosition();
@@ -1425,7 +1522,8 @@
        }
        userPosition.setMarginAdd(userPosition.getMarginAdd().add(marginAdd));
        BigDecimal existMarginAdd = userPosition.getMarginAdd() == null ? BigDecimal.ZERO : userPosition.getMarginAdd();
        userPosition.setMarginAdd(existMarginAdd.add(marginAdd));
        int updatePositionCount = this.userPositionMapper.updateByPrimaryKeySelective(userPosition);
        if (updatePositionCount > 0) {
@@ -1823,13 +1921,11 @@
        BigDecimal buy_amt_autual = buy_amt.divide(new BigDecimal(lever.intValue()), 2, 4);
        int compareUserAmtInt = user_enable_amt.compareTo(buy_amt_autual);
        log.info("用户可用金额 = {}  实际购买金额 =  {}", user_enable_amt, buy_amt_autual);
        log.info("比较 用户金额 和 实际 购买金额 =  {}", Integer.valueOf(compareUserAmtInt));
        if (compareUserAmtInt == -1) {
            log.info("下单失败,用户可用金额小于" + buy_amt_autual + "元");
            return ServerResponse.createByErrorMsg("下单失败,用户可用金额小于" + buy_amt_autual + "元");
        BigDecimal buy_fee_amt_check = TradeFeeUtil.calcBuyFee(buy_amt);
        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);
            return ServerResponse.createByErrorMsg("下单失败,用户可用金额小于" + buy_debit_check + "元(含保证金及手续费)");
        }
        if (user.getUserIndexAmt().compareTo(new BigDecimal("0")) == -1) {
@@ -1886,7 +1982,7 @@
        userPosition.setOrderStayDays(1);
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt).setScale(2, 4);
        log.info("创建模拟持仓 手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
        userPosition.setOrderFee(buy_fee_amt);
@@ -1931,20 +2027,19 @@
        userPosition.setOrderStayFee(new BigDecimal("0"));
        userPosition.setSpreadRatePrice(new BigDecimal("0"));
        int insertPositionCount = this.userPositionMapper.insert(userPosition);
        if (insertPositionCount > 0) {
            log.info("【创建持仓】保存记录成功");
        } else {
        this.userPositionMapper.insert(userPosition);
        if (userPosition.getId() == null || userPosition.getId() <= 0) {
            log.error("【创建持仓】保存记录出错");
            return ServerResponse.createByErrorMsg("生成持仓失败");
        }
        BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual);
        user.setEnableAmt(reckon_enable);
        int updateUserCount = this.userMapper.updateByPrimaryKeySelective(user);
        if (updateUserCount > 0) {
            log.info("【用户交易下单】修改用户金额成功");
        } else {
            log.error("用户交易下单】修改用户金额出错");
        log.info("【创建持仓】保存记录成功");
        BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
        try {
            deductUserEnableOnBuy(user, buy_debit, buy_amt_autual, buy_fee_amt, userPosition);
        } catch (Exception e) {
            this.userPositionMapper.deleteByPrimaryKey(userPosition.getId());
            log.error("【创建持仓】扣款失败,已回滚持仓记录", e);
            return ServerResponse.createByErrorMsg("生成持仓失败:" + e.getMessage());
        }
        iAgentAgencyFeeService.AgencyFeeIncome(1, userPosition.getPositionSn());
        return ServerResponse.createBySuccess("生成持仓成功");
@@ -2375,7 +2470,7 @@
            userPosition.setOrderStayDays(1);
            userPosition.setOrderTotalPrice(userStockSubscribe.getBond());
            //            BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
            //            BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt).setScale(2, 4);
            BigDecimal buy_fee_amt = new BigDecimal(0);
            log.info("用戶購買手續費(配資後總資金 * 百分比) = {}", buy_fee_amt);
            userPosition.setOrderFee(buy_fee_amt);
@@ -2649,11 +2744,10 @@
        }
        int compareUserAmtInt = user_enable_amt.compareTo(buy_amt_autual);
        log.info("用戶可用金額 = {}  實際購買金額 =  {}", user_enable_amt, buy_amt_autual);
        log.info("比較 用戶金額 和 實際 購買金額 =  {}", Integer.valueOf(compareUserAmtInt));
        if (compareUserAmtInt == -1) {
            return ServerResponse.createByErrorMsg("下单失败,融资可用金额小于" + buy_amt_autual + "元");
        BigDecimal buy_fee_amt_dz = TradeFeeUtil.calcBuyFee(buy_amt);
        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 + "元(含保证金及手续费)");
        }
//        if (user.getUserIndexAmt().compareTo(new BigDecimal("0")) == -1) {
@@ -2690,7 +2784,7 @@
        userPosition.setOrderStayDays(1);
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        BigDecimal buy_fee_amt = buy_fee_amt_dz;
        log.info("用戶購買手續費(配資後總資金 * 百分比) = {}", buy_fee_amt);
        userPosition.setOrderFee(buy_fee_amt);
@@ -2723,36 +2817,14 @@
        log.info("--------------购买逻辑股票数据 buyDz  stock------" + new Gson().toJson(userPosition));
        int insertPositionCount = 0;
        this.userPositionMapper.insert(userPosition);
        insertPositionCount = userPosition.getId();
        if (insertPositionCount > 0) {
        if (userPosition.getId() != null && userPosition.getId() > 0) {
            //修改大宗剩余
            stockDz.setStockShare(stockDz.getStockShare() - num);
            stockDz.setStockSurplus(stockDz.getStockSurplus() + num);
            stockDzMapper.updateById(stockDz);
            //修改用戶可用余額= 當前余額-下單金額-買入手續費-印花稅-點差費
            //BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual).subtract(buy_fee_amt).subtract(buy_yhs_amt).subtract(spread_rate_amt);
            //修改用戶可用余額= 當前余額-下單總金額
            BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual);
            //修改用戶可取余額=當前可取余額-下單總金額
            int compareUserWithdrawAmtInt = user_enable_withdraw_amt.compareTo(buy_amt_autual);
            if (compareUserWithdrawAmtInt == -1) {
                //若可取余額小於下單總額,但是可用余額充足,令可取余額為0
                user.setEnaleWithdrawAmt(BigDecimal.ZERO);
            } else {
                user_enable_withdraw_amt = user_enable_withdraw_amt.subtract(buy_amt_autual);
                user.setEnaleWithdrawAmt(user_enable_withdraw_amt);
            }
            user.setEnableAmt(reckon_enable);
            int updateUserCount = this.userMapper.updateByPrimaryKeySelective(user);
            if (updateUserCount > 0) {
                log.info("【用戶交易下單】修改用戶金額成功");
            } else {
                log.error("用戶交易下單】修改用戶金額出錯");
                throw new Exception("用戶交易下單】修改用戶金額出錯");
            }
            //核算代理收入-入倉手續費
            deductUserEnableOnBuy(user, buy_debit_dz, buy_amt_autual, buy_fee_amt_dz, userPosition);
            syncUserCacheAfterTrade(request);
            iAgentAgencyFeeService.AgencyFeeIncome(1, userPosition.getPositionSn());
            log.info("【用戶交易下單】保存持倉記錄成功");
        } else {
@@ -2980,11 +3052,10 @@
        }
        int compareUserAmtInt = user_enable_amt.compareTo(buy_amt_autual);
        log.info("用戶可用金額 = {}  實際購買金額 =  {}", user_enable_amt, buy_amt_autual);
        log.info("比較 用戶金額 和 實際 購買金額 =  {}", Integer.valueOf(compareUserAmtInt));
        if (compareUserAmtInt == -1) {
            return ServerResponse.createByErrorMsg("下單失敗,可用金額小於" + buy_amt_autual + "元");
        BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt);
        BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
        if (user_enable_amt.compareTo(buy_debit) == -1) {
            return ServerResponse.createByErrorMsg("下單失敗,可用金額小於" + buy_debit + "元(含保证金及手续费)");
        }
//        if (user.getUserIndexAmt().compareTo(new BigDecimal("0")) == -1) {
@@ -3038,7 +3109,6 @@
        userPosition.setOrderStayDays(1);
        BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
        log.info("用戶購買手續費(配資後總資金 * 百分比) = {}", buy_fee_amt);
        userPosition.setOrderFee(buy_fee_amt);
@@ -3072,31 +3142,10 @@
        log.info("--------------购买逻辑股票数据 buyVipQc  stock------" + new Gson().toJson(userPosition));
        int insertPositionCount = 0;
        this.userPositionMapper.insert(userPosition);
        insertPositionCount = userPosition.getId();
        if (insertPositionCount > 0) {
            //修改用戶可用余額= 當前余額-下單金額-買入手續費-印花稅-點差費
            //BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual).subtract(buy_fee_amt).subtract(buy_yhs_amt).subtract(spread_rate_amt);
            //修改用戶可用余額= 當前余額-下單總金額
            BigDecimal reckon_enable = user_enable_amt.subtract(buy_amt_autual);
            //修改用戶可取余額=當前可取余額-下單總金額
            int compareUserWithdrawAmtInt = user_enable_withdraw_amt.compareTo(buy_amt_autual);
            if (compareUserWithdrawAmtInt < 0) {
                //若可取余額小於下單總額,但是可用余額充足,令可取余額為0
                user.setEnaleWithdrawAmt(BigDecimal.ZERO);
            } else {
                user_enable_withdraw_amt = user_enable_withdraw_amt.subtract(buy_amt_autual);
                user.setEnaleWithdrawAmt(user_enable_withdraw_amt);
            }
            user.setEnableAmt(reckon_enable);
            int updateUserCount = this.userMapper.updateByPrimaryKeySelective(user);
            if (updateUserCount > 0) {
                log.info("【用戶交易下單】修改用戶金額成功");
            } else {
                log.error("用戶交易下單】修改用戶金額出錯");
                throw new Exception("用戶交易下單】修改用戶金額出錯");
            }
        if (userPosition.getId() != null && userPosition.getId() > 0) {
            deductUserEnableOnBuy(user, buy_debit, buy_amt_autual, buy_fee_amt, userPosition);
            syncUserCacheAfterTrade(request);
            //核算代理收入-入倉手續費
            //iAgentAgencyFeeService.AgencyFeeIncome(1, userPosition.getPositionSn());
            log.info("【用戶交易下單】保存持倉記錄成功");
src/main/java/com/nq/service/impl/UserServiceImpl.java
@@ -310,18 +310,11 @@
                stock.setStockName(stockFutures.getFuturesName());
                stock.setIsLock(0);
            }
        } else if(code.contains("sh") || code.contains("sz")){
         return ServerResponse.createByErrorMsg("添加失败,指数不支持自选");
//            StockIndex stockIndex = this.stockIndexMapper.selectIndexByCode(stockcode);
//            if(stockIndex != null){
//                stock.setId(stockIndex.getId());
//                stock.setStockCode(stockIndex.getIndexCode());
//                stock.setStockGid(stockIndex.getIndexGid()+"zs");
//                stock.setStockName(stockIndex.getIndexName());
//                stock.setIsLock(0);
//            }
        } else {
            stock = this.stockMapper.findStockByCode(code);
            stock = this.stockMapper.findStockByCode(stockcode);
            if (stock == null && !code.equals(stockcode)) {
                stock = this.stockMapper.findStockByCode(code);
            }
        }
        if (stock == null) {
            return ServerResponse.createByErrorMsg("添加失败,股票不存在");
@@ -400,6 +393,26 @@
        return ServerResponse.createBySuccess(userInfoVO);
    }
    @Override
    public void syncUserCache(HttpServletRequest request) {
        if (request == null) {
            return;
        }
        String cookieName = PropertiesUtil.getProperty("user.cookie.name");
        String loginToken = request.getHeader(cookieName);
        if (StringUtils.isBlank(loginToken)) {
            return;
        }
        String userJson = RedisShardedPoolUtils.get(loginToken);
        User cached = (User) JsonUtil.string2Obj(userJson, User.class);
        if (cached == null || cached.getId() == null) {
            return;
        }
        User dbUser = this.userMapper.selectByPrimaryKey(cached.getId());
        if (dbUser != null) {
            RedisShardedPoolUtils.setEx(loginToken, JsonUtil.obj2String(dbUser), 9999);
        }
    }
    public ServerResponse updatePwd(String oldPwd, String newPwd, HttpServletRequest request) {
        if (StringUtils.isBlank(oldPwd) || StringUtils.isBlank(newPwd)) {
@@ -1874,10 +1887,20 @@
        BigDecimal allProfitAndLose = positionVO.getAllProfitAndLose();
        userInfoVO.setAllProfitAndLose(allProfitAndLose);
//        BigDecimal userAllAmt = user.getUserAmt();
        BigDecimal userAllAmt = user.getEnableAmt();
        userAllAmt = userAllAmt.add(allProfitAndLose);
        // 账户总资产 = 两融可用 + 冻结保证金 + 浮动盈亏
        // 浮动盈亏里已扣过买入手续费,而买入手续费下单时已从 enableAmt 扣除,此处加回 openBuyFees 避免总资产「少扣手续费」
        BigDecimal allFreezAmt = positionVO.getAllFreezAmt() == null ? BigDecimal.ZERO : positionVO.getAllFreezAmt();
        BigDecimal enableAmt = user.getEnableAmt() == null ? BigDecimal.ZERO : user.getEnableAmt();
        BigDecimal openBuyFees = BigDecimal.ZERO;
        List<UserPosition> openPositions = this.iUserPositionService.findPositionByUserIdAndSellIdIsNull(user.getId());
        if (openPositions != null) {
            for (UserPosition position : openPositions) {
                if (position.getOrderFee() != null) {
                    openBuyFees = openBuyFees.add(position.getOrderFee());
                }
            }
        }
        BigDecimal userAllAmt = enableAmt.add(allFreezAmt).add(allProfitAndLose).add(openBuyFees);
        userInfoVO.setEnableIndexAmt(user.getEnableIndexAmt());
        userInfoVO.setEnaleWithdrawAmt(user.getEnaleWithdrawAmt());
@@ -1897,8 +1920,7 @@
            }
        }
        userInfoVO.setBuyAmtAutual(buyAmtAutual);
        userAllAmt = userAllAmt.add(buyAmtAutual);
        userInfoVO.setUserAmt(userAllAmt);
        userInfoVO.setUserAmt(userAllAmt.setScale(2, RoundingMode.HALF_UP));
        List<UserPosition> userPositions = this.userPositionMapper.findMyPositionByCodeAndSpell(user.getId(), "", "", 2);
src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java
@@ -814,7 +814,7 @@
                userPosition.setOrderStayDays(1);
                BigDecimal buy_fee_amt = buy_amt.multiply(siteSetting.getBuyFee()).setScale(2, 4);
                BigDecimal buy_fee_amt = com.nq.utils.TradeFeeUtil.calcBuyFee(buy_amt);
                log.info("创建模拟持仓 手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
                userPosition.setOrderFee(buy_fee_amt);
src/main/java/com/nq/utils/stock/sina/SinaStockApi.java
@@ -37,17 +37,25 @@
    public static String getSinaStock(String stockGid) {
        String sina_result = "";
        try {
//            System.out.println(sina_url + stockGid);
//            sina_result = HttpClientRequest.doGet(sina_url + stockGid);
//            System.out.println("请求返回:"+sina_result);
            System.out.println(PropertiesUtil.getProperty("sina.single.stock.proxy.url") + stockGid);
            sina_result = HttpClientRequest.doGet(PropertiesUtil.getProperty("sina.single.stock.proxy.url") + stockGid);
            System.out.println("请求返回:"+sina_result);
//            sina_result = "var hq_str_sz300270=\"中威电子,0.000,11.710,0.000,0.000,0.000,0.000,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,2025-12-03,09:10:06,00\";";
            System.out.println("请求返回:" + sina_result);
        } catch (Exception e) {
            log.error("获取股票行情出错,错误信息 = {}", e);
            log.error("获取股票行情出错,gid={},错误信息 = {}", stockGid, e);
        }
        return sina_result.substring(sina_result.indexOf("=") + 2);
        if (StringUtils.isBlank(sina_result) || !sina_result.contains("=")) {
            log.warn("新浪行情返回为空或格式异常, gid={}, raw={}", stockGid, StringUtils.abbreviate(sina_result, 200));
            return "";
        }
        String body = sina_result.substring(sina_result.indexOf("=") + 2);
        return body.replace("\"", "").replace(";", "").trim();
    }
    private static String hqField(String[] hqarr, int index) {
        if (hqarr == null || index < 0 || index >= hqarr.length) {
            return "0";
        }
        return StringUtils.defaultIfBlank(hqarr[index], "0");
    }
@@ -198,57 +206,57 @@
    public static StockVO assembleStockVO(String sinaResult) {
        StockVO stockVO = new StockVO();
        if (StringUtils.isBlank(sinaResult)) {
            return stockVO;
        }
        String[] hqarr = sinaResult.split(",");
        if (hqarr.length < 4) {
            log.warn("新浪行情字段不足,无法解析,length={}, raw={}", hqarr.length, StringUtils.abbreviate(sinaResult, 200));
            return stockVO;
        }
        stockVO.setName(hqarr[0]);
        stockVO.setNowPrice(hqarr[3]);
        stockVO.setName(hqField(hqarr, 0));
        stockVO.setNowPrice(hqField(hqarr, 3));
        BigDecimal chang_rate = new BigDecimal("0");
        if ((new BigDecimal(hqarr[2])).compareTo(new BigDecimal("0")) != 0 && new BigDecimal(hqarr[3]).compareTo(new BigDecimal("0")) != 0) {
            chang_rate = (new BigDecimal(hqarr[3])).subtract(new BigDecimal(hqarr[2]));
            chang_rate = chang_rate.multiply(new BigDecimal("100")).divide(new BigDecimal(hqarr[2]), 2, RoundingMode.HALF_UP);
        BigDecimal preclose = new BigDecimal(hqField(hqarr, 2));
        BigDecimal now = new BigDecimal(hqField(hqarr, 3));
        if (preclose.compareTo(BigDecimal.ZERO) != 0 && now.compareTo(BigDecimal.ZERO) != 0) {
            chang_rate = now.subtract(preclose);
            chang_rate = chang_rate.multiply(new BigDecimal("100")).divide(preclose, 2, RoundingMode.HALF_UP);
        }
        stockVO.setHcrate(chang_rate);
        stockVO.setToday_max(hqarr[4]);
        stockVO.setToday_max(hqField(hqarr, 4));
        stockVO.setToday_min(hqField(hqarr, 5));
        stockVO.setBusiness_amount(hqField(hqarr, 8));
        stockVO.setBusiness_balance(hqField(hqarr, 9));
        stockVO.setPreclose_px(hqField(hqarr, 2));
        stockVO.setOpen_px(hqField(hqarr, 1));
        stockVO.setToday_min(hqarr[5]);
        stockVO.setBuy1(hqField(hqarr, 6));
        stockVO.setBuy2(hqField(hqarr, 13));
        stockVO.setBuy3(hqField(hqarr, 15));
        stockVO.setBuy4(hqField(hqarr, 17));
        stockVO.setBuy5(hqField(hqarr, 19));
        stockVO.setBusiness_amount(hqarr[8]);
        stockVO.setSell1(hqField(hqarr, 7));
        stockVO.setSell2(hqField(hqarr, 23));
        stockVO.setSell3(hqField(hqarr, 25));
        stockVO.setSell4(hqField(hqarr, 27));
        stockVO.setSell5(hqField(hqarr, 29));
        stockVO.setBusiness_balance(hqarr[9]);
        stockVO.setBuy1_num(hqField(hqarr, 10));
        stockVO.setBuy2_num(hqField(hqarr, 12));
        stockVO.setBuy3_num(hqField(hqarr, 14));
        stockVO.setBuy4_num(hqField(hqarr, 16));
        stockVO.setBuy5_num(hqField(hqarr, 18));
        stockVO.setPreclose_px(hqarr[2]);
        stockVO.setOpen_px(hqarr[1]);
        stockVO.setBuy1(hqarr[6]);
        stockVO.setBuy2(hqarr[13]);
        stockVO.setBuy3(hqarr[15]);
        stockVO.setBuy4(hqarr[17]);
        stockVO.setBuy5(hqarr[19]);
        stockVO.setSell1(hqarr[7]);
        stockVO.setSell2(hqarr[23]);
        stockVO.setSell3(hqarr[25]);
        stockVO.setSell4(hqarr[27]);
        stockVO.setSell5(hqarr[29]);
        stockVO.setBuy1_num(hqarr[10]);
        stockVO.setBuy2_num(hqarr[12]);
        stockVO.setBuy3_num(hqarr[14]);
        stockVO.setBuy4_num(hqarr[16]);
        stockVO.setBuy5_num(hqarr[18]);
        stockVO.setSell1_num(hqarr[20]);
        stockVO.setSell2_num(hqarr[22]);
        stockVO.setSell3_num(hqarr[24]);
        stockVO.setSell4_num(hqarr[26]);
        stockVO.setSell5_num(hqarr[28]);
        stockVO.setSell1_num(hqField(hqarr, 20));
        stockVO.setSell2_num(hqField(hqarr, 22));
        stockVO.setSell3_num(hqField(hqarr, 24));
        stockVO.setSell4_num(hqField(hqarr, 26));
        stockVO.setSell5_num(hqField(hqarr, 28));
        return stockVO;
    }
src/main/resources/application.properties
@@ -217,7 +217,16 @@
#?????
website.domain.url=http://www.huijuwang888.com
# Ococn ??
ococn.pay.pid=185583446
ococn.pay.key=JcMJIbNUAcq0GyMf
ococn.pay.url=https://pay.ococn.cn/submit.php
ococn.pay.sitename=
ococn.pay.name=账户充值
ococn.pay.frontend_redirect=https://www.zhonghenginvest.com/#/user
website.domain.url=https://api.zhonghenginvest.com
frontend.domain.url=https://www.zhonghenginvest.com
website.token=0DC8F78384C7AAFF3192A9C60A473FEE7F89C62888689616B98A06910E86B510
#?????