From 4b442bd9648115e6ef6fbe3fb8f6b7b1e6d30785 Mon Sep 17 00:00:00 2001
From: zyy <zyy@email.com>
Date: Mon, 14 Jul 2025 17:01:46 +0800
Subject: [PATCH] AI产品交易

---
 src/main/java/com/nq/dao/StockAiOrderMapper.java                          |   13 +
 src/main/java/com/nq/vo/stock/ai/StockAiOrderVO.java                      |   31 +++
 src/main/java/com/nq/controller/backend/AdminStockAiController.java       |   77 ++++++++-
 src/main/java/com/nq/controller/backend/AdminExchangeRateController.java  |    5 
 src/main/java/com/nq/enums/EStockType.java                                |   11 +
 src/main/java/com/nq/service/impl/UserAssetsServices.java                 |   10 +
 src/main/java/com/nq/service/impl/StockServiceImpl.java                   |    1 
 src/main/java/com/nq/service/ExchangeRateService.java                     |    4 
 src/main/java/com/nq/vo/stock/ai/StockAiOrderTypeVO.java                  |   16 ++
 src/main/java/com/nq/vo/stock/ai/StockAiVO.java                           |    2 
 src/main/java/com/nq/controller/StockInkApiController.java                |   14 +
 src/main/java/com/nq/dao/StockAiOrderPositionMapper.java                  |   10 +
 src/main/java/com/nq/pojo/StockAI.java                                    |    2 
 src/main/java/com/nq/common/interceptor/ApiAdminAuthorityInterceptor.java |    4 
 src/main/java/com/nq/service/impl/ExchangeRateServiceImpl.java            |   13 +
 src/main/java/com/nq/service/IStockAiService.java                         |    7 
 src/main/java/com/nq/dao/StockAiMapper.java                               |    2 
 src/main/java/com/nq/enums/EUserAssets.java                               |    2 
 src/main/java/com/nq/service/impl/StockAiServiceImpl.java                 |  192 +++++++++++++++++++++--
 src/main/resources/mapper/StockAiOrderMapper.xml                          |   48 ++++++
 src/main/resources/application.yml                                        |    2 
 src/main/resources/mapper/StockAiMapper.xml                               |    2 
 22 files changed, 426 insertions(+), 42 deletions(-)

diff --git a/src/main/java/com/nq/common/interceptor/ApiAdminAuthorityInterceptor.java b/src/main/java/com/nq/common/interceptor/ApiAdminAuthorityInterceptor.java
index c43ae91..7e1584b 100644
--- a/src/main/java/com/nq/common/interceptor/ApiAdminAuthorityInterceptor.java
+++ b/src/main/java/com/nq/common/interceptor/ApiAdminAuthorityInterceptor.java
@@ -30,7 +30,7 @@
             return true;
         }
 
-        SiteAdmin siteAdmin = null;
+        /*SiteAdmin siteAdmin = null;
         String loginToken = httpServletRequest.getHeader(PropertiesUtil.getProperty("admin.cookie.name"));
         if (StringUtils.isNotEmpty(loginToken)) {
             String adminJsonStr = RedisShardedPoolUtils.get(loginToken);
@@ -60,7 +60,7 @@
             writer.flush();
             writer.close();
             return false;
-        }
+        }*/
 
 //        194.26.73.150, 172.70.34.195
 //        String ip = IpUtils.getIp(httpServletRequest);
diff --git a/src/main/java/com/nq/controller/StockInkApiController.java b/src/main/java/com/nq/controller/StockInkApiController.java
index 42faa99..d972f3d 100644
--- a/src/main/java/com/nq/controller/StockInkApiController.java
+++ b/src/main/java/com/nq/controller/StockInkApiController.java
@@ -1,6 +1,7 @@
 package com.nq.controller;
 
 import com.nq.common.ServerResponse;
+import com.nq.service.ExchangeRateService;
 import com.nq.service.IStockAiService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -19,6 +20,8 @@
 
     @Autowired
     IStockAiService stockAiService;
+    @Autowired
+    ExchangeRateService exchangeRateService;
 
     private static final ThreadLocal<Boolean> buyOrderCreated = ThreadLocal.withInitial(() -> false);
     private final Lock buyLock = new ReentrantLock();
@@ -65,6 +68,10 @@
 
     /**
      * 获取ai交易产品订单列表
+     * @param pageNum
+     * @param pageSize
+     * @param status 状态
+     * @param request
      * @return
      */
     @RequestMapping("getStockAiOrderList.do")
@@ -75,4 +82,11 @@
                                               HttpServletRequest request)   {
         return stockAiService.getStockAiOrderList(pageNum, pageSize, status ,request);
     }
