zyy
2025-07-11 1e42d705a1f3f040c00e499e1bf42159c601aa40
AI产品交易
5 files modified
10 files added
548 ■■■■■ changed files
src/main/java/com/nq/controller/StockApiController.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/StockInkApiController.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/dao/StockAiMapper.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/dao/StockAiOrderMapper.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/enums/EStockAIOrderStatus.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/enums/EStockAIStatus.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/enums/EStockType.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/enums/EUserAssets.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/pojo/StockAI.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/pojo/StockAIOrder.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/pojo/StockAIOrderPosition.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IStockAiService.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IUserAssetsServices.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/StockAiServiceImpl.java 161 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserAssetsServices.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/StockApiController.java
@@ -1,7 +1,6 @@
package com.nq.controller;
import com.nq.common.ServerResponse;
import com.nq.pojo.SiteSetting;
import com.nq.service.IStockService;
import com.nq.service.StockDzService;
import org.slf4j.Logger;
@@ -152,4 +151,5 @@
                                             @RequestParam(value = "stockType", defaultValue = "US") String stockType) {
        return this.iStockService.getIndicesAndKData(pid, stockType);
    }
}
src/main/java/com/nq/controller/StockInkApiController.java
New file
@@ -0,0 +1,78 @@
package com.nq.controller;
import com.nq.common.ServerResponse;
import com.nq.service.IStockAiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Controller
@RequestMapping({"/api/lnk/"})
public class StockInkApiController {
    @Autowired
    IStockAiService stockAiService;
    private static final ThreadLocal<Boolean> buyOrderCreated = ThreadLocal.withInitial(() -> false);
    private final Lock buyLock = new ReentrantLock();
    /**
     * 获取ai交易产品列表
     * @return
     */
    @RequestMapping("getStockAiList.do")
    @ResponseBody
    public ServerResponse getStockAiList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
                                         @RequestParam(value = "pageSize", defaultValue = "5") int pageSize)   {
        return stockAiService.getStockAiList(pageNum, pageSize);
    }
    /**
     * ai量化交易购买
     * @param id
     * @param buyNum  买入金额
     * @return
     */
    @RequestMapping("buyStockAi.do")
    @ResponseBody
    public ServerResponse buyStockAi(@RequestParam(value = "id") Long id,
                                     @RequestParam(value = "buyNum") BigDecimal buyNum, HttpServletRequest request) {
        if (buyNum.compareTo(BigDecimal.ZERO) <= 0) {
            return ServerResponse.createByErrorMsg("购买金额不能小于0");
        }
        buyLock.lock();
        try {
            if (buyOrderCreated.get()) {
                return ServerResponse.createByErrorMsg("当前下单人数过多,请稍后重试");
            }
            buyOrderCreated.set(true);
            return stockAiService.buyStockAi(id, buyNum, request);
        } catch (Exception e) {
            e.printStackTrace();
            return ServerResponse.createByErrorMsg("订单异常,请稍后重试");
        }  finally{
            buyLock.unlock();
            buyOrderCreated.set(false);
        }
    }
    /**
     * 获取ai交易产品订单列表
     * @return
     */
    @RequestMapping("getStockAiOrderList.do")
    @ResponseBody
    public ServerResponse getStockAiOrderList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
                                              @RequestParam(value = "pageSize", defaultValue = "5") int pageSize,
                                              @RequestParam(value = "status") String status,
                                              HttpServletRequest request)   {
        return stockAiService.getStockAiOrderList(pageNum, pageSize, status ,request);
    }
}
src/main/java/com/nq/dao/StockAiMapper.java
New file
@@ -0,0 +1,10 @@
package com.nq.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nq.pojo.StockAI;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StockAiMapper extends BaseMapper<StockAI> {
}
src/main/java/com/nq/dao/StockAiOrderMapper.java
New file
@@ -0,0 +1,10 @@
package com.nq.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nq.pojo.StockAIOrder;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StockAiOrderMapper extends BaseMapper<StockAIOrder> {
}
src/main/java/com/nq/enums/EStockAIOrderStatus.java
New file
@@ -0,0 +1,24 @@
package com.nq.enums;
import lombok.Getter;
@Getter
public enum EStockAIOrderStatus {
    //等待审核
    wait("wait"),
    //已通过
    passed("passed"),
    //不通过
    notPass("notPass"),
    //已完成
    finished("finished"),
    ;
    private String status;
    EStockAIOrderStatus(String status) {
        this.status = status;
    }
}
src/main/java/com/nq/enums/EStockAIStatus.java
New file
@@ -0,0 +1,20 @@
package com.nq.enums;
import lombok.Getter;
@Getter
public enum EStockAIStatus {
    //上线
    online("online"),
    //下架
    out("out"),
    ;
    private String value;
    EStockAIStatus(String value) {
        this.value = value;
    }
}
src/main/java/com/nq/enums/EStockType.java
@@ -17,7 +17,7 @@
//    MAS("MAS","马来西亚股票","42",PropertiesUtil.getProperty("MAS_HTTP_API"),PropertiesUtil.getProperty("MAS_KEY"),"MYR","RM"),
//
//    IN("IN","印度股票","14", PropertiesUtil.getProperty("JS_IN_HTTP_URL"),PropertiesUtil.getProperty("JS_IN_KEY"),"INR","₹"),
    MX("MEX","墨西哥股票","7",PropertiesUtil.getProperty("MX_HTTP_API"),PropertiesUtil.getProperty("MX_KEY"),"MXN","MXN$");
    MX("MEX","墨西哥股票","7",PropertiesUtil.getProperty("MX_HTTP_API"),PropertiesUtil.getProperty("MX_KEY"),"MXN","MX$");
