Selaa lähdekoodia

Merge remote-tracking branch 'origin/master'

million 1 vuosi sitten
vanhempi
commit
3f6f33f486
34 muutettua tiedostoa jossa 1011 lisäystä ja 118 poistoa
  1. 10 8
      pom.xml
  2. 2 0
      src/main/java/com/ichaoj/ams/AmsBackendApplication.java
  3. 0 32
      src/main/java/com/ichaoj/ams/common/util/FileUtils.java
  4. 78 0
      src/main/java/com/ichaoj/ams/common/util/PoiUtils.java
  5. 45 4
      src/main/java/com/ichaoj/ams/controller/AirdropProjectController.java
  6. 10 1
      src/main/java/com/ichaoj/ams/controller/AirdropTaskController.java
  7. 182 18
      src/main/java/com/ichaoj/ams/controller/StatisticsController.java
  8. 12 0
      src/main/java/com/ichaoj/ams/mapper/AmsAddressAccountMapper.java
  9. 10 0
      src/main/java/com/ichaoj/ams/mapper/AmsAirdropTaskMapper.java
  10. 20 1
      src/main/java/com/ichaoj/ams/mapper/AmsTradeRecordMapper.java
  11. 10 0
      src/main/java/com/ichaoj/ams/request/airdrop/UpdateAirdropProject.java
  12. 28 0
      src/main/java/com/ichaoj/ams/request/airdrop/UpdateProjectStatus.java
  13. 18 0
      src/main/java/com/ichaoj/ams/request/statistics/DailyCostRequest.java
  14. 25 0
      src/main/java/com/ichaoj/ams/response/address/CountAddressResponse.java
  15. 2 0
      src/main/java/com/ichaoj/ams/response/airdrop/AirdropProjectResponse.java
  16. 19 0
      src/main/java/com/ichaoj/ams/response/statistics/CostResponse.java
  17. 22 0
      src/main/java/com/ichaoj/ams/response/statistics/DailyCostResponse.java
  18. 30 0
      src/main/java/com/ichaoj/ams/response/statistics/ExportResponse.java
  19. 30 0
      src/main/java/com/ichaoj/ams/response/statistics/PredictCostResponse.java
  20. 95 0
      src/main/java/com/ichaoj/ams/response/statistics/SheetDTO.java
  21. 23 0
      src/main/java/com/ichaoj/ams/response/task/TaskProgressResponse.java
  22. 15 17
      src/main/java/com/ichaoj/ams/response/task/TaskResponse.java
  23. 17 2
      src/main/java/com/ichaoj/ams/service/IAmsAddressAccountService.java
  24. 8 0
      src/main/java/com/ichaoj/ams/service/IAmsAirdropProjectService.java
  25. 9 0
      src/main/java/com/ichaoj/ams/service/IAmsAirdropTaskService.java
  26. 15 0
      src/main/java/com/ichaoj/ams/service/IAmsTradeRecordService.java
  27. 19 4
      src/main/java/com/ichaoj/ams/service/impl/AmsAddressAccountServiceImpl.java
  28. 81 7
      src/main/java/com/ichaoj/ams/service/impl/AmsAirdropProjectServiceImpl.java
  29. 14 0
      src/main/java/com/ichaoj/ams/service/impl/AmsAirdropTaskServiceImpl.java
  30. 36 5
      src/main/java/com/ichaoj/ams/service/impl/AmsExecuteRecordServiceImpl.java
  31. 50 19
      src/main/java/com/ichaoj/ams/service/impl/AmsTradeRecordServiceImpl.java
  32. 10 0
      src/main/resources/mapper/AmsAddressAccountMapper.xml
  33. 35 0
      src/main/resources/mapper/AmsAirdropTaskMapper.xml
  34. 31 0
      src/main/resources/mapper/AmsTradeRecordMapper.xml

+ 10 - 8
pom.xml

@@ -38,14 +38,6 @@
             <artifactId>core</artifactId>
             <version>5.0.0</version>
         </dependency>
-        <dependency>
-            <groupId>com.ichaoj</groupId>
-            <artifactId>super-whale-uc-provider</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.baomidou</groupId>
-            <artifactId>mybatis-plus-generator</artifactId>
-        </dependency>
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
@@ -88,6 +80,16 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>3.17</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 2 - 0
src/main/java/com/ichaoj/ams/AmsBackendApplication.java

@@ -3,12 +3,14 @@ package com.ichaoj.ams;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
  * @author cjwen
  */
 @SpringBootApplication
 @MapperScan("com.ichaoj.ams.mapper")
