From befbf57e4112d07003bff18102f556a1e5a154de Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Wed, 22 Apr 2026 10:53:37 +0800
Subject: [PATCH] 1
---
trading-order-admin/src/main/java/com/yami/trading/api/controller/trader/ApiTraderController.java | 623 +++++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 477 insertions(+), 146 deletions(-)
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/trader/ApiTraderController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/trader/ApiTraderController.java
index bca842c..b8842c0 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/trader/ApiTraderController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/trader/ApiTraderController.java
@@ -2,10 +2,15 @@
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yami.trading.bean.contract.domain.ContractOrder;
+import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.bean.model.User;
+import com.yami.trading.common.domain.BaseEntity;
+import com.yami.trading.bean.trader.FollowCommissionType;
import com.yami.trading.bean.trader.domain.Trader;
import com.yami.trading.bean.trader.domain.TraderFollowSetting;
import com.yami.trading.bean.trader.domain.TraderFollowUser;
+import com.yami.trading.bean.trader.domain.TraderFollowUserOrder;
import com.yami.trading.bean.trader.domain.TraderInviteLink;
import com.yami.trading.common.constants.Constants;
import com.yami.trading.common.exception.BusinessException;
@@ -14,6 +19,7 @@
import com.yami.trading.common.web.ResultObject;
import com.yami.trading.security.common.util.SecurityUtils;
import com.yami.trading.service.contract.ContractOrderService;
+import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.trader.*;
import com.yami.trading.service.user.UserService;
import org.apache.commons.logging.Log;
@@ -25,11 +31,16 @@
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.*;
+import java.util.regex.Pattern;
@@ -55,6 +66,9 @@
private TraderFollowUserService traderFollowUserService;
@Autowired
+ private TraderFollowUserOrderService traderFollowUserOrderService;
+
+ @Autowired
private TraderOrderService traderOrderService;
@Autowired
@@ -62,6 +76,9 @@
@Autowired
private UserService userService;
+
+ @Autowired
+ private ItemService itemService;
@Autowired
private TraderFollowSettingService traderFollowSettingService;
@@ -101,21 +118,25 @@
for (int i = 0; i < data.size(); i++) {
Map<String, Object> map = data.get(i);
String partyId = SecurityUtils.getCurrentUserId();
+ Object commissionTypeForRemaining = map.get("follow_commission_type");
if (partyId != null) {
TraderFollowUser user = this.traderFollowUserService.findByPartyIdAndTrader_partyId(partyId, map.get("partyId").toString());
if (user != null) {
- /**
- * 1跟随 2未跟随
- */
- map.put("follow_state", "1");
+ map.put("follow_state", user.getState());
+ map.put("follow_volume", user.getVolume());
+ map.put("follow_volume_max", user.getVolumeMax());
+ map.put("follow_monthly_remaining_days",
+ computeFollowMonthlyRemainingDays(commissionTypeForRemaining, user));
map.remove("partyId");
} else {
map.put("follow_state", "2");
+ map.put("follow_monthly_remaining_days", null);
map.remove("partyId");
}
} else {
map.put("follow_state", "2");
+ map.put("follow_monthly_remaining_days", null);
map.remove("partyId");
}
}
@@ -148,27 +169,97 @@
@RequestMapping(action + "istrader.action")
public Object istrader(HttpServletRequest request) {
ResultObject resultObject = new ResultObject();
- String id = request.getParameter("id");
+ try {
+ String id = request.getParameter("id");
- Trader data = null;
- if(StringUtils.isNotEmpty(id)) {
- data = traderService.findById(id);
- } else{
- String partyId = SecurityUtils.getCurrentUserId();
- data = traderService.findByPartyId(partyId);
- }
- if(null == data) {
+ Trader data = null;
+ if (StringUtils.isNotEmpty(id)) {
+ data = traderService.findById(id);
+ } else {
+ String partyId = SecurityUtils.getCurrentUserId();
+ if (StringUtils.isEmptyString(partyId)) {
+ resultObject.setCode("1");
+ resultObject.setMsg("用户未登录");
+ Map<String, Object> empty = new HashMap<>();
+ empty.put("exists", false);
+ empty.put("checked", null);
+ empty.put("can_reapply", false);
+ empty.put("reject_reason", "");
+ empty.put("name", "");
+ empty.put("symbols", "");
+ empty.put("follow_volumn_min", 0d);
+ empty.put("follow_commission_type", FollowCommissionType.LEGACY);
+ empty.put("follow_commission_monthly_amount", "0");
+ empty.put("follow_commission_daily_pct", 0d);
+ empty.put("profit_share_ratio", 0d);
+ empty.put("id", "");
+ empty.put("img", "");
+ empty.put("img_path", "");
+ resultObject.setData(empty);
+ return resultObject;
+ }
+ data = traderService.findByPartyId(partyId);
+ }
+ Map<String, Object> ret = new HashMap<>();
+ ret.put("exists", data != null);
+ ret.put("checked", data == null ? null : data.getChecked());
+ ret.put("can_reapply", data != null && data.getChecked() == -1);
+ ret.put("reject_reason", data != null && data.getChecked() == -1 ? data.getRemarks() : "");
+ ret.put("name", data == null ? "" : data.getName());
+ ret.put("symbols", data == null ? "" : data.getSymbols());
+ ret.put("follow_volumn_min", data == null || data.getFollowVolumnMin() == null ? 0d : data.getFollowVolumnMin().doubleValue());
+ if (data != null) {
+ ret.put("follow_commission_type", FollowCommissionType.normalizeOrLegacy(data.getFollowCommissionType()));
+ ret.put("follow_commission_monthly_amount",
+ data.getFollowCommissionMonthlyAmount() == null ? "0"
+ : data.getFollowCommissionMonthlyAmount().stripTrailingZeros().toPlainString());
+ ret.put("follow_commission_daily_pct", Arith.mul(data.getFollowCommissionDailyPct(), 100));
+ ret.put("profit_share_ratio", Arith.mul(data.getProfitShareRatio(), 100));
+ } else {
+ ret.put("follow_commission_type", FollowCommissionType.LEGACY);
+ ret.put("follow_commission_monthly_amount", "0");
+ ret.put("follow_commission_daily_pct", 0d);
+ ret.put("profit_share_ratio", 0d);
+ }
+ ret.put("id", data == null ? "" : data.getUuid());
+ if (data != null && !StringUtils.isEmptyString(data.getImg())) {
+ ret.put("img", Constants.WEB_URL + "/public/showimg!showImg.action?imagePath=" + data.getImg());
+ ret.put("img_path", data.getImg());
+ } else {
+ ret.put("img", "");
+ ret.put("img_path", "");
+ }
+ resultObject.setData(ret);
+ resultObject.setCode("0");
+ return resultObject;
+ } catch (Exception e) {
+ logger.error("istrader error", e);
resultObject.setCode("1");
- resultObject.setData(false);
+ resultObject.setMsg(e.getMessage() != null ? e.getMessage() : "程序错误");
+ Map<String, Object> err = new HashMap<>();
+ err.put("exists", false);
+ err.put("checked", null);
+ err.put("can_reapply", false);
+ err.put("reject_reason", "");
+ err.put("name", "");
+ err.put("symbols", "");
+ err.put("follow_volumn_min", 0d);
+ err.put("follow_commission_type", FollowCommissionType.LEGACY);
+ err.put("follow_commission_monthly_amount", "0");
+ err.put("follow_commission_daily_pct", 0d);
+ err.put("profit_share_ratio", 0d);
+ err.put("id", "");
+ err.put("img", "");
+ err.put("img_path", "");
+ resultObject.setData(err);
return resultObject;
}
-
- resultObject.setData(true);
- resultObject.setCode("0");
- return resultObject;
}
- @RequestMapping(action + "get.action")
+ /**
+ * 交易员详情(本人或指定 id)。同时注册 {@code /api/trader/get},避免网关对 {@code !} 路径返回 404。
+ */
+ @RequestMapping(value = { action + "get.action", "/api/trader/get" })
public Object get(HttpServletRequest request) {
ResultObject resultObject = new ResultObject();
String id = request.getParameter("id");
@@ -187,16 +278,27 @@
}
Page<Trader> page = new Page<>(1, 1000000);
Trader data = null;
- if(StringUtils.isNotEmpty(id)) {
+ if (StringUtils.isNotEmpty(id)) {
data = traderService.findById(id);
- } else{
+ if (null == data) {
+ resultObject.setCode("1");
+ resultObject.setMsg("交易员不存在");
+ return resultObject;
+ }
+ } else {
String partyId = SecurityUtils.getCurrentUserId();
+ if (StringUtils.isEmptyString(partyId)) {
+ resultObject.setCode("1");
+ resultObject.setMsg("用户未登录");
+ return resultObject;
+ }
data = traderService.findByPartyId(partyId);
- }
- if(null == data) {
- resultObject.setCode("1");
- resultObject.setMsg("交易员不存在");
- return resultObject;
+ if (null == data) {
+ resultObject.setCode("1");
+ /** 未传 id 时按当前登录用户查 T_TRADER;无记录表示从未申请或数据未写入,与「传错 uuid」区分 */
+ resultObject.setMsg("当前账号暂无交易员记录,请先提交申请或确认登录账号是否正确");
+ return resultObject;
+ }
}
Map<String, Object> retData = bulidData(data, type, symbol, page);
@@ -345,21 +447,86 @@
return remain;
}
+ /** 带单品种分隔:分号、逗号(含全角) */
+ private static final Pattern TRADER_SYMBOL_SPLIT = Pattern.compile("[;;,,]+");
+
+ /**
+ * 校验带单品种是否在系统合约品种(Item)中存在;支持多品种(与 T_TRADER.SYMBOLS 约定一致,用分号拼接规范 symbol)。
+ * 匹配顺序:symbol(含缓存/remarks 映射)→ 小写 symbol → 展示名 name(兼容旧数据)。
+ */
+ private String validateAndNormalizeTraderSymbols(String raw) throws BusinessException {
+ if (StringUtils.isEmptyString(raw)) {
+ throw new BusinessException("请输入带单品种");
+ }
+ String trimmed = raw.trim();
+ String[] tokens = TRADER_SYMBOL_SPLIT.split(trimmed);
+ LinkedHashSet<String> seen = new LinkedHashSet<>();
+ List<String> normalized = new ArrayList<>();
+ for (String token : tokens) {
+ if (token == null) {
+ continue;
+ }
+ String part = token.trim();
+ if (part.isEmpty()) {
+ continue;
+ }
+ Item item = itemService.findBySymbol(part);
+ if (item == null) {
+ item = itemService.findBySymbol(part.toLowerCase(Locale.ROOT));
+ }
+ if (item == null) {
+ item = itemService.lambdaQuery()
+ .eq(Item::getName, part)
+ .eq(BaseEntity::getDelFlag, 0)
+ .last("LIMIT 1")
+ .one();
+ }
+ if (item == null || StringUtils.isEmptyString(item.getSymbol())) {
+ throw new BusinessException("带单品种无效或不支持:" + part);
+ }
+ String canon = item.getSymbol();
+ if (seen.add(canon)) {
+ normalized.add(canon);
+ }
+ }
+ if (normalized.isEmpty()) {
+ throw new BusinessException("请输入带单品种");
+ }
+ return String.join(";", normalized);
+ }
+
@RequestMapping(action + "apply.action")
public Object apply(HttpServletRequest request) {
ResultObject resultObject = new ResultObject();
String partyId = SecurityUtils.getCurrentUserId();
String symbols = request.getParameter("symbols");
+ if (symbols != null) {
+ symbols = symbols.trim();
+ }
String name = request.getParameter("name");
String follow_volumn_min_param = request.getParameter("follow_volumn_min");
- int follow_volumn_min = StringUtils.isEmptyString(follow_volumn_min_param)?0:Integer.parseInt(follow_volumn_min_param);
-
String state = "1";
String img = request.getParameter("img");
+ String remarks = request.getParameter("remarks");
try {
+ BigDecimal follow_volumn_min = BigDecimal.ZERO;
+ if (!StringUtils.isEmptyString(follow_volumn_min_param)) {
+ try {
+ follow_volumn_min = new BigDecimal(follow_volumn_min_param.trim());
+ } catch (NumberFormatException e) {
+ throw new BusinessException("最小跟单币数量格式不正确");
+ }
+ }
+ if (follow_volumn_min.signum() < 0) {
+ throw new BusinessException("最小跟单币数量不能小于0");
+ }
+ if (StringUtils.isEmptyString(remarks)) {
+ throw new BusinessException("交易员简介不能为空");
+ }
+
User party = userService.findByUserId(partyId);
if (party == null) {
@@ -368,24 +535,87 @@
if (Constants.SECURITY_ROLE_TEST.equals(party.getRoleName())) {
throw new BusinessException("试用用户无法添加!");
}
+ symbols = validateAndNormalizeTraderSymbols(symbols);
Trader exist = traderService.findByPartyId(partyId);
- if (exist != null) {
- throw new BusinessException("交易员已存在!");
-
+ if (exist != null && exist.getChecked() != -1) {
+ throw new BusinessException("Trader application already exists!");
}
- Trader trader = new Trader();
- trader.setUuid(ApplicationUtil.getCurrentTimeUUID());
- trader.setPartyId(partyId);
+ Trader trader = exist == null ? new Trader() : exist;
+ if (exist == null) {
+ trader.setUuid(ApplicationUtil.getCurrentTimeUUID());
+ trader.setPartyId(partyId);
+ trader.setCreateTime(new Date());
+ }
trader.setName(StringUtils.isEmptyString(name)?party.getUserName():name);
trader.setSymbols(symbols);
-
trader.setState(state);
- trader.setCreateTime(new Date());
trader.setImg(img);
- trader.setFollowVolumnMin(follow_volumn_min);
+ trader.setRemarks(remarks);
+ trader.setFollowVolumnMin(follow_volumn_min.stripTrailingZeros());
trader.setChecked(0);
- traderService.save(trader);
+ String fcType = request.getParameter("follow_commission_type");
+ String fcMonthly = request.getParameter("follow_commission_monthly_amount");
+ String fcDaily = request.getParameter("follow_commission_daily_pct");
+ String normalizedType = FollowCommissionType.normalizeOrLegacy(fcType);
+ trader.setFollowCommissionType(normalizedType);
+ if (FollowCommissionType.isMonthlyFixed(normalizedType)) {
+ BigDecimal m = BigDecimal.ZERO;
+ if (!StringUtils.isEmptyString(fcMonthly)) {
+ try {
+ m = new BigDecimal(fcMonthly.trim());
+ } catch (NumberFormatException e) {
+ throw new BusinessException("月跟单费金额格式不正确");
+ }
+ }
+ if (m.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new BusinessException("按月跟单模式须填写大于 0 的月费用");
+ }
+ trader.setFollowCommissionMonthlyAmount(m);
+ trader.setFollowCommissionDailyPct(0D);
+ } else if (FollowCommissionType.isDailyProfitPct(normalizedType)) {
+ double pct = 0D;
+ if (!StringUtils.isEmptyString(fcDaily)) {
+ try {
+ pct = Double.parseDouble(fcDaily.trim());
+ } catch (NumberFormatException e) {
+ throw new BusinessException("按日利润提成比例格式不正确");
+ }
+ }
+ if (pct <= 0 || pct > 100) {
+ throw new BusinessException("按日利润提成比例须在 0~100 之间");
+ }
+ trader.setFollowCommissionDailyPct(Arith.div(pct, 100));
+ trader.setFollowCommissionMonthlyAmount(BigDecimal.ZERO);
+ } else {
+ trader.setFollowCommissionMonthlyAmount(BigDecimal.ZERO);
+ trader.setFollowCommissionDailyPct(0D);
+ }
+
+ String profitShareParam = request.getParameter("profit_share_ratio");
+ if (FollowCommissionType.isLegacy(normalizedType)) {
+ if (StringUtils.isEmptyString(profitShareParam)) {
+ throw new BusinessException("单笔盈利分成须填写利润分成比例(0~100,单位:%)");
+ }
+ double psPct;
+ try {
+ psPct = Double.parseDouble(profitShareParam.trim());
+ } catch (NumberFormatException e) {
+ throw new BusinessException("利润分成比例格式不正确");
+ }
+ if (psPct <= 0 || psPct > 100) {
+ throw new BusinessException("利润分成比例须在 0~100 之间(单位:%)");
+ }
+ trader.setProfitShareRatio(Arith.div(psPct, 100));
+ } else {
+ trader.setProfitShareRatio(0D);
+ }
+
+ if (exist == null) {
+ traderService.save(trader);
+ } else {
+ traderService.update(trader);
+ }
@@ -394,6 +624,9 @@
} catch (BusinessException e) {
resultObject.setCode("1");
resultObject.setMsg(e.getMessage());
+ } catch (NumberFormatException e) {
+ resultObject.setCode("1");
+ resultObject.setMsg("最小跟单币数量格式不正确");
} catch (Throwable t) {
logger.error("UserAction.register error ", t);
resultObject.setCode("1");
@@ -411,13 +644,9 @@
@RequestMapping("showfollowsetting.action")
public Object show_follow_setting(HttpServletRequest request){
ResultObject resultObject = new ResultObject();
- String partyId = SecurityUtils.getCurrentUserId();
-
- TraderFollowSetting traderFollowSetting = traderFollowSettingService.findByPartyId(partyId);
-
- resultObject.setCode("0");
- resultObject.setMsg("设置成功");
- resultObject.setData(traderFollowSetting);
+ resultObject.setCode("1");
+ resultObject.setMsg("跟单利息设置功能已下线");
+ resultObject.setData(null);
return resultObject;
}
@@ -429,46 +658,8 @@
@RequestMapping("followsetting.action")
public Object follow_setting(HttpServletRequest request){
ResultObject resultObject = new ResultObject();
- String partyId = SecurityUtils.getCurrentUserId();
-
- User user = userService.findByUserId(partyId);
-
- String days_setting= request.getParameter("days_setting");
-
- if(StringUtils.isEmptyString(days_setting)) {
- resultObject.setCode("1");
- resultObject.setMsg("借款天数不能为空");
- return resultObject;
- }
-
- String rate = request.getParameter("rate");
-
- if(StringUtils.isEmptyString(rate)) {
- resultObject.setCode("1");
- resultObject.setMsg("带单佣金比例不能为空");
- return resultObject;
- }
-
- if(!StringUtils.isDouble(rate)) {
- resultObject.setCode("1");
- resultObject.setMsg("带单佣金比例格式不正确");
- return resultObject;
- }
-
- if(null != traderFollowSettingService.findByPartyId(partyId)) {
- resultObject.setCode("1");
- resultObject.setMsg("带单设置已存在");
- return resultObject;
- }
-
- TraderFollowSetting traderFollowSetting = new TraderFollowSetting();
- traderFollowSetting.setPartyId(partyId);
- traderFollowSetting.setUsername(user.getUserName());
- traderFollowSetting.setDaysSetting(days_setting);
- traderFollowSetting.setRate(Double.parseDouble(rate));
-
- resultObject.setCode("0");
- resultObject.setMsg("设置成功");
+ resultObject.setCode("1");
+ resultObject.setMsg("跟单利息设置功能已下线");
return resultObject;
}
@@ -480,54 +671,8 @@
@RequestMapping("updatefollowsetting.action")
public Object update_follow_setting(HttpServletRequest request){
ResultObject resultObject = new ResultObject();
- String partyId = SecurityUtils.getCurrentUserId();
-
- String id = request.getParameter("id");
-
- String days_setting= request.getParameter("days_setting");
-
- if(StringUtils.isEmptyString(id)) {
- resultObject.setCode("1");
- resultObject.setMsg("更改记录ID不能为空");
- return resultObject;
- }
-
- if(StringUtils.isEmptyString(days_setting)) {
- resultObject.setCode("1");
- resultObject.setMsg("借款天数不能为空");
- return resultObject;
- }
-
- String rate = request.getParameter("rate");
-
- if(StringUtils.isEmptyString(rate)) {
- resultObject.setCode("1");
- resultObject.setMsg("带单佣金比例不能为空");
- return resultObject;
- }
-
- if(!StringUtils.isDouble(rate)) {
- resultObject.setCode("1");
- resultObject.setMsg("带单佣金比例格式不正确");
- return resultObject;
- }
-
- TraderFollowSetting traderFollowSetting = traderFollowSettingService.findById(id);
-
- if(null == traderFollowSetting) {
- resultObject.setCode("1");
- resultObject.setMsg("记录不存在");
- return resultObject;
- }
-
-
- traderFollowSetting.setDaysSetting(days_setting);
- traderFollowSetting.setRate(Double.parseDouble(rate));
-
- traderFollowSettingService.update(traderFollowSetting);
-
- resultObject.setCode("0");
- resultObject.setMsg("设置成功");
+ resultObject.setCode("1");
+ resultObject.setMsg("跟单利息设置功能已下线");
return resultObject;
}
@@ -570,10 +715,6 @@
return "当前跟随人数加偏差值不能小于0";
if (profit_share_ratio < 0.0D)
return "利润分成比例不能小于0";
- if (follower_max <= 0)
- return "此次跟单最多跟随人数不能小于等于0";
- if (follower_max < Arith.add(follower_now, deviation_follower_now))
- return "此次跟单最多跟随人数不能小于当前跟随人数加偏差值";
if (follow_volumn_min < 0)
return "最小跟单张数不能小于0";
return null;
@@ -621,7 +762,8 @@
/**
* 查询类型 orders 当前委托 ,hisorders 历史委托 ,user 跟随者
*/
- trader_order = this.contractOrderService.getPaged(Long.valueOf(page.getCurrent()).intValue(), Long.valueOf(page.getSize()).intValue(), entity.getPartyId(), symbol, type, null);
+ trader_order = this.contractOrderService.buildDataFromOrders(
+ this.contractOrderService.findSubmittedTraderOwn(entity.getPartyId(), symbol));
} else if("hisorders".equals(type)) {
trader_order = this.traderOrderService.getPaged(page, entity.getPartyId());
}
@@ -666,13 +808,20 @@
map.put("name", entity.getName());
map.put("remarks", entity.getRemarks());
+ /** 带单品种(与 T_TRADER.SYMBOLS 一致;交易员广场列表用 symbol_name,此处双字段避免前端遗漏) */
+ String symbolsVal = StringUtils.isEmptyString(entity.getSymbols()) ? "" : entity.getSymbols();
+ map.put("symbols", symbolsVal);
+ map.put("symbol_name", symbolsVal);
+ /** 带单开关:0 停止 1 开启 2 禁止(与后台交易员管理一致) */
+ map.put("state", StringUtils.isEmptyString(entity.getState()) ? "" : entity.getState());
/**
* 累计金额order_amount
*/
map.put("order_amount", df2.format(Arith.add(entity.getOrderAmount(), entity.getDeviationOrderAmount())));
// map.put("symbol_name", "BTC/USDT;ETH/USDT");
- map.put("profit", df2.format(Arith.add(entity.getProfit(), entity.getDeviationProfit())));
+ double historyProfitTotal = contractOrderService.historyProfitForTraderTotalYield(entity);
+ map.put("profit", df2.format(historyProfitTotal));
map.put("order_profit", (int) Arith.add(entity.getOrderProfit(), entity.getDeviationOrderProfit()));
@@ -688,8 +837,41 @@
map.put("profit_ratio", df2.format(Arith.add(Arith.mul(entity.getDeviationProfitRatio(), 100),
Arith.mul(entity.getProfitRatio(), 100))));
+ // 与广场 list 一致:历史=合约表已平仓全品种盈亏+偏差;分母优先已平仓保证金合计(与当前持仓 deposit 一致)
+ double historyAmountTotal = contractOrderService.historyAmountBasisForTraderTotalYield(entity);
+ double openProfitAgg = 0D;
+ double openDepositAgg = 0D;
+ List<ContractOrder> openTraderOwn = contractOrderService.findSubmittedTraderOwn(entity.getPartyId(), "");
+ if (openTraderOwn != null) {
+ for (ContractOrder one : openTraderOwn) {
+ if (one == null) {
+ continue;
+ }
+ contractOrderService.wrapProfit(one);
+ openProfitAgg = Arith.add(openProfitAgg, one.getProfit() == null ? 0D : one.getProfit().doubleValue());
+ openDepositAgg = Arith.add(openDepositAgg, one.getDeposit() == null ? 0D : one.getDeposit().doubleValue());
+ }
+ }
+ double totalProfitAgg = Arith.add(historyProfitTotal, openProfitAgg);
+ double totalAmountAgg = Arith.add(historyAmountTotal, openDepositAgg);
+ double totalProfitRatioAgg = 0D;
+ if (totalAmountAgg > 0D) {
+ totalProfitRatioAgg = Arith.mul(Arith.div(totalProfitAgg, totalAmountAgg), 100);
+ }
+ map.put("total_profit", df2.format(totalProfitAgg));
+ map.put("total_profit_ratio", df2.format(totalProfitRatioAgg));
+
map.put("profit_share_ratio", Arith.mul(entity.getProfitShareRatio(), 100));
+ map.put("follow_commission_type", FollowCommissionType.normalizeOrLegacy(entity.getFollowCommissionType()));
+ map.put("follow_commission_monthly_amount",
+ entity.getFollowCommissionMonthlyAmount() == null ? "0" : entity.getFollowCommissionMonthlyAmount().stripTrailingZeros().toPlainString());
+ map.put("follow_commission_daily_pct", Arith.mul(entity.getFollowCommissionDailyPct(), 100));
map.put("follower_max", entity.getFollowerMax());
+ if (entity.getCreateTime() != null) {
+ map.put("create_time", DateUtils.format(entity.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
+ } else {
+ map.put("create_time", "");
+ }
Date date_now = new Date();// 取时单
int days = daysBetween(entity.getCreateTime(), date_now);
if (days < 0) {
@@ -714,28 +896,177 @@
TraderFollowUser user = this.traderFollowUserService
.findByPartyIdAndTrader_partyId(partyId, entity.getPartyId());
if (user != null) {
-
+ map.put("follow_relation_exists", true);
map.put("follow_volume", user.getVolume());
map.put("follow_volume_max", user.getVolumeMax());
- /**
- * 跟单固定张数/固定比例---选择 1,固定张数,固定比例
- */
map.put("follow_type", user.getFollowType());
- map.put("follow_state", "1");
- } else {
+ map.put("follow_state", user.getState());
+ map.put("follow_symbol", StringUtils.isEmptyString(user.getSymbol()) ? "" : user.getSymbol());
+ map.put("follow_lever_rate", user.getLeverRate());
+ String followFailReason = StringUtils.isEmptyString(user.getFailReason()) ? "" : user.getFailReason();
+ map.put("follow_fail_reason", followFailReason);
+ map.put("follow_fail_reason_key", followFailReasonKeyI18n(followFailReason));
+ map.put("follow_last_fail_time", user.getLastFailTime());
+ map.put("follow_user_cumulative_profit", df2.format(user.getProfit()));
+ double cumAmt = user.getAmountSum();
+ double cumYieldPct = 0D;
+ if (cumAmt > 0D) {
+ cumYieldPct = Arith.mul(Arith.div(user.getProfit(), cumAmt), 100);
+ }
+ map.put("follow_user_cumulative_yield_pct", df2.format(cumYieldPct));
+ // 是否已有成功开仓(仅当前 submitted 持仓算成功) + 当前跟单持仓浮盈亏合计
+ boolean followOpenSuccess = false;
+ String followOpenDirection = "";
+ String followOpenTime = "";
+ String followOpenOrderNo = "";
+ double followMyOpenProfit = 0D;
+ double followMyOpenDeposit = 0D;
+ List<TraderFollowUserOrder> userFollowOrders = traderFollowUserOrderService
+ .findByPartyIdAndTraderPartyIdAndState(partyId, entity.getPartyId(), ContractOrder.STATE_SUBMITTED);
+ if (userFollowOrders != null && !userFollowOrders.isEmpty()) {
+ TraderFollowUserOrder latest = userFollowOrders.get(0);
+ for (TraderFollowUserOrder one : userFollowOrders) {
+ if (one != null && !StringUtils.isEmptyString(one.getUserOrderNo())) {
+ ContractOrder co = contractOrderService.findByOrderNo(one.getUserOrderNo());
+ if (co != null && ContractOrder.STATE_SUBMITTED.equals(co.getState())) {
+ contractOrderService.wrapProfit(co);
+ followMyOpenProfit = Arith.add(followMyOpenProfit,
+ co.getProfit() == null ? 0D : co.getProfit().doubleValue());
+ followMyOpenDeposit = Arith.add(followMyOpenDeposit,
+ co.getDeposit() == null ? 0D : co.getDeposit().doubleValue());
+ }
+ }
+ if (one != null && one.getCreateTime() != null && latest != null
+ && latest.getCreateTime() != null && one.getCreateTime().after(latest.getCreateTime())) {
+ latest = one;
+ }
+ }
+ if (latest != null && !StringUtils.isEmptyString(latest.getUserOrderNo())) {
+ ContractOrder latestUserOrder = contractOrderService.findByOrderNo(latest.getUserOrderNo());
+ if (latestUserOrder != null && ContractOrder.STATE_SUBMITTED.equals(latestUserOrder.getState())) {
+ followOpenSuccess = true;
+ followOpenDirection = StringUtils.isEmptyString(latestUserOrder.getDirection()) ? ""
+ : latestUserOrder.getDirection();
+ if (latestUserOrder.getCreateTime() != null) {
+ followOpenTime = DateUtils.format(latestUserOrder.getCreateTime(), "yyyy-MM-dd HH:mm:ss");
+ }
+ followOpenOrderNo = latestUserOrder.getOrderNo();
+ }
+ }
+ }
+ map.put("follow_open_success", followOpenSuccess);
+ map.put("follow_open_direction", followOpenDirection);
+ map.put("follow_open_time", followOpenTime);
+ map.put("follow_open_order_no", followOpenOrderNo);
+ map.put("follow_my_open_profit", df2.format(followMyOpenProfit));
+ map.put("follow_my_open_deposit", df2.format(followMyOpenDeposit));
+ double followMyOpenYieldPct = 0D;
+ if (followMyOpenDeposit > 0D) {
+ followMyOpenYieldPct = Arith.mul(Arith.div(followMyOpenProfit, followMyOpenDeposit), 100);
+ }
+ map.put("follow_my_open_yield_pct", df2.format(followMyOpenYieldPct));
+ map.put("follow_monthly_remaining_days",
+ computeFollowMonthlyRemainingDays(entity.getFollowCommissionType(), user));
+ } else {
+ map.put("follow_relation_exists", false);
map.put("follow_state", "2");
+ map.put("follow_fail_reason", "");
+ map.put("follow_fail_reason_key", "");
+ map.put("follow_last_fail_time", null);
+ map.put("follow_open_success", false);
+ map.put("follow_open_direction", "");
+ map.put("follow_open_time", "");
+ map.put("follow_open_order_no", "");
+ map.put("follow_user_cumulative_profit", df2.format(0D));
+ map.put("follow_user_cumulative_yield_pct", df2.format(0D));
+ map.put("follow_my_open_profit", df2.format(0D));
+ map.put("follow_my_open_deposit", df2.format(0D));
+ map.put("follow_my_open_yield_pct", df2.format(0D));
+ map.put("follow_monthly_remaining_days", null);
}
} else {
map.put("follow_state", "2");
+ map.put("follow_relation_exists", false);
+ map.put("follow_fail_reason", "");
+ map.put("follow_fail_reason_key", "");
+ map.put("follow_user_cumulative_profit", df2.format(0D));
+ map.put("follow_user_cumulative_yield_pct", df2.format(0D));
+ map.put("follow_my_open_profit", df2.format(0D));
+ map.put("follow_my_open_deposit", df2.format(0D));
+ map.put("follow_my_open_yield_pct", df2.format(0D));
+ map.put("follow_monthly_remaining_days", null);
map.remove("partyId");
}
- map.put("follow_volumn_min", entity.getFollowVolumnMin());
+ map.put("follow_volumn_min", entity.getFollowVolumnMin() == null ? 0d : entity.getFollowVolumnMin().doubleValue());
+ /** 审核状态:0 待审、1 通过、-1 驳回(供前端在 istrader 异常时从 get 兜底) */
+ map.put("checked", entity.getChecked());
return map;
}
+ /** 与 ApiTraderUserController 一致:供 H5/PC i18n 映射跟单失败原因 */
+ private static String followFailReasonKeyI18n(String reason) {
+ if (reason == null) {
+ return "";
+ }
+ String r = reason.trim();
+ if (r.isEmpty()) {
+ return "";
+ }
+ if (r.contains("余额不足")) {
+ return "INSUFFICIENT_BALANCE";
+ }
+ if (r.contains("只能选择交易员带单币种")) {
+ return "SYMBOL_NOT_IN_TRADER_LIST";
+ }
+ if (r.contains("跟单参数输入错误")) {
+ return "INVALID_FOLLOW_PARAMS";
+ }
+ if (r.contains("Insufficient balance") || r.contains("insufficient balance")) {
+ return "INSUFFICIENT_BALANCE";
+ }
+ return "";
+ }
+
+ /**
+ * 月固定跟单费:当前用户对带单员已缴本月费用时,返回本月剩余自然日(含当天);否则 null。
+ */
+ private Integer computeFollowMonthlyRemainingDays(Object traderCommissionTypeObj, TraderFollowUser user) {
+ if (user == null) {
+ return null;
+ }
+ String typeNorm = traderCommissionTypeObj == null ? FollowCommissionType.LEGACY
+ : FollowCommissionType.normalizeOrLegacy(traderCommissionTypeObj.toString());
+ if (!FollowCommissionType.isMonthlyFixed(typeNorm)) {
+ return null;
+ }
+ String st = user.getState();
+ if (!(TraderFollowUser.STATE_FOLLOWING.equals(st) || TraderFollowUser.STATE_STOPPING.equals(st)
+ || TraderFollowUser.STATE_FAILED.equals(st))) {
+ return null;
+ }
+ String paid = user.getMonthlyFeePaidPeriod();
+ if (StringUtils.isEmptyString(paid)) {
+ return null;
+ }
+ try {
+ ZoneId z = ZoneId.systemDefault();
+ YearMonth paidYm = YearMonth.parse(paid);
+ YearMonth nowYm = YearMonth.now(z);
+ if (!paidYm.equals(nowYm)) {
+ return null;
+ }
+ LocalDate today = LocalDate.now(z);
+ LocalDate end = nowYm.atEndOfMonth();
+ long days = ChronoUnit.DAYS.between(today, end) + 1L;
+ return (int) Math.max(0L, days);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
public static int daysBetween(Date smdate, Date bdate) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
smdate = sdf.parse(sdf.format(smdate));
--
Gitblit v1.9.3