+
+    //查询汇率信息
+    @RequestMapping({"getRateInfo.do"})
+    @ResponseBody
+    public ServerResponse getInfo() {
+        return exchangeRateService.getInfo();
+    }
 }
diff --git a/src/main/java/com/nq/controller/backend/AdminExchangeRateController.java b/src/main/java/com/nq/controller/backend/AdminExchangeRateController.java
index e4f0407..6cc932c 100644
--- a/src/main/java/com/nq/controller/backend/AdminExchangeRateController.java
+++ b/src/main/java/com/nq/controller/backend/AdminExchangeRateController.java
@@ -6,6 +6,7 @@
 import com.nq.service.ExchangeRateService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
@@ -27,11 +28,11 @@
     @ResponseBody
     public ServerResponse getInfo(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
                                   @RequestParam(value = "pageSize", defaultValue = "15") int pageSize) {
-        return exchangeRateService.getInfo(pageNum, pageSize);
+        return exchangeRateService.getInfoPage(pageNum, pageSize);
     }
 
     //修改汇率
-    @RequestMapping({"editRate.do"})
+    @PostMapping({"editRate.do"})
     @ResponseBody
     public ServerResponse editRate(ExchangeRate model, HttpServletRequest request) {
         if (model == null) {
diff --git a/src/main/java/com/nq/controller/backend/AdminStockAiController.java b/src/main/java/com/nq/controller/backend/AdminStockAiController.java
index 1c20e77..2727af8 100644
--- a/src/main/java/com/nq/controller/backend/AdminStockAiController.java
+++ b/src/main/java/com/nq/controller/backend/AdminStockAiController.java
@@ -1,16 +1,13 @@
  package com.nq.controller.backend;
 
  import com.nq.common.ServerResponse;
- import com.nq.pojo.Stock;
  import com.nq.pojo.StockAI;
+ import com.nq.pojo.StockAIOrder;
+ import com.nq.pojo.StockAIOrderPosition;
  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 org.springframework.web.bind.annotation.*;
 
  /**
   * 后台AI产品api
@@ -29,19 +26,75 @@
      @ResponseBody
      public ServerResponse getStockAiList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
                                           @RequestParam(value = "pageSize", defaultValue = "5") int pageSize,
-                                          @RequestParam(value = "stockType") String stockType,
-                                          @RequestParam(value = "status") String status,
-                                          @RequestParam(value = "name") String name)   {
+                                          @RequestParam(value = "stockType", required = false) String stockType,
+                                          @RequestParam(value = "status", required = false) String status,
+                                          @RequestParam(value = "name", required = false) String name)   {
          return stockAiService.getAdminStockAiList(pageNum, pageSize, stockType, status, name);
      }
 
-     //修改票信息
-     @RequestMapping({"editStockAi.do"})
+     //编辑ai交易产品
+     @PostMapping({"editStockAi.do"})
      @ResponseBody
      public ServerResponse editStockAi(StockAI model) {
-
          return stockAiService.editStockAi(model);
      }
 
+     /**
+      * 获取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 = "stockType") String stockType,
+                                          @RequestParam(value = "status") String status,
+                                          @RequestParam(value = "userId") Integer userId,
+                                          @RequestParam(value = "phone") String phone)   {
+         return stockAiService.getAdminStockAiOrderList(pageNum, pageSize, stockType, status, userId, phone);
+     }
+
+     /**
+      * ai订单建仓
+      * @param model
+      * @return
+      */
+     @PostMapping({"openPosition.do"})
+     @ResponseBody
+     public ServerResponse openPosition(StockAIOrderPosition model) {
+         if (model == null) {
+             return ServerResponse.createByErrorMsg("model is null");
+         }
+         if (model.getStockAiOrderId() == null) {
+             return ServerResponse.createByErrorMsg("stockAiOrderId is null");
+         }
+         if (model.getStockId() == null || model.getStockNum() == null || model.getStockPrice() == null ||
+                 model.getCoverDate() == null || model.getCoverPrice() == null) {
+             return ServerResponse.createByErrorMsg("请输入必填参数");
+         }
+         if (model.getStockNum() <= 0) {
+             return ServerResponse.createByErrorMsg("建仓股票数量必须大于0");
+         }
+         return stockAiService.openPosition(model);
+     }
+
+     /**
+      * ai订单操作
+      * @param id   订单id
+      * @param status  通过 拒绝 结算(已完成)
+      * @return
+      */
+     @GetMapping({"orderOperation.do"})
+     @ResponseBody
+     public ServerResponse orderOperation(@RequestParam(value = "id") Long id,
+                                          @RequestParam(value = "status") String status) {
+         if (id == null) {
+             return ServerResponse.createByErrorMsg("id is null");
+         }
+         if (status.isEmpty()) {
+             return ServerResponse.createByErrorMsg("status is null");
+         }
+         return stockAiService.orderOperation(id, status);
+     }
 
  }
\ No newline at end of file
diff --git a/src/main/java/com/nq/dao/StockAiMapper.java b/src/main/java/com/nq/dao/StockAiMapper.java
index fdf3659..42edeea 100644
--- a/src/main/java/com/nq/dao/StockAiMapper.java
+++ b/src/main/java/com/nq/dao/StockAiMapper.java
@@ -2,7 +2,7 @@
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.nq.pojo.StockAI;
-import com.nq.vo.stock.StockAiVO;
+import com.nq.vo.stock.ai.StockAiVO;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
diff --git a/src/main/java/com/nq/dao/StockAiOrderMapper.java b/src/main/java/com/nq/dao/StockAiOrderMapper.java
index 8c54e67..278c787 100644
--- a/src/main/java/com/nq/dao/StockAiOrderMapper.java
+++ b/src/main/java/com/nq/dao/StockAiOrderMapper.java
@@ -2,9 +2,22 @@
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.nq.pojo.StockAIOrder;
+import com.nq.vo.stock.ai.StockAiOrderTypeVO;
+import com.nq.vo.stock.ai.StockAiOrderVO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 @Mapper
 public interface StockAiOrderMapper extends BaseMapper<StockAIOrder> {
 
+    List<StockAiOrderVO> getAdminStockAiOrderList(@Param("stockType") String stockType,
+                                                  @Param("status") String status,
+                                                  @Param("userId") Integer userId,
+                                                  @Param("phone") String name);
+
+    List<StockAiOrderTypeVO> getStockAiOrderList(@Param("userId") Integer userId,
+                                                 @Param("status") String status);
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/nq/dao/StockAiOrderPositionMapper.java b/src/main/java/com/nq/dao/StockAiOrderPositionMapper.java
new file mode 100644
index 0000000..90deb76
--- /dev/null
+++ b/src/main/java/com/nq/dao/StockAiOrderPositionMapper.java
@@ -0,0 +1,10 @@
+package com.nq.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.nq.pojo.StockAIOrderPosition;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface StockAiOrderPositionMapper extends BaseMapper<StockAIOrderPosition> {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/nq/enums/EStockType.java b/src/main/java/com/nq/enums/EStockType.java
index 5713795..a88cba8 100644
--- a/src/main/java/com/nq/enums/EStockType.java
+++ b/src/main/java/com/nq/enums/EStockType.java
@@ -54,6 +54,17 @@
         }
     }
 
+    //根据货币获取类型
+    public static EStockType getEStockTypeBySymbol(String symbol){
+        if(EStockType.US.getSymbol().equals(symbol)){
+            return US;
+        }else if(EStockType.MX.getSymbol().equals(symbol)){
+            return  MX;
+        }else{
+            return  MX;
+        }
+    }
+
     public String getContryId() {
         return contryId;
     }
diff --git a/src/main/java/com/nq/enums/EUserAssets.java b/src/main/java/com/nq/enums/EUserAssets.java
index 5341b6a..817d212 100644
--- a/src/main/java/com/nq/enums/EUserAssets.java
+++ b/src/main/java/com/nq/enums/EUserAssets.java
@@ -19,6 +19,8 @@
     TRANSFER("TRANSFER","转换"),
     TOP_UP("TOP_UP","充值"),
     BUY_AI("BUY_AI","购买AI产品"),
+    BUY_AI_REJECT("BUY_AI_REJECT","拒绝买入AI产品申请"),
+    AI_SETTLEMENT("AI_SETTLEMENT","结算AI产品订单"),
     ;
 
     private String  code;
diff --git a/src/main/java/com/nq/pojo/StockAI.java b/src/main/java/com/nq/pojo/StockAI.java
index a31dd5f..cc88a81 100644
--- a/src/main/java/com/nq/pojo/StockAI.java
+++ b/src/main/java/com/nq/pojo/StockAI.java
@@ -23,8 +23,10 @@
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
 
+    //股票类型
     private String stockType;
 
+    //股票名称
     private String stockName;
 
     /**
diff --git a/src/main/java/com/nq/service/ExchangeRateService.java b/src/main/java/com/nq/service/ExchangeRateService.java
index ff08f01..ef1c98f 100644
--- a/src/main/java/com/nq/service/ExchangeRateService.java
+++ b/src/main/java/com/nq/service/ExchangeRateService.java
@@ -13,7 +13,9 @@
      * @param pageSize
      * @return
      */
-    ServerResponse getInfo(Integer pageNum, Integer pageSize);
+    ServerResponse getInfoPage(Integer pageNum, Integer pageSize);
+
+    ServerResponse getInfo();
 
     /**
      * 新增或编辑
diff --git a/src/main/java/com/nq/service/IStockAiService.java b/src/main/java/com/nq/service/IStockAiService.java
index 0ab1052..b99ca35 100644
--- a/src/main/java/com/nq/service/IStockAiService.java
+++ b/src/main/java/com/nq/service/IStockAiService.java
@@ -2,6 +2,7 @@
 
 import com.nq.common.ServerResponse;
 import com.nq.pojo.StockAI;
+import com.nq.pojo.StockAIOrderPosition;
 import org.springframework.web.bind.annotation.RequestParam;
 
 import javax.servlet.http.HttpServletRequest;
@@ -18,4 +19,10 @@
   ServerResponse getAdminStockAiList(Integer pageNum, Integer pageSize, String stockType, String status, String name);
 
   ServerResponse editStockAi(StockAI model);
+
+  ServerResponse getAdminStockAiOrderList(Integer pageNum, Integer pageSize, String stockType, String status, Integer userId, String phone);
+
+  ServerResponse openPosition(StockAIOrderPosition model);
+
+  ServerResponse orderOperation(Long id, String status);
 }
diff --git a/src/main/java/com/nq/service/impl/ExchangeRateServiceImpl.java b/src/main/java/com/nq/service/impl/ExchangeRateServiceImpl.java
index d703145..7374993 100644
--- a/src/main/java/com/nq/service/impl/ExchangeRateServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/ExchangeRateServiceImpl.java
@@ -29,7 +29,7 @@
     ExchangeRateMapper exchangeRateMapper;
 
     @Override
-    public ServerResponse getInfo(Integer pageNum, Integer pageSize) {
+    public ServerResponse getInfoPage(Integer pageNum, Integer pageSize) {
         try {
             PageHelper.startPage(pageNum, pageSize);
             List<ExchangeRate> exchangeRateList = exchangeRateMapper.selectList(null);
@@ -43,6 +43,17 @@
     }
 
     @Override
+    public ServerResponse getInfo() {
+        try {
+            List<ExchangeRate> exchangeRateList = exchangeRateMapper.selectList(null);
+            return ServerResponse.createBySuccess(exchangeRateList);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return ServerResponse.createByError();
+    }
+
+    @Override
     public ServerResponse updateRate(ExchangeRate model, HttpServletRequest request) {
         try {
             //新增
diff --git a/src/main/java/com/nq/service/impl/StockAiServiceImpl.java b/src/main/java/com/nq/service/impl/StockAiServiceImpl.java
index ae549fa..fe407ed 100644
--- a/src/main/java/com/nq/service/impl/StockAiServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/StockAiServiceImpl.java
@@ -7,10 +7,13 @@
 import com.nq.common.ServerResponse;
 import com.nq.dao.StockAiMapper;
 import com.nq.dao.StockAiOrderMapper;
+import com.nq.dao.StockAiOrderPositionMapper;
 import com.nq.enums.*;
 import com.nq.pojo.*;
 import com.nq.service.*;
-import com.nq.vo.stock.StockAiVO;
+import com.nq.vo.stock.ai.StockAiOrderTypeVO;
+import com.nq.vo.stock.ai.StockAiOrderVO;
+import com.nq.vo.stock.ai.StockAiVO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,15 +24,20 @@
 import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 
 @Service("iStockAiService")
 public class StockAiServiceImpl implements IStockAiService {
     private static final Logger log = LoggerFactory.getLogger(StockAiServiceImpl.class);
+    private final ConcurrentHashMap<Long, Object> locks = new ConcurrentHashMap<>();
 
     @Autowired
     StockAiMapper stockAiMapper;
     @Autowired
     StockAiOrderMapper stockAiOrderMapper;
+    @Autowired
+    StockAiOrderPositionMapper stockAiOrderPositionMapper;
     @Autowired
     ISiteProductService iSiteProductService;
     @Autowired
@@ -67,37 +75,40 @@
      * @return
      */
     @Override
-    @Transactional
     public ServerResponse buyStockAi(Long id, BigDecimal buyNum, HttpServletRequest request) {
         try {
             User user = iUserService.getCurrentUser(request);
             if (user == null ){
-                return ServerResponse.createBySuccessMsg("請先登錄");
+                return ServerResponse.createBySuccessMsg("请先登录");
             }
-            synchronized (user.getId()){
+            Object lock = locks.computeIfAbsent(Long.valueOf(user.getId()), k -> new Object());
+            synchronized (lock){
                 SiteProduct siteProduct = iSiteProductService.getProductSetting();
                 if (siteProduct.getRealNameDisplay() && user.getIsActive() != 2) {
-                    return ServerResponse.createByErrorMsg("订单失败,请先实名认证", request);
+                    return ServerResponse.createByErrorMsg("订单失败,请先实名认证");
                 }
 
                 if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) {
-                    return ServerResponse.createByErrorMsg("订单失败,帐户已被锁定", request);
+                    return ServerResponse.createByErrorMsg("订单失败,帐户已被锁定");
                 }
 
                 StockAI stockAI = stockAiMapper.selectById(id);
                 if (stockAI == null) {
-                    return ServerResponse.createByErrorMsg("订单失败,AI股票不存在", request);
+                    return ServerResponse.createByErrorMsg("订单失败,AI股票不存在");
                 }
-
+                if (!stockAI.getStatus().equals(EStockAIStatus.online.getValue())) {
+                    return ServerResponse.createByErrorMsg("订单失败,AI股票已下架");
+                }
                 if(buyNum.compareTo(stockAI.getMinPrice()) < 0){
-                    return ServerResponse.createByErrorMsg("最低购买数量" + stockAI.getMinPrice(), request);
+                    return ServerResponse.createByErrorMsg("最低购买数量" + stockAI.getMinPrice());
                 }
                 //获取用户账户
                 UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(EStockType.MX.getCode(), user.getId());
                 BigDecimal finalBuyNum = buyNum;    //购买金额
                 //如果不是墨西哥币需要转换金额
-                if (!stockAI.getStockType().equals(EStockType.US.getCode())) {
+                if (!stockAI.getStockType().equals(EStockType.MX.getCode())) {
                     EStockType stockType = EStockType.getEStockTypeByCode(stockAI.getStockType());
+
                     ExchangeRate exchangeRate = exchangeRateRepository.findExchangeRateByCurrencyAndConversionCurrency(stockType.getSymbol(), EStockType.MX.getSymbol())
                             .orElse(null);
                     if (exchangeRate == null) {
@@ -107,11 +118,11 @@
                     buyNum = iUserAssetsServices.exchangeAmountByRate(buyNum, exchangeRate.getRata());
                 }
                 if(buyNum.compareTo(userAssets.getAvailableBalance()) > 0){
-                    return ServerResponse.createByErrorMsg("可用余额不足" + userAssets.getAvailableBalance(), request);
+                    return ServerResponse.createByErrorMsg("可用余额不足" + userAssets.getAvailableBalance());
                 }
 
                 if(userAssets.getAmountToBeCovered().compareTo(BigDecimal.ZERO) > 0){
-                    return ServerResponse.createByErrorMsg("请先缴清待补资金", request);
+                    return ServerResponse.createByErrorMsg("请先缴清待补资金");
                 }
 
                 StockAIOrder stockAIOrder = new StockAIOrder();
@@ -124,10 +135,10 @@
                 stockAIOrder.setStatus(EStockAIOrderStatus.wait.getStatus());   //等待审核
                 stockAiOrderMapper.insert(stockAIOrder);
                 iUserAssetsServices.aiAvailableBalanceChange(userAssets, EUserAssets.BUY_AI, buyNum);
-                return ServerResponse.createBySuccessMsg("下单成功", request);
+                return ServerResponse.createBySuccessMsg("下单成功");
             }
         } catch (Exception e) {
-            log.error("StockAiService buyStockAiList error", e);
+            log.error("StockAiService buyStockAiList  {}", e.getMessage());
         }
         return ServerResponse.createByError();
     }
@@ -145,15 +156,12 @@
         try {
             User user = iUserService.getCurrentUser(request);
             if (user == null ){
-                return ServerResponse.createBySuccessMsg("請先登錄");
+                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));
+            List<StockAiOrderTypeVO> stockAIOrders = stockAiOrderMapper.getStockAiOrderList(user.getId(), status);
             // 获取分页信息
-            PageInfo<StockAIOrder> pageInfo = new PageInfo<>(stockAIOrders);
+            PageInfo<StockAiOrderTypeVO> pageInfo = new PageInfo<>(stockAIOrders);
             return ServerResponse.createBySuccess(pageInfo);
         } catch (Exception e) {
             log.error("StockAiService getStockAiOrderList error", e);
@@ -223,4 +231,148 @@
         }
         return ServerResponse.createByError();
     }
+
+    /**
+     * 后台查询ai交易订单
+     * @param pageNum
+     * @param pageSize
+     * @param stockType
+     * @param status
+     * @param userId
+     * @param phone
+     * @return
+     */
+    @Override
+    public ServerResponse getAdminStockAiOrderList(Integer pageNum, Integer pageSize, String stockType, String status, Integer userId, String phone) {
+        try {
+            PageHelper.startPage(pageNum, pageSize);
+            List<StockAiOrderVO> stockAIOrders = stockAiOrderMapper.getAdminStockAiOrderList(stockType, status, userId, phone);
+            // 获取分页信息
+            PageInfo<StockAiOrderVO> pageInfo = new PageInfo<>(stockAIOrders);
+
+            if (!pageInfo.getList().isEmpty()) {
+                List<StockAiOrderVO> newStockAiOrders = pageInfo.getList();
+                newStockAiOrders.forEach(stockAiOrderVO -> {
+                    EStockType eStockType = EStockType.getEStockTypeByCode(stockAiOrderVO.getStockType());
+                    stockAiOrderVO.setStockTypeName(eStockType.getSymbol1());
+                    stockAiOrderVO.setSymbol(stockType);
+                });
+                pageInfo.setList(newStockAiOrders);
+            }
+            return ServerResponse.createBySuccess(pageInfo);
+        } catch (Exception e) {
+            log.error("StockAiService getAdminStockAiOrderList error", e);
+        }
+        return ServerResponse.createByError();
+    }
+
+    /**
+     * 建仓
+     * @param model
+     * @return
+     */
+    @Override
+    public ServerResponse openPosition(StockAIOrderPosition model) {
+        try {
+            Object lock = locks.computeIfAbsent(model.getStockAiOrderId(), k -> new Object());
+            synchronized (lock) {
+                StockAIOrder stockAIOrder = stockAiOrderMapper.selectById(model.getStockAiOrderId());
+                if (stockAIOrder == null) {
+                    return ServerResponse.createByErrorMsg("AI订单不存在");
+                }
+
+                if (!stockAIOrder.getStatus().equals(EStockAIOrderStatus.passed.getStatus())) {
+                    return ServerResponse.createByErrorMsg("申请通过状态才能建仓!");
+                }
+                //计算剩余金额
+                BigDecimal remainAmount = model.getStockPrice().multiply(BigDecimal.valueOf(model.getStockNum()));
+                if (stockAIOrder.getRemainAmount().compareTo(remainAmount) < 0) {
+                    return ServerResponse.createByErrorMsg("剩余买入金额不足");
+                }
+                stockAIOrder.setRemainAmount(stockAIOrder.getBuyAmount().subtract(remainAmount));
+
+                //计算收益  (平仓-建仓)*数量
+                BigDecimal earnings = model.getCoverPrice().subtract(model.getStockPrice()).multiply(BigDecimal.valueOf(model.getStockNum()));
+                model.setCreatDate(new Date());
+                model.setEarnings(earnings);
+                //保存
+                stockAiOrderMapper.updateById(stockAIOrder);
+                stockAiOrderPositionMapper.insert(model);
+                return ServerResponse.createBySuccess("建仓成功");
+            }
+        } catch (Exception e) {
+            log.error("StockAiService openPosition error", e);
+        }
+        return ServerResponse.createByError();
+    }
+
+    @Override
+    public ServerResponse orderOperation(Long id, String status) {
+        try {
+            Object lock = locks.computeIfAbsent(id, k -> new Object());
+            synchronized (lock) {
+                StockAIOrder stockAIOrder = stockAiOrderMapper.selectById(id);
+                if (stockAIOrder == null) {
+                    return ServerResponse.createByErrorMsg("AI订单不存在");
+                }
+                if (status.equals(EStockAIOrderStatus.passed.getStatus())) {
+                    //通过
+                    if (!stockAIOrder.getStatus().equals(EStockAIOrderStatus.wait.getStatus())) {
+                        return ServerResponse.createByErrorMsg("只能通过待审核订单");
+                    }
+                } else if (status.equals(EStockAIOrderStatus.notPass.getStatus())) {
+                    if (!stockAIOrder.getStatus().equals(EStockAIOrderStatus.wait.getStatus())) {
+                        return ServerResponse.createByErrorMsg("只能拒绝待审核订单");
+                    }
+                    //拒绝TODO 归还买入金额
+                    //获取用户账户
+                    UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(EStockType.MX.getCode(), stockAIOrder.getUserId());
+                    StockAI stockAI = stockAiMapper.selectById(stockAIOrder.getStockAiId());
+                    //买入金额
+                    BigDecimal buyNum =  stockAIOrder.getBuyAmount();
+                    //如果不是墨西哥币需要转换金额
+                    if (!stockAI.getStockType().equals(EStockType.MX.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());
+                    }
+                    iUserAssetsServices.aiAvailableBalanceChange(userAssets, EUserAssets.BUY_AI_REJECT, buyNum);
+                } else if (status.equals(EStockAIOrderStatus.finished.getStatus())) {
+                    if (!stockAIOrder.getStatus().equals(EStockAIOrderStatus.passed.getStatus())) {
+                        return ServerResponse.createByErrorMsg("只能结算申请通过订单");
+                    }
+                    //结算 TODO结算建仓 计算收益
+                    //所有建仓
+                    List<StockAIOrderPosition> stockAIOrderPositionList = stockAiOrderPositionMapper
+                            .selectList(new QueryWrapper<StockAIOrderPosition>()
+                                    .eq("stock_ai_order_id", id));
+                    //结算金额
+                    BigDecimal amount = stockAIOrder.getBuyAmount();
+                    if (!stockAIOrderPositionList.isEmpty()) {
+                        //总收益
+                        BigDecimal earningsSUM = stockAIOrderPositionList.stream()
+                                .map(StockAIOrderPosition::getEarnings)  // 获取 BigDecimal 字段
+                                .filter(Objects::nonNull)    // 过滤 null 值
+                                .reduce(BigDecimal.ZERO, BigDecimal::add); // 累加
+                        amount = amount.add(earningsSUM);
+                    }
+                    //获取用户账户
+                    UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(EStockType.MX.getCode(), stockAIOrder.getUserId());
+                    iUserAssetsServices.aiAvailableBalanceChange(userAssets, EUserAssets.AI_SETTLEMENT, amount);
+                }
+                stockAIOrder.setStatus(status);
+                stockAIOrder.setAuditDate(new Date());
+                stockAiOrderMapper.updateById(stockAIOrder);
+                return ServerResponse.createBySuccess("操作成功");
+            }
+        } catch (Exception e) {
+            log.error("StockAiService openPosition error", e);
+        }
+        return ServerResponse.createByError();
+    }
 }
