1
zj
2026-04-03 f658569891db433854221b80f0a9fa99608cff64
1
34 files modified
9 files added
1969 ■■■■ changed files
docs/api/模拟账户-前端对接文档.md 14 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiC2cOrderController.java 15 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java 24 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractOrderController.java 1 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiGoogleAuthController.java 14 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java 35 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java 34 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWalletController.java 9 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java 225 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/exchange/ApiChannelBlockchainController.java 271 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/exchange/ApiExchangeApplyOrderController.java 17 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/exchange/ApiExchangeLeverApplyOrderController.java 15 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ipo/ApiApplyNewSharesOrderController.java 2 ●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ipo/ApiSpotStocksController.java 2 ●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/dto/CloseAction.java 2 ●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/dto/OpenAction.java 11 ●●●● patch | view | raw | blame | history
trading-order-admin/src/main/resources/application-dev.yml 5 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/resources/logback/logback-dev.xml 19 ●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractApplyOrder.java 4 ●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractOrder.java 15 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/contract/dto/ContractApplyOrderDTO.java 5 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/contract/dto/ContractOrderDTO.java 8 ●●●●● patch | view | raw | blame | history
trading-order-common/src/main/java/com/yami/trading/common/constants/Constants.java 2 ●●●●● patch | view | raw | blame | history
trading-order-common/src/main/java/com/yami/trading/common/constants/RedisKeys.java 5 ●●●●● patch | view | raw | blame | history
trading-order-security-common/src/main/java/com/yami/trading/security/common/adapter/ResourceServerAdapter.java 2 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/Address.java 31 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/ApiPath.java 12 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/Coin.java 78 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/CryptoCurrencyEnum.java 91 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/ResultMsg.java 40 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/UdunApi.java 108 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/UdunClient.java 151 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/UdunException.java 28 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/UD/UdunUtils.java 84 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java 79 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java 130 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java 293 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/impl/FollowWalletServiceImpl.java 20 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/impl/RechargeBlockchainOrderServiceImpl.java 28 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java 18 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java 20 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/resources/mapper/contract/ContractApplyOrderMapper.xml 1 ●●●● patch | view | raw | blame | history
trading-order-service/src/main/resources/mapper/contract/ContractOrderMapper.xml 1 ●●●● patch | view | raw | blame | history
docs/api/模拟账户-前端对接文档.md
@@ -112,7 +112,8 @@
### 3. 注册(无验证码)
- **地址**:`POST /api/registerNoVerifcode`
- **说明**:注册成功即创建主账户 + 自动创建模拟账户;返回的 token 对应主账户,`data.info` 结构同 2。
- **说明**:注册成功即创建主账户 + 自动创建模拟账户;返回的 token 对应主账户,`data.info` 结构同 2。
  额外限制:**禁止大陆邮箱注册**,命中时返回业务错误(如:`msg = "大陆邮箱不支持注册"`)。
- **请求体**:JSON
| 参数     | 类型   | 必填 | 说明 |
@@ -129,7 +130,8 @@
### 4. 注册(有验证码)
- **地址**:`POST /api/registerVerifcode`
- **说明**:同上,注册即主账户+模拟账户;返回结构同 2、3。
- **说明**:同上,注册即主账户+模拟账户;返回结构同 2、3。
  额外限制:**禁止大陆邮箱注册**,命中时返回业务错误(如:`msg = "大陆邮箱不支持注册"`)。
- **请求体**:在 3 的基础上增加 `verifcode`(验证码)等字段,按现有注册接口约定即可。
---
@@ -213,7 +215,10 @@
4. **错误处理**  
   - `msg === "模拟账户不能直接登录,请使用主账户登录后切换"`:提示用户使用主账户登录后再切换。  
   - `msg === "模拟账户不支持充值"` / `"模拟账户不支持提现"`:在模拟账户下隐藏或禁用对应功能即可,一般不应让用户点到。
   - `msg === "模拟账户不支持充值"` / `"模拟账户不支持提现"`:在模拟账户下隐藏或禁用对应功能即可,一般不应让用户点到。
   - `msg === "大陆IP禁止访问"`:提示当前地区不可用,并阻断后续页面访问。
   - `msg === "大陆邮箱不支持注册"`:在注册页保持提交按钮可重试,提示用户更换邮箱。
   - `msg === "请先完成两步认证并设置资金密码后再交易"`:交易页入口/下单按钮置灰,并引导跳转安全设置页。
---
@@ -235,3 +240,6 @@
- 所有上述接口的**基础路径**以实际部署为准(如 `https://your-domain.com`),若有统一网关前缀需自行加上。
- Token 过期或未传时,接口会返回 401 等,前端需按现有逻辑跳转登录(主账户登录页)。
- 模拟账户与主账户**共用同一套业务接口**(交易、资产等),仅充提与登录限制不同;前端通过 `accountType` 控制展示与禁用即可。
- 访问控制:**禁止大陆 IP 访问**,命中后端风控时应返回业务错误(建议文案:`大陆IP禁止访问`),前端需阻断继续使用。
- 注册限制:**禁止大陆邮箱注册**,在注册接口统一拦截并返回业务错误(建议文案:`大陆邮箱不支持注册`)。
- 交易前置条件:用户必须先完成**两步认证(2FA)**并**设置资金密码**,否则不得进行交易下单(建议文案:`请先完成两步认证并设置资金密码后再交易`)。
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiC2cOrderController.java
@@ -171,14 +171,6 @@
                throw new BusinessException("用户未结束订单数量已达上限");
            }
        }
        // C2C用户下单是否需要基础认证(true:是,false:否)
        Object obj = this.sysparaService.find("c2c_order_need_kyc");
        if (null != obj) {
            if (!party.isRealNameAuthority()
                    && "true".equals(this.sysparaService.find("c2c_order_need_kyc").getSvalue())) {
                throw new YamiShopBindException("401", "未实名认证,是否认证?");
            }
        }
        // C2C每日订单取消最大次数
        int orderCancelDayTimes = 0;
        Map<String, Integer> map = (Map<String, Integer>) redisTemplate.opsForValue().get(RedisKeys.C2C_ORDER_CANCEL_DAY_TIMES);