+@EnableScheduling
 public class AmsBackendApplication {
 
     public static void main(String[] args) {

+ 0 - 32
src/main/java/com/ichaoj/ams/common/util/FileUtils.java

@@ -40,38 +40,6 @@ public class FileUtils {
         return new CommonsMultipartFile(item);
     }
 
-
-    public static void downloadZip(File file) {
-        HttpServletResponse response = SuperWhaleContext.getContext(HttpServletResponse.class);
-        OutputStream toClient = null;
-        try {
-            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.getPath()));
-            byte[] bytes = new byte[bis.available()];
-            bis.read(bytes);
-            bis.close();
-            // 设置response头
-            response.reset();
-            response.setCharacterEncoding("UTF-8");
-            response.setHeader(Header.CONTENT_TYPE.getValue(), "multipart/form-data");
-//            response.setContentType("application/octet-stream");
-            response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
-            toClient.write(bytes);
-            toClient.flush();
-        } catch (Exception e) {
-            file.delete();
-            log.error("下载压缩包失败:{}", e.getMessage());
-        } finally {
-            if (toClient != null) {
-                try {
-                    toClient.close();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-                file.delete();
-            }
-        }
-    }
-
     /**
      * 将文件夹压缩成zip文件下载
      *

+ 78 - 0
src/main/java/com/ichaoj/ams/common/util/PoiUtils.java

@@ -0,0 +1,78 @@
+package com.ichaoj.ams.common.util;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
+import com.ichaoj.ams.response.statistics.SheetDTO;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.util.IOUtils;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.util.List;
+
+/**
+ * POI相关操作
+ * @author cjwen
+ */
+public class PoiUtils {
+    /**
+     * 导出多个 Sheet 页
+     * @param response 响应
+     * @param sheetList 页数据
+     * @param fileName 文件名
+     */
+    public static void exportExcel(HttpServletResponse response, List<SheetDTO> sheetList, String fileName) {
+        ExcelWriter bigWriter = ExcelUtil.getBigWriter();
+        // 重命名第一个Sheet的名称,不然会默认多出一个Sheet1的页
+        bigWriter.renameSheet(0, sheetList.get(0).getSheetName());
+        for (SheetDTO sheet : sheetList) {
+            // 指定要写出的 Sheet 页
+            bigWriter.setSheet(sheet.getSheetName());
+            List<Integer> columnWidth = sheet.getColumnWidth();
+            if (columnWidth == null || columnWidth.size() != sheet.getFieldAndAlias().size()) {
+                // 设置默认宽度
+                for (int i = 0; i < sheet.getFieldAndAlias().size(); i++) {
+                    bigWriter.setColumnWidth(i, 25);
+                }
+            } else {
+                // 设置自定义宽度
+                for (int i = 0; i < columnWidth.size(); i++) {
+                    bigWriter.setColumnWidth(i, columnWidth.get(i));
+                }
+            }
+            // 设置字段和别名
+            bigWriter.setHeaderAlias(sheet.getFieldAndAlias());
+            // 设置只导出有别名的字段
+            bigWriter.setOnlyAlias(true);
+            // 设置默认行高
+            bigWriter.setDefaultRowHeight(18);
+            // 设置冻结行
+            bigWriter.setFreezePane(1);
+            // 一次性写出内容,使用默认样式,强制输出标题
+            bigWriter.write(sheet.getCollection(), true);
+            // 设置所有列为自动宽度,不考虑合并单元格
+        }
+
+        ServletOutputStream out = null;
+        try {
+            //response为HttpServletResponse对象
+            response.setContentType("application/vnd.ms-excel;charset=utf-8");
+            response.setHeader("Content-Disposition",
+                    "attachment;filename=" +
+                            URLEncoder.encode(fileName + DateUtil.today() + ".xlsx", "UTF-8"));
+            out = response.getOutputStream();
+            bigWriter.flush(out, true);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            // 关闭writer,释放内存
+            bigWriter.close();
+        }
+        //此处记得关闭输出Servlet流
+        IoUtil.close(out);
+    }
+}

+ 45 - 4
src/main/java/com/ichaoj/ams/controller/AirdropProjectController.java

@@ -3,10 +3,12 @@ package com.ichaoj.ams.controller;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ichaoj.ams.entity.AmsAirdropProject;
 import com.ichaoj.ams.entity.AmsAirdropTask;
 import com.ichaoj.ams.request.airdrop.CreateAirdropProject;
 import com.ichaoj.ams.request.airdrop.PageProjectRequest;
 import com.ichaoj.ams.request.airdrop.UpdateAirdropProject;
+import com.ichaoj.ams.request.airdrop.UpdateProjectStatus;
 import com.ichaoj.ams.response.airdrop.AirdropProjectResponse;
 import com.ichaoj.ams.response.airdrop.ProjectDetailResponse;
 import com.ichaoj.ams.response.task.TaskResponse;
@@ -18,11 +20,16 @@ import com.ichaoj.common.model.PublicPage;
 import com.ichaoj.common.model.PublicResult;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jnr.ffi.annotations.In;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import static com.ichaoj.ams.constant.AmsConstant.AirdropStatus.ONGOING;
 
 /**
  * <p>
@@ -44,7 +51,7 @@ public class AirdropProjectController {
 
     @PostMapping
     @Operation(summary = "创建空投项目")
-    @AuthResource(true)
+    @AuthResource
     public PublicResult<Object> createAirdropProject(@RequestBody CreateAirdropProject createAirdropProject) {
         airdropProjectService.createAirdropProject(createAirdropProject);
         return PublicResult.success();
@@ -52,15 +59,31 @@ public class AirdropProjectController {
 
     @PutMapping
     @Operation(summary = "修改空投项目")
-    @AuthResource(true)
+    @AuthResource
     public PublicResult<Object> updateAirdropProject(@RequestBody @Validated UpdateAirdropProject updateAirdropProject) {
         airdropProjectService.updateAirdropProject(updateAirdropProject);
         return PublicResult.success();
     }
 
+    @PutMapping("update-status")
+    @Operation(summary = "修改项目状态")
+    @AuthResource
+    public PublicResult<Object> updateProjectStatus(@RequestBody @Validated UpdateProjectStatus projectStatus) {
+        if (projectStatus.getStatus() == null) {
+            throw new ErrorServiceException("状态不能为空");
+        }
+        AmsAirdropProject project = airdropProjectService.getById(projectStatus.getAmsProjectId());
+        if (project == null) {
+            throw new ErrorServiceException("项目id错误");
+        }
+        project.setStatus(projectStatus.getStatus());
+        airdropProjectService.updateById(project);
+        return PublicResult.success();
+    }
+
     @DeleteMapping("/{projectId}")
     @Operation(summary = "删除空投项目")
-    @AuthResource(true)
+    @AuthResource
     public PublicResult<Object> deleteAirdropProject(@PathVariable String projectId) {
         airdropProjectService.removeById(projectId);
         return PublicResult.success();
@@ -74,12 +97,18 @@ public class AirdropProjectController {
         return PublicResult.success(result);
     }
 
+    @GetMapping("/list")
+    @Operation(summary = "查询项目列表")
+    @AuthResource
+    public PublicResult<List<AirdropProjectResponse>> listAirdropProject() {
+        return PublicResult.success(BeanUtil.copyToList(airdropProjectService.list(), AirdropProjectResponse.class));
+    }
 
     @GetMapping("/detail/{projectId}")
     @Operation(summary = "根据项目id查询详情")
     @AuthResource
     public PublicResult<ProjectDetailResponse> getByProjectId(@PathVariable String projectId) {
-        if (StrUtil.isBlank(projectId)){
+        if (StrUtil.isBlank(projectId)) {
             throw new ErrorServiceException("projectId 不能为空");
         }
         ProjectDetailResponse project = BeanUtil.copyProperties(airdropProjectService.getById(projectId), ProjectDetailResponse.class);
@@ -88,4 +117,16 @@ public class AirdropProjectController {
         project.setTasks(taskResponses);
         return PublicResult.success(project);
     }
+
+    @GetMapping("/count/projectNum")
+    @Operation(summary = "统计项目数量")
+    @AuthResource
+    public PublicResult<Map<String, Object>> getProjectNum() {
+        Map<String, Object> map = new HashMap<>(4);
+        map.put("totalCount", airdropProjectService.count());
+        map.put("runningCount", airdropProjectService.count(new LambdaQueryWrapper<AmsAirdropProject>().eq(AmsAirdropProject::getStatus, ONGOING)));
+        return PublicResult.success(map);
+    }
+
+
 }

+ 10 - 1
src/main/java/com/ichaoj/ams/controller/AirdropTaskController.java

@@ -21,6 +21,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.List;
 
 /**
  * <p>
@@ -62,7 +63,7 @@ public class AirdropTaskController {
         if (task != null) {
             task.setTaskStatus(statusRequest.getTaskStatus());
             taskService.updateById(task);
-        }else {
+        } else {
             throw new ErrorServiceException("Task Error");
         }
         return PublicResult.success();
@@ -83,4 +84,12 @@ public class AirdropTaskController {
         PublicPage<TaskResponse> result = taskService.pageAirdropProject(taskRequest);
         return PublicResult.success(result);
     }
+
+    @GetMapping("/list")
+    @Operation(summary = "查询任务列表")
+    @AuthResource
+    public PublicResult<List<TaskResponse>> listTask() {
+        List<TaskResponse> result = taskService.listTask();
+        return PublicResult.success(result);
+    }
 }

+ 182 - 18
src/main/java/com/ichaoj/ams/controller/StatisticsController.java

@@ -1,22 +1,47 @@
 package com.ichaoj.ams.controller;
 
-import com.ichaoj.ams.service.IAmsAddressAccountService;
-import com.ichaoj.ams.service.IAmsTradeRecordService;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.poi.excel.ExcelBase;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ichaoj.ams.common.util.FileUtils;
+import com.ichaoj.ams.common.util.PoiUtils;
+import com.ichaoj.ams.entity.AmsAirdropProject;
+import com.ichaoj.ams.entity.AmsAirdropTask;
+import com.ichaoj.ams.entity.AmsExecuteRecord;
+import com.ichaoj.ams.entity.AmsTradeRecord;
+import com.ichaoj.ams.request.statistics.DailyCostRequest;
+import com.ichaoj.ams.response.address.CountAddressResponse;
+import com.ichaoj.ams.response.statistics.*;
+import com.ichaoj.ams.response.task.TaskProgressResponse;
+import com.ichaoj.ams.service.*;
 import com.ichaoj.common.annotation.AuthResource;
+import com.ichaoj.common.exception.ErrorServiceException;
 import com.ichaoj.common.model.PublicResult;
 import com.ichaoj.common.model.PublicUserInfo;
 import com.ichaoj.web.context.SuperWhaleContext;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author : cjwen
@@ -30,35 +55,174 @@ public class StatisticsController {
 
     @Resource
     private IAmsTradeRecordService tradeService;
+    @Resource
+    private IAmsAirdropProjectService projectService;
 
     @Resource
     private IAmsAddressAccountService accountService;
+    @Resource
+    private IAmsAirdropTaskService taskService;
+    @Resource
+    private IAmsExecuteRecordService executeRecordService;
+
+
+    @GetMapping("address-group")
+    @Operation(summary = "地址统计")
+    @AuthResource
+    private PublicResult<List<CountAddressResponse>> getGroupAndWalletNum() {
+        List<String> list = accountService.queryGroupList(0);
+        List<CountAddressResponse> responseList = new ArrayList<>();
+        CountAddressResponse preCount = new CountAddressResponse();
+        preCount.setGroupName("精品号");
+        preCount.setAddressCount(list.size());
+        responseList.add(0, preCount);
+        String userId = SuperWhaleContext.getContext(PublicUserInfo.class).getUserId();
+        List<CountAddressResponse> batchList = accountService.countBatchCount(userId);
+        responseList.addAll(batchList);
+        return PublicResult.success(responseList);
+    }
 
     @GetMapping("daily-cost")
     @Operation(summary = "每日消耗gas和本金")
     @AuthResource
-    public PublicResult<Map<String, Object>> dailyCostStatistics() {
-        Map<String, Object> map = new HashMap<>(5);
-        String userId = SuperWhaleContext.getContext(PublicUserInfo.class).getUserId();
-        List<String> batchAddresses = accountService.queryGroupList(1);
+    public PublicResult<List<DailyCostResponse>> dailyCostStatistics() {
+        List<DailyCostResponse> list = tradeService.dailyCostStatistics();
+        return PublicResult.success(list);
+    }
+
+    @GetMapping("predict-cost")
+    @Operation(summary = "统计预计投入")
+    @AuthResource
+    public PublicResult<PredictCostResponse> predictCostStatistics() {
+        PredictCostResponse response = projectService.predictCostStatistics();
+        return PublicResult.success(response);
+    }
 
+    @GetMapping("count-fee")
+    @Operation(summary = "费用统计")
+    @AuthResource
+    public PublicResult<Map<String, Object>> feeCostStatistics() {
+        Map<String, Object> map = new HashMap<>(5);
+        List<String> batchAddresses = accountService.queryBatchAddress(1);
         // 从链上获取当日gas费用
-        BigDecimal dailyGasCost = getDailyGasCost(userId);
+        BigDecimal dailyGasCost = getGasCost(batchAddresses);
+        map.put("expendGas", dailyGasCost);
         // 从链上获取当日本金消耗量
-//        BigDecimal dailyPrincipalCost = getDailyPrincipalCost(userId);
-        // 总消耗 = gas费用 + 本金消耗
-//        BigDecimal totalCost = dailyGasCost.add(dailyPrincipalCost);
+        BigDecimal dailyPrincipalCost = getPrincipalCost(batchAddresses);
+        // 本金余额
+        map.put("principalBalance", dailyPrincipalCost);
+
+        map.put("predictTotalCost", projectService.list().stream().mapToDouble(p -> Double.parseDouble(p.getEstimatedCost())).sum());
         return PublicResult.success(map);
     }
 
-    private void getGroupAndWalletNum(Map<String, Object> map) {
-        List<String> list = accountService.queryGroupList(0);
-        map.put("premiumCount", list.size());
+    @GetMapping("task-progress")
+    @Operation(summary = "统计任务进度")
+    @AuthResource
+    public PublicResult<List<TaskProgressResponse>> countTaskProgress() {
+        List<TaskProgressResponse> responseList = new ArrayList<>();
+        List<AmsAirdropTask> list = taskService.list();
+        for (AmsAirdropTask task : list) {
+            TaskProgressResponse response = new TaskProgressResponse();
+            Integer planTimes = task.getPlanTimes();
+            response.setTotalCount(planTimes);
+            long count = executeRecordService.count(
+                    new LambdaQueryWrapper<AmsExecuteRecord>()
+                            .eq(AmsExecuteRecord::getTaskId, task.getAmsTaskId())
+                            .eq(AmsExecuteRecord::getExecuteStatus, 1)
+            );
+            response.setFinishCount((int) count);
+            response.setTaskName(task.getTaskName());
+            responseList.add(response);
+        }
+        return PublicResult.success(responseList);
+    }
+
+
+    @GetMapping("/export")
+    @Operation(summary = "导出项目报表")
+    @AuthResource
+//    public void createExcelFile(@RequestBody DailyCostRequest dailyCostRequest, HttpServletResponse res) {
+    public void createExcelFile(HttpServletResponse res) {
+        try {
+            // 支出趋势
+            List<DailyCostResponse> trendList = tradeService.dailyCostStatistics();
+            CostResponse trendCost = new CostResponse();
+            trendCost.setActualCost(BigDecimal.valueOf(trendList.stream().mapToDouble(t -> t.getGas().doubleValue()).sum()));
+
+            // 支付详情
+            List<ExportResponse> detailData = tradeService.getExportData();
+            List<String> projectIds = detailData.stream().map(ExportResponse::getProjectId).collect(Collectors.toList());
+            List<AmsAirdropProject> projects = projectService.listByIds(projectIds);
+            CostResponse detailCost = new CostResponse();
+            detailCost.setActualCost(BigDecimal.valueOf(trendList.stream().mapToDouble(t -> t.getGas().doubleValue()).sum()));
+            detailCost.setPredictCost(BigDecimal.valueOf(projects.stream().mapToDouble(d -> Double.parseDouble(d.getEstimatedCost())).sum()));
+
+            Map<String, String> trendMap = new LinkedHashMap<>();
+            trendMap.put("date", "时间");
+            trendMap.put("gas", "消耗gas");
+            trendMap.put("principal", "消耗本金");
+            trendMap.put("actualCost", "实际支出");
+            trendMap.put("predictCost", "预计支出");
+
+            Map<String, String> detailMap = new LinkedHashMap<>();
+            detailMap.put("projectName", "项目名称");
+            detailMap.put("taskName", "任务名称");
+            detailMap.put("address", "钱包");
+            detailMap.put("amount", "支出金额");
+            detailMap.put("gas", "消耗gas");
+            detailMap.put("createTime", "时间");
+            detailMap.put("txId", "txid");
+            detailMap.put("predictCost", "预计支出");
+            detailMap.put("actualCost", "实际支出");
+
+            List<SheetDTO> arrayList = new ArrayList<>();
+            arrayList.add(new SheetDTO("支出趋势", trendMap, trendList));
+            arrayList.add(new SheetDTO("支付详情", detailMap, detailData));
+            PoiUtils.exportExcel(res, arrayList, DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN) + "财务报表");
+        } catch (Exception e) {
+            throw new ErrorServiceException(e.getMessage());
+        }
+    }
+
+    private File createProjectExcelFile() {
+        return null;
+    }
+
+    private File createProjectExcelFile(DailyCostRequest dailyCostRequest) {
+
+        return null;
+    }
+
+    private BigDecimal getPrincipalCost(List<String> batchAddresses) {
+        List<AmsTradeRecord> list = getTradeRecordList(batchAddresses);
+        double sumAmount = list.stream().mapToDouble(t -> Double.parseDouble(t.getCurrentBalance())).sum();
+        return BigDecimal.valueOf(sumAmount);
+    }
+
+    private BigDecimal getGasCost(List<String> batchAddresses) {
+        List<AmsTradeRecord> list = getTradeRecordList(batchAddresses);
+        double sumGas = list.stream().mapToDouble(t -> Double.parseDouble(t.getGas())).sum();
+        return BigDecimal.valueOf(sumGas);
+    }
+
 
+    private List<AmsTradeRecord> getTradeRecordList(List<String> batchAddresses) {
+        return tradeService.list(new LambdaQueryWrapper<AmsTradeRecord>()
+                .in(AmsTradeRecord::getAddress, batchAddresses)
+        );
     }
 
-    private BigDecimal getDailyGasCost(String userId) {
-        return BigDecimal.ZERO;
+    private static LocalDateTime getTodayStartTime() {
+        // 获取Calendar实例
+        Calendar cal = Calendar.getInstance();
+        // 获取今天凌晨的日期时间
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        Date todayStart = cal.getTime();
+        return LocalDateTimeUtil.of(todayStart);
     }
 
 }

+ 12 - 0
src/main/java/com/ichaoj/ams/mapper/AmsAddressAccountMapper.java

@@ -5,8 +5,12 @@ import com.ichaoj.ams.entity.AmsAddressAccount;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ichaoj.ams.request.address.PageAddressRequest;
 import com.ichaoj.ams.response.address.AddressResponse;
+import com.ichaoj.ams.response.address.CountAddressResponse;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * <p>
  * 地址管理 Mapper 接口
@@ -26,4 +30,12 @@ public interface AmsAddressAccountMapper extends BaseMapper<AmsAddressAccount> {
      * @return 分页结果
      */
     Page<AddressResponse> pageAddress(Page<AmsAddressAccount> buildPageObj, @Param("request") PageAddressRequest request, @Param("userId") String userId);
+
+    /**
+     * 统计用户批量地址的数量(按地址组名称分组)
+     *
+     * @param userId 用户id
+     * @return 分组地址map
+     */
+    List<CountAddressResponse> countBatchCount(@Param("userId") String userId);
 }