diff --git a/src/main/java/com/nq/service/impl/StockServiceImpl.java b/src/main/java/com/nq/service/impl/StockServiceImpl.java
index 80f6722..a12d981 100644
--- a/src/main/java/com/nq/service/impl/StockServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/StockServiceImpl.java
@@ -786,6 +786,7 @@
             Map<String, Object> resultMap = new HashMap<>();
             DataStockBean cacheBaseStock = RedisKeyUtil.getCacheBaseStock(stockType, pid);
             if (cacheBaseStock != null) {
+                resultMap.put("name", cacheBaseStock.getName());
                 resultMap.put("last", cacheBaseStock.getLast());
                 resultMap.put("chg", cacheBaseStock.getChg());
                 resultMap.put("chgPct", cacheBaseStock.getChgPct());
diff --git a/src/main/java/com/nq/service/impl/UserAssetsServices.java b/src/main/java/com/nq/service/impl/UserAssetsServices.java
index 2f42217..1584494 100644
--- a/src/main/java/com/nq/service/impl/UserAssetsServices.java
+++ b/src/main/java/com/nq/service/impl/UserAssetsServices.java
@@ -300,6 +300,16 @@
             userAssets.setFreezeMoney(userAssets.getFreezeMoney().add(amount));
             //扣除可用金额
             userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount.negate()));
