Procházet zdrojové kódy

fix: ams 任务执行模块优化

cjwen před 1 rokem
rodič
revize
95e359915f

+ 8 - 4
src/main/java/com/ichaoj/ams/controller/AddressController.java

@@ -14,10 +14,7 @@ import com.ichaoj.common.model.PublicResult;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
@@ -68,5 +65,12 @@ public class AddressController {
         return PublicResult.success(result);
     }
 
+    @Operation(summary = "查询地址组列表")
+    @GetMapping("/group-list")
+    @AuthResource
+    public PublicResult<List<String>> queryGroupList() {
+        List<String> result = addressService.queryGroupList();
+        return PublicResult.success(result);
+    }
 
 }

+ 2 - 2
src/main/java/com/ichaoj/ams/controller/ExecuteController.java

@@ -28,8 +28,8 @@ public class ExecuteController {
     private IAmsExecuteRecordService executeService;
 
     @PostMapping
-    @Operation(summary = "创建执行")
-    @AuthResource(true)
+    @Operation(summary = "执行任务")
+    @AuthResource
     public PublicResult<Object> createExecute(@RequestBody CreateExecute createExecute) {
         executeService.createExecute(createExecute);
         return PublicResult.success();

+ 1 - 1
src/main/java/com/ichaoj/ams/request/address/BatchAddressRequest.java

@@ -15,7 +15,7 @@ import javax.validation.constraints.NotBlank;
 public class BatchAddressRequest {
 
     @Schema(title = "钱包数量")
-    @Min(value = 1,message = "The minimum number of wallets is 1")
+    @Min(value = 2,message = "The minimum number of wallets is 2")
     private Integer numWallet;
 
     @Schema(title = "keystore密码")

+ 2 - 0
src/main/java/com/ichaoj/ams/request/execute/CreateExecute.java

@@ -71,4 +71,6 @@ public class CreateExecute {
      */
     private Integer executeStatus;
 
+    private String password;
+
 }

+ 30 - 3
src/main/java/com/ichaoj/ams/request/record/CreateTradeRecordRequest.java

@@ -1,15 +1,42 @@
 package com.ichaoj.ams.request.record;
 
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
 /**
  * @author : cjwen
  * @date : 2023/05/10 10:38
  */
+@Data
+@Builder
 public class CreateTradeRecordRequest {
 
-    private String transactionId;
+    /**
+     * 链上交易id
+     */
+    private String txId;
+
+    /**
+     * 执行id
+     */
+    private String executeId;
+
+    /**
+     * 交易状态(0:进行中,1:已完成)
+     */
+    private Integer status;
+
+    /**
+     * 地址
+     */
+    private String address;
 
-    private String from;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
 
-    private String to;
 
 }

+ 7 - 0
src/main/java/com/ichaoj/ams/service/IAmsAddressAccountService.java

@@ -58,4 +58,11 @@ public interface IAmsAddressAccountService extends IService<AmsAddressAccount> {
      * @return 分页结果
      */
     PublicPage<AddressResponse> pageAddress(PageAddressRequest pageAddressRequest);
+
+    /**
+     * 查询地址组列表
+     * @return 地址组列表
+     */
+    List<String> queryGroupList();
+
 }

+ 22 - 0
src/main/java/com/ichaoj/ams/service/IAmsTradeRecordService.java

@@ -1,11 +1,14 @@
 package com.ichaoj.ams.service;
 
+import com.ichaoj.ams.entity.AmsAddressAccount;
 import com.ichaoj.ams.entity.AmsTradeRecord;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ichaoj.ams.request.record.PageTradeRecordRequest;
 import com.ichaoj.ams.response.record.TradeRecordResponse;
 import com.ichaoj.common.model.PublicPage;
 
+import java.util.List;
+
 /**
  * <p>
  * 交易 服务类
@@ -18,8 +21,27 @@ public interface IAmsTradeRecordService extends IService<AmsTradeRecord> {
 
     /**
      * 分页查询交互记录
+     *
      * @param pageRequest 分页参数
      * @return 分页结果
      */
     PublicPage<TradeRecordResponse> pageTradeRecord(PageTradeRecordRequest pageRequest);
+
+    /**
+     * 发起随机交易
+     *
+     * @param accountList 地址列表
+     * @param intervalMin 时间间隔最小分钟数
+     * @param intervalMax 时间间隔最大分钟数
+     * @param amount      交易金额
+     * @param maxGas      最大gas
+     * @param executeId   执行id
+     */
+    void randomTrans(
+            List<AmsAddressAccount> accountList,
+            Integer intervalMin,
+            Integer intervalMax,
+            String amount,
+            String maxGas,
+            String executeId);
 }

+ 9 - 1
src/main/java/com/ichaoj/ams/service/impl/AmsAddressAccountServiceImpl.java

@@ -103,6 +103,14 @@ public class AmsAddressAccountServiceImpl extends SuperWhaleServiceImpl<AmsAddre
         return this.convertPublicPage(result, resp -> BeanUtil.copyProperties(resp, AddressResponse.class));
     }
 
+    @Override
+    public List<String> queryGroupList() {
+        LambdaQueryWrapper<AmsAddressAccount> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(AmsAddressAccount::getUserId,SuperWhaleContext.getContext(PublicUserInfo.class).getUserId())
+                .groupBy(AmsAddressAccount::getGroupName);
+        return this.list(wrapper).stream().map(AmsAddressAccount::getGroupName).distinct().collect(Collectors.toList());
+    }
+
     /**
      * 添加精品号
      *
@@ -124,7 +132,7 @@ public class AmsAddressAccountServiceImpl extends SuperWhaleServiceImpl<AmsAddre
         }
 
         if (addresses.get(0).getAddressType() != 1) {
-            throw new ErrorServiceException(groupName + "为非批量号,不允许下载");
+            throw new ErrorServiceException(groupName + "为非批量号地址组,不允许下载");
         }
         ZipUtil.createZip(groupName, response, userId);
     }

+ 39 - 4
src/main/java/com/ichaoj/ams/service/impl/AmsExecuteRecordServiceImpl.java

@@ -1,11 +1,14 @@
 package com.ichaoj.ams.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ichaoj.ams.entity.AmsAddressAccount;
 import com.ichaoj.ams.entity.AmsAirdropProject;
 import com.ichaoj.ams.entity.AmsAirdropTask;
 import com.ichaoj.ams.entity.AmsExecuteRecord;
@@ -14,18 +17,18 @@ import com.ichaoj.ams.request.execute.CreateExecute;
 import com.ichaoj.ams.request.execute.PageExecuteRequest;
 import com.ichaoj.ams.request.execute.UpdateExecute;
 import com.ichaoj.ams.response.execute.ExecuteResponse;
-import com.ichaoj.ams.service.IAmsAirdropProjectService;
-import com.ichaoj.ams.service.IAmsAirdropTaskService;
-import com.ichaoj.ams.service.IAmsExecuteRecordService;
+import com.ichaoj.ams.service.*;
 import com.ichaoj.common.exception.ErrorServiceException;
 import com.ichaoj.common.model.PublicPage;
 import com.ichaoj.common.model.PublicUserInfo;
 import com.ichaoj.mybatis.service.SuperWhaleServiceImpl;
 import com.ichaoj.web.context.SuperWhaleContext;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * <p>
@@ -43,6 +46,12 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
     @Resource
     private IAmsAirdropProjectService projectService;
 
+    @Resource
+    private IAmsAddressAccountService accountService;
+
+    @Resource
+    private IAmsTradeRecordService tradeService;
+
     @Override
     public PublicPage<ExecuteResponse> pageExecute(PageExecuteRequest executeRequest) {
         LambdaQueryWrapper<AmsExecuteRecord> wrapper = Wrappers.lambdaQuery();
@@ -55,14 +64,40 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void createExecute(CreateExecute createExecute) {
+        String userId = SuperWhaleContext.getContext(PublicUserInfo.class).getUserId();
+        if (StrUtil.isBlank(createExecute.getGroupName())) {
+            throw new ErrorServiceException("地址组名称不能为空!");
+        }
+        List<AmsAddressAccount> accountList = accountService.getByGroupNameAndUserId(createExecute.getGroupName(), userId);
+        if (CollectionUtil.isEmpty(accountList)) {
+            throw new ErrorServiceException("地址组错误!");
+        }
+        if (accountList.get(0).getAddressType() != 1) {
+            throw new ErrorServiceException(createExecute.getGroupName() + "为非批量号地址组,不允许交易");
+        }
         AmsAirdropProject project = verifyProject(createExecute.getProjectId());
         AmsAirdropTask task = verifyTask(createExecute.getTaskId(), project.getAmsProjectId());
         AmsExecuteRecord amsExecute = BeanUtil.copyProperties(createExecute, AmsExecuteRecord.class);
-        amsExecute.setUserId(SuperWhaleContext.getContext(PublicUserInfo.class).getUserId());
+        amsExecute.setUserId(userId);
         amsExecute.setCreateTime(LocalDateTime.now());
         amsExecute.setTaskId(task.getAmsTaskId());
+        amsExecute.setProjectId(project.getAmsProjectId());
+        // todo 查询是第几次执行
         this.save(amsExecute);
+        if (StrUtil.isBlank(createExecute.getPassword())) {
+            throw new ErrorServiceException("密码不能为空!");
+        }
+        // todo 验证密码是否正确
+
+        ThreadUtil.execute(() -> tradeService.randomTrans(accountList
+                , createExecute.getIntervalMin()
+                , createExecute.getIntervalMax()
+                , createExecute.getAmount()
+                , createExecute.getMaxGas()
+                , amsExecute.getExecuteId()));
+
     }
 
     @Override

+ 95 - 3
src/main/java/com/ichaoj/ams/service/impl/AmsTradeRecordServiceImpl.java

@@ -1,22 +1,33 @@
 package com.ichaoj.ams.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ichaoj.ams.entity.AmsExecuteRecord;
+import com.ichaoj.ams.entity.AmsAddressAccount;
 import com.ichaoj.ams.entity.AmsTradeRecord;
 import com.ichaoj.ams.mapper.AmsTradeRecordMapper;
+import com.ichaoj.ams.request.record.CreateTradeRecordRequest;
 import com.ichaoj.ams.request.record.PageTradeRecordRequest;
-import com.ichaoj.ams.response.execute.ExecuteResponse;
 import com.ichaoj.ams.response.record.TradeRecordResponse;
 import com.ichaoj.ams.service.IAmsTradeRecordService;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ichaoj.common.model.PublicPage;
 import com.ichaoj.mybatis.service.SuperWhaleServiceImpl;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
 /**
  * <p>
  * 交易 服务实现类
@@ -26,6 +37,7 @@ import org.springframework.stereotype.Service;
  * @since 2023-05-18
  */
 @Service
+@Slf4j
 public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRecordMapper, AmsTradeRecord> implements IAmsTradeRecordService {
 
     @Override
@@ -39,4 +51,84 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
         return this.convertPublicPage(result, s -> BeanUtil.copyProperties(s, TradeRecordResponse.class));
     }
 
+    @Override
+    public void randomTrans(List<AmsAddressAccount> accountList, Integer intervalMin, Integer intervalMax, String amount, String maxGas, String executeId) {
+        Map<String, Boolean> addressMap = new HashMap<>(accountList.size());
+        Set<String> set = new HashSet<>();
+
+        List<String> addresses = accountList.stream().map(AmsAddressAccount::getAddress).collect(Collectors.toList());
+
+        for (String address : addresses) {
+            addressMap.put(address, false);
+        }
+
+        while (set.size() != addresses.size()) {
+            for (int i = 0; i < addresses.size(); i++) {
+                BigDecimal gas = getCurrentGasPrice(maxGas);
+                log.info("当前gas: {}", gas);
+                BigDecimal currenAmount = getCurrenAmount(amount);
+                log.info("amount: {}", currenAmount);
+                String address = getUnusedAddress(addressMap, addresses);
+                if (i == 0) {
+                    saveTrade(executeId, address);
+                    // todo 调用链上交易
+                } else {
+                    int sleepSeconds = RandomUtil.randomInt(intervalMin, intervalMax);
+                    log.info("当前是第{} 笔交易,需要等待{} 分钟", i + 1, sleepSeconds);
+                    try {
+                        AmsTradeRecord record = this.getOne(
+                                new LambdaQueryWrapper<AmsTradeRecord>()
+                                        .eq(AmsTradeRecord::getExecuteId, executeId)
+                                        .eq(AmsTradeRecord::getAddress, address)
+                        );
+                        if (record == null) {
+                            TimeUnit.MILLISECONDS.sleep(sleepSeconds * 1000L + RandomUtil.randomInt(100, 999));
+                            saveTrade(executeId, address);
+                            // todo 调用链上交易
+                        }
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+                addressMap.put(address, true);
+                set.add(address);
+            }
+        }
+    }
+
+    private BigDecimal getCurrenAmount(String amount) {
+        return RandomUtil.randomBigDecimal(BigDecimal.ZERO, new BigDecimal(amount));
+    }
+
+    private BigDecimal getCurrentGasPrice(String maxGas) {
+        return RandomUtil.randomBigDecimal(BigDecimal.ZERO, new BigDecimal(maxGas));
+    }
+
+    private void saveTrade(String executeId, String address) {
+        CreateTradeRecordRequest trade = CreateTradeRecordRequest.builder()
+                .address(address)
+                .executeId(executeId)
+                .createTime(LocalDateTime.now())
+                .status(0)
+                .txId(IdUtil.simpleUUID())
+                .build();
+        this.save(BeanUtil.copyProperties(trade, AmsTradeRecord.class));
+    }
+
+    private String getUnusedAddress(Map<String, Boolean> addressMap, List<String> addresses) {
+        String address = null;
+        while (address == null) {
+            address = getRandomAddress(addresses);
+            if (addressMap.get(address)) {
+                address = null;
+            }
+        }
+        return address;
+    }
+
+    private String getRandomAddress(List<String> accountList) {
+        int randomInt = RandomUtil.randomInt(0, accountList.size() - 1);
+        return accountList.get(randomInt);
+    }
+
 }