+ 10 - 0
src/main/java/com/ichaoj/ams/mapper/AmsAirdropTaskMapper.java

@@ -2,6 +2,10 @@ package com.ichaoj.ams.mapper;
 
 import com.ichaoj.ams.entity.AmsAirdropTask;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ichaoj.ams.response.task.TaskResponse;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * <p>
@@ -13,4 +17,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  */
 public interface AmsAirdropTaskMapper extends BaseMapper<AmsAirdropTask> {
 
+    /**
+     * 查询任务列表
+     * @param userId 用户id
+     * @return 任务列表
+     */
+    List<TaskResponse> listTask(@Param("userId") String userId);
 }

+ 20 - 1
src/main/java/com/ichaoj/ams/mapper/AmsTradeRecordMapper.java

@@ -4,7 +4,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ichaoj.ams.entity.AmsTradeRecord;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ichaoj.ams.request.record.PageTradeRecordRequest;
+import com.ichaoj.ams.response.statistics.DailyCostResponse;
 import com.ichaoj.ams.response.record.TradeRecordResponse;
+import com.ichaoj.ams.response.statistics.ExportResponse;
+
+import java.util.List;
 
 /**
  * <p>
@@ -18,9 +22,24 @@ public interface AmsTradeRecordMapper extends BaseMapper<AmsTradeRecord> {
 
     /**
      * 分页查询交易记录
+     *
      * @param buildPageObj 分页对象
-     * @param request 请求参数
+     * @param request      请求参数
      * @return 分页结果
      */
     Page<TradeRecordResponse> pageTradeRecord(Page<AmsTradeRecord> buildPageObj, PageTradeRecordRequest request);