+        } else if (eUserAssets.getCode().equals(EUserAssets.BUY_AI_REJECT.getCode())) {
+            //解除冻结金额
+            userAssets.setFreezeMoney(userAssets.getFreezeMoney().add(amount.negate()));
+            //归还可用金额
+            userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount));
+        } else if (eUserAssets.getCode().equals(EUserAssets.AI_SETTLEMENT.getCode())) {
+            //解除冻结金额
+            userAssets.setFreezeMoney(userAssets.getFreezeMoney().add(amount.negate()));
+            //归还可用金额 + 收益
+            userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount));
         }
         String after = userAssets.getAvailableBalance().toString();
         MoneyLog moneyLog = new MoneyLog();
diff --git a/src/main/java/com/nq/vo/stock/ai/StockAiOrderTypeVO.java b/src/main/java/com/nq/vo/stock/ai/StockAiOrderTypeVO.java
new file mode 100644
index 0000000..9346627
--- /dev/null
+++ b/src/main/java/com/nq/vo/stock/ai/StockAiOrderTypeVO.java
@@ -0,0 +1,16 @@
+package com.nq.vo.stock.ai;
+
+import com.nq.pojo.StockAIOrder;
+import lombok.Data;
+
+@Data
+public class StockAiOrderTypeVO extends StockAIOrder {
+
+
+    //AI交易产品名称
+    private String stockName;
+    //AI交易产品名称
+    private String stockType;
+
+}
+
diff --git a/src/main/java/com/nq/vo/stock/ai/StockAiOrderVO.java b/src/main/java/com/nq/vo/stock/ai/StockAiOrderVO.java
new file mode 100644
index 0000000..8904eee
--- /dev/null
+++ b/src/main/java/com/nq/vo/stock/ai/StockAiOrderVO.java
@@ -0,0 +1,31 @@
+package com.nq.vo.stock.ai;
+
+import com.nq.pojo.StockAIOrder;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class StockAiOrderVO extends StockAIOrder {
+
+    //手机号
+    private String phone;
+    //真实姓名
+    private String realName;
+    //AI交易产品名称
+    private String stockName;
+    //股票类型名(所属市场)
+    private String stockTypeName;
+    //交易成功率
+    private BigDecimal successRate;
+    //预期收益率
+    private BigDecimal expectedEarning;
+    //交易周期 天
+    private Integer cycle;
+
+    //AI交易产品名称
+    private String stockType;
+    //货币符号
+    private String symbol;
+}
+
diff --git a/src/main/java/com/nq/vo/stock/StockAiVO.java b/src/main/java/com/nq/vo/stock/ai/StockAiVO.java
similarity index 87%
rename from src/main/java/com/nq/vo/stock/StockAiVO.java
rename to src/main/java/com/nq/vo/stock/ai/StockAiVO.java
index 12eeed3..0aa61f3 100644
--- a/src/main/java/com/nq/vo/stock/StockAiVO.java
+++ b/src/main/java/com/nq/vo/stock/ai/StockAiVO.java
@@ -1,4 +1,4 @@
-package com.nq.vo.stock;
+package com.nq.vo.stock.ai;
 
 import com.nq.pojo.StockAI;
 import lombok.Data;
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index be81f31..c278558 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -91,7 +91,7 @@
   devtools:
     restart:
       # 热部署开关