//    TH("TH","泰国股票","41",PropertiesUtil.getProperty("TH_HTTP_API"),PropertiesUtil.getProperty("TH_KEY")),
//    HG("HG","韩国股票","11",PropertiesUtil.getProperty("HG_HTTP_API"),PropertiesUtil.getProperty("HG_KEY")),
src/main/java/com/nq/enums/EUserAssets.java
@@ -17,8 +17,9 @@
    WITHDRAW("WITHDRAW","提现"),
    TRANSFER("TRANSFER","转换"),
    TOP_UP("TOP_UP","充值");
    TOP_UP("TOP_UP","充值"),
    BUY_AI("BUY_AI","购买AI产品"),
    ;
    private String  code;
src/main/java/com/nq/pojo/StockAI.java
New file
@@ -0,0 +1,57 @@
package com.nq.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;
/**
 * AI产品表
 */
@Data
@TableName("stock_ai")
@Entity
public class StockAI {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String stockType;
    private String stockName;
    /**
     * 最低买入金额
     */
    private BigDecimal minPrice;
    /**
     * 交易成功率
     */
    private BigDecimal successRate;
    /**
     * 预期收益率
     */
    private BigDecimal expectedEarning;
    /**
     * 周期 天
     */
    private Integer cycle;
    /**
     * 状态 上线 下架
     */
    private String status;
    //创建时间
    private Date createDate;
}
src/main/java/com/nq/pojo/StockAIOrder.java
New file
@@ -0,0 +1,56 @@
package com.nq.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;
/**
 * AI产品订单表
 */
@Data
@TableName("stock_ai_order")
@Entity
public class StockAIOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //用户id
    private Integer userId;
    //ai交易产品id
    private Long stockAiId;
    //买入时间
    private Date buyDate;
    /**
     * 买入金额
     */
    private BigDecimal buyAmount;
    /**
     * 剩余金额
     */
    private BigDecimal remainAmount;
    /**
     * 实际收益
     */
    private BigDecimal realEarning;
    /**
     * 状态 待审核 申请通过 申请通过 已完成
     */
    private String status;
    //审核时间
    private Date auditDate;
}
src/main/java/com/nq/pojo/StockAIOrderPosition.java
New file
@@ -0,0 +1,62 @@
package com.nq.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;
/**
 * AI产品订单建仓表
 */