+
+    /**
+     * 统计每日消耗gas和本金
+     *
+     * @return 统计数据
+     */
+    List<DailyCostResponse> dailyCostStatistics();
+
+    /**
+     * 查询报表数据
+     * @param userId 用户id
+     * @return 报表数据
+     */
+    List<ExportResponse> getExportData(String userId);
 }

+ 10 - 0
src/main/java/com/ichaoj/ams/request/airdrop/UpdateAirdropProject.java

@@ -5,6 +5,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import javax.validation.constraints.NotBlank;
+import java.util.List;
 
 /**
  * @author : cjwen
@@ -19,4 +20,13 @@ public class UpdateAirdropProject extends CreateAirdropProject{
     @NotBlank(message = "amsProjectId 不能为空")
     private String amsProjectId;
 
+
+    private List<Task> tasks;
+
+    @Data
+    public static class Task{
+        private String taskName;
+        private String taskId;
+    }
+
 }

+ 28 - 0
src/main/java/com/ichaoj/ams/request/airdrop/UpdateProjectStatus.java

@@ -0,0 +1,28 @@
+package com.ichaoj.ams.request.airdrop;
+
+import com.ichaoj.ams.constant.AmsConstant;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * @author : cjwen
+ * @date : 2023/04/26 14:25
+ */
+@Data
+@Schema(title = "修改空投项目")
+public class UpdateProjectStatus {
+
+    @Schema(title = "空投项目Id")
+    @NotBlank(message = "amsProjectId 不能为空")
+    private String amsProjectId;
+
+    /**
+     * 空投状态
+     */
+    private AmsConstant.AirdropStatus status;
+
+}

+ 18 - 0
src/main/java/com/ichaoj/ams/request/statistics/DailyCostRequest.java

@@ -0,0 +1,18 @@
+package com.ichaoj.ams.request.statistics;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author : cjwen
+ * @date : 2023/05/29 15:13
+ */
+@Data
+@Schema(title = "日常花费统计")
+public class DailyCostRequest {
+
+    private String startTime;
+
+    private String endTime;
+
+}

+ 25 - 0
src/main/java/com/ichaoj/ams/response/address/CountAddressResponse.java

@@ -0,0 +1,25 @@
+package com.ichaoj.ams.response.address;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author : cjwen
+ * @date : 2023/04/26 18:18
+ */
+@Data
+public class CountAddressResponse {
+
+    /**
+     * 地址组名称
+     */
+    private String groupName;
+
+    /**
+     * 地址数量
+     */
+    private Integer addressCount;
+
+}

+ 2 - 0
src/main/java/com/ichaoj/ams/response/airdrop/AirdropProjectResponse.java

@@ -58,4 +58,6 @@ public class AirdropProjectResponse {
 
     @Schema(title = "任务完成数")
     private long taskFinishCount;
+
+    private BigDecimal totalGas;
 }

+ 19 - 0
src/main/java/com/ichaoj/ams/response/statistics/CostResponse.java

@@ -0,0 +1,19 @@
+package com.ichaoj.ams.response.statistics;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author : cjwen
+ * @date : 2023/05/30 14:54
+ */
+
+@Data
+public class CostResponse {
+
+    private BigDecimal actualCost;
+
+    private BigDecimal predictCost;
+
+}

+ 22 - 0
src/main/java/com/ichaoj/ams/response/statistics/DailyCostResponse.java

@@ -0,0 +1,22 @@
+package com.ichaoj.ams.response.statistics;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author : cjwen
+ * @date : 2023/05/29 15:13
+ */
+@Data
+@Schema(title = "日常花费统计")
+public class DailyCostResponse {
+
+    private String date;
+
+    private BigDecimal gas;
+
+    private BigDecimal principal;
+
+}

+ 30 - 0
src/main/java/com/ichaoj/ams/response/statistics/ExportResponse.java

@@ -0,0 +1,30 @@
+package com.ichaoj.ams.response.statistics;
+
+import lombok.Data;
+
+/**
+ * @author : cjwen
+ * @date : 2023/05/30 11:23
+ */
+@Data
+public class ExportResponse {
+
+    private String projectId;
+
+    private String projectName;
+
+    private String taskId;
+
+    private String taskName;
+
+    private String address;
+
+    private String amount;
+
+    private String gas;
+
+    private String createTime;
+
+    private String txId;
+
+}

+ 30 - 0
src/main/java/com/ichaoj/ams/response/statistics/PredictCostResponse.java