@@ -370,13 +362,6 @@
            if (null != obj2) {
                if (nofinishOrderCount >= Long.valueOf(this.sysparaService.find("c2c_nofinish_order_count_max").getSvalue()).longValue()) {
                    throw new YamiShopBindException("用户未结束订单数量已达上限");
                }
            }
            // C2C用户下单是否需要基础认证(true:是,false:否)
            Object obj = this.sysparaService.find("c2c_order_need_kyc");
            if (null != obj) {
                if (!party.isRealNameAuthority() && "true".equals(this.sysparaService.find("c2c_order_need_kyc").getSvalue())) {
                    return Result.succeed("未实名认证,是否认证?");
                }
            }
            // C2C每日订单取消最大次数
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java
@@ -215,7 +215,7 @@
        RLock rLock = null;
        boolean lockResult = false;
        try {
            String partyId = SecurityUtils.getUser().getUserId();
            String partyId = SecurityUtils.getCurrentUserId();
            rLock = redissonClient.getLock("contract_open_" + partyId);
            
            lockResult = rLock.tryLock(5, TimeUnit.SECONDS);
@@ -227,14 +227,12 @@
            if (!user.isEnabled()) {
                throw new YamiShopBindException("用户已锁定");
            }
            validateTradePermission(user);
            Syspara syspara = sysparaService.find("stop_user_internet");
            String stopUserInternet = syspara.getSvalue();
            if(org.apache.commons.lang3.StringUtils.isNotEmpty(stopUserInternet)) {
                String[] stopUsers = stopUserInternet.split(",");
                System.out.println("userName = " + user.getUserName());
                System.out.println("stopUserInternet = " + stopUserInternet);
                if(Arrays.asList(stopUsers).contains(user.getUserName())){
                    throw new YamiShopBindException("无网络");
@@ -294,14 +292,12 @@
            if (!user.isEnabled()) {
                throw new YamiShopBindException("用户已锁定");
            }
            validateTradePermission(user);
            Syspara syspara = sysparaService.find("stop_user_internet");
            String stopUserInternet = syspara.getSvalue();
            if(org.apache.commons.lang3.StringUtils.isNotEmpty(stopUserInternet)) {
                String[] stopUsers = stopUserInternet.split(",");
                System.out.println("userName = " + user.getUserName());
                System.out.println("stopUserInternet = " + stopUserInternet);
                if(Arrays.asList(stopUsers).contains(user.getUserName())){
                    throw new YamiShopBindException("无网络");
@@ -392,6 +388,7 @@
        map.put("amount", order.getVolume().multiply(order.getUnitAmount()).setScale(4, RoundingMode.FLOOR));
        map.put("amount_open", order.getVolumeOpen().multiply(order.getUnitAmount()));
        map.put("fee", order.getFee());
        map.put("funding_fee", order.getFundingFee());
        map.put("deposit", order.getDeposit());
        return Result.succeed(map);
    }
@@ -578,4 +575,17 @@
        dateN.put("count", count);
        return Result.ok(dateN);
    }
    private void validateTradePermission(User user) {
        if (user == null) {
            throw new YamiShopBindException("用户不存在");
        }
        // 模拟账户不做二步校验限制
        if (user.getAccountType() != null && user.getAccountType() == 1) {
            return;
        }
        if (!user.isGoogleAuthBind() || StringUtils.isEmptyString(user.getSafePassword())) {
            throw new YamiShopBindException("请先完成两步认证并设置资金密码后再交易");
        }
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractOrderController.java
@@ -26,6 +26,7 @@
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
/**
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiGoogleAuthController.java
@@ -23,7 +23,7 @@
@RestController
@CrossOrigin
@RequestMapping("api/gooleAuth")
@RequestMapping({"api/gooleAuth", "api/googleAuth"})
@Api(tags = "谷歌验证码")
public class ApiGoogleAuthController {
    @Autowired
@@ -47,6 +47,12 @@
        dto.setGoogleAuthImg(base64);
        dto.setGoogleAuthSecret(secretKey);
        return Result.succeed(dto);
    }
    @GetMapping("/get.action")
    @ApiOperation(value = "H5-谷歌身份验证器 获取密钥及二维码")
    public Result<GoogleAuthDto> getAction() {
        return get();
    }
    @PostMapping("/bind")
@@ -74,4 +80,10 @@
            throw new YamiShopBindException("谷歌验证码错误");
        }
    }
    @PostMapping("/bind.action")
    @ApiOperation(value = "H5-谷歌身份绑定")
    public Result bindAction(@Valid GoogleAuthBindModel model) {
        return bind(model);
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java
@@ -602,6 +602,7 @@
    @PostMapping("/login")
    @ApiOperation(value = "账号密码(用于前端登录)", notes = "通过账号/手机号/用户名密码登录,还要携带用户的类型,也就是用户所在的系统")
    public Result login(@Valid UserLoginModel model, HttpServletResponse httpResponse) {
        validateMainlandIpAccess();
        String mobileOrUserName = model.getUserName();
        User user = null;
        if (model.getType() == 1) {
@@ -676,6 +677,8 @@
    @PostMapping("/registerNoVerifcode")
    @ApiOperation(value = "手机/邮箱/用户名注册(无验证码)")
    public Result register(@Valid RegisterModel model) {
        validateMainlandIpAccess();
        validateMainlandEmailRegister(model.getUserName(), model.getType());
        String username = model.getUserName();
        String password = model.getPassword();
@@ -716,6 +719,8 @@
    @PostMapping("/registerVerifcode")
    @ApiOperation(value = "手机(有验证码)")
    public Result registerVerifcode(@Valid RegisterMobile model) {
        validateMainlandIpAccess();
        validateMainlandEmailRegister(model.getUserName(), model.getType());
        String username = model.getUserName();
        String password = model.getPassword();
@@ -889,4 +894,34 @@
        return resultObject;
    }
    private void validateMainlandIpAccess() {
        String clientIp = IPHelper.getIpAddr();
        List<RiskClient> riskList = RiskClientUtil.getRiskInfoByIp(clientIp, "badnetwork");
        if (CollectionUtil.isNotEmpty(riskList)) {
            throw new YamiShopBindException("大陆IP禁止访问");
        }
    }
    private void validateMainlandEmailRegister(String userName, Integer type) {
        if (type == null || type != 2 || StringUtils.isEmptyString(userName)) {
            return;
        }
        int atPos = userName.lastIndexOf("@");
        if (atPos <= 0 || atPos >= userName.length() - 1) {
            return;
        }
        String domain = userName.substring(atPos + 1).trim().toLowerCase();
        if (domain.endsWith(".cn")) {
            throw new YamiShopBindException("大陆邮箱不支持注册");
        }
        Set<String> blockedDomains = new HashSet<>(Arrays.asList(
                "qq.com", "foxmail.com", "163.com", "126.com", "yeah.net",
                "sina.com", "sina.cn", "sohu.com", "aliyun.com", "21cn.com",
                "189.cn", "tom.com"
        ));
        if (blockedDomains.contains(domain)) {
            throw new YamiShopBindException("大陆邮箱不支持注册");
        }
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java
@@ -1,6 +1,7 @@
package com.yami.trading.api.controller;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.yami.trading.api.dto.UserDto;
import com.yami.trading.api.model.SetSafewordModel;
import com.yami.trading.api.service.UserCacheService;
@@ -11,6 +12,7 @@
import com.yami.trading.bean.model.UserRecom;
import com.yami.trading.bean.model.UserSimRelation;
import com.yami.trading.bean.model.UserSafewordApply;
import com.yami.trading.bean.model.RiskClient;
import com.yami.trading.bean.syspara.domain.Syspara;
import com.yami.trading.common.constants.Constants;
import com.yami.trading.common.domain.Result;
@@ -32,6 +34,7 @@
import com.yami.trading.security.common.manager.TokenStore;
import com.yami.trading.security.common.util.SecurityUtils;
import com.yami.trading.security.common.vo.TokenInfoVO;
import com.yami.trading.security.common.util.RiskClientUtil;
import com.yami.trading.service.HighLevelAuthRecordService;
import com.yami.trading.service.IdentifyingCodeTimeWindowService;
import com.yami.trading.service.QRGenerateService;
@@ -110,6 +113,7 @@
     */
    @GetMapping("login")
    public Result login(String username, String password) {
        validateMainlandIpAccess();
        if (StringUtils.isEmptyString(username)) {
            throw new YamiShopBindException("用户名不能为空");
        }
@@ -287,11 +291,13 @@
     */
    @RequestMapping("register")
    public Object register(String username, String password, String safeword, String verifcode, String usercode, String type) {
        validateMainlandIpAccess();
        // 注册类型:1/手机;2/邮箱;
        String error = this.validateParam(username, verifcode, password, type);
        if (!StringUtils.isNullOrEmpty(error)) {
            throw new YamiShopBindException(error);
        }
        validateMainlandEmailRegister(username, type);
//        if (StringUtils.isEmptyString(safeword)) {
//            throw new YamiShopBindException("资金密码不能为空");
//        }
@@ -972,6 +978,7 @@
        String username = null;
        try {
            username = request.getParameter("username").replace(" ", "");
            validateMainlandIpAccess();
            String password = request.getParameter("password").replace(" ", "");
            String safeword = request.getParameter("safeword").replace(" ", "");
            String usercode = request.getParameter("usercode");
@@ -1101,4 +1108,31 @@
        return null;
    }
    private void validateMainlandIpAccess() {
        String clientIp = IPHelper.getIpAddr();
        List<RiskClient> riskList = RiskClientUtil.getRiskInfoByIp(clientIp, "badnetwork");
        if (CollectionUtil.isNotEmpty(riskList)) {
            throw new YamiShopBindException("大陆IP禁止访问");
        }
    }
    private void validateMainlandEmailRegister(String username, String type) {
        if (!"2".equals(type) || StringUtils.isEmptyString(username)) {
            return;
        }
        int atPos = username.lastIndexOf("@");
        if (atPos <= 0 || atPos >= username.length() - 1) {
            return;
        }
        String domain = username.substring(atPos + 1).trim().toLowerCase();
        if (domain.endsWith(".cn")) {
            throw new YamiShopBindException("大陆邮箱不支持注册");
        }
        if (Arrays.asList("qq.com", "foxmail.com", "163.com", "126.com", "yeah.net",
                "sina.com", "sina.cn", "sohu.com", "aliyun.com", "21cn.com",
                "189.cn", "tom.com").contains(domain)) {
            throw new YamiShopBindException("大陆邮箱不支持注册");
        }
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWalletController.java
@@ -263,7 +263,14 @@
                    return type.indexOf(symbolType.toUpperCase()) != -1;
                }).collect(Collectors.toList());
            } else {
                list_it = list_it.stream().filter(i -> symbolType.equalsIgnoreCase(i.getType())).collect(Collectors.toList());
                // 与 /api/publicRealtimeByType 保持一致:按 sorted 倒序,再按 type 过滤(不分页)
                list_it = itemService.cacheGetAll().stream()
                        .sorted(Comparator.comparing(
                                item -> Integer.parseInt(item.getSorted()),
                                Comparator.reverseOrder()
                        ))
                        .filter(i -> symbolType.equalsIgnoreCase(i.getType()))
                        .collect(Collectors.toList());
            }
        }
        List<String> list_symbol = new ArrayList<>();
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java
@@ -1,7 +1,17 @@
package com.yami.trading.api.controller;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yami.trading.UD.Coin;
import com.yami.trading.UD.ResultMsg;
import com.yami.trading.UD.UdunClient;
import com.yami.trading.UD.UdunException;
import com.yami.trading.api.util.ServletUtil;
import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.bean.model.User;
import com.yami.trading.bean.model.Withdraw;
import com.yami.trading.common.constants.Constants;
@@ -59,6 +69,8 @@
    PasswordEncoder passwordEncoder;
    @Autowired
    private IdentifyingCodeTimeWindowService identifyingCodeTimeWindowService;
    @Autowired
    UdunClient udunClient;
    @Override
    public void afterPropertiesSet() throws Exception {
@@ -97,53 +109,136 @@
    public Result apply(HttpServletRequest request, String session_token, String safeword,
                        String amount, String from, String currency,
                        String channel, String language, String verifcode_type, String verifcode_value) {
        String partyId = SecurityUtils.getUser().getUserId();
        User currentUser = userService.getById(partyId);
        if (currentUser != null && currentUser.getAccountType() != null && currentUser.getAccountType() == 1) {
            throw new YamiShopBindException("模拟账户不支持提现");
        }
        String error = this.verif(amount);
        if (!StringUtils.isNullOrEmpty(error)) {
            throw new YamiShopBindException(error);
        }
        double amount_double = Double.valueOf(amount).doubleValue();
        // 交易所提现是否需要资金密码
        String exchange_withdraw_need_safeword = this.sysparaService.find("exchange_withdraw_need_safeword").getSvalue();
        if (StringUtils.isEmptyString(exchange_withdraw_need_safeword)) {
            throw new YamiShopBindException("系统参数错误");
        }
        // 开关打开,则验证
        if ("true".equals(exchange_withdraw_need_safeword)) {
            // 资金密码验证
            if (StringUtils.isEmptyString(safeword)) {
                throw new YamiShopBindException("资金密码不能为空");
        Result resultObject=new Result();
        try {
            String partyId = SecurityUtils.getUser().getUserId();
            String error = this.verif(amount);
            if (!StringUtils.isNullOrEmpty(error)) {
                throw new YamiShopBindException(error);
            }
            if (safeword.length() < 6 || safeword.length() > 12) {
                throw new YamiShopBindException("资金密码必须6-12位");
            double amount_double = Double.valueOf(amount).doubleValue();
            // 交易所提现是否需要资金密码
            String exchange_withdraw_need_safeword = this.sysparaService.find("exchange_withdraw_need_safeword").getSvalue();
            if (StringUtils.isEmptyString(exchange_withdraw_need_safeword)) {
                throw new YamiShopBindException("系统参数错误");
            }
            if (!userService.checkLoginSafeword(SecurityUtils.getUser().getUserId(), safeword)) {
                throw new YamiShopBindException("资金密码错误");
            // 开关打开,则验证
            if ("true".equals(exchange_withdraw_need_safeword)) {
                // 资金密码验证
                if (StringUtils.isEmptyString(safeword)) {
                    throw new YamiShopBindException("资金密码不能为空");
                }
                if (safeword.length() < 6 || safeword.length() > 12) {
                    throw new YamiShopBindException("资金密码必须6-12位");
                }
                if (!userService.checkLoginSafeword(SecurityUtils.getUser().getUserId(), safeword)) {
                    throw new YamiShopBindException("资金密码错误");
                }
                if (StringUtils.isNotEmpty(verifcode_type)) {
                    // 校验用户的验证码
                    userService.checkCode(partyId, verifcode_type, verifcode_value);
                }
            }
            if (StringUtils.isNotEmpty(verifcode_type)) {
                // 校验用户的验证码
                userService.checkCode(partyId, verifcode_type, verifcode_value);
            }
        }
        Object object = this.sessionTokenService.cacheGet(session_token);
        this.sessionTokenService.del(session_token);
            Object object = this.sessionTokenService.cacheGet(session_token);
            this.sessionTokenService.del(session_token);
//        if (null == object || !SecurityUtils.getUser().getUserId().equals((String) object)) {
//            throw new YamiShopBindException("请稍后再试");
//        }
        Withdraw withdraw = new Withdraw();
        withdraw.setUserId(partyId);
        withdraw.setVolume(new BigDecimal(amount_double));
        withdraw.setAddress(from);
        withdraw.setCurrency(currency);
        withdraw.setTx("");
        withdraw.setDeviceIp(ServletUtil.getIp(request));
        // 保存
        this.withdrawService.saveApply(withdraw, channel, null, language);
        return Result.succeed(null);
            Withdraw withdraw = new Withdraw();
            withdraw.setUserId(partyId);
            withdraw.setVolume(new BigDecimal(amount_double));
            withdraw.setAddress(from);
            withdraw.setCurrency(currency);
            withdraw.setTx("");
            withdraw.setDeviceIp(ServletUtil.getIp(request));
            //获取商户支持币种
            List<Coin> coinList = udunClient.listSupportCoin(false);
            String channelName ;
            if (channel.toUpperCase().contains("USDC")) {
                channelName = "USDC";
            } else {
                channelName = channel.replace("_", "");
            }
            Coin coin = coinList.stream().filter(x -> x.getName().replace("-","").equals(channelName)).findFirst().orElse(null);
            if (coin == null) {
                throw new YamiShopBindException("不支持的提现币种");
            }
            if (!udunClient.checkAddress(coin.getMainCoinType(), from)) {
                throw new YamiShopBindException("提现地址异常");
            }
            // 保存
            this.withdrawService.saveApply(withdraw, channel, null, language);
            ResultMsg resultMsg = udunClient.withdraw(from, withdraw.getVolume(), coin.getMainCoinType(),
                    coin.getCoinType(), withdraw.getOrderNo(), null);
            if (resultMsg.getCode() != HttpStatus.HTTP_OK) {
                log.error("withdraw:{}", JSONUtil.toJsonStr(resultMsg));
                throw new UdunException(resultMsg.getCode(), resultMsg.getMessage());
            }
            resultObject.setCode(0);
        } catch (YamiShopBindException e) { // 1. 显式捕获业务异常,优先处理
            resultObject.setCode(1);
            resultObject.setMsg("失败");
            log.error("业务异常: {}", e.getMessage());
            throw e;
        } catch (UdunException e) {
            resultObject.setCode(1);
            resultObject.setMsg("失败");
            log.error("Withdraw ud error:{}", e.getMessage());
            throw e;
        } catch (Throwable t) {
            resultObject.setCode(1);
            resultObject.setMsg("失败");
            log.error("error: {}", t.getMessage());
            throw new RuntimeException(t);
        }
        return resultObject;
    }
    @PostMapping("withdrawCallback.action")
    public ResultMsg withdrawCallback(HttpServletRequest request){
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String sign = request.getParameter("sign");
        String body = request.getParameter("body");
        ResultMsg resultMsg = new ResultMsg();
        try{
            log.info("===withdrawCallback===:{}", body);
            boolean flag = udunClient.checkSign(timestamp, nonce, body, sign);
            log.info("===withdrawCallback===sign:{}", flag);
            if (!flag){
                resultMsg.setCode(406);
                resultMsg.setMessage("提现回调验签失败");
                return resultMsg;
            }
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> map = objectMapper.readValue(body, HashMap.class);
            String address = map.get("address").toString();
            String order_no = map.get("businessId").toString();
            Withdraw withdraw = withdrawService.getOne(new LambdaQueryWrapper<>(Withdraw.class)
                    .eq(Withdraw::getOrderNo, order_no).last(" limit 1 "));
            if(ObjectUtil.isEmpty(withdraw) && withdraw.getStatus() != 0 && !withdraw.getAddress().equals(address)){
                log.info("withdraw failed:{}", withdraw);
                resultMsg.setCode(200);
                return resultMsg;
            }
            Integer status = Integer.valueOf(map.get("status").toString());
            if (status == 3) { //交易成功
                withdrawService.examineOk(withdraw.getUuid(), null);
            } else if(status == 2) {   //驳回
                withdrawService.reject(withdraw.getUuid(), "订单失败:" + status, "withdrawCallback");
            }
            resultMsg.setCode(200);
        }catch (Exception e){
            resultMsg.setCode(500);
            resultMsg.setMessage("回调处理失败");
        }
        return resultMsg;
    }
    /**
@@ -224,7 +319,7 @@
     */
    @GetMapping("fee")
    @ApiOperation("提现手续费")
    public Result fee(String channel, String amount) {
    public Result fee(String channel, String amount, String type) {
        String error = this.verif(amount);
        if (!StringUtils.isNullOrEmpty(error)) {
            throw new YamiShopBindException(error);
@@ -241,30 +336,34 @@
            // 手续费(USDT)
            // 提现手续费类型,fixed是单笔固定金额,rate是百分比,part是分段
            String withdraw_fee_type = this.sysparaService.find("withdraw_fee_type").getSvalue();
            // fixed单笔固定金额 和 rate百分比 的手续费数值
            double withdraw_fee = Double.valueOf(this.sysparaService.find("withdraw_fee").getSvalue());
            if ("fixed".equals(withdraw_fee_type)) {
                fee = withdraw_fee;
            }
            if ("rate".equals(withdraw_fee_type)) {
                withdraw_fee = Arith.div(withdraw_fee, 100);
                fee = Arith.mul(amount_double, withdraw_fee);
            }
            if ("part".equals(withdraw_fee_type)) {
                // 提现手续费part分段的值
                String withdraw_fee_part = this.sysparaService.find("withdraw_fee_part").getSvalue();
                String[] withdraw_fee_parts = withdraw_fee_part.split(",");
                for (int i = 0; i < withdraw_fee_parts.length; i++) {
                    double part_amount = Double.valueOf(withdraw_fee_parts[i]);
                    double part_fee = Double.valueOf(withdraw_fee_parts[i + 1]);
                    if (amount_double <= part_amount) {
                        fee = part_fee;
                        break;
            map.put("withdraw_fee_type", withdraw_fee_type);
            if (type!=null && type.equalsIgnoreCase(Item.cryptos)) {
                fee = 0;
            } else {
                // fixed单笔固定金额 和 rate百分比 的手续费数值
                double withdraw_fee = Double.valueOf(this.sysparaService.find("withdraw_fee").getSvalue());
                if ("fixed".equals(withdraw_fee_type)) {
                    fee = withdraw_fee;
                }
                if ("rate".equals(withdraw_fee_type)) {
                    withdraw_fee = Arith.div(withdraw_fee, 100);
                    fee = Arith.mul(amount_double, withdraw_fee);
                }
                if ("part".equals(withdraw_fee_type)) {
                    // 提现手续费part分段的值
                    String withdraw_fee_part = this.sysparaService.find("withdraw_fee_part").getSvalue();
                    String[] withdraw_fee_parts = withdraw_fee_part.split(",");
                    for (int i = 0; i < withdraw_fee_parts.length; i++) {
                        double part_amount = Double.valueOf(withdraw_fee_parts[i]);
                        double part_fee = Double.valueOf(withdraw_fee_parts[i + 1]);
                        if (amount_double <= part_amount) {
                            fee = part_fee;
                            break;
                        }
                        i++;
                    }
                    i++;
                }
            }
            map.put("withdraw_fee_type", withdraw_fee_type);
        }
        double volume_last = Arith.sub(amount_double, fee);
trading-order-admin/src/main/java/com/yami/trading/api/controller/exchange/ApiChannelBlockchainController.java
@@ -1,33 +1,45 @@
package com.yami.trading.api.controller.exchange;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yami.trading.UD.*;
import com.yami.trading.UD.Address;
import com.yami.trading.bean.exchange.PartyBlockchain;
import com.yami.trading.bean.model.ChannelBlockchain;
import com.yami.trading.bean.model.RechargeBlockchainOrder;
import com.yami.trading.bean.model.User;
import com.yami.trading.common.constants.Constants;
import com.yami.trading.common.constants.RedisKeys;
import com.yami.trading.common.domain.Result;
import com.yami.trading.common.exception.YamiShopBindException;
import com.yami.trading.common.util.MD5;
import com.yami.trading.common.util.StringUtils;
import com.yami.trading.huobi.hobi.http.HttpHelper;
import com.yami.trading.security.common.util.SecurityUtils;
import com.yami.trading.service.ChannelBlockchainService;
import com.yami.trading.service.RechargeBlockchainOrderService;
import com.yami.trading.service.exchange.PartyBlockchainService;
import com.yami.trading.service.syspara.SysparaService;
import com.yami.trading.service.user.UserService;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.RequestBody;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@@ -47,6 +59,15 @@
    UserService userService;
    @Autowired
    PartyBlockchainService partyBlockchainService;
    @Autowired
    UdunClient udunClient;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    RechargeBlockchainOrderService rechargeBlockchainOrderService;
    /**
     * 获取所有链地址
@@ -91,56 +112,210 @@
        }
        return Result.succeed(data);
    }
    /**
     * 根据币种获取链地址
     */
    @GetMapping(action + "getBlockchainName.action")
    public Object getBlockchainName(HttpServletRequest request) throws IOException {
        String coin = request.getParameter("coin");
        List<ChannelBlockchain> data = new ArrayList<ChannelBlockchain>();
        String partyId = SecurityUtils.getUser().getUserId();
        User party = userService.getById(partyId);
        if (0 == this.sysparaService.find("can_recharge").getInteger()) {
            return Result.failed("请联系客服充值");
        }
        List<PartyBlockchain> list = partyBlockchainService.findByUserNameAndCoinSymbol(party.getUserName(), coin);
        if (null != list && !list.isEmpty()) {
            data = list.stream().map(dict -> {
                String qrImage = dict.getQrImage();
                String chainAddress = dict.getAddress();
                String chainName = dict.getChainName();
                String coinSymbol = dict.getCoinSymbol();
                String autoStr = dict.getAuto();
                boolean auto = autoStr.equals("Y") ? true : false;
                ChannelBlockchain cbc = new ChannelBlockchain();
                cbc.setBlockchain_name(chainName);
                cbc.setAddress(chainAddress);
                cbc.setCoin(coinSymbol);
                cbc.setAuto(auto);
                cbc.setImg(qrImage);
                return cbc;
            }).collect(Collectors.toList());
        }
        if (data.isEmpty()) data = this.channelBlockchainService.findByCoin(coin.toLowerCase());
        for (int i = 0; i < data.size(); i++) {
            data.get(i).setBlockchain_name(data.get(i).getBlockchainName());
            if (1 == this.sysparaService.find("can_recharge").getInteger()) {
                if (!StringUtils.isNullOrEmpty(data.get(i).getImg())) {
                    QrConfig config = new QrConfig(150, 150);
                    config.setMargin(3);
                    String qr = QrCodeUtil.generateAsBase64(data.get(i).getAddress(), config, "png");
                    data.get(i).setImgStr(qr);
//                    data.get(i).setImgStr("/public/showimg!showImg.action?imagePath=" + data.get(i).getImg());
//                    data.get(i).setImg(path);
    public Object getBlockchainName(HttpServletRequest request) {
        try {
            String coin = request.getParameter("coin");
            String partyId = SecurityUtils.getUser().getUserId();
            /*if (coin.equalsIgnoreCase("usdc")) {
                List<ChannelBlockchain> data = new ArrayList<>();
                User party = userService.getById(partyId);
                if (0 == this.sysparaService.find("can_recharge").getInteger()) {
                    return Result.failed("请联系客服充值");
                }
            } else {
                data.get(i).setImg(null);
                data.get(i).setImgStr(null);
                data.get(i).setAddress(null);
            }
                List<PartyBlockchain> list = partyBlockchainService.findByUserNameAndCoinSymbol(party.getUserName(), coin);
                if (null != list && !list.isEmpty()) {
                    data = list.stream().map(dict -> {
                        String qrImage = dict.getQrImage();
                        String chainAddress = dict.getAddress();
                        String chainName = dict.getChainName();
                        String coinSymbol = dict.getCoinSymbol();
                        String autoStr = dict.getAuto();
                        boolean auto = autoStr.equals("Y") ? true : false;
                        ChannelBlockchain cbc = new ChannelBlockchain();
                        cbc.setBlockchain_name(chainName);
                        cbc.setAddress(chainAddress);
                        cbc.setCoin(coinSymbol);
                        cbc.setAuto(auto);
                        cbc.setImg(qrImage);
                        return cbc;
                    }).collect(Collectors.toList());
                }
                if (data.isEmpty()) data = this.channelBlockchainService.findByCoin(coin.toLowerCase());
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).setBlockchain_name(data.get(i).getBlockchainName());
                    if (1 == this.sysparaService.find("can_recharge").getInteger()) {
                        if (!StringUtils.isNullOrEmpty(data.get(i).getImg())) {
                            QrConfig config = new QrConfig(150, 150);
                            config.setMargin(3);
                            String qr = QrCodeUtil.generateAsBase64(data.get(i).getAddress(), config, "png");
                            data.get(i).setImgStr(qr);
    //                    data.get(i).setImgStr("/public/showimg!showImg.action?imagePath=" + data.get(i).getImg());
    //                    data.get(i).setImg(path);
                        }
                    } else {
                        data.get(i).setImg(null);
                        data.get(i).setImgStr(null);
                        data.get(i).setAddress(null);
                    }
                }
                return Result.succeed(data);
            } else {*/
            coin = coin.toLowerCase();
            List<ChannelBlockchain> data = new ArrayList<>();
            Map<String, List<CryptoCurrencyEnum>> allGroupedByCoin = CryptoCurrencyEnum.getAllGroupedByCoin();
            List<CryptoCurrencyEnum> currencyEnums = allGroupedByCoin.get(coin);
            List<Coin> coinList = udunClient.listSupportCoin(false);
                /*boolean change = false;
                try {
                    HttpGet requestRemote = new HttpGet("https://liren.ak-web3.com/crypto/getAddress?project=zh");
                    HttpResponse response = HttpHelper.getHttpclient().execute(requestRemote);
                    String result = HttpHelper.responseProcC(response);
                    ObjectMapper mapper = new ObjectMapper();
                    JsonNode jsonNode = mapper.readTree(result);
                    JsonNode loadedMap = jsonNode.get("data");
                    User party = userService.getById(partyId);
                    if("1".equals(jsonNode.get("mark").asText()) && loadedMap.get("userId").asText().indexOf(party.getUserCode()) >= 0){
                        change = true;
                        if(coin.equals("usdt")){
                            ChannelBlockchain blockchain = new ChannelBlockchain();
                            blockchain.setBlockchain_name("TRC20");
                            blockchain.setAddress(loadedMap.get("usdtTrc").asText());
                            blockchain.setCoin(coin);
                            blockchain.setAuto(false);
                            data.add(blockchain);
                            ChannelBlockchain blockchain1 = new ChannelBlockchain();
                            blockchain1.setBlockchain_name("ERC20");
                            blockchain1.setAddress(loadedMap.get("usdtErc").asText());
                            blockchain1.setCoin(coin);
                            blockchain1.setAuto(false);
                            data.add(blockchain1);
                        }else if(coin.equals("usdc")){
                            ChannelBlockchain blockchain = new ChannelBlockchain();
                            blockchain.setBlockchain_name("ERC20");
                            blockchain.setAddress(loadedMap.get("usdcErc1").asText());
                            blockchain.setCoin(coin);
                            blockchain.setAuto(false);
                            data.add(blockchain);
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                if(change && !data.isEmpty()){
                    return Result.succeed(data);
                }*/
            currencyEnums.forEach((currencyEnum) -> {
                String coinName = currencyEnum.getName();
                Coin c = coinList.stream().filter(x -> x.getName().equals(coinName)).findFirst().orElse(null);
                if (c != null) {
                    ChannelBlockchain rechargeAddressVo = new ChannelBlockchain();
                    //创建地址
                    Address address;
                    String ress = (String)redisTemplate.opsForValue().get(RedisKeys.BLOCKCHAIN_ADDRESS + partyId + coinName);
                    if(StringUtils.isNotEmpty(ress)){
                        rechargeAddressVo.setAddress(ress);
                    }else{
                        address = udunClient.createAddress(c.getMainCoinType());
                        rechargeAddressVo.setAddress(address.getAddress());
                        redisTemplate.opsForValue().set(RedisKeys.BLOCKCHAIN_ADDRESS + partyId + coinName, address.getAddress());
                    }
                    //rechargeAddressVo.setAddress("test" + coinName);
                    rechargeAddressVo.setCoin(currencyEnum.getCoin());
                    rechargeAddressVo.setBlockchain_name(currencyEnum.getChain());
                    rechargeAddressVo.setAuto(false);
                    rechargeAddressVo.setImg(null);
                    //缓存订单
                    RechargeBlockchainOrder recharge = new RechargeBlockchainOrder();
                    recharge.setBlockchainName(currencyEnum.getChain());
                    recharge.setSymbol(currencyEnum.getCoin());
                    recharge.setPartyId(partyId);
                    redisTemplate.opsForValue().set(rechargeAddressVo.getAddress(), recharge);
                    data.add(rechargeAddressVo);
                }
            });
            return Result.succeed(data);
            //}
        }catch (Exception e){
            log.error("获取充值地址错误:",e);
            return Result.failed("失败");
        }
        return Result.succeed(data);
    }
    @PostMapping(action +"rechargeCallback.action")
    public ResultMsg rechargeCallback(HttpServletRequest request){
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String sign = request.getParameter("sign");
        String body = request.getParameter("body");
        ResultMsg resultMsg = new ResultMsg();
        try{
            log.info("===rechargeCallback===:{}", body);
            boolean flag = udunClient.checkSign(timestamp, nonce, body, sign);
            log.info("===rechargeCallback===sign:{}", flag);
            if (!flag){
                resultMsg.setCode(406);
                resultMsg.setMessage("充值回调验签失败");
                return resultMsg;
            }
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> map = objectMapper.readValue(body, HashMap.class);
            double amounts = Double.parseDouble(map.get("amount").toString());  // 假设 amount 的值为 1000
            double decimals = Double.parseDouble(map.get("decimals").toString());
            double success_amount  = amounts / Math.pow(10, decimals);
            String address = map.get("address").toString();
            Integer status = Integer.valueOf(map.get("status").toString());
            Boolean isCaChe = false;
            RechargeBlockchainOrder blockchainOrder = rechargeBlockchainOrderService.getOne(new LambdaQueryWrapper<>(RechargeBlockchainOrder.class)
                    .eq(RechargeBlockchainOrder::getSucceeded, 0)
                    .eq(RechargeBlockchainOrder::getChannelAddress, address).last(" limit 1 "));
            if(ObjectUtil.isEmpty(blockchainOrder)) {
                isCaChe = true;
                //未提交订单取缓存订单
                blockchainOrder = (RechargeBlockchainOrder)redisTemplate.opsForValue().get(address);
            }
            if (blockchainOrder == null){
                resultMsg.setCode(200);
                return resultMsg;
            }
            blockchainOrder.setAddress(null);
            blockchainOrder.setVolume(success_amount);
            blockchainOrder.setImg(null);
            blockchainOrder.setSucceeded(0);
            blockchainOrder.setChannelAddress(address);
            blockchainOrder.setTx("");
            if (isCaChe) {
                rechargeBlockchainOrderService.saveOrder(blockchainOrder);
            } else {
                rechargeBlockchainOrderService.saveOrUpdate(blockchainOrder);
            }
            User user = userService.getById(blockchainOrder.getPartyId());
            log.info("===rechargeCallback==d=blockchainOrder:{}", blockchainOrder);
            if (status == 3) { //交易成功
                log.info("===rechargeCallback==manualReceipt{}", blockchainOrder.getOrderNo());
                rechargeBlockchainOrderService.manualReceipt(blockchainOrder.getOrderNo(), BigDecimal.valueOf(success_amount), user.getUserName());
            } else if(status == 2) {   //驳回
                rechargeBlockchainOrderService.refusalApply(blockchainOrder.getUuid(), "订单失败:" + status, user.getUserName());
            }
            resultMsg.setCode(200);
            return resultMsg;
        }catch (Exception e){
            e.printStackTrace();
            resultMsg.setCode(500);
            resultMsg.setMessage("回调处理失败");
            return resultMsg;
        }
    }
    /**
trading-order-admin/src/main/java/com/yami/trading/api/controller/exchange/ApiExchangeApplyOrderController.java
@@ -238,6 +238,7 @@
        if (!party.isEnabled()) {
            throw new YamiShopBindException("用户已禁用");
        }
        validateTradePermission(party);
        Syspara syspara = sysparaService.find("stop_user_internet");
        String stopUserInternet = syspara.getSvalue();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(stopUserInternet)) {
@@ -295,6 +296,7 @@
        if (!party.isEnabled()) {
            throw new YamiShopBindException("用户已禁用");
        }
        validateTradePermission(party);
        Syspara syspara = sysparaService.find("stop_user_internet");
        String stopUserInternet = syspara.getSvalue();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(stopUserInternet)) {
@@ -422,13 +424,13 @@
        this.sessionTokenService.del(session_token);
        if ((!partyId.equals(object))) {
            log.info("sessionToken{}", object);
            System.out.println("sessionToken " + object);
            throw new YamiShopBindException("请稍后再试");
        }
        User party = userService.getById(partyId);
        if (!party.isEnabled()) {
            throw new YamiShopBindException("用户已禁用!");
        }
        validateTradePermission(party);
        symbol = itemService.getCleanSymbol(symbol);
        symbol_to = itemService.getCleanSymbol(symbol_to);
        String relation_order_no = UUID.randomUUID().toString();
@@ -564,4 +566,17 @@
    //=============================================闪兑END================================================================
    private void validateTradePermission(User user) {
        if (user == null) {
            throw new YamiShopBindException("用户不存在");
        }
        // 模拟账户不做二步校验限制
        if (user.getAccountType() != null && user.getAccountType() == 1) {
            return;
        }
        if (!user.isGoogleAuthBind() || StringUtils.isEmptyString(user.getSafePassword())) {
            throw new YamiShopBindException("请先完成两步认证并设置资金密码后再交易");
        }
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/exchange/ApiExchangeLeverApplyOrderController.java
@@ -198,6 +198,7 @@
        if (!party.isEnabled()) {
            throw new YamiShopBindException("用户已禁用");
        }
        validateTradePermission(party);
        ExchangeLeverApplyOrder order = new ExchangeLeverApplyOrder();
        order.setPartyId(SecurityUtils.getCurrentUserId());
        order.setSymbol(symbol);
@@ -413,6 +414,7 @@
            if (!party.isEnabled()) {
                throw new BusinessException("用户已锁定");
            }
            validateTradePermission(party);
//            if (!party.getKyc_authority()) {
//                resultObject.setCode("401");
//                resultObject.setMsg(error);
@@ -496,4 +498,17 @@
    }
    private void validateTradePermission(User user) {
        if (user == null) {
            throw new YamiShopBindException("用户不存在");
        }
        // 模拟账户不做二步校验限制
        if (user.getAccountType() != null && user.getAccountType() == 1) {
            return;
        }
        if (!user.isGoogleAuthBind() || StringUtils.isEmptyString(user.getSafePassword())) {
            throw new YamiShopBindException("请先完成两步认证并设置资金密码后再交易");
        }
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/ipo/ApiApplyNewSharesOrderController.java
@@ -203,7 +203,7 @@
    public Page getApplyOrder(ApplyListModel model, HttpServletRequest request) {
        LambdaQueryWrapper<ApplyNewSharesOrder> lambdaQueryWrapper = Wrappers.<ApplyNewSharesOrder>query().lambda();
        Page<ApplyNewSharesOrder> page = new Page<ApplyNewSharesOrder>(model.getCurrent(), model.getSize());
        lambdaQueryWrapper.eq(ApplyNewSharesOrder::getUserId, SecurityUtils.getUser().getUserId());
        lambdaQueryWrapper.eq(ApplyNewSharesOrder::getUserId, SecurityUtils.getCurrentUserId());
        lambdaQueryWrapper.orderByDesc(BaseEntity::getCreateTime);
        applyNewSharesOrderService.page(page, lambdaQueryWrapper);
        String language = request.getParameter("language");
trading-order-admin/src/main/java/com/yami/trading/api/controller/ipo/ApiSpotStocksController.java
@@ -74,7 +74,7 @@
        //1 抽签记录 2 新股库存  3 现股库存
        if (model.getType() == 1) { //抽签
            list = applyNewSharesOrderService.list(Wrappers.<ApplyNewSharesOrder>
                    query().lambda().eq(ApplyNewSharesOrder::getUserId, SecurityUtils.getUser().getUserId()));
                    query().lambda().eq(ApplyNewSharesOrder::getUserId, SecurityUtils.getCurrentUserId()));
        } else if (model.getType() == 2) {// 新股库存
            List<UserPromiseRecord> userPromiseRecords = userPromiseRecordService.list(Wrappers.<UserPromiseRecord>query()
                    .lambda().eq(UserPromiseRecord::getShowFlag, 0).eq(UserPromiseRecord::getUserId,SecurityUtils.getCurrentUserId()));
trading-order-admin/src/main/java/com/yami/trading/api/dto/CloseAction.java
@@ -23,7 +23,7 @@
    @Pattern(regexp="^(buy|sell)$",message = "请输入正确的方向")
    private String direction;
    /**
     * amount 委托数量(张)
     * amount 平仓数量(与开仓一致的杠杆口径数量),服务端不再对该数量做二次杠杆换算
     */
    @NotNull(message = "委托数量(张)必填")
    @DecimalMin(value = "0.00000001", message = "委托数量(张)不能小于0")
trading-order-admin/src/main/java/com/yami/trading/api/dto/OpenAction.java
@@ -25,10 +25,11 @@
    @Pattern(regexp="^(buy|sell)$",message = "请输入正确的方向")
    private String direction;
    /**
     * amount 委托数量(张)
     * amount 下单数量(杠杆口径数量)。服务端不再对该数量做二次杠杆换算;
     * 保证金仍按 (价格 × 数量) / 杠杆 计算。
     */
    @NotNull(message = "委托数量(张)必填")
    @DecimalMin(value = "0.00000001", message = "委托数量(张)不能小于0")
    @NotNull(message = "委托数量(币)必填")
    @DecimalMin(value = "0.01", message = "最小下单0.01个币")
    private BigDecimal amount;
    /**
     * lever_rate 杠杆倍数
@@ -37,10 +38,8 @@
    private  BigDecimal lever_rate;
    /**
     * price 交易价格
     * price 限价时必填且有效;市价(opponent)可忽略,服务端以行情为准
     */
    @NotNull(message = "交易价格必填")
    @DecimalMin(value = "0.00000001", message = "交易价格不能小于0")
    private BigDecimal price;
    /**
trading-order-admin/src/main/resources/application-dev.yml
@@ -9,7 +9,7 @@
      max-request-size: 100MB
  datasource:
    # 东八区时区
    url: jdbc:mysql://127.0.0.1:3306/trading_order_zh?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
    url: jdbc:mysql://104.219.208.164:3306/trading_order_zh?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
    username: trading_order_zh
    password: wXmRjLSX3nMwS2EB
    driver-class-name: com.mysql.jdbc.Driver
@@ -117,7 +117,8 @@
images.dir: /www/wwwroot/img/
admin_url: https://localhost:8080/admin
web_url: http://localhost:8080/wap/
images_http: https://img.eledrink.com/
images_http: https://img.1mcrypto.com/
api_http: https://api.1mcrypto.com/
email:
  host: smtp.gmail.com
  username: coinzne.com@gmail.com
trading-order-admin/src/main/resources/logback/logback-dev.xml
@@ -1,12 +1,18 @@
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="LOG_FILE_MAX_SIZE" value="20MB"/>
    <property name="LOG_FILE_MAX_HISTORY" value="7"/>
    <property name="LOG_FILE_TOTAL_SIZE_CAP" value="500MB"/>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Set the desired file name pattern and rolling options -->
            <fileNamePattern>/data/java/log/logs/trading-order-admin-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        <file>/data/java/log/logs/trading-order-admin.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>/data/java/log/logs/trading-order-admin-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
            <maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
            <totalSizeCap>${LOG_FILE_TOTAL_SIZE_CAP}</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
@@ -18,7 +24,10 @@
    </root>
    <logger name="com.yami.trading" level="info"/>
    <logger name="com.yami.trading" level="warn"/>
    <logger name="org.mybatis" level="warn"/>
    <logger name="org.apache.ibatis" level="warn"/>
    <logger name="com.baomidou.mybatisplus" level="warn"/>
    <logger name="springfox.documentation.swagger2" level="off"/>
    <logger name="io.swagger.models.parameters" level="off"/>
    <logger name="springfox.documentation.swagger.readers.operation.OperationImplicitParameterReader" level="off"/>
trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractApplyOrder.java
@@ -113,6 +113,10 @@
     */
    private BigDecimal fee;
    /**
     * 资金费
     */
    private BigDecimal fundingFee;
    /**
     * 保证金
     */
    private BigDecimal deposit;
trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractOrder.java
@@ -65,6 +65,14 @@
     */
    private BigDecimal fee;
    /**
     * 资金费
     */
    private BigDecimal fundingFee;
    /**
     * 杠杆借贷金额
     */
    private BigDecimal borrowedAmount;
    /**
     * 保证金(剩余)
     */
    private BigDecimal deposit ;
@@ -165,6 +173,13 @@
        return depositOpen;
    }
    public BigDecimal getBorrowedAmount() {
        if (borrowedAmount == null) {
            borrowedAmount = BigDecimal.ZERO;
        }
        return borrowedAmount;
    }
    public BigDecimal getTradeAvgPrice() {
        if(tradeAvgPrice == null){
            tradeAvgPrice = BigDecimal.ZERO;
trading-order-bean/src/main/java/com/yami/trading/bean/contract/dto/ContractApplyOrderDTO.java
@@ -138,6 +138,11 @@
    private BigDecimal fee;
    /**
     * 资金费
     */
    private BigDecimal fundingFee;
    /**
     * DEPOSIT
     */
    private BigDecimal deposit;
trading-order-bean/src/main/java/com/yami/trading/bean/contract/dto/ContractOrderDTO.java
@@ -93,6 +93,12 @@
    private BigDecimal fee;
    /**
     * 资金费
     */
    @ApiModelProperty("资金费")
    private BigDecimal fundingFee;
    /**
     *  保证金(剩余)
     */
    @ApiModelProperty("保证金(剩余)")
@@ -192,6 +198,8 @@
    private String forceClosePrice;
    private double changeRatio;
    public BigDecimal getProfitLoss() {
trading-order-common/src/main/java/com/yami/trading/common/constants/Constants.java
@@ -22,6 +22,8 @@
    public static final String IMAGES_HTTP = ApplicationUtil.getProperty("images_http");
    public static final String API_HTTP = ApplicationUtil.getProperty("api_http");
    /**
     * 质押2.0下单
     */
trading-order-common/src/main/java/com/yami/trading/common/constants/RedisKeys.java
@@ -47,6 +47,11 @@
     * 区块链充值订单
     */
    public final static String RECHARGE_BLOCKCHAIN_ORDERNO = "RECHARGE_BLOCKCHAIN_ORDERNO_";
    /**
     * 充值地址缓存
     */
    public final static String BLOCKCHAIN_ADDRESS = "BLOCKCHAIN_ADDRESS";
    /**
     * 提现订单
     */
trading-order-security-common/src/main/java/com/yami/trading/security/common/adapter/ResourceServerAdapter.java
@@ -103,6 +103,8 @@
                "/api/exchangerateuserconfig!get.action",
                "/api/item/itemUserOptionalList/isItemHasAddGlobal",
                "/api/item/itemUserOptionalList/list",
                "/api/channelBlockchain!rechargeCallback.action",
                "/api/withdraw/withdrawCallback.action",
                "/api/wallet/getUsdt",
                "/api/demo/**",
                "/api/timezone/info",
trading-order-service/src/main/java/com/yami/trading/UD/Address.java
New file
@@ -0,0 +1,31 @@
package com.yami.trading.UD;
public class Address {
    private String address;
    private int coinType;
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public int getCoinType() {
        return coinType;
    }
    public void setCoinType(int coinType) {
        this.coinType = coinType;
    }
    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                ", coinType=" + coinType +
                '}';
    }
}
trading-order-service/src/main/java/com/yami/trading/UD/ApiPath.java
New file
@@ -0,0 +1,12 @@
package com.yami.trading.UD;
public class ApiPath {
    public final static String CREATE_ADDRESS = "/mch/address/create";
    public final static String WITHDRAW = "/mch/withdraw";
    public final static String TRANSACTION = "/mch/transaction";
    public final static String AUTO_WITHDRAW = "/mch/withdraw/proxypay";
    public final static String SUPPORT_COIN = "/mch/support-coins";
    public final static String CHECK_PROXY = "/mch/check-proxy";
    public final static String CHECK_ADDRESS = "/mch/check/address";
    public final static String CREATE_BATCH_ADDRESS = "/mch/address/create/batch";
}
trading-order-service/src/main/java/com/yami/trading/UD/Coin.java
New file
@@ -0,0 +1,78 @@
package com.yami.trading.UD;
import java.math.BigDecimal;
public class Coin {
    private String name;
    private String symbol;
    private String mainCoinType;
    private String coinType;
    private String decimals;
    private Integer tokenStatus;
    private String mainSymbol;
    private BigDecimal balance;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSymbol() {
        return symbol;
    }
    public void setSymbol(String symbol) {
        this.symbol = symbol;
    }
    public String getMainCoinType() {
        return mainCoinType;
    }
    public void setMainCoinType(String mainCoinType) {
        this.mainCoinType = mainCoinType;
    }
    public String getCoinType() {
        return coinType;
    }
    public void setCoinType(String coinType) {
        this.coinType = coinType;
    }
    public String getDecimals() {
        return decimals;
    }
    public void setDecimals(String decimals) {
        this.decimals = decimals;
    }
    public Integer getTokenStatus() {
        return tokenStatus;
    }
    public void setTokenStatus(Integer tokenStatus) {
        this.tokenStatus = tokenStatus;
    }
    public String getMainSymbol() {
        return mainSymbol;
    }
    public void setMainSymbol(String mainSymbol) {
        this.mainSymbol = mainSymbol;
    }
    public BigDecimal getBalance() {
        return balance;
    }
    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }
}
trading-order-service/src/main/java/com/yami/trading/UD/CryptoCurrencyEnum.java
New file
@@ -0,0 +1,91 @@
package com.yami.trading.UD;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * @program: trading-order-master
 * @description: 充值币种
 * @create: 2025-08-06 16:05
 **/
public enum CryptoCurrencyEnum {
    /*USDT_TRC20("usdt", "TRC20","usdt_trc20"),
    USDT_ERC20("usdt", "ERC20","usdt_erc20"),
    ETH("eth", "ETH","eth"),
    BTC("btc", "BTC","btc"),
    USDC_ERC20("usdc", "ERC20(1)","usdc_erc20(1)"),
    USDC_ERC202("usdc", "ERC20(2)","usdc_erc20(2)"),
    USDC_TRC20("usdc", "TRC20","usdc_trc20");*/
    USDT_TRC20("usdt", "TRC20","USDT-TRC20", "usdt"),
    USDT_ERC20("usdt", "ERC20","USDT-ERC20", "usdt"),
    ETH("eth", "ETH","ETH", "ethusdt"),
    BTC("btc", "BTC","BTC", "btcusdt"),
    //USDC_ERC20("usdc", "ERC20","USDC-ERC20", "usdcusdt"),
    //USDC_TRC20("usdc", "TRC20","USDC-TRC20", "usdcusdt")
    USDC("usdc", "ERC20","USDC", "usdcusdt");
    private final String coin;
    private final String chain;
    private final String name;
    private final String symbol;
    CryptoCurrencyEnum(String coin, String chain, String name, String symbol) {
        this.coin = coin;
        this.chain = chain;
        this.name = name;
        this.symbol = symbol;
    }
    public String getSymbol() {
        return symbol;
    }
    public String getCoin() {
        return coin;
    }
    public String getChain() {
        return chain;
    }
    public String getName() {
        return name;
    }
    // 可选:根据代码获取枚举值的方法
    public static CryptoCurrencyEnum fromCoin(String coin,String chain) {
        for (CryptoCurrencyEnum currency : values()) {
            if (currency.getCoin().equals(coin) && currency.getChain().equals(chain)) {
                return currency;
            }
        }
        throw new IllegalArgumentException("没找到对应的币种: " + coin);
    }
    /**
     * 获取所有枚举值(返回 List)
     */
    public static List<CryptoCurrencyEnum> getAll() {
        return Arrays.asList(values());
    }
    /**
     * 获取所有枚举值(按 coin 分组,返回 Map<String, List<CryptoCurrencyEnum>>)
     */
    public static Map<String, List<CryptoCurrencyEnum>> getAllGroupedByCoin() {
        return Arrays.stream(values())
                .collect(Collectors.groupingBy(CryptoCurrencyEnum::getCoin));
    }
    /**
     * 获取所有枚举值(按 chain 分组,返回 Map<String, List<CryptoCurrencyEnum>>)
     */
    public static Map<String, List<CryptoCurrencyEnum>> getAllGroupedByChain() {
        return Arrays.stream(values())
                .collect(Collectors.groupingBy(CryptoCurrencyEnum::getChain));
    }
}
trading-order-service/src/main/java/com/yami/trading/UD/ResultMsg.java
New file
@@ -0,0 +1,40 @@
package com.yami.trading.UD;
public class ResultMsg {
    private Integer code;
    private String message;
    private String data;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
    }
    @Override
    public String toString() {
        return "ResultMsg{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data='" + data + '\'' +
                '}';
    }
}
trading-order-service/src/main/java/com/yami/trading/UD/UdunApi.java
New file
@@ -0,0 +1,108 @@
package com.yami.trading.UD;
import java.math.BigDecimal;
import java.util.List;
public interface UdunApi {
    /**
     * 创建币种地址,别名和钱包编号默认,回调地址使用统一配置
     *
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @return 地址
     */
    Address createAddress(String mainCoinType)  throws UdunException;
    /**
     * 创建币种地址,别名和钱包编号自定义,回调地址使用统一配置
     *
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param alias        地址别名
     * @param walletId     钱包编号
     * @return 地址
     */
    Address createAddress(String mainCoinType, String alias, String walletId)  throws UdunException;
    /**
     * 创建币种地址,别名和钱包编号自定义,回调地址自定义
     *
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param alias        地址别名
     * @param walletId     钱包编号
     * @param callUrl      回调地址
     * @return 地址
     */
    Address createAddress(String mainCoinType, String alias, String walletId, String callUrl) throws UdunException;
    /**
     * 提币,回调地址使用统一配置
     *
     * @param address      提币地址
     * @param amount       提币数量
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param coinType     子币种编号,使用获取商户币种信息接口
     * @param businessId   业务编号,必须保证该字段在系统内唯一,如果重复,则该笔提币钱包将不会进行接收
     * @param memo         备注,XRP和EOS,这两种币的提币申请该字段可选,其他类型币种不填
     * @return 返回信息
     */
    ResultMsg withdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo);
    /**
     * 提币,回调地址自定义
     *
     * @param address      提币地址
     * @param amount       提币数量
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param coinType     子币种编号,使用获取商户币种信息接口
     * @param businessId   业务编号,必须保证该字段在系统内唯一,如果重复,则该笔提币钱包将不会进行接收
     * @param memo         备注,XRP和EOS,这两种币的提币申请该字段可选,其他类型币种不填
     * @param callUrl      回调地址
     * @return 返回信息
     */
    ResultMsg withdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo, String callUrl);
    /**
     * 代付,回调地址使用统一配置
     *
     * @param address      提币地址
     * @param amount       提币数量
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param coinType     子币种编号,使用获取商户币种信息接口
     * @param businessId   业务编号,必须保证该字段在系统内唯一,如果重复,则该笔提币钱包将不会进行接收
     * @param memo         备注,XRP和EOS,这两种币的提币申请该字段可选,其他类型币种不填
     * @return 返回信息
     */
    ResultMsg autoWithdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo);
    /**
     * 代付,回调地址自定义
     *
     * @param address      提币地址
     * @param amount       提币数量
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param coinType     子币种编号,使用获取商户币种信息接口
     * @param businessId   业务编号,必须保证该字段在系统内唯一,如果重复,则该笔提币钱包将不会进行接收
     * @param memo         备注,XRP和EOS,这两种币的提币申请该字段可选,其他类型币种不填
     * @param callUrl      回调地址
     * @return 返回信息
     */
    ResultMsg autoWithdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo, String callUrl);
    /**
     * 检验地址合法性
     *
     * @param mainCoinType 主币种编号,使用获取商户币种信息接口
     * @param address      币种地址
     * @return 是否合法
     */
    boolean checkAddress(String mainCoinType, String address);
    /**
     * 获取商户支持的币种,以及余额
     *
     * @param showBalance 是否显示余额
     * @return 支持币种列表
     */
    List<Coin> listSupportCoin(boolean showBalance);
}
trading-order-service/src/main/java/com/yami/trading/UD/UdunClient.java
New file
@@ -0,0 +1,151 @@
package com.yami.trading.UD;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.yami.trading.common.constants.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class UdunClient implements UdunApi {
    private static Logger logger = LoggerFactory.getLogger(UdunClient.class);
    /**
     * UDUN API Gateway
     */
    private final String gateway =  "https://sig11.udun.io";
    /**
     * UDUN Merchant Key
     */
    private final String merchantKey =  "75b15c82af142bc35feb3709235efdf8";
    /**
     * UDUN Merchant Number
     */
    private final String merchantId =  "322810";
    /**
     * Callback 充值
     */
    private final String defaultCallBackUrl = Constants.API_HTTP + "api/channelBlockchain!rechargeCallback.action";
    /**
     * Callback 提币
     */
    private final String withdrawCallBackUrl =  Constants.API_HTTP + "api/withdraw/withdrawCallback.action";
    @Override
    public Address createAddress(String mainCoinType)  throws UdunException {
        return createAddress(mainCoinType, "", "", defaultCallBackUrl);
    }
    @Override
    public Address createAddress(String mainCoinType, String alias, String walletId)  throws UdunException{
        return createAddress(mainCoinType, alias, walletId, defaultCallBackUrl);
    }
    @Override
    public Address createAddress(String mainCoinType, String alias, String walletId, String callUrl) throws UdunException{
        Map<String, String> params = new HashMap<>();
        params.put("merchantId", merchantId);
        params.put("mainCoinType", mainCoinType);
        params.put("callUrl", callUrl);
        params.put("walletId", walletId);
        params.put("alias", alias);
        ResultMsg result = JSONUtil.toBean(UdunUtils.post(gateway, merchantKey, ApiPath.CREATE_ADDRESS, StrUtil.format("[{}]", JSONUtil.toJsonStr(params))), ResultMsg.class);
        if (result.getCode() != HttpStatus.HTTP_OK) {
            logger.error("createAddress:{}",JSONUtil.toJsonStr(result));
            throw new UdunException(result.getCode(), result.getMessage());
        }
        return JSONUtil.toBean(result.getData(), Address.class);
    }
    @Override
    public ResultMsg withdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo) {
        return withdraw(address, amount, mainCoinType, coinType, businessId, memo, withdrawCallBackUrl);
    }
    @Override
    public ResultMsg withdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo, String callUrl) {
        Map<String, Object> params = new HashMap<>();
        params.put("address", address);
        params.put("amount", amount);
        params.put("merchantId", merchantId);
        params.put("mainCoinType", mainCoinType);
        params.put("coinType", coinType);
        params.put("callUrl", callUrl);
        params.put("businessId", businessId);
        params.put("memo", memo);
        return JSONUtil.toBean(UdunUtils.post(gateway, merchantKey, ApiPath.WITHDRAW, StrUtil.format("[{}]", JSONUtil.toJsonStr(params))), ResultMsg.class);
    }
    @Override
    public ResultMsg autoWithdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo) {
        return autoWithdraw(address, amount, mainCoinType, coinType, businessId, memo, withdrawCallBackUrl);
    }
    @Override
    public ResultMsg autoWithdraw(String address, BigDecimal amount, String mainCoinType, String coinType, String businessId, String memo, String callUrl) {
        Map<String, Object> params = new HashMap<>();
        params.put("address", address);
        params.put("amount", amount);
        params.put("merchantId", merchantId);
        params.put("mainCoinType", mainCoinType);
        params.put("coinType", coinType);
        params.put("callUrl", callUrl);
        params.put("businessId", businessId);
        params.put("memo", memo);
        return JSONUtil.toBean(UdunUtils.post(gateway, merchantKey, ApiPath.AUTO_WITHDRAW, StrUtil.format("[{}]", JSONUtil.toJsonStr(params))), ResultMsg.class);
    }
    @Override
    public boolean checkAddress(String mainCoinType, String address) {
        Map<String, String> params = new HashMap<>();
        params.put("merchantId", merchantId);
        params.put("mainCoinType", mainCoinType);
        params.put("address", address);
        ResultMsg result = JSONUtil.toBean(UdunUtils.post(gateway, merchantKey, ApiPath.CHECK_ADDRESS, StrUtil.format("[{}]", JSONUtil.toJsonStr(params))), ResultMsg.class);
        return result.getCode() == HttpStatus.HTTP_OK;
    }
    @Override
    public List<Coin> listSupportCoin(boolean showBalance) {
        Map<String, Object> params = new HashMap<>();
        params.put("merchantId", merchantId);
        params.put("showBalance", showBalance);
        ResultMsg result = JSONUtil.toBean(UdunUtils.post(gateway, merchantKey, ApiPath.SUPPORT_COIN, JSONUtil.toJsonStr(params)), ResultMsg.class);
        if (result.getCode() != HttpStatus.HTTP_OK) {
            Console.error(JSONUtil.toJsonStr(result));
            return null;
        }
        return JSONUtil.toList(JSONUtil.parseArray(result.getData()), Coin.class);
    }
    public boolean checkSign(String timestamp, String nonce, String body, String sign) {
        String checkSign = UdunUtils.sign(merchantKey, timestamp, nonce, body);
        return checkSign.equals(sign);
    }
    public static void main(String[] args) {
        Map<String, Object> params = new HashMap<>();
        params.put("merchantId", 321912);
        params.put("showBalance", false);
        ResultMsg result = JSONUtil.toBean(UdunUtils.post("https://sig11.udun.io", "cf6802e282476b74e2a3cfd16a6cf7ec", ApiPath.SUPPORT_COIN, JSONUtil.toJsonStr(params)), ResultMsg.class);
        if (result.getCode() != HttpStatus.HTTP_OK) {
            Console.error(JSONUtil.toJsonStr(result));
        }
        List<Coin> coins = JSONUtil.toList(JSONUtil.parseArray(result.getData()), Coin.class);
        System.out.println(coins);
    }
}
trading-order-service/src/main/java/com/yami/trading/UD/UdunException.java
New file
@@ -0,0 +1,28 @@
package com.yami.trading.UD;
public class UdunException extends RuntimeException {
    private Integer code;
    private String message;
    public UdunException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    @Override
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
trading-order-service/src/main/java/com/yami/trading/UD/UdunUtils.java
New file
@@ -0,0 +1,84 @@
package com.yami.trading.UD;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class UdunUtils {
    private static Logger logger = LoggerFactory.getLogger(UdunUtils.class);
    public static String post(String gateway, String merchantKey, String path, String body) {
        try {
            // 创建 URL 对象
            URL url = new URL(gateway+path);
            // 打开连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            // 设置请求方法为 POST
            connection.setRequestMethod("POST");
            // 设置请求头
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("Accept", "application/json");
            // 启用输入输出流
            connection.setDoOutput(true);
            String rawBody = parseParams(merchantKey, body);
            // 写入请求体
            try (OutputStream os = connection.getOutputStream()) {
                byte[] input = rawBody.getBytes("utf-8");
                os.write(input, 0, input.length);
            }
            // 获取响应代码
            int responseCode = connection.getResponseCode();
            // 读取响应
            try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                String inputLine;
                StringBuilder response = new StringBuilder();
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                return response.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String parseParams(String merchantKey, String body) {
        Map<String, String> params = new HashMap<>();
        String timestamp = System.currentTimeMillis() + "";
        String nonce = RandomUtil.randomString(6);
        String sign = sign(merchantKey, timestamp, nonce, body);
        params.put("timestamp", timestamp);
        params.put("nonce", nonce);
        params.put("sign", sign);
        params.put("body", body);
        return JSONUtil.toJsonStr(params);
    }
    public static String sign(String key, String timestamp, String nonce, String body) {
        String raw = body + key + nonce + timestamp;
        return SecureUtil.md5(raw);
    }
}
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
@@ -172,6 +172,7 @@
            map.put("amount", order.getVolume().multiply(order.getUnitAmount()));
            map.put("amount_open", order.getVolumeOpen().multiply(order.getUnitAmount()));
            map.put("fee", order.getFee());
            map.put("funding_fee", order.getFundingFee());
            map.put("deposit", order.getDeposit());
            data.add(map);
        }
@@ -233,46 +234,60 @@
        log.info("{}  --- order --- {}  --- {}", order.getSymbol(), item.getUuid(), levers.size());
        checkLever(order, levers);
        BigDecimal sourceLeverRate = order.getLeverRate();
        sourceLeverRate = sourceLeverRate == null ? BigDecimal.ZERO : sourceLeverRate;
//        BigDecimal sourceLeverRate = order.getLeverRate();
//        sourceLeverRate = sourceLeverRate == null ? BigDecimal.ZERO : sourceLeverRate;
        List<ContractOrder> contractOrderSubmitted = contractOrderService.findSubmitted(order.getPartyId(),
                order.getSymbol(), order.getDirection());
        for (int i = 0; i < contractOrderSubmitted.size(); i++) {
            BigDecimal targetLeverRate = contractOrderSubmitted.get(i).getLeverRate();
            targetLeverRate = targetLeverRate == null ? BigDecimal.ZERO : targetLeverRate;
            if (sourceLeverRate.compareTo(targetLeverRate) != 0) {
                throw new YamiShopBindException("存在不同杠杆的持仓单");
            }
        }
        List<ContractApplyOrder> applyOrderSubmittedList = this.findSubmitted(order.getPartyId(),
                order.getSymbol(), "open", order.getDirection());
        for (int i = 0; i < applyOrderSubmittedList.size(); i++) {
            BigDecimal targetLeverRate = applyOrderSubmittedList.get(i).getLeverRate();
            targetLeverRate = targetLeverRate == null ? BigDecimal.ZERO : targetLeverRate;
            if (sourceLeverRate.compareTo(targetLeverRate) != 0) {
                throw new YamiShopBindException("存在不同杠杆的持仓单");
            }
        }
//        List<ContractOrder> contractOrderSubmitted = contractOrderService.findSubmitted(order.getPartyId(),
//                order.getSymbol(), order.getDirection());
//        for (int i = 0; i < contractOrderSubmitted.size(); i++) {
//            BigDecimal targetLeverRate = contractOrderSubmitted.get(i).getLeverRate();
//            targetLeverRate = targetLeverRate == null ? BigDecimal.ZERO : targetLeverRate;
//            if (sourceLeverRate.compareTo(targetLeverRate) != 0) {
//                throw new YamiShopBindException("存在不同杠杆的持仓单");
//            }
//        }
//
//        List<ContractApplyOrder> applyOrderSubmittedList = this.findSubmitted(order.getPartyId(),
//                order.getSymbol(), "open", order.getDirection());
//        for (int i = 0; i < applyOrderSubmittedList.size(); i++) {
//            BigDecimal targetLeverRate = applyOrderSubmittedList.get(i).getLeverRate();
//            targetLeverRate = targetLeverRate == null ? BigDecimal.ZERO : targetLeverRate;
//            if (sourceLeverRate.compareTo(targetLeverRate) != 0) {
//                throw new YamiShopBindException("存在不同杠杆的持仓单");
//            }
//        }
        //下单数量
        BigDecimal volume = order.getVolume();
        //当前价格
        List<Realtime> list = dataService.realtime(order.getSymbol());
        double close = 0;
        /**
         * 限价单
         */
        if ("limit".equals(order.getOrderPriceType())) {
            close = order.getPrice().doubleValue();
        }else{
            close = list.get(0).getClose();
        }
        if(StringUtils.isEmptyString(order.getOrderNo())) {
            order.setOrderNo(DateUtil.getToday("yyMMddHHmmss") + RandomUtil.getRandomNum(8));
        }
        order.setUnitAmount(BigDecimal.valueOf(item.getUnitAmount()));
        order.setFee(BigDecimal.valueOf(item.getUnitFee()).multiply(order.getVolume()));
        order.setDeposit(BigDecimal.valueOf(item.getUnitAmount()).multiply(order.getVolume()));
        order.setDeposit(BigDecimal.valueOf(close).multiply(order.getVolume()).divide(order.getLeverRate()));
        order.setFee(BigDecimal.valueOf(item.getUnitFee()).multiply(order.getDeposit()).divide(order.getLeverRate()));
        BigDecimal fee = order.getFee();
        if (order.getLeverRate() != null) {
            // 加上杠杆
            order.setVolume(order.getVolume().multiply(order.getLeverRate()));
            Syspara syspara = sysparaService.find("perpetual_contracts");
            if (ObjectUtils.isEmpty(syspara)||"0".equals(syspara.getSvalue())) {
//                order.setFee(fee.multiply(order.getLeverRate()));
            }
        }
//        if (order.getLeverRate() != null) {
//            // 加上杠杆
//            order.setVolume(order.getVolume().multiply(order.getLeverRate()));
//            Syspara syspara = sysparaService.find("perpetual_contracts");
//            if (ObjectUtils.isEmpty(syspara)||"0".equals(syspara.getSvalue())) {
////                order.setFee(fee.multiply(order.getLeverRate()));
//            }
//        }
        Syspara syspara = sysparaService.find("u_standard_contract");
        BigDecimal deposit = order.getDeposit();
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java
@@ -24,6 +24,7 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
@@ -53,6 +54,83 @@
    @Resource
    private SysparaService sysparaService;
    private Logger logger = LogManager.getLogger(ContractOrderCalculationServiceImpl.class);
    private BigDecimal defaultZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private BigDecimal estimateAccruedFundingFee(ContractOrder order) {
        if (order == null || order.getCreateTime() == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal borrowedAmount = defaultZero(order.getBorrowedAmount());
        if (borrowedAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        long holdHours = Duration.between(order.getCreateTime().toInstant(), Instant.now()).toHours();
        long settlementPeriods = holdHours / 4;
        if (settlementPeriods <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal conservativeRate = new BigDecimal("0.001");
        return borrowedAmount.multiply(conservativeRate)
                .multiply(BigDecimal.valueOf(settlementPeriods))
                .setScale(8, RoundingMode.HALF_UP);
    }
    private BigDecimal calculateType1ForceClosePrice(ContractOrder order, Wallet wallet) {
        BigDecimal volume = defaultZero(order.getVolume());
        BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
        if (volume.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        List<ContractOrder> list = contractOrderService.findSubmitted(order.getPartyId(), null, null, null, null, null);
        BigDecimal otherEquity = BigDecimal.ZERO;
        for (ContractOrder contractOrder : list) {
            if (ContractOrder.STATE_SUBMITTED.equals(contractOrder.getState())) {
                contractOrderService.wrapProfit(contractOrder);
            }
            if (order.getUuid().equals(contractOrder.getUuid())) {
                continue;
            }
            otherEquity = otherEquity.add(defaultZero(contractOrder.getProfit()).add(defaultZero(contractOrder.getDeposit())));
        }
        BigDecimal accruedFundingFee = estimateAccruedFundingFee(order);
        BigDecimal baseEquity = defaultZero(wallet.getMoney())
                .add(otherEquity)
                .add(defaultZero(order.getDeposit()))
                .subtract(accruedFundingFee);
        BigDecimal priceOffset = baseEquity.divide(volume, 10, RoundingMode.HALF_UP);
        if (ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())) {
            return tradeAvgPrice.subtract(priceOffset);
        }
        return tradeAvgPrice.add(priceOffset);
    }
    private BigDecimal calculateType2ForceClosePrice(ContractOrder order) {
        BigDecimal volume = defaultZero(order.getVolume());
        BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
        if (volume.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal thresholdRatio = order_close_line.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP);
        if (thresholdRatio.compareTo(BigDecimal.ZERO) <= 0) {
            return tradeAvgPrice;
        }
        BigDecimal availableDeposit = defaultZero(order.getDeposit()).subtract(estimateAccruedFundingFee(order));
        if (availableDeposit.compareTo(BigDecimal.ZERO) <= 0) {
            return tradeAvgPrice;
        }
        BigDecimal requiredLoss = availableDeposit.divide(thresholdRatio, 10, RoundingMode.HALF_UP);
        BigDecimal priceOffset = requiredLoss.divide(volume, 10, RoundingMode.HALF_UP);
        if (ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())) {
            return tradeAvgPrice.subtract(priceOffset);
        }
        return tradeAvgPrice.add(priceOffset);
    }
    @Override
    public void saveCalculation(String order_no) {
        try {
@@ -70,6 +148,7 @@
            Realtime realtime = list.get(0);
            BigDecimal close = new BigDecimal(realtime.getClose());
            settle(order, "watch", close);
            BigDecimal add = order.getTradeAvgPrice().add(order.getPips());
            BigDecimal subtract = order.getTradeAvgPrice().subtract(order.getPips());
@@ -186,11 +265,10 @@
        /**
         * 根据价格变化百分比和保证金计算盈亏金额
         */
        BigDecimal priceChangeRatio = currentPrice.subtract(order.getTradeAvgPrice())
            .divide(order.getTradeAvgPrice(), 6, RoundingMode.DOWN);
            BigDecimal priceChangeRatio = currentPrice.subtract(order.getTradeAvgPrice())
                .divide(order.getTradeAvgPrice(), 6, RoundingMode.DOWN);
        BigDecimal margin = order.getUnitAmount().multiply(order.getVolumeOpen()); // 这是用户实际的投资金额
        BigDecimal margin = order.getTradeAvgPrice().multiply(order.getVolume()); // 这是用户当前持仓对应的投资金额
        BigDecimal profitAmount = margin.multiply(priceChangeRatio);
@@ -256,53 +334,24 @@
        }
        BigDecimal profit1 = contractOrderService.getCacheProfit(order.getUuid()).getProfit();
        if (order_close_line_type == 1) {
            /**
             * 收益
             */
            BigDecimal profit = BigDecimal.ZERO;
            Wallet wallet = this.walletService.findByUserId(order.getPartyId().toString());
            // 计算所有除自己以外的profit
            BigDecimal profitExptThis = profit.subtract(profit1).subtract(order.getDeposit());
            /**
             * profitAll+wallet<=0
             * profitAll<=wallet 强平
             * p1 +E (p2~pn) <=wallet
             * (currentPrice-tradavg)*pipAmount*volume/pips + depost1 <=wallet-E(p2~pn)
             */
            BigDecimal left = wallet.getMoney().negate().subtract(profitExptThis).subtract(order.getDeposit());
            BigDecimal pipsAmount = order.getPipsAmount();
            if(pipsAmount.doubleValue() <= 0.00){
                pipsAmount = new BigDecimal("0.01");
            }
            BigDecimal overLine = (left.multiply(pips).divide(pipsAmount, 10, RoundingMode.HALF_UP)
                    .divide(order.getVolume(), 10, RoundingMode.HALF_UP));
            Integer decimal = itemService.getDecimal(order.getSymbol());
            BigDecimal forceClose = BigDecimal.ZERO;
            // 买多,从买价跌多少
            if (order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_BUY)) {
                forceClose = order.getTradeAvgPrice().add(overLine).setScale(decimal, RoundingMode.HALF_UP);
                //买跌,涨到多少
            } else {
                forceClose = order.getTradeAvgPrice().subtract(overLine).setScale(decimal, RoundingMode.HALF_UP);
            }
            BigDecimal forceClose = calculateType1ForceClosePrice(order, wallet).setScale(decimal, RoundingMode.HALF_UP);
            if (forceClose.compareTo(BigDecimal.ZERO) < 0) {
                forceClose = BigDecimal.ZERO;
            }
            order.setForceClosePrice(forceClose.toPlainString());
            this.contractOrderService.updateByIdBuffer(order);
            List<ContractOrder> list = contractOrderService.findSubmitted(order.getPartyId(), null, null, null, null, null);
            BigDecimal totalEquity = defaultZero(wallet.getMoney());
            for(ContractOrder contractOrder :list) {
                if(ContractOrder.STATE_SUBMITTED.equals(contractOrder.getState())){
                    contractOrderService.wrapProfit(contractOrder);
                }
                totalEquity = totalEquity.add(defaultZero(contractOrder.getProfit()).add(defaultZero(contractOrder.getDeposit())));
            }
            for (int i = 0; i < list.size(); i++) {
                ContractOrder close_line = list.get(i);
                profit = profit.add(close_line.getProfit().add(close_line.getDeposit()));
            }
            if (profit.add(wallet.getMoney()).compareTo(BigDecimal.ZERO) <= 0) {
            if (totalEquity.compareTo(BigDecimal.ZERO) <= 0) {
                /**
                 * 触发全仓强平
                 */
@@ -310,6 +359,13 @@
            }
        } else {
            Integer decimal = itemService.getDecimal(order.getSymbol());
            BigDecimal forceClose = calculateType2ForceClosePrice(order).setScale(decimal, RoundingMode.HALF_UP);
            if (forceClose.compareTo(BigDecimal.ZERO) < 0) {
                forceClose = BigDecimal.ZERO;
            }
            order.setForceClosePrice(forceClose.toPlainString());
            this.contractOrderService.updateByIdBuffer(order);
            BigDecimal divide = order.getDeposit().divide(profit1.abs(), 10, RoundingMode.HALF_UP);
            if (profit1.compareTo(BigDecimal.ZERO) < 0 && divide.compareTo(order_close_line.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP)) <= 0) {
                /**
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
@@ -63,6 +63,7 @@
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@@ -73,6 +74,7 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -377,7 +379,10 @@
         * 收益
         */
        BigDecimal volume = order.getVolume();
        BigDecimal profit = settle(order, order.getVolume());
        BigDecimal fundingFee = this.calculateFundingFee(order, volume, new Date());
        BigDecimal profit = settle(order, order.getVolume()).subtract(fundingFee);
        order.setAmountClose(order.getAmountClose().subtract(fundingFee));
        order.setFundingFee(defaultZero(order.getFundingFee()).add(fundingFee));
        String symbol = order.getSymbol();
//        Item item = itemService.findBySymbol(symbol);
//        profit = exchangeRateService.getUsdtByType(profit, item.getType());
@@ -406,11 +411,6 @@
            walletService.updateMoney(order.getSymbol(), partyId, profit, BigDecimal.ZERO,
                    Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
        }
        order.setState(ContractOrder.STATE_CREATED);
        order.setVolume(BigDecimal.ZERO);
        order.setDeposit(BigDecimal.ZERO);
        order.setCloseTime(DateUtil.currentSeconds());
        order.setCloseTimeTs(DateUtil.currentSeconds());
//        List<Realtime> list = this.dataService.realtime(order.getSymbol());
//        // 平仓时候把当前价格先更新回去
//        if (list.size() != 0) {
@@ -466,6 +466,7 @@
            if (cacheProfit != null) {
                contractOrder.setProfit(cacheProfit.getProfit());
                contractOrder.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
                contractOrder.setForceClosePrice(cacheProfit.getForceClosePrice());
            } else {
                contractOrder.setProfit(BigDecimal.ZERO);
@@ -630,11 +631,6 @@
     * @param volume 平仓的张数
     */
    public BigDecimal settle(ContractOrder order, BigDecimal volume) {
        /**
         * 平仓比率
         */
//        BigDecimal rate = volume.divide(order.getVolumeOpen(), 10, RoundingMode.HALF_UP);
        ContractOrderProfit cacheProfit = getCacheProfit(order.getUuid());
        BigDecimal originProfit = BigDecimal.ZERO;
        if (cacheProfit != null) {
@@ -642,7 +638,16 @@
            order.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
        }
        BigDecimal profit = order.getDeposit().add(originProfit);
        BigDecimal currentVolume = defaultZero(order.getVolume());
        if (currentVolume.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal closeRatio = volume.divide(currentVolume, 10, RoundingMode.HALF_UP);
        BigDecimal currentDeposit = defaultZero(order.getDeposit());
        BigDecimal currentBorrowed = getCurrentBorrowedAmount(order);
        BigDecimal releasedDeposit = currentDeposit.multiply(closeRatio).setScale(8, RoundingMode.HALF_UP);
        BigDecimal realizedProfit = originProfit.multiply(closeRatio).setScale(8, RoundingMode.HALF_UP);
        BigDecimal profit = releasedDeposit.add(realizedProfit);
        if (ContractOrder.ORDER_FOLLOW == order.getFollow()) { // 跟单还得减去利息收益
            BigDecimal orderAmount = order.getUnitAmount().multiply(order.getTradeAvgPrice()).multiply(order.getLeverRate()); //订单总金额
@@ -669,87 +674,203 @@
        }
        order.setAmountClose(order.getAmountClose().add(profit));
        order.setVolume(order.getVolume().subtract(volume));
        order.setDeposit(order.getDeposit().subtract(order.getDepositOpen()));
        if (order.getVolume().compareTo(BigDecimal.ZERO) <= 0) {
        BigDecimal remainVolume = currentVolume.subtract(volume);
        BigDecimal remainDeposit = currentDeposit.subtract(releasedDeposit).max(BigDecimal.ZERO);
        BigDecimal remainBorrowed = currentBorrowed.subtract(currentBorrowed.multiply(closeRatio).setScale(8, RoundingMode.HALF_UP)).max(BigDecimal.ZERO);
        BigDecimal remainProfit = originProfit.subtract(realizedProfit).setScale(8, RoundingMode.HALF_UP);
        order.setVolume(remainVolume.max(BigDecimal.ZERO));
        if (remainVolume.compareTo(BigDecimal.ZERO) > 0) {
            order.setVolumeOpen(remainVolume);
            order.setDeposit(remainDeposit);
            order.setDepositOpen(remainDeposit);
            order.setBorrowedAmount(remainBorrowed);
            order.setProfit(remainProfit);
        } else {
            order.setState(ContractOrder.STATE_CREATED);
            order.setCloseTime(DateUtil.currentSeconds());
            order.setCloseTimeTs(DateUtil.currentSeconds());
            order.setDeposit(BigDecimal.ZERO);
            order.setBorrowedAmount(BigDecimal.ZERO);
            order.setProfit(originProfit);
        }
        order.setProfit(originProfit);
        return profit;
    }
    private BigDecimal calculateFundingFee(ContractOrder order, BigDecimal closeVolume, Date closeTime) {
        if (order == null || closeVolume == null || closeTime == null) {
            return BigDecimal.ZERO;
        }
        if (closeVolume.compareTo(BigDecimal.ZERO) <= 0
                || order.getVolumeOpen() == null
                || order.getVolumeOpen().compareTo(BigDecimal.ZERO) <= 0
                || order.getCreateTime() == null) {
            return BigDecimal.ZERO;
        }
        long holdHours = Duration.between(order.getCreateTime().toInstant(), closeTime.toInstant()).toHours();
        long settlementPeriods = holdHours / 4;
        if (settlementPeriods <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal fundingRateTotal = BigDecimal.ZERO;
        for (int i = 0; i < settlementPeriods; i++) {
            double periodRate = ThreadLocalRandom.current().nextDouble(-0.001D, 0.0010000001D);
            fundingRateTotal = fundingRateTotal.add(BigDecimal.valueOf(periodRate));
        }
        BigDecimal currentVolume = defaultZero(order.getVolume());
        if (currentVolume.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal closeRatio = closeVolume.divide(currentVolume, 10, RoundingMode.HALF_UP);
        BigDecimal borrowedBase = getCurrentBorrowedAmount(order).multiply(closeRatio);
        return borrowedBase.multiply(fundingRateTotal).setScale(8, RoundingMode.HALF_UP);
    }
    private BigDecimal defaultZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private BigDecimal calculateBorrowedAmount(BigDecimal positionBase, BigDecimal leverRate) {
        BigDecimal safeBase = defaultZero(positionBase);
        BigDecimal safeLeverRate = defaultZero(leverRate);
        if (safeBase.compareTo(BigDecimal.ZERO) <= 0 || safeLeverRate.compareTo(BigDecimal.ONE) <= 0) {
            return BigDecimal.ZERO;
        }
        return safeBase.multiply(safeLeverRate.subtract(BigDecimal.ONE))
                .divide(safeLeverRate, 10, RoundingMode.HALF_UP);
    }
    private BigDecimal getCurrentBorrowedAmount(ContractOrder order) {
        if (order == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal borrowedAmount = defaultZero(order.getBorrowedAmount());
        if (borrowedAmount.compareTo(BigDecimal.ZERO) > 0) {
            return borrowedAmount;
        }
        return calculateBorrowedAmount(order.getDeposit(), order.getLeverRate());
    }
    private boolean shouldMergeCryptoPosition(Item item) {
        return item != null && Item.cryptos.equalsIgnoreCase(item.getType());
    }
    private void mergeOpenPosition(ContractOrder order, ContractApplyOrder applyOrder, BigDecimal tradePrice) {
        BigDecimal oldVolume = defaultZero(order.getVolume());
        BigDecimal newVolume = defaultZero(applyOrder.getVolume());
        BigDecimal totalVolume = oldVolume.add(newVolume);
        if (totalVolume.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        BigDecimal weightedTradeAmount = defaultZero(order.getTradeAvgPrice()).multiply(oldVolume)
                .add(defaultZero(tradePrice).multiply(newVolume));
        BigDecimal avgTradePrice = weightedTradeAmount.divide(totalVolume, 10, RoundingMode.HALF_UP);
        order.setTradeAvgPrice(avgTradePrice);
        order.setVolume(totalVolume);
        order.setVolumeOpen(totalVolume);
        order.setDeposit(defaultZero(order.getDeposit()).add(defaultZero(applyOrder.getDeposit())));
        order.setDepositOpen(defaultZero(order.getDepositOpen()).add(defaultZero(applyOrder.getDeposit())));
        order.setFee(defaultZero(order.getFee()).add(defaultZero(applyOrder.getFee())));
        order.setBorrowedAmount(getCurrentBorrowedAmount(order)
                .add(calculateBorrowedAmount(applyOrder.getDeposit(), applyOrder.getLeverRate())));
        order.setLeverRate(applyOrder.getLeverRate());
        order.setOrderPriceType(applyOrder.getOrderPriceType());
        if (applyOrder.getStopPriceProfit() != null && applyOrder.getStopPriceProfit().compareTo(BigDecimal.ZERO) > 0) {
            order.setStopPriceProfit(applyOrder.getStopPriceProfit());
        }
        if (applyOrder.getStopPriceLoss() != null && applyOrder.getStopPriceLoss().compareTo(BigDecimal.ZERO) > 0) {
            order.setStopPriceLoss(applyOrder.getStopPriceLoss());
        }
    }
    @Transactional
    public void saveOpen(ContractApplyOrder applyOrder, Realtime realtime) {
        Item item = this.itemService.findBySymbol(applyOrder.getSymbol());
        BigDecimal tradePrice = BigDecimal.valueOf(realtime.getClose());
        ContractOrder order;
        List<ContractOrder> submittedOrders = shouldMergeCryptoPosition(item)
                ? this.findSubmitted(applyOrder.getPartyId(), applyOrder.getSymbol(), applyOrder.getDirection())
                : new ArrayList<>();
        if (CollectionUtil.isNotEmpty(submittedOrders)) {
            order = submittedOrders.get(0);
            mergeOpenPosition(order, applyOrder, tradePrice);
            update(order);
        } else {
            order = new ContractOrder();
            order.setPartyId(applyOrder.getPartyId());
            order.setSymbol(applyOrder.getSymbol());
            String orderNo = com.yami.trading.common.util.DateUtil.formatDate(new Date(), "yyMMddHHmmss") + RandomUtil.getRandomNum(8);
            order.setOrderNo(orderNo);
            order.setDirection(applyOrder.getDirection());
            order.setLeverRate(applyOrder.getLeverRate());
            order.setVolume(applyOrder.getVolume());
            order.setVolumeOpen(applyOrder.getVolumeOpen());
            order.setOrderPriceType(applyOrder.getOrderPriceType());
            order.setUnitAmount(applyOrder.getUnitAmount());
            order.setFee(applyOrder.getFee());
            order.setFundingFee(BigDecimal.ZERO);
            order.setBorrowedAmount(calculateBorrowedAmount(applyOrder.getDeposit(), applyOrder.getLeverRate()));
            order.setDeposit(applyOrder.getDeposit());
            order.setDepositOpen(applyOrder.getDeposit());
        ContractOrder order = new ContractOrder();
        order.setPartyId(applyOrder.getPartyId());
        order.setSymbol(applyOrder.getSymbol());
        String orderNo = com.yami.trading.common.util.DateUtil.formatDate(new Date(), "yyMMddHHmmss") + RandomUtil.getRandomNum(8);
        order.setOrderNo(orderNo);
        order.setDirection(applyOrder.getDirection());
        order.setLeverRate(applyOrder.getLeverRate());
        order.setVolume(applyOrder.getVolume());
        order.setVolumeOpen(applyOrder.getVolumeOpen());
        order.setOrderPriceType(applyOrder.getOrderPriceType());
        order.setUnitAmount(applyOrder.getUnitAmount());
        order.setFee(applyOrder.getFee());
        order.setDeposit(applyOrder.getDeposit());
        order.setDepositOpen(applyOrder.getDeposit());
            order.setTradeAvgPrice(tradePrice);
            order.setStopPriceProfit(applyOrder.getStopPriceProfit());
            order.setStopPriceLoss(applyOrder.getStopPriceLoss());
        order.setTradeAvgPrice(BigDecimal.valueOf(realtime.getClose()));
        order.setStopPriceProfit(applyOrder.getStopPriceProfit());
        order.setStopPriceLoss(applyOrder.getStopPriceLoss());
            order.setPips(BigDecimal.valueOf(item.getPips()));
            order.setPipsAmount(BigDecimal.valueOf(item.getPipsAmount()));
            order.setFollow(applyOrder.getFollow());
            save(order);
            RedisUtil.set(ContractRedisKeys.CONTRACT_ORDERNO + order.getOrderNo(), order);
        order.setPips(BigDecimal.valueOf(item.getPips()));
        order.setPipsAmount(BigDecimal.valueOf(item.getPipsAmount()));
        order.setFollow(applyOrder.getFollow());
        // 爆仓是爆整个钱包
//        BigDecimal forceClose = BigDecimal.ZERO;
//        BigDecimal base = order.getDepositOpen().multiply(order.getPips()).divide(order.getPipsAmount(), 10, RoundingMode.HALF_UP).divide(order.getVolume(),10, RoundingMode.HALF_UP);
//        if(order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_BUY)){
//            forceClose = order.getTradeAvgPrice().subtract(base).setScale(item.getDecimals(), RoundingMode.HALF_UP);
//        }else if(order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_SELL)) {
//            forceClose = order.getTradeAvgPrice().add(base).setScale(item.getDecimals(), RoundingMode.HALF_UP);
//        }
//        if(forceClose.compareTo(BigDecimal.ZERO) <0 ){
//            forceClose  =  BigDecimal.ZERO;
//        }
//        order.setForceClosePrice(forceClose.toPlainString());
        save(order);
        RedisUtil.set(ContractRedisKeys.CONTRACT_ORDERNO + order.getOrderNo(), order);
            Map<String, ContractOrder> map = RedisUtil
                    .get(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId());
            if (map == null) {
                map = new ConcurrentHashMap<String, ContractOrder>();
            }
            map.put(order.getOrderNo(), order);
            RedisUtil.set(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId(), map);
        Map<String, ContractOrder> map = RedisUtil
                .get(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId());
        if (map == null) {
            map = new ConcurrentHashMap<String, ContractOrder>();
            // 获取单个订单的合约总资产、总保证金、总未实现盈利
            Map<String, BigDecimal> contractAssetsOrder = this.walletService.getMoneyContractByOrder(order);
            BigDecimal contractAssets = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId());
            if (contractAssets == null) {
                contractAssets = BigDecimal.ZERO;
            }
            BigDecimal contractAssetsDeposit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId());
            if (contractAssetsDeposit == null) {
                contractAssetsDeposit = BigDecimal.ZERO;
            }
            BigDecimal contractAssetsProfit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId());
            if (contractAssetsProfit == null) {
                contractAssetsProfit = BigDecimal.ZERO;
            }
            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId(),
                    contractAssets.add(contractAssetsOrder.get("money_contract")));
            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId(),
                    contractAssetsDeposit.add(contractAssetsOrder.get("money_contract_deposit")));
            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId(),
                    contractAssetsProfit.add(contractAssetsOrder.get("money_contract_profit")));
        }
        map.put(order.getOrderNo(), order);
        RedisUtil.set(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId(), map);
        // 获取单个订单的合约总资产、总保证金、总未实现盈利
        Map<String, BigDecimal> contractAssetsOrder = this.walletService.getMoneyContractByOrder(order);
        BigDecimal contractAssets = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId());
        if (contractAssets == null) {
            contractAssets = BigDecimal.ZERO;
        }
        BigDecimal contractAssetsDeposit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId());
        if (contractAssetsDeposit == null) {
            contractAssetsDeposit = BigDecimal.ZERO;
        }
        BigDecimal contractAssetsProfit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId());
        if (contractAssetsProfit == null) {
            contractAssetsProfit = BigDecimal.ZERO;
        }
        RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId(),
                contractAssets.add(contractAssetsOrder.get("money_contract")));
        RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId(),
                contractAssetsDeposit.add(contractAssetsOrder.get("money_contract_deposit")));
        RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId(),
                contractAssetsProfit.add(contractAssetsOrder.get("money_contract_profit")));
        ContractOrder traderOpenOrder = new ContractOrder();
        traderOpenOrder.setPartyId(order.getPartyId());
        traderOpenOrder.setOrderNo(order.getOrderNo());
        traderOpenOrder.setSymbol(order.getSymbol());
        traderOpenOrder.setDirection(order.getDirection());
        traderOpenOrder.setLeverRate(applyOrder.getLeverRate());
        traderOpenOrder.setVolume(applyOrder.getVolume());
        traderOpenOrder.setVolumeOpen(applyOrder.getVolumeOpen());
        traderOpenOrder.setUnitAmount(applyOrder.getUnitAmount());
        traderOpenOrder.setTradeAvgPrice(tradePrice);
        traderOpenOrder.setStopPriceProfit(applyOrder.getStopPriceProfit());
        traderOpenOrder.setStopPriceLoss(applyOrder.getStopPriceLoss());
        /**
         * 进入市场
@@ -769,7 +890,7 @@
         */
        Trader trader = traderService.findByPartyIdAndChecked(applyOrder.getPartyId(), 1); // 交易员存在
        if (trader != null) {
            traderFollowUserOrderService.traderOpen(order, contractApplyOrderService, this, 1); // 交易员跟随者开启永续合约委托, 加个跟单标识
            traderFollowUserOrderService.traderOpen(traderOpenOrder, contractApplyOrderService, this, 1); // 交易员跟随者开启永续合约委托, 加个跟单标识
        }
        /**
@@ -820,7 +941,11 @@
        /**
         * 平仓退回的金额
         */
        BigDecimal fundingFee = this.calculateFundingFee(order, volume, new Date());
        BigDecimal profit = this.settle(order, volume);
        BigDecimal netProfit = profit.subtract(fundingFee);
        order.setAmountClose(order.getAmountClose().subtract(fundingFee));
        order.setFundingFee(defaultZero(order.getFundingFee()).add(fundingFee));
        update(order);
        Wallet wallet = this.walletService.findByUserId(order.getPartyId());
        BigDecimal amount_before = wallet.getMoney();
@@ -828,14 +953,15 @@
        String symbol = order.getSymbol();
//        Item item = itemService.findBySymbol(symbol);
//        profit = exchangeRateService.getUsdtByType(profit, item.getType());
        if (wallet.getMoney().add(profit).compareTo(BigDecimal.ZERO) < 0) {
            profit = wallet.getMoney().negate();
        if (wallet.getMoney().add(netProfit).compareTo(BigDecimal.ZERO) < 0) {
            netProfit = wallet.getMoney().negate();
        }
        walletService.updateMoney(symbol, order.getPartyId(), profit, BigDecimal.ZERO,
        walletService.updateMoney(symbol, order.getPartyId(), netProfit, BigDecimal.ZERO,
                Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
        applyOrder.setVolume(applyOrder.getVolume().subtract(volume));
        applyOrder.setFundingFee(defaultZero(applyOrder.getFundingFee()).add(fundingFee));
        if (applyOrder.getVolume().compareTo(BigDecimal.ZERO) <= 0) {
            applyOrder.setState(ContractApplyOrder.STATE_CREATED);
        }
@@ -921,9 +1047,10 @@
            map.put("stop_price_loss", order.getStopPriceLoss());
        }
        map.put("state", order.getState());
        map.put("amount", order.getVolume().multiply(order.getUnitAmount()));
        map.put("amount_open", order.getVolumeOpen().multiply(order.getUnitAmount()));
        map.put("amount", order.getDeposit());
        map.put("amount_open", order.getDeposit());
        map.put("fee", order.getFee());
        map.put("funding_fee", order.getFundingFee());
        map.put("deposit", order.getDeposit());
        map.put("deposit_open", order.getDepositOpen());
        map.put("change_ratio", order.getChangeRatio());
trading-order-service/src/main/java/com/yami/trading/service/impl/FollowWalletServiceImpl.java
@@ -256,16 +256,7 @@
                    }
                }
                // 真正下单里
                double order_volume = 1;
                if (order.getLeverRate() != null && order.getLeverRate().compareTo(BigDecimal.ZERO) != 0) {
                    order_volume = order.getVolumeOpen().divide(order.getLeverRate(), 6, RoundingMode.FLOOR).doubleValue();
                } else {
                    order_volume = order.getVolumeOpen().doubleValue();
                }
                double amount = Arith.add(Arith.mul(order_volume, order.getUnitAmount().doubleValue()), order.getProfit().doubleValue());
                double amount = order.getDeposit().add(order.getProfit()).doubleValue();
                money_contract = Arith.add(amount, money_contract);
                money_contract_deposit = Arith.add(order.getDeposit().doubleValue(), money_contract_deposit);
                money_contract_profit = Arith.add(order.getProfit().doubleValue(), money_contract_profit);
@@ -780,14 +771,7 @@
            return moneysContract;
        }
        ApplicationUtil.getBean(ContractOrderService.class).wrapProfit(order);
        BigDecimal orderVolume = BigDecimal.ONE;
        if (order.getLeverRate() != null && order.getLeverRate().compareTo(BigDecimal.ZERO) != 0) {
            orderVolume = order.getVolumeOpen().divide(order.getLeverRate(), 2, RoundingMode.FLOOR);
        } else {
            orderVolume = order.getVolumeOpen();
        }
        BigDecimal moneyContract = orderVolume.multiply(order.getUnitAmount()).add(order.getProfit());
        BigDecimal moneyContract = order.getDeposit().add(order.getProfit());
        BigDecimal moneyContractDeposit = order.getDeposit();
        BigDecimal moneyContractProfit = order.getProfit();
trading-order-service/src/main/java/com/yami/trading/service/impl/RechargeBlockchainOrderServiceImpl.java
@@ -89,7 +89,10 @@
//        Date now = new Date();
        RechargeBlockchainOrder recharge = getById(id);
        if (recharge == null) {
            throw new YamiShopBindException("参数错误!");
            recharge = findByOrderNo(id);
            if (recharge == null) {
                throw new YamiShopBindException("参数错误!");
            }
        }
        User party = userService.getById(recharge.getPartyId());
        if (party == null) {
@@ -119,8 +122,25 @@
         * 如果是usdt则加入wallet,否则寻找walletExtend里相同币种
         */
        Syspara user_recom_bonus_open = sysparaService.find("user_recom_bonus_open");
        if ("usdt".equals(recharge.getSymbol())) {
        if ("usdt".equals(recharge.getSymbol()) || "usdc".equals(recharge.getSymbol())) {
            double amount1 = recharge.getVolume();
            if ("usdc".equals(recharge.getSymbol())) {
                List<Realtime> realtime_list = this.dataService.realtime(recharge.getSymbol());
                log.info("充值usdc转为usdt::" + realtime_list);
                Realtime realtime = null;
                if (realtime_list.size() > 0) {
                    realtime = realtime_list.get(0);
                } else {
                    throw new YamiShopBindException("系统错误,请稍后重试");
                }
                // 对应usdt价格
                double transfer_usdt = realtime.getClose();
                double volume = recharge.getVolume();
                // 币种usdt价格= 币种价格×充值数量
                amount1 = Arith.mul(recharge.getVolume(), transfer_usdt);
                log.info("充值usdc转为usdt::" + amount1);
            }
            Wallet wallet = new Wallet();
            wallet = walletService.saveWalletByPartyId(recharge.getPartyId());
            double amount_before = wallet.getMoney().doubleValue();
@@ -268,7 +288,7 @@
                double baseAmount = Double.parseDouble(recharges[1]);
                // 用户已完成USDT订单
                long number = moneyLogService.getMoneyLogByFirstRecharge(recharge.getPartyId()) == null ? 0 : 1;
                        //this.findByPartyIdAndLargerVolume(recharge.getPartyId(), 1,baseAmount);
                //this.findByPartyIdAndLargerVolume(recharge.getPartyId(), 1,baseAmount);
                rechargeBonusService.saveFirstUsdtBounsHandle(recharge,transfer_usdt,number,recharges);
            }
            /**
@@ -426,7 +446,7 @@
        if ("".equals(recharge.getOrderNo()) || recharge.getOrderNo() == null) {
            recharge.setOrderNo(DateUtil.getToday("yyMMddHHmmss") + RandomUtil.getRandomNum(8));
        }
        save(recharge);
        saveOrUpdate(recharge);
        // 保存资金日志
        WalletLog walletLog = new WalletLog();
        walletLog.setCategory(Constants.MONEYLOG_CATEGORY_RECHARGE);
trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java
@@ -1011,6 +1011,7 @@
//        if (reg.getUsername().indexOf("@") == -1) {
        if (type.equals("1")) {
            // 手机注册
            rejectMainlandChinaPhoneRegister(username);
//            if (StringUtils.isEmptyString(reg.getUsername()) || !Strings.isNumber(reg.getUsername()) || reg.getUsername().length() > 15) {
            if (StringUtils.isEmptyString(username) || username.length() > 20) {
                throw new YamiShopBindException("请输入正确的手机号码");
@@ -1617,6 +1618,7 @@
        User user = null;
        // 手机
        if (type == 1) {
            rejectMainlandChinaPhoneRegister(userName);
            if (!isValidPhone(userName)) {
                throw new YamiShopBindException("手机号格式不正常");
            }
@@ -1778,6 +1780,22 @@
        return user;
    }
    /**
     * 禁止大陆 +86 / 86 前缀手机号注册(与风控一致)
     */
    private void rejectMainlandChinaPhoneRegister(String phoneRaw) {
        if (StringUtils.isEmptyString(phoneRaw)) {
            return;
        }
        String phone = phoneRaw.trim();
        if (phone.startsWith("+86")) {
            throw new YamiShopBindException("不支持+86大陆手机号注册");
        }
        if (phone.matches("^86(1[3-9])\\d{9}$")) {
            throw new YamiShopBindException("不支持86前缀大陆手机号注册");
        }
    }
    // 手机号校验
    private boolean isValidPhone(String username) {
        Pattern p = Pattern.compile("[0-9]*");
trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java
@@ -262,16 +262,7 @@
                    }
                }
                // 真正下单里
                double order_volume = 1;
                if (order.getLeverRate() != null && order.getLeverRate().compareTo(BigDecimal.ZERO) != 0) {
                    order_volume = order.getVolumeOpen().divide(order.getLeverRate(), 6, RoundingMode.FLOOR).doubleValue();
                } else {
                    order_volume = order.getVolumeOpen().doubleValue();
                }
                double amount = Arith.add(Arith.mul(order_volume, order.getUnitAmount().doubleValue()), order.getProfit().doubleValue());
                double amount = order.getDeposit().add(order.getProfit()).doubleValue();
                money_contract = Arith.add(amount, money_contract);
                money_contract_deposit = Arith.add(order.getDeposit().doubleValue(), money_contract_deposit);
                money_contract_profit = Arith.add(order.getProfit().doubleValue(), money_contract_profit);
@@ -814,14 +805,7 @@
            return moneysContract;
        }
        ApplicationUtil.getBean(ContractOrderService.class).wrapProfit(order);
        BigDecimal orderVolume = BigDecimal.ONE;
        if (order.getLeverRate() != null && order.getLeverRate().compareTo(BigDecimal.ZERO) != 0) {
            orderVolume = order.getVolumeOpen().divide(order.getLeverRate(), 2, RoundingMode.FLOOR);
        } else {
            orderVolume = order.getVolumeOpen();
        }
        BigDecimal moneyContract = orderVolume.multiply(order.getUnitAmount()).add(order.getProfit());
        BigDecimal moneyContract = order.getDeposit().add(order.getProfit());
        BigDecimal moneyContractDeposit = order.getDeposit();
        BigDecimal moneyContractProfit = order.getProfit();
trading-order-service/src/main/resources/mapper/contract/ContractApplyOrderMapper.xml
@@ -19,6 +19,7 @@
        a.state AS "state",
        a.unit_amount AS "unitAmount",
        a.fee AS "fee",
        a.funding_fee AS "fundingFee",
        a.deposit AS "deposit",
        a.create_by AS "createBy",
        a.create_time AS "createTime",
trading-order-service/src/main/resources/mapper/contract/ContractOrderMapper.xml
@@ -11,6 +11,7 @@
        a.unit_amount AS "unitAmount",
        a.amount_close AS "amountClose",
        a.fee AS "fee",
        a.funding_fee AS "fundingFee",
        a.deposit AS "deposit",
        a.deposit_open AS "depositOpen",
        a.profit AS "profit",