@Data
@TableName("stock_ai_order_position")
@Entity
public class StockAIOrderPosition {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //ai交易产品订单id
    private Long stockAiOrderId;
    //建仓股票id
    private Integer stockId;
    /**
     * 建仓股票数量
     */
    private Long stockNum;
    /**
     * 建仓股票单价
     */
    private BigDecimal stockPrice;
    /**
     * 建仓时间
     */
    private Date creatDate;
    /**
     * 平仓单价
     */
    private BigDecimal coverPrice;
    /**
     * 平仓时间
     */
    private Date coverDate;
    /**
     * 收益 (平仓单价-建仓股票单价)*建仓股票数量
     */
    private BigDecimal earnings;
}
src/main/java/com/nq/service/IStockAiService.java
New file
@@ -0,0 +1,17 @@
package com.nq.service;
import com.nq.common.ServerResponse;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
public interface IStockAiService {
  ServerResponse getStockAiList(Integer pageNum, Integer pageSize);
  ServerResponse buyStockAi(Long id, BigDecimal buyNum, HttpServletRequest request);
  ServerResponse getStockAiOrderList(Integer pageNum, Integer pageSize, String status, HttpServletRequest request);
}
src/main/java/com/nq/service/IUserAssetsServices.java
@@ -47,5 +47,20 @@
     * */
    Boolean availablebalanceChange(String accetType, Integer userId, EUserAssets eUserAssets, BigDecimal amount, String desc, String descType);
    /**
     * ai交易
     * @param userAssets
     * @param eUserAssets
     * @param amount
     * @return
     */
    Boolean aiAvailableBalanceChange(UserAssets userAssets, EUserAssets eUserAssets, BigDecimal amount);
    /**
     * 根据汇率转换金额
     * @param amount    转换金额
     * @param rate      转换汇率
     * @return
     */
    BigDecimal exchangeAmountByRate(BigDecimal amount, BigDecimal rate);
}
src/main/java/com/nq/service/impl/StockAiServiceImpl.java
New file
@@ -0,0 +1,161 @@
package com.nq.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.nq.Repository.ExchangeRateRepository;
import com.nq.common.ServerResponse;
import com.nq.dao.StockAiMapper;
import com.nq.dao.StockAiOrderMapper;
import com.nq.enums.*;
import com.nq.pojo.*;
import com.nq.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Service("iStockAiService")
public class StockAiServiceImpl implements IStockAiService {
    private static final Logger log = LoggerFactory.getLogger(StockAiServiceImpl.class);
    @Autowired
    StockAiMapper stockAiMapper;
    @Autowired
    StockAiOrderMapper stockAiOrderMapper;
    @Autowired
    ISiteProductService iSiteProductService;
    @Autowired
    IUserService iUserService;
    @Autowired
    IUserAssetsServices iUserAssetsServices;
    @Autowired
    ExchangeRateRepository exchangeRateRepository;
    /**
     * 获取上架ai产品
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public ServerResponse getStockAiList(Integer pageNum, Integer pageSize) {
        try {
            PageHelper.startPage(pageNum, pageSize);
            List<StockAI> stockAiList = stockAiMapper.selectList(new QueryWrapper<StockAI>().eq("status", EStockAIStatus.online.getValue()));
            // 获取分页信息
            PageInfo<StockAI> pageInfo = new PageInfo<>(stockAiList);
            return ServerResponse.createBySuccess(pageInfo);
        } catch (Exception ex) {
            log.error("StockAiService getStockAiList error", ex);
        }
        return ServerResponse.createByError();
    }
    /**
     * 购买ai产品
     * @param id
     * @param buyNum
     * @return
     */
    @Override
    @Transactional
    public ServerResponse buyStockAi(Long id, BigDecimal buyNum, HttpServletRequest request) {
        try {
            User user = iUserService.getCurrentUser(request);
            if (user == null ){
                return ServerResponse.createBySuccessMsg("請先登錄");
            }
            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);
                }
                StockAI stockAI = stockAiMapper.selectById(id);
                if (stockAI == null) {
                    return ServerResponse.createByErrorMsg("订单失败,AI股票不存在", request);
                }
                if(buyNum.compareTo(stockAI.getMinPrice()) < 0){
                    return ServerResponse.createByErrorMsg("最低购买数量" + stockAI.getMinPrice(), request);
                }
                //获取用户账户
                UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(EStockType.MX.getCode(), user.getId());
                BigDecimal finalBuyNum = buyNum;    //购买金额
                //如果不是墨西哥币需要转换金额
                if (!stockAI.getStockType().equals(EStockType.US.getCode())) {
                    EStockType stockType = EStockType.getEStockTypeByCode(stockAI.getStockType());
                    ExchangeRate exchangeRate = exchangeRateRepository.findExchangeRateByCurrencyAndConversionCurrency(stockType.getSymbol(), EStockType.MX.getSymbol())
                            .orElse(null);
                    if (exchangeRate == null) {
                        return ServerResponse.createByErrorMsg("当前货币汇率无法转换");
                    }
                    //转换为墨西哥币
                    buyNum = iUserAssetsServices.exchangeAmountByRate(buyNum, exchangeRate.getRata());
                }
                if(buyNum.compareTo(userAssets.getAvailableBalance()) > 0){
                    return ServerResponse.createByErrorMsg("可用余额不足" + userAssets.getAvailableBalance(), request);
                }
                if(userAssets.getAmountToBeCovered().compareTo(BigDecimal.ZERO) > 0){
                    return ServerResponse.createByErrorMsg("请先缴清待补资金", request);
                }
                StockAIOrder stockAIOrder = new StockAIOrder();
                stockAIOrder.setUserId(user.getId());
                stockAIOrder.setStockAiId(id);
                stockAIOrder.setBuyDate(new Date());
                stockAIOrder.setBuyAmount(finalBuyNum);
                stockAIOrder.setRemainAmount(finalBuyNum);
                stockAIOrder.setRealEarning(BigDecimal.valueOf(0));
                stockAIOrder.setStatus(EStockAIOrderStatus.wait.getStatus());   //等待审核
                stockAiOrderMapper.insert(stockAIOrder);
                iUserAssetsServices.aiAvailableBalanceChange(userAssets, EUserAssets.BUY_AI, buyNum);
                return ServerResponse.createBySuccessMsg("下单成功", request);
            }
        } catch (Exception e) {
            log.error("StockAiService buyStockAiList error", e);
        }
        return ServerResponse.createByError();
    }
    /**
     * 获取ai产品订单
     * @param pageNum
     * @param pageSize
     * @param status
     * @param request
     * @return
     */
    @Override
    public ServerResponse getStockAiOrderList(Integer pageNum, Integer pageSize, String status, HttpServletRequest request) {
        try {
            User user = iUserService.getCurrentUser(request);
            if (user == null ){
                return ServerResponse.createBySuccessMsg("請先登錄");
            }
            PageHelper.startPage(pageNum, pageSize);
            List<StockAIOrder> stockAIOrders = stockAiOrderMapper.selectList(new QueryWrapper<StockAIOrder>()
                    .eq("user_id", user.getId())
                    .eq(status != null && !status.isEmpty(), "status", status));
            // 获取分页信息
            PageInfo<StockAIOrder> pageInfo = new PageInfo<>(stockAIOrders);
            return ServerResponse.createBySuccess(pageInfo);
        } catch (Exception e) {
            log.error("StockAiService getStockAiOrderList error", e);
        }
        return ServerResponse.createByError();
    }
}
src/main/java/com/nq/service/impl/UserAssetsServices.java
@@ -291,4 +291,33 @@
        }
        extracted(userAssets);
    }
    public Boolean aiAvailableBalanceChange(UserAssets userAssets, EUserAssets eUserAssets, BigDecimal amount) {
        String before = userAssets.getAvailableBalance().toString();
        if (eUserAssets.getCode().equals(EUserAssets.BUY_AI.getCode())) {
            //冻结金额
            userAssets.setFreezeMoney(userAssets.getFreezeMoney().add(amount));
            //扣除可用金额
            userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount.negate()));
        }
        String after = userAssets.getAvailableBalance().toString();
        MoneyLog moneyLog = new MoneyLog();
        moneyLog.setDescs(eUserAssets.getDesc());
        moneyLog.setBeFore(before);
        moneyLog.setAfter(after);
        moneyLog.setAmount(amount.toString());
        moneyLog.setAccectType(userAssets.getAccectType());
        moneyLog.setType(eUserAssets.getCode());
        moneyLog.setUserId(userAssets.getId()+"");
        moneyLog.setSymbol(EStockType.getEStockTypeByCode(userAssets.getAccectType()).getSymbol());
        moneyLog.setCreateTime(new Date());
        moneyLogMapper.insert(moneyLog);
        return userAssetsMapper.updateById(userAssets)>1;
    }
    @Override
    public BigDecimal exchangeAmountByRate(BigDecimal amount, BigDecimal rate) {
        return amount.multiply(rate);
    }
}