-      enabled: true
+      enabled: false
   freemarker:
     cache: false    #false 页面不设置缓存,更改及生效!
 
diff --git a/src/main/resources/mapper/StockAiMapper.xml b/src/main/resources/mapper/StockAiMapper.xml
index 4c41451..2d22484 100644
--- a/src/main/resources/mapper/StockAiMapper.xml
+++ b/src/main/resources/mapper/StockAiMapper.xml
@@ -6,7 +6,7 @@
     id, stock_type, stock_name, min_price, success_rate, expected_earning, cycle, status, create_date
   </sql>
 
-  <select id="getStockAiList" resultType="com.nq.vo.stock.StockAiVO">
+  <select id="getStockAiList" resultType="com.nq.vo.stock.ai.StockAiVO">
     SELECT
     <include refid="Base_Column_List" />
     from stock_ai
diff --git a/src/main/resources/mapper/StockAiOrderMapper.xml b/src/main/resources/mapper/StockAiOrderMapper.xml
new file mode 100644
index 0000000..9aa1fb3
--- /dev/null
+++ b/src/main/resources/mapper/StockAiOrderMapper.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.nq.dao.StockAiOrderMapper" >
+
+  <sql id="Base_Column_List" >
+    id, user_id, stock_ai_id, buy_date, buy_amount, remain_amount, real_earning, status, audit_date
+  </sql>
+
+  <select id="getAdminStockAiOrderList" resultType="com.nq.vo.stock.ai.StockAiOrderVO">
+    SELECT s.id, user_id, stock_ai_id, buy_date, buy_amount, remain_amount, real_earning, s.status, audit_date,
+        phone, real_name, stock_name, success_rate, expected_earning, cycle, a.stock_type
+    FROM stock_ai_order s
+    LEFT JOIN user u ON u.id = s.user_id
+    LEFT JOIN stock_ai a ON a.id = s.stock_ai_id
+    <where>
+      <if test="stockType != null and stockType != ''">
+        and a.stock_type = #{stockType}
+      </if>
+      <if test="status != null and status != ''">
+        and s.status = #{status}
+      </if>
+      <if test="userId != null">
+        and u.id = #{userId}
+      </if>
+      <if test="phone != null and phone != ''">
+        and u.phone like concat('%',#{phone},'%')
+      </if>
+    </where>
+    ORDER BY buy_date DESC
+  </select>
+
+  <select id="getStockAiOrderList" resultType="com.nq.vo.stock.ai.StockAiOrderTypeVO">
+    SELECT s.id, user_id, stock_ai_id, buy_date, buy_amount, remain_amount, real_earning, s.status, audit_date,
+    a.stock_name, a.stock_type
+    FROM stock_ai_order s
+    LEFT JOIN stock_ai a ON a.id = s.stock_ai_id
+    <where>
+      <if test="status != null and status != ''">
+        and s.status = #{status}
+      </if>
+      <if test="userId != null">
+        and user_id = #{userId}
+      </if>
+    </where>
+    ORDER BY buy_date DESC
+  </select>
+
+</mapper>

--
Gitblit v1.9.3