@@ -0,0 +1,30 @@
+package com.ichaoj.ams.response.statistics;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @author : cjwen
+ * @date : 2023/05/29 15:13
+ */
+@Data
+@Schema(title = "预计投入统计")
+public class PredictCostResponse {
+
+    private List<Predict> predicts;
+
+    private Integer totalCount;
+
+    private BigDecimal totalCost;
+
+    @Data
+    public static class Predict {
+        private String projectName;
+
+        private BigDecimal estimatedCost;
+    }
+
+}

+ 95 - 0
src/main/java/com/ichaoj/ams/response/statistics/SheetDTO.java

@@ -0,0 +1,95 @@
+package com.ichaoj.ams.response.statistics;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Excel - Sheet页
+ * @author cjwen
+ */
+public class SheetDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * sheet页名称
+     */
+    private String sheetName;
+
+    /**
+     * 字段和别名,如果使用这个,properties 和 titles可以不用处理
+     * Map<字段, 别名>  如:Map<"name", "姓名">
+     */
+    private Map<String, String> fieldAndAlias;
+
+    /**
+     * 列宽<br/>
+     * 设置列宽时必须每个字段都设置才生效(columnWidth.size = fieldAndAlias.size)
+     */
+    private List<Integer> columnWidth;
+
+    /**
+     * 数据集
+     */
+    private Collection<?> collection;
+
+    public SheetDTO() {
+
+    }
+
+    /**
+     * @param sheetName     sheet页名称
+     * @param fieldAndAlias 字段和别名
+     * @param collection    数据集
+     */
+    public SheetDTO(String sheetName, Map<String, String> fieldAndAlias, Collection<?> collection) {
+        super();
+        this.sheetName = sheetName;
+        this.fieldAndAlias = fieldAndAlias;
+        this.collection = collection;
+    }
+
+    public String getSheetName() {
+        return sheetName;
+    }
+
+    public void setSheetName(String sheetName) {
+        this.sheetName = sheetName;
+    }
+
+    public Map<String, String> getFieldAndAlias() {
+        return fieldAndAlias;
+    }
+
+    public void setFieldAndAlias(Map<String, String> fieldAndAlias) {
+        this.fieldAndAlias = fieldAndAlias;
+    }
+
+    public List<Integer> getColumnWidth() {
+        return this.columnWidth;
+    }
+
+    public void setColumnWidth(List<Integer> columnWidth) {
+        this.columnWidth = columnWidth;
+    }
+
+    public Collection<?> getCollection() {
+        return collection;
+    }
+
+    public void setCollection(Collection<?> collection) {
+        this.collection = collection;
+    }
+
+    @Override
+    public String toString() {
+        return "SheetDTO{" +
+                "sheetName='" + sheetName + '\'' +
+                ", fieldAndAlias=" + fieldAndAlias +
+                ", columnWidth=" + columnWidth +
+                ", collection=" + collection +
+                '}';
+    }
+}

+ 23 - 0
src/main/java/com/ichaoj/ams/response/task/TaskProgressResponse.java

@@ -0,0 +1,23 @@
+package com.ichaoj.ams.response.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author : cjwen
+ * @date : 2023/05/29 11:58
+ */
+@Data
+@Schema(title = "任务进度统计")
+public class TaskProgressResponse {
+
+    @Schema(title = "任务名称")
+    private String taskName;
+
+    @Schema(title = "任务计划执行总次数")
+    private Integer totalCount;
+
+    @Schema(title = "任务完成次数")
+    private Integer finishCount;
+
+}

+ 15 - 17
src/main/java/com/ichaoj/ams/response/task/TaskResponse.java

@@ -1,7 +1,5 @@
 package com.ichaoj.ams.response.task;
 
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.ichaoj.ams.constant.AmsConstant;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -16,9 +14,6 @@ import java.time.LocalDateTime;
 @Schema(title = "空投任务")
 public class TaskResponse {
 
-    /**
-     * 主键
-     */
     private String amsTaskId;
 
     /**
@@ -32,25 +27,19 @@ public class TaskResponse {
     private String contractAddress;
 
     /**
-     * 任务类型
-     */
-    private AmsConstant.TaskType taskType;
-
-    /**
-     * 任务状态;是否完成(0否,1是)
+     * 预计gas消耗
      */
-    private Integer taskStatus;
+    private String estimatedGas;
 
     /**
-     * 创建时间
+     * 计划任务执行次数
      */
-    private LocalDateTime createTime;
+    private Integer planTimes;
 
     /**
-     * 更新时间
+     * 任务类型
      */
-    private LocalDateTime updateTime;
-
+    private AmsConstant.TaskType taskType;
 
     private String amsProjectId;
 
@@ -64,5 +53,14 @@ public class TaskResponse {
      */
     private String projectName;
 
+    private Integer executeTimes;
+
+    /**
+     * 执行状态;是否完成(0否,1是)
+     */
+    private Integer executeStatus;
+
+    private String lastExecuteTime;
+
 
 }

+ 17 - 2
src/main/java/com/ichaoj/ams/service/IAmsAddressAccountService.java

@@ -1,11 +1,12 @@
 package com.ichaoj.ams.service;
 
-import com.ichaoj.ams.entity.AmsAddressAccount;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ichaoj.ams.entity.AmsAddressAccount;
 import com.ichaoj.ams.request.address.BatchAddressRequest;
 import com.ichaoj.ams.request.address.PageAddressRequest;
 import com.ichaoj.ams.request.address.PremiumAddressRequest;
 import com.ichaoj.ams.response.address.AddressResponse;
+import com.ichaoj.ams.response.address.CountAddressResponse;
 import com.ichaoj.common.model.PublicPage;
 
 import javax.servlet.http.HttpServletResponse;
@@ -62,7 +63,7 @@ public interface IAmsAddressAccountService extends IService<AmsAddressAccount> {
     /**
      * 查询地址组列表
      * @return 地址组列表
-     * @param addressType
+     * @param addressType 地址类型
      */
     List<String> queryGroupList(int addressType);
 
@@ -72,4 +73,18 @@ public interface IAmsAddressAccountService extends IService<AmsAddressAccount> {
      * @return 地址账户
      */
     AmsAddressAccount getByAddress(String address);
+
+    /**
+     * 统计用户批量地址的数量(按地址组名称分组)
+     * @param userId 用户id
+     * @return 统计结果
+     */
+    List<CountAddressResponse> countBatchCount(String userId);
+
+    /**
+     * 查询地址组列表
+     * @return 地址组列表
+     * @param addressType 地址类型
+     */
+    List<String> queryBatchAddress(int addressType);
 }

+ 8 - 0
src/main/java/com/ichaoj/ams/service/IAmsAirdropProjectService.java

@@ -6,6 +6,7 @@ import com.ichaoj.ams.request.airdrop.CreateAirdropProject;
 import com.ichaoj.ams.request.airdrop.PageProjectRequest;
 import com.ichaoj.ams.request.airdrop.UpdateAirdropProject;
 import com.ichaoj.ams.response.airdrop.AirdropProjectResponse;
+import com.ichaoj.ams.response.statistics.PredictCostResponse;
 import com.ichaoj.common.model.PublicPage;
 
 /**
@@ -36,4 +37,11 @@ public interface IAmsAirdropProjectService extends IService<AmsAirdropProject> {
      * @param updateAirdropProject 修改参数
      */
     void updateAirdropProject(UpdateAirdropProject updateAirdropProject);
+
+    /**
+     * 统计预计花费
+     * @return 统计结果
+     */
+    PredictCostResponse predictCostStatistics();
+
 }

+ 9 - 0
src/main/java/com/ichaoj/ams/service/IAmsAirdropTaskService.java

@@ -7,6 +7,8 @@ import com.ichaoj.ams.request.task.PageTaskRequest;
 import com.ichaoj.ams.response.task.TaskResponse;
 import com.ichaoj.common.model.PublicPage;
 
+import java.util.List;
+
 /**
  * <p>
  * 空投项目任务 服务类
@@ -33,4 +35,11 @@ public interface IAmsAirdropTaskService extends IService<AmsAirdropTask> {
      * @return 分页结果
      */
     PublicPage<TaskResponse> pageAirdropProject(PageTaskRequest taskRequest);
+
+    /**
+     * 查询任务列表
+     * @return 任务列表
+     */
+    List<TaskResponse> listTask();
+
 }

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

