Просмотр исходного кода

fix(robot): 对齐机器人配置前后端校验与默认值

新增 ConfigRobotService 校验/默认值逻辑,并在创建与更新接口中统一应用,避免与 config_robot 表约束不一致导致保存失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
jean 1 месяц назад
Родитель
Сommit
54ea10192c

+ 38 - 18
doge/admin/src/main/java/com/bizzan/bitrade/controller/system/ConfigRobotController.java

@@ -13,6 +13,7 @@ import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Sort;
 import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
@@ -62,9 +63,18 @@ public class ConfigRobotController extends BaseAdminController {
 
     @PostMapping("create")
     public MessageResult create(@RequestBody ConfigRobot robot) {
+        if (!StringUtils.hasText(robot.getSymbol())) {
+            return error("交易对不能为空");
+        }
+        robot.setSymbol(robot.getSymbol().trim().toLowerCase());
         if (configRobotService.findBySymbol(robot.getSymbol()) != null) {
             return error("该交易对已配置: " + robot.getSymbol());
         }
+        configRobotService.applyDefaults(robot);
+        String validateErr = configRobotService.validate(robot);
+        if (validateErr != null) {
+            return error(validateErr);
+        }
         return success(configRobotService.create(robot));
     }
 
@@ -72,30 +82,40 @@ public class ConfigRobotController extends BaseAdminController {
     public MessageResult update(@RequestBody ConfigRobot robot) {
         ConfigRobot existing = configRobotService.findOne(robot.getId());
         Assert.notNull(existing, "记录不存在");
-        if (!existing.getSymbol().equals(robot.getSymbol())) {
-            ConfigRobot dup = configRobotService.findBySymbol(robot.getSymbol());
-            if (dup != null) {
-                return error("该交易对已配置: " + robot.getSymbol());
+        if (StringUtils.hasText(robot.getSymbol())) {
+            robot.setSymbol(robot.getSymbol().trim().toLowerCase());
+            if (!existing.getSymbol().equals(robot.getSymbol())) {
+                ConfigRobot dup = configRobotService.findBySymbol(robot.getSymbol());
+                if (dup != null) {
+                    return error("该交易对已配置: " + robot.getSymbol());
+                }
             }
+        } else {
+            robot.setSymbol(existing.getSymbol());
+        }
+        configRobotService.applyDefaults(robot);
+        String validateErr = configRobotService.validate(robot);
+        if (validateErr != null) {
+            return error(validateErr);
         }
         existing.setSymbol(robot.getSymbol());
         existing.setTrend(robot.getTrend());
         existing.setOpenPrice(robot.getOpenPrice());
-        if (robot.getTargetPrice() != null) existing.setTargetPrice(robot.getTargetPrice());
-        if (robot.getHoverRange() != null) existing.setHoverRange(robot.getHoverRange());
-        if (robot.getMinChangeRate() != null) existing.setMinChangeRate(robot.getMinChangeRate());
-        if (robot.getMaxChangeRate() != null) existing.setMaxChangeRate(robot.getMaxChangeRate());
-        if (robot.getHoverMinChangeRate() != null) existing.setHoverMinChangeRate(robot.getHoverMinChangeRate());
-        if (robot.getHoverMaxChangeRate() != null) existing.setHoverMaxChangeRate(robot.getHoverMaxChangeRate());
-        if (robot.getBridgeMinRatio() != null) existing.setBridgeMinRatio(robot.getBridgeMinRatio());
-        if (robot.getBridgeMaxRatio() != null) existing.setBridgeMaxRatio(robot.getBridgeMaxRatio());
-        if (robot.getUpProbability() != null) existing.setUpProbability(robot.getUpProbability());
-        if (robot.getIntervalMs() != null) existing.setIntervalMs(robot.getIntervalMs());
-        if (robot.getKlineMaxHighRatio() != null) existing.setKlineMaxHighRatio(robot.getKlineMaxHighRatio());
-        if (robot.getKlineMaxLowRatio() != null) existing.setKlineMaxLowRatio(robot.getKlineMaxLowRatio());
-        if (robot.getMinuteChangeLimit() != null) existing.setMinuteChangeLimit(robot.getMinuteChangeLimit());
+        existing.setTargetPrice(robot.getTargetPrice());
+        existing.setHoverRange(robot.getHoverRange());
+        existing.setMinChangeRate(robot.getMinChangeRate());
+        existing.setMaxChangeRate(robot.getMaxChangeRate());
+        existing.setHoverMinChangeRate(robot.getHoverMinChangeRate());
+        existing.setHoverMaxChangeRate(robot.getHoverMaxChangeRate());
+        existing.setBridgeMinRatio(robot.getBridgeMinRatio());
+        existing.setBridgeMaxRatio(robot.getBridgeMaxRatio());
+        existing.setUpProbability(robot.getUpProbability());
+        existing.setIntervalMs(robot.getIntervalMs());
+        existing.setKlineMaxHighRatio(robot.getKlineMaxHighRatio());
+        existing.setKlineMaxLowRatio(robot.getKlineMaxLowRatio());
+        existing.setMinuteChangeLimit(robot.getMinuteChangeLimit());
         existing.setStatus(robot.getStatus());
-        if (robot.getRemark() != null) existing.setRemark(robot.getRemark());
+        existing.setRemark(robot.getRemark());
         return success(configRobotService.update(existing));
     }
 

+ 173 - 0
doge/core/src/main/java/com/bizzan/bitrade/service/ConfigRobotService.java

@@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 
 import javax.persistence.criteria.Predicate;
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -19,6 +20,16 @@ import java.util.List;
 @Service
 public class ConfigRobotService extends BaseService {
 
+    private static final BigDecimal DEFAULT_HOVER_RANGE = new BigDecimal("0.0200");
+    private static final BigDecimal DEFAULT_MIN_CHANGE_RATE = new BigDecimal("0.001000");
+    private static final BigDecimal DEFAULT_MAX_CHANGE_RATE = new BigDecimal("0.005000");
+    private static final BigDecimal DEFAULT_HOVER_MIN_CHANGE_RATE = new BigDecimal("0.000500");
+    private static final BigDecimal DEFAULT_HOVER_MAX_CHANGE_RATE = new BigDecimal("0.002000");
+    private static final BigDecimal DEFAULT_BRIDGE_MIN_RATIO = new BigDecimal("0.0500");
+    private static final BigDecimal DEFAULT_BRIDGE_MAX_RATIO = new BigDecimal("0.1000");
+    private static final BigDecimal DEFAULT_KLINE_MAX_HIGH_RATIO = new BigDecimal("0.0050");
+    private static final BigDecimal DEFAULT_KLINE_MAX_LOW_RATIO = new BigDecimal("0.0050");
+
     @Autowired
     private ConfigRobotDao configRobotDao;
 
@@ -48,8 +59,153 @@ public class ConfigRobotService extends BaseService {
         return configRobotDao.findAll(spec, pageable);
     }
 
+    /**
+     * 校验机器人配置,与 config_robot 表 NOT NULL / 业务规则对齐
+     *
+     * @return 错误信息,通过时返回 null
+     */
+    public String validate(ConfigRobot robot) {
+        if (robot == null) {
+            return "配置不能为空";
+        }
+        if (!StringUtils.hasText(robot.getSymbol())) {
+            return "交易对不能为空";
+        }
+        if (robot.getTrend() == null || robot.getTrend() < 1 || robot.getTrend() > 3) {
+            return "趋势方向必须为 1(涨)、2(跌) 或 3(横盘)";
+        }
+        if (robot.getStatus() == null || robot.getStatus() < 1 || robot.getStatus() > 4) {
+            return "状态必须为 1~4";
+        }
+
+        String err = requirePositive(robot.getOpenPrice(), "起始参考价");
+        if (err != null) {
+            return err;
+        }
+
+        err = requirePositive(robot.getHoverRange(), "盘整区间比例");
+        if (err != null) {
+            return err;
+        }
+
+        err = requirePositive(robot.getMinChangeRate(), "趋势最小波动率");
+        if (err != null) {
+            return err;
+        }
+        err = requirePositive(robot.getMaxChangeRate(), "趋势最大波动率");
+        if (err != null) {
+            return err;
+        }
+        err = requireMinNotGreaterThanMax(robot.getMinChangeRate(), robot.getMaxChangeRate(),
+                "趋势最小波动率不能大于最大波动率");
+        if (err != null) {
+            return err;
+        }
+
+        err = requirePositive(robot.getHoverMinChangeRate(), "盘整最小波动率");
+        if (err != null) {
+            return err;
+        }
+        err = requirePositive(robot.getHoverMaxChangeRate(), "盘整最大波动率");
+        if (err != null) {
+            return err;
+        }
+        err = requireMinNotGreaterThanMax(robot.getHoverMinChangeRate(), robot.getHoverMaxChangeRate(),
+                "盘整最小波动率不能大于最大波动率");
+        if (err != null) {
+            return err;
+        }
+
+        if (robot.getBridgeMinRatio() == null) {
+            return "成桥最小比例不能为空";
+        }
+        if (robot.getBridgeMaxRatio() == null) {
+            return "成桥最大比例不能为空";
+        }
+        if (robot.getBridgeMinRatio().compareTo(BigDecimal.ZERO) < 0
+                || robot.getBridgeMaxRatio().compareTo(BigDecimal.ZERO) < 0) {
+            return "成桥比例不能为负数";
+        }
+        err = requireMinNotGreaterThanMax(robot.getBridgeMinRatio(), robot.getBridgeMaxRatio(),
+                "成桥最小比例不能大于最大比例");
+        if (err != null) {
+            return err;
+        }
+
+        err = requirePositive(robot.getKlineMaxHighRatio(), "K线最高价上限比例");
+        if (err != null) {
+            return err;
+        }
+        err = requirePositive(robot.getKlineMaxLowRatio(), "K线最低价下限比例");
+        if (err != null) {
+            return err;
+        }
+
+        if (robot.getIntervalMs() == null || robot.getIntervalMs() < 1) {
+            return "执行间隔不能小于 1ms";
+        }
+
+        if (robot.getTargetPrice() != null && robot.getTargetPrice().compareTo(BigDecimal.ZERO) <= 0) {
+            return "目标价格必须大于 0";
+        }
+        if (robot.getUpProbability() != null) {
+            if (robot.getUpProbability().compareTo(BigDecimal.ZERO) < 0
+                    || robot.getUpProbability().compareTo(BigDecimal.ONE) > 0) {
+                return "上涨概率必须在 0~1 之间";
+            }
+        }
+        if (robot.getMinuteChangeLimit() != null && robot.getMinuteChangeLimit().compareTo(BigDecimal.ZERO) <= 0) {
+            return "分钟涨跌幅限制必须大于 0";
+        }
+        if (robot.getRemark() != null && robot.getRemark().length() > 200) {
+            return "备注不能超过 200 字";
+        }
+        return null;
+    }
+
+    /** 写入表默认值(仅对 NOT NULL 且未传值的字段) */
+    public void applyDefaults(ConfigRobot robot) {
+        if (robot.getTrend() == null) {
+            robot.setTrend(1);
+        }
+        if (robot.getHoverRange() == null) {
+            robot.setHoverRange(DEFAULT_HOVER_RANGE);
+        }
+        if (robot.getMinChangeRate() == null) {
+            robot.setMinChangeRate(DEFAULT_MIN_CHANGE_RATE);
+        }
+        if (robot.getMaxChangeRate() == null) {
+            robot.setMaxChangeRate(DEFAULT_MAX_CHANGE_RATE);
+        }
+        if (robot.getHoverMinChangeRate() == null) {
+            robot.setHoverMinChangeRate(DEFAULT_HOVER_MIN_CHANGE_RATE);
+        }
+        if (robot.getHoverMaxChangeRate() == null) {
+            robot.setHoverMaxChangeRate(DEFAULT_HOVER_MAX_CHANGE_RATE);
+        }
+        if (robot.getBridgeMinRatio() == null) {
+            robot.setBridgeMinRatio(DEFAULT_BRIDGE_MIN_RATIO);
+        }
+        if (robot.getBridgeMaxRatio() == null) {
+            robot.setBridgeMaxRatio(DEFAULT_BRIDGE_MAX_RATIO);
+        }
+        if (robot.getIntervalMs() == null) {
+            robot.setIntervalMs(3000);
+        }
+        if (robot.getStatus() == null) {
+            robot.setStatus(1);
+        }
+        if (robot.getKlineMaxHighRatio() == null) {
+            robot.setKlineMaxHighRatio(DEFAULT_KLINE_MAX_HIGH_RATIO);
+        }
+        if (robot.getKlineMaxLowRatio() == null) {
+            robot.setKlineMaxLowRatio(DEFAULT_KLINE_MAX_LOW_RATIO);
+        }
+    }
+
     @Transactional(rollbackFor = Exception.class)
     public ConfigRobot create(ConfigRobot robot) {
+        applyDefaults(robot);
         robot.setCreateTime(new Date());
         robot.setUpdateTime(new Date());
         return configRobotDao.save(robot);
@@ -65,4 +221,21 @@ public class ConfigRobotService extends BaseService {
     public void delete(Long id) {
         configRobotDao.delete(id);
     }
+
+    private static String requirePositive(BigDecimal value, String label) {
+        if (value == null) {
+            return label + "不能为空";
+        }
+        if (value.compareTo(BigDecimal.ZERO) <= 0) {
+            return label + "必须大于 0";
+        }
+        return null;
+    }
+
+    private static String requireMinNotGreaterThanMax(BigDecimal min, BigDecimal max, String message) {
+        if (min != null && max != null && min.compareTo(max) > 0) {
+            return message;
+        }
+        return null;
+    }
 }