@@ -4,7 +4,9 @@ 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.statistics.DailyCostResponse;
 import com.ichaoj.ams.response.record.TradeRecordResponse;
+import com.ichaoj.ams.response.statistics.ExportResponse;
 import com.ichaoj.common.model.PublicPage;
 
 import java.util.List;
@@ -44,4 +46,17 @@ public interface IAmsTradeRecordService extends IService<AmsTradeRecord> {
             String amount,
             String maxGas,
             String executeId);
+
+    /**
+     * 统计每日消耗gas和本金
+     * @return 统计数据
+     */
+    List<DailyCostResponse> dailyCostStatistics();
+
+    /**
+     * 获取导出数据
+     * @return 导出的数据
+     */
+    List<ExportResponse> getExportData();
+
 }

+ 19 - 4
src/main/java/com/ichaoj/ams/service/impl/AmsAddressAccountServiceImpl.java

@@ -15,6 +15,7 @@ import com.ichaoj.ams.request.address.BatchAddressRequest;
 import com.ichaoj.ams.request.address.PageAddressRequest;
 import com.ichaoj.ams.request.address.PremiumAddressRequest;
 import com.ichaoj.ams.response.address.AddressResponse;
+import com.ichaoj.ams.response.address.CountAddressResponse;
 import com.ichaoj.ams.service.IAmsAddressAccountService;
 import com.ichaoj.ams.service.IAmsTradeRecordService;
 import com.ichaoj.common.exception.ErrorServiceException;
@@ -30,10 +31,7 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import static com.ichaoj.ams.constant.AmsConstant.WALLET_FILE_PATH;
@@ -122,6 +120,23 @@ public class AmsAddressAccountServiceImpl extends SuperWhaleServiceImpl<AmsAddre
         return this.getOne(wrapper);
     }
 
+    @Override
+    public List<CountAddressResponse> countBatchCount(String userId) {
+        if (StrUtil.isBlank(userId)) {
+            userId = SuperWhaleContext.getContext(PublicUserInfo.class).getUserId();
+        }
+        return this.baseMapper.countBatchCount(userId);
+    }
+
+    @Override
+    public List<String> queryBatchAddress(int addressType) {
+        LambdaQueryWrapper<AmsAddressAccount> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(AmsAddressAccount::getUserId, SuperWhaleContext.getContext(PublicUserInfo.class).getUserId())
+                .eq(addressType >= 0, AmsAddressAccount::getAddressType, addressType)
+                .groupBy(AmsAddressAccount::getAddress);
+        return this.list(wrapper).stream().map(AmsAddressAccount::getAddress).distinct().collect(Collectors.toList());
+    }
+
     /**
      * 添加精品号
      */

+ 81 - 7
src/main/java/com/ichaoj/ams/service/impl/AmsAirdropProjectServiceImpl.java

@@ -2,6 +2,7 @@ package com.ichaoj.ams.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.comparator.CompareUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -9,21 +10,30 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ichaoj.ams.constant.AmsConstant;
 import com.ichaoj.ams.entity.AmsAirdropProject;
 import com.ichaoj.ams.entity.AmsAirdropTask;
+import com.ichaoj.ams.entity.AmsExecuteRecord;
+import com.ichaoj.ams.entity.AmsTradeRecord;
 import com.ichaoj.ams.mapper.AmsAirdropProjectMapper;
 import com.ichaoj.ams.request.airdrop.CreateAirdropProject;
 import com.ichaoj.ams.request.airdrop.PageProjectRequest;
 import com.ichaoj.ams.request.airdrop.UpdateAirdropProject;
 import com.ichaoj.ams.response.airdrop.AirdropProjectResponse;
+import com.ichaoj.ams.response.statistics.PredictCostResponse;
 import com.ichaoj.ams.service.IAmsAirdropProjectService;
 import com.ichaoj.ams.service.IAmsAirdropTaskService;
+import com.ichaoj.ams.service.IAmsExecuteRecordService;
+import com.ichaoj.ams.service.IAmsTradeRecordService;
 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.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -39,6 +49,13 @@ public class AmsAirdropProjectServiceImpl extends SuperWhaleServiceImpl<AmsAirdr
     @Resource
     private IAmsAirdropTaskService taskService;
 
+    @Resource
+    @Lazy
+    private IAmsExecuteRecordService executeRecordService;
+    @Resource
+    @Lazy
+    private IAmsTradeRecordService tradeRecordService;
+
     @Override
     public PublicPage<AirdropProjectResponse> pageAirdropProject(PageProjectRequest projectRequest) {
         LambdaQueryWrapper<AmsAirdropProject> wrapper = Wrappers.lambdaQuery();
@@ -49,13 +66,36 @@ public class AmsAirdropProjectServiceImpl extends SuperWhaleServiceImpl<AmsAirdr
         Page<AmsAirdropProject> result = this.page(this.buildPageObj(projectRequest), wrapper);
         return this.convertPublicPage(result, project -> {
             AirdropProjectResponse response = BeanUtil.copyProperties(project, AirdropProjectResponse.class);
-            long count = taskService.count(new LambdaQueryWrapper<AmsAirdropTask>()
+            List<AmsExecuteRecord> list = executeRecordService.list(
+                    new LambdaQueryWrapper<AmsExecuteRecord>()
+                            .eq(AmsExecuteRecord::getExecuteStatus, 1)
+                            .eq(AmsExecuteRecord::getProjectId, project.getAmsProjectId())
+                            .eq(AmsExecuteRecord::getUserId, SuperWhaleContext.getContext(PublicUserInfo.class).getUserId())
+            );
+            if (CollectionUtil.isNotEmpty(list)) {
+                List<AmsTradeRecord> records = tradeRecordService.listByIds(list.stream().map(AmsExecuteRecord::getExecuteId).collect(Collectors.toList()));
+                double totalGas = records.stream().mapToDouble(t -> Double.parseDouble(t.getGas())).sum();
+                response.setTotalGas(BigDecimal.valueOf(totalGas));
+            } else {
+                response.setTotalGas(BigDecimal.ZERO);
+            }
+            List<AmsAirdropTask> tasks = taskService.list(new LambdaQueryWrapper<AmsAirdropTask>()
                     .eq(AmsAirdropTask::getAirdropProjectId, project.getAmsProjectId()));
-            response.setTaskTotalCount(count);
-            long finishCount = taskService.count(new LambdaQueryWrapper<AmsAirdropTask>()
-                    .eq(AmsAirdropTask::getAirdropProjectId, project.getAmsProjectId())
-                    .eq(AmsAirdropTask::getTaskStatus, 1));
-            response.setTaskFinishCount(finishCount);
+            if (CollectionUtil.isNotEmpty(tasks)) {
+                long sum = tasks.stream().mapToLong(AmsAirdropTask::getPlanTimes).sum();
+                response.setTaskTotalCount(sum);
+                long finishCount = executeRecordService.count(
+                        new LambdaQueryWrapper<AmsExecuteRecord>()
+                                .eq(AmsExecuteRecord::getExecuteStatus, 1)
+                                .in(AmsExecuteRecord::getTaskId, tasks.stream()
+                                        .map(AmsAirdropTask::getAmsTaskId)
+                                        .collect(Collectors.toList()))
+                );
+                response.setTaskFinishCount(finishCount);
+            } else {
+                response.setTaskTotalCount(0);
+                response.setTaskFinishCount(0);
+            }
             return response;
         });
     }
@@ -66,6 +106,14 @@ public class AmsAirdropProjectServiceImpl extends SuperWhaleServiceImpl<AmsAirdr
         project.setCreateTime(LocalDateTime.now());
         project.setStatus(AmsConstant.AirdropStatus.NOT_STARTED);
         this.save(project);
+//        List<String> taskIds = createAirdropProject.getTaskIds();
+//        if (CollectionUtil.isNotEmpty(taskIds)) {
+//            List<AmsAirdropTask> tasks = taskService.listByIds(taskIds)
+//                    .stream()
+//                    .peek(t -> t.setAirdropProjectId(project.getAmsProjectId()))
+//                    .collect(Collectors.toList());
+//            taskService.updateBatchById(tasks);
+//        }
     }
 
     @Override
@@ -73,6 +121,32 @@ public class AmsAirdropProjectServiceImpl extends SuperWhaleServiceImpl<AmsAirdr
         AmsAirdropProject project = BeanUtil.copyProperties(updateAirdropProject, AmsAirdropProject.class);
         project.setUpdateTime(LocalDateTime.now());
         this.updateById(project);
+        List<UpdateAirdropProject.Task> taskList = updateAirdropProject.getTasks();
+        if (CollectionUtil.isEmpty(taskList)) {
+            return;
+        }
+        List<AmsAirdropTask> tasks = taskService.listByIds(taskList.stream().map(UpdateAirdropProject.Task::getTaskId).collect(Collectors.toList()));
+        if (CollectionUtil.isNotEmpty(tasks)) {
+            for (AmsAirdropTask task : tasks) {
+                for (UpdateAirdropProject.Task t : taskList) {
+                    if (task.getAmsTaskId().equals(t.getTaskId())) {
+                        task.setTaskName(t.getTaskName());
+                        taskService.updateById(task);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public PredictCostResponse predictCostStatistics() {
+        List<AmsAirdropProject> list = this.list();
+        PredictCostResponse response = new PredictCostResponse();
+        response.setTotalCount(list.size());
+        response.setTotalCost(BigDecimal.valueOf(list.stream().mapToDouble(p -> Double.parseDouble(p.getEstimatedCost())).sum()));
+        List<PredictCostResponse.Predict> predicts = BeanUtil.copyToList(list, PredictCostResponse.Predict.class);
+        response.setPredicts(predicts.stream().sorted((p1, p2) -> CompareUtil.compare(p2.getEstimatedCost(), p1.getEstimatedCost())).collect(Collectors.toList()));
+        return response;
     }
 
 }

+ 14 - 0
src/main/java/com/ichaoj/ams/service/impl/AmsAirdropTaskServiceImpl.java

@@ -14,11 +14,14 @@ import com.ichaoj.ams.response.task.TaskResponse;
 import com.ichaoj.ams.service.IAmsAirdropProjectService;
 import com.ichaoj.ams.service.IAmsAirdropTaskService;
 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.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.util.List;
 
 /**
  * <p>
@@ -61,4 +64,15 @@ public class AmsAirdropTaskServiceImpl extends SuperWhaleServiceImpl<AmsAirdropT
             return response;
         });
     }
+
+    @Override
+    public List<TaskResponse> listTask() {
+        List<TaskResponse> responses = this.baseMapper.listTask(SuperWhaleContext.getContext(PublicUserInfo.class).getUserId());
+        for (TaskResponse response : responses) {
+            if (response.getExecuteStatus() == null) {
+                response.setExecuteStatus(1);
+            }
+        }
+        return responses;
+    }
 }

+ 36 - 5
src/main/java/com/ichaoj/ams/service/impl/AmsExecuteRecordServiceImpl.java

@@ -8,10 +8,7 @@ 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;
+import com.ichaoj.ams.entity.*;
 import com.ichaoj.ams.mapper.AmsExecuteRecordMapper;
 import com.ichaoj.ams.request.execute.CreateExecute;
 import com.ichaoj.ams.request.execute.PageExecuteRequest;
@@ -23,12 +20,16 @@ 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.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -57,7 +58,7 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
         LambdaQueryWrapper<AmsExecuteRecord> wrapper = Wrappers.lambdaQuery();
         wrapper
                 .eq(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getExecuteId, executeRequest.getQueryValue())
-                .or(w->w.like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getGroupName, executeRequest.getQueryValue())
+                .or(w -> w.like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getGroupName, executeRequest.getQueryValue())
                         .or().like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getTaskId, executeRequest.getQueryValue()))
                 .orderByDesc(AmsExecuteRecord::getCreateTime);
         Page<AmsExecuteRecord> result = this.page(this.buildPageObj(executeRequest), wrapper);
@@ -150,4 +151,34 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
         }
         return task;
     }
+
+    @Scheduled(cron = "0/59 * * * * ? ")
+    public void scanExecuteStatus() {
+        List<AmsExecuteRecord> list = this.list();
+        for (AmsExecuteRecord executeRecord : list) {
+            // 验证用户执行的状态
+            String executeId = executeRecord.getExecuteId();
+            String userId = executeRecord.getUserId();
+            String groupName = executeRecord.getGroupName();
+            List<AmsAddressAccount> accounts = accountService.getByGroupNameAndUserId(groupName, userId);
+            Set<String> predictSet = accounts.stream().map(AmsAddressAccount::getAddress).collect(Collectors.toSet());
+            List<AmsTradeRecord> tradeRecords = tradeService.list(new LambdaQueryWrapper<AmsTradeRecord>()
+                    .eq(AmsTradeRecord::getExecuteId, executeId));
+            Set<String> actualSet = tradeRecords.stream().map(AmsTradeRecord::getAddress).collect(Collectors.toSet());
+            // 判断交易记录的地址是否
+            if (CollectionUtil.containsAll(predictSet, actualSet) && CollectionUtil.containsAll(actualSet, predictSet)) {
+                Set<String> confirmSet = new HashSet<>();
+                for (AmsTradeRecord tradeRecord : tradeRecords) {
+                    if (tradeRecord.getStatus() == 1) {
+                        confirmSet.add(tradeRecord.getAddress());
+                    }
+                }
+                // 修改状态
+                if (CollectionUtil.containsAll(confirmSet, actualSet) && CollectionUtil.containsAll(actualSet, confirmSet)) {
+                    executeRecord.setExecuteStatus(1);
+                    this.updateById(executeRecord);
+                }
+            }
+        }
+    }
 }

+ 50 - 19
src/main/java/com/ichaoj/ams/service/impl/AmsTradeRecordServiceImpl.java

@@ -3,18 +3,17 @@ package com.ichaoj.ams.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 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.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.statistics.DailyCostResponse;
 import com.ichaoj.ams.response.record.TradeRecordResponse;
+import com.ichaoj.ams.response.statistics.ExportResponse;
 import com.ichaoj.ams.service.IAmsAddressAccountService;
-import com.ichaoj.ams.service.IAmsAirdropTaskService;
 import com.ichaoj.ams.service.IAmsTradeRecordService;
 import com.ichaoj.common.model.PublicPage;
 import com.ichaoj.common.model.PublicUserInfo;
@@ -23,6 +22,7 @@ import com.ichaoj.web.context.SuperWhaleContext;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -66,7 +66,12 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
             addressMap.put(address, false);
         }
 
-        while (set.size() <= addresses.size()) {
+        doTrans(intervalMin, intervalMax, amount, maxGas, executeId, addressMap, set, addresses);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    protected void doTrans(Integer intervalMin, Integer intervalMax, String amount, String maxGas, String executeId, Map<String, Boolean> addressMap, Set<String> set, List<String> addresses) {
+        while (set.size() < addresses.size() - 1) {
             for (int i = 0; i < addresses.size(); i++) {
                 BigDecimal gas = getCurrentGasPrice(maxGas);
                 log.info("当前gas: {}", gas);
@@ -82,15 +87,8 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
                         i + 2,
                         sleep / 60);
                 if (i == 0) {
-                    saveTrade(executeId, address, gas, currenAmount);
-
-                    // 修改地址操作时间
-                    AmsAddressAccount account = accountService.getByAddress(address);
-                    account.setLastOperTime(LocalDateTime.now());
-                    accountService.updateById(account);
-                    // todo 调用链上交易
+                    executeTrans(executeId, address, gas, currenAmount);
                 } else {
-
                     AmsTradeRecord record = this.getOne(
                             new LambdaQueryWrapper<AmsTradeRecord>()
                                     .eq(AmsTradeRecord::getExecuteId, executeId)
@@ -99,12 +97,11 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
 
                     if (record == null) {
                         AMS_SCHEDULER.schedule(() -> {
-                            saveTrade(executeId, address, gas, currenAmount);
-                            // 修改地址操作时间
-                            AmsAddressAccount account = accountService.getByAddress(address);
-                            account.setLastOperTime(LocalDateTime.now());
-                            accountService.updateById(account);
                             // todo 调用链上交易
+
+                            //交易入库
+                            executeTrans(executeId, address, gas, currenAmount);
+
                         }, sleep, TimeUnit.SECONDS);
                     }
                 }
@@ -115,6 +112,33 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
         }
     }
 
+    private void executeTrans(String executeId, String address, BigDecimal gas, BigDecimal currenAmount) {
+        // todo 调用链上交易
+        //交易入库
+        String txId = saveTrade(executeId, address, gas, currenAmount);
+
+        // 修改地址操作时间
+        AmsAddressAccount account = accountService.getByAddress(address);
+        account.setLastOperTime(LocalDateTime.now());
+        accountService.updateById(account);
+
+        // 修改当前余额
+        AmsTradeRecord record = this.getById(txId);
+        record.setCurrentBalance(getCurrentBalance(address));
+        this.updateById(record);
+    }
+
+    @Override
+    public List<DailyCostResponse> dailyCostStatistics() {
+        return this.baseMapper.dailyCostStatistics();
+    }
+
+    @Override
+    public List<ExportResponse> getExportData() {
+        String userId = SuperWhaleContext.getContext(PublicUserInfo.class).getUserId();
+        return this.baseMapper.getExportData(userId);
+    }
+
     private BigDecimal getCurrenAmount(String amount) {
         return RandomUtil.randomBigDecimal(BigDecimal.ZERO, new BigDecimal(amount));
     }
@@ -123,7 +147,7 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
         return RandomUtil.randomBigDecimal(BigDecimal.ZERO, new BigDecimal(maxGas));
     }
 
-    private void saveTrade(String executeId, String address, BigDecimal gas, BigDecimal amount) {
+    private String saveTrade(String executeId, String address, BigDecimal gas, BigDecimal amount) {
         CreateTradeRecordRequest trade = CreateTradeRecordRequest.builder()
                 .address(address)
                 .executeId(executeId)
@@ -133,7 +157,14 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
                 .gas(gas.toPlainString())
                 .amount(amount.toPlainString())
                 .build();
-        this.save(BeanUtil.copyProperties(trade, AmsTradeRecord.class));
+        AmsTradeRecord record = BeanUtil.copyProperties(trade, AmsTradeRecord.class);
+        this.save(record);
+        return record.getTradeRecordId();
+    }
+
+    private String getCurrentBalance(String address) {
+        // todo 查询链上余额
+        return "100";
     }
 
     private String getUnusedAddress(Map<String, Boolean> addressMap, List<String> addresses) {

+ 10 - 0
src/main/resources/mapper/AmsAddressAccountMapper.xml

@@ -43,4 +43,14 @@
             order by a.create_time desc
         </if>
     </select>
+
+    <select id="countBatchCount" resultType="com.ichaoj.ams.response.address.CountAddressResponse">
+        SELECT a.group_name,
+               count(*) addressCount
+        FROM `ams_address_account` a
+        WHERE a.flag = 0
+          and a.user_id = #{userId}
+          AND a.address_type = 1
+        GROUP BY a.group_name
+    </select>
 </mapper>

+ 35 - 0
src/main/resources/mapper/AmsAirdropTaskMapper.xml

@@ -1,5 +1,40 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.ichaoj.ams.mapper.AmsAirdropTaskMapper">
+    <select id="listTask" resultType="com.ichaoj.ams.response.task.TaskResponse">
+        SELECT a.task_name,
+               a.ams_task_id,
+               a.airdrop_project_id              amsProjectId,
+               a.task_name,
+               a.contract_address,
+               a.task_type,
+               a.task_status,
+               a.plan_times,
+               a.estimated_gas,
+               b.project_name,
+               b.project_logo,
+               (SELECT COUNT(*)
+                FROM ams_execute_record er
+                WHERE er.flag = 0
+                  and er.task_id = a.ams_task_id
+                  and er.user_id = #{userId}) AS executeTimes,
 
+               (SELECT aer1.execute_status
+                FROM ams_execute_record aer1
+                WHERE aer1.flag = 0
+                  and aer1.task_id = a.ams_task_id
+                  and aer1.user_id = #{userId}
+                ORDER BY aer1.create_time DESC
+                LIMIT 1)                      AS executeStatus,
+               (SELECT aer.create_time
+                FROM ams_execute_record aer
+                WHERE aer.flag = 0
+                  and aer.task_id = a.ams_task_id
+                  and aer.user_id = #{userId}
+                ORDER BY aer.create_time DESC
+                LIMIT 1)                      AS lastExecuteTime
+        FROM ams_airdrop_task a
+                 LEFT JOIN ams_airdrop_project b ON a.airdrop_project_id = b.ams_project_id and b.flag = 0
+        where a.flag = 0
+    </select>
 </mapper>

+ 31 - 0
src/main/resources/mapper/AmsTradeRecordMapper.xml

@@ -28,4 +28,35 @@
         </if>
         ORDER BY tr.create_time DESC
     </select>
+
+    <select id="dailyCostStatistics" resultType="com.ichaoj.ams.response.statistics.DailyCostResponse">
+        SELECT DATE_FORMAT(create_time, '%m-%d') `date`,
+               SUM(gas)    AS                    gas,
+               SUM(amount) AS                    principal
+        FROM ams_trade_record
+        WHERE flag = 0
+        GROUP BY `date`;
+    </select>
+
+    <select id="getExportData" resultType="com.ichaoj.ams.response.statistics.ExportResponse">
+        SELECT project_name,
+               p.ams_project_id,
+               t.ams_task_id,
+               task_name,
+               tr.address,
+               SUM(tr.amount) as amount,
+               sum(tr.gas)    as gas,
+               tr.create_time,
+               tr.tx_id
+        FROM `ams_trade_record` tr
+                 INNER JOIN ams_execute_record er ON tr.execute_id = er.execute_id
+            AND er.flag = 0
+                 INNER JOIN ams_airdrop_project p ON er.project_id = p.ams_project_id
+            AND p.flag = 0
+                 INNER JOIN ams_airdrop_task t ON t.ams_task_id = er.task_id
+            AND t.flag = 0
+        where tr.flag = 0
+          and er.user_id = #{userId}
+        group by tr.address, task_name, project_name
+    </select>
 </mapper>