first commit
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>skins-service</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>service-task</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.ruoyi</groupId>-->
|
||||
<!-- <artifactId>skins-model</artifactId>-->
|
||||
<!-- <version>4.8.2</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>service-admin</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>message-service</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.task.MQReceiver;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.task.DTO.pWelfareMQData;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static com.ruoyi.task.config.DirectExchangeConfig.*;
|
||||
|
||||
|
||||
// 不用
|
||||
@Slf4j
|
||||
// @Component
|
||||
public class DirectReceiver {
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.task.config;
|
||||
|
||||
import org.springframework.amqp.core.AmqpAdmin;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
|
||||
|
||||
@Configurable
|
||||
public class DirectExchangeConfig {
|
||||
|
||||
@Autowired
|
||||
private AmqpAdmin amqpAdmin;
|
||||
public static final String PROMOTION_WELFARE_QUEUE = "pWelfareQueue";
|
||||
public static final String PROMOTION_WELFARE_EXCHANGE = "pWelfareExchange";
|
||||
public static final String PROMOTION_WELFARE_KEY1 = "pwKey1";
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
|
||||
// }
|
||||
|
||||
|
||||
// @Bean
|
||||
// public Queue directQueue() {
|
||||
// return new Queue(DIRECT_QUEUE, true);
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public DirectExchange directExchange() {
|
||||
// return new DirectExchange(DIRECT_EXCHANGE, true, false);
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public Binding bindingDirectExchange(Queue directQueue, DirectExchange directExchange) {
|
||||
// return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
|
||||
// }
|
||||
|
||||
// @Bean
|
||||
// public Queue directQueue2() {
|
||||
// return new Queue(DIRECT_QUEUE2, true);
|
||||
// }
|
||||
|
||||
// @Bean
|
||||
// public Binding bindingDirectExchange2(Queue directQueue2, DirectExchange directExchange) {
|
||||
// return BindingBuilder.bind(directQueue2).to(directExchange).with(DIRECT_ROUTING_KEY);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.task.contract.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetAwardRequest {
|
||||
private Integer taskDoingid;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.task.contract.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TaskTestRequest {
|
||||
private String kind;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.task.controller;
|
||||
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.domain.task.DTO.pWelfareMQData;
|
||||
import com.ruoyi.domain.task.VO.TtTaskDoingVO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ruoyi.domain.task.constant.mq.MQMoudle.PROMOTION_WELFARE_EXCHANGE;
|
||||
import static com.ruoyi.domain.task.constant.mq.MQMoudle.PROMOTION_WELFARE_KEY1;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/mq")
|
||||
public class MQController {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@GetMapping("/t1")
|
||||
@Anonymous
|
||||
public AjaxResult t1() {
|
||||
|
||||
log.debug("mq测试");
|
||||
return AjaxResult.success("mq测试");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.ruoyi.task.controller;
|
||||
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.domain.task.VO.TtTaskDoingVO;
|
||||
import com.ruoyi.task.contract.request.GetAwardRequest;
|
||||
import com.ruoyi.task.contract.request.TaskTestRequest;
|
||||
import com.ruoyi.task.scheduled.TaskUpdate;
|
||||
import com.ruoyi.task.service.TtTaskService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Api(tags = "任务模块")
|
||||
@RestController
|
||||
@RequestMapping("/api/ttTask")
|
||||
public class TtTaskController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private TtTaskService ttTaskService;
|
||||
|
||||
@Autowired
|
||||
private TaskUpdate taskUpdate;
|
||||
|
||||
@ApiOperation("关于我的任务")
|
||||
@PostMapping("/taskOfme")
|
||||
@Anonymous
|
||||
// public List<TtTaskDoing> taskOfme(@PathVariable("page") Integer page,@PathVariable("size") Integer size) {
|
||||
public List<TtTaskDoingVO> taskOfme() {
|
||||
Long userId = getUserId();
|
||||
return ttTaskService.taskOfme(1, 5, Integer.valueOf(String.valueOf(userId))); //暂时不需要分页
|
||||
// return ttTaskService.taskOfme(1,5,1);
|
||||
}
|
||||
|
||||
// 领取奖励
|
||||
@PostMapping("/getAward")
|
||||
@Anonymous
|
||||
public AjaxResult getAward(@RequestBody GetAwardRequest request) {
|
||||
Long userId = getUserId();
|
||||
return ttTaskService.getAward(Integer.valueOf(String.valueOf(userId)), request.getTaskDoingid());
|
||||
// return ttTaskService.getAward(1,1);
|
||||
}
|
||||
|
||||
// 接首次下载任务
|
||||
@PostMapping("/firsDownLoadTask")
|
||||
@Anonymous
|
||||
public AjaxResult firsDownLoadTask() {
|
||||
Long userId = getUserId();
|
||||
return ttTaskService.firsDownLoadTask(Integer.valueOf(String.valueOf(userId)));
|
||||
}
|
||||
|
||||
// 接首次下载任务
|
||||
@PostMapping("/test")
|
||||
@Anonymous
|
||||
public AjaxResult firsDownLoadTask(@RequestBody TaskTestRequest request) {
|
||||
String kind = request.getKind();
|
||||
|
||||
switch (kind) {
|
||||
// case "rank":
|
||||
// // 已废弃:综合流水排行榜奖励改由 RechargeRankingRewardTask 统一发放
|
||||
// taskUpdate.blendErcashRankPrize();
|
||||
case "zhubo":
|
||||
taskUpdate.pWelfarePrize();
|
||||
}
|
||||
return AjaxResult.success("测试完成");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.task.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.task.TtTask;
|
||||
import com.ruoyi.domain.task.TtTaskDoing;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface TtTaskDoingMapper extends BaseMapper<TtTaskDoing> {
|
||||
|
||||
TtTaskDoing isOwnUser(@Param("userId") Integer userId,@Param("tid") Integer tid);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.task.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.task.TtTask;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface TtTaskMapper extends BaseMapper<TtTask> {
|
||||
|
||||
TtTask byId(Integer id);
|
||||
|
||||
List<TtTask> listByState(@Param("state") Integer state);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.task.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcashArchive;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface TtUserBlendErcashArchiveMapper extends BaseMapper<TtUserBlendErcashArchive> {
|
||||
|
||||
/**
|
||||
* 获取待归档的 id 列表(按 id 排序,每批 batchSize 条)
|
||||
*/
|
||||
List<Long> selectArchiveIds(@Param("cutoffTime") String cutoffTime,
|
||||
@Param("batchSize") int batchSize);
|
||||
|
||||
/**
|
||||
* 将指定 id 列表的记录插入归档表
|
||||
*/
|
||||
int insertFromMain(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 从主表删除已归档的记录
|
||||
*/
|
||||
int deleteFromMain(@Param("ids") List<Long> ids);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.ruoyi.task.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.entity.TtUserSourceReport;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface TtUserSourceReportTaskMapper extends BaseMapper<TtUserSourceReport> {
|
||||
|
||||
/**
|
||||
* 增量模式:统计昨日各 user+source 的 delta(仅扫昨日 1 天流水)
|
||||
*/
|
||||
List<Map<String, Object>> selectYesterdayDelta(
|
||||
@Param("yesterdayStart") String yesterdayStart,
|
||||
@Param("todayStart") String todayStart);
|
||||
|
||||
/**
|
||||
* 全量模式:统计 report_date 之前所有流水的累计值(仅首次运行时使用)
|
||||
*/
|
||||
List<Map<String, Object>> selectFullCumulative(
|
||||
@Param("todayStart") String todayStart);
|
||||
|
||||
/**
|
||||
* 批量 UPSERT 报表记录(INSERT ... ON DUPLICATE KEY UPDATE)
|
||||
*/
|
||||
int batchUpsert(@Param("list") List<TtUserSourceReport> list,
|
||||
@Param("reportDate") String reportDate);
|
||||
|
||||
/**
|
||||
* 检查指定日期是否已有报表数据(幂等检查)
|
||||
*/
|
||||
int countByReportDate(@Param("reportDate") String reportDate);
|
||||
|
||||
/**
|
||||
* 查询前一天的报表记录(用于增量递推)
|
||||
*/
|
||||
List<TtUserSourceReport> selectByReportDate(@Param("reportDate") String reportDate);
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.ruoyi.task.scheduled;
|
||||
|
||||
import com.ruoyi.system.config.ArchiveDaysConfig;
|
||||
import com.ruoyi.task.mapper.TtUserBlendErcashArchiveMapper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流水归档定时任务 —— 每天凌晨 5 点执行。
|
||||
* <p>
|
||||
* 注册到 RuoYi 系统监控-定时任务:
|
||||
* - 任务名称:流水数据归档
|
||||
* - 任务组名:ARCHIVE
|
||||
* - 调用目标:flowArchiveTask.archiveOldData
|
||||
* - cron:0 0 5 * * ?
|
||||
* <p>
|
||||
* 将 tt_user_blend_ercash 中 7 天前的数据迁移到 tt_user_blend_ercash_archive。
|
||||
* 分批处理(每批 1000 条),先插后删,每批独立事务,确保数据安全。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("flowArchiveTask")
|
||||
public class FlowArchiveTask {
|
||||
|
||||
private static final int BATCH_SIZE = 1000;
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashArchiveMapper archiveMapper;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
/** 每批归档的事务模板:把"插入归档表 + 删除主表"包成单事务,保证原子提交/回滚 */
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
@PostConstruct
|
||||
private void initTransactionTemplate() {
|
||||
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档 7 天前的流水数据
|
||||
*/
|
||||
public void archiveOldData() {
|
||||
log.info("FlowArchiveTask start ----------");
|
||||
try {
|
||||
LocalDate cutoff = LocalDate.now().minusDays(ArchiveDaysConfig.getRetainDays());
|
||||
String cutoffTime = cutoff.format(DATE_FMT) + " 00:00:00";
|
||||
log.info("归档截止时间: {}", cutoffTime);
|
||||
|
||||
int totalArchived = 0;
|
||||
int batchNum = 0;
|
||||
|
||||
while (true) {
|
||||
batchNum++;
|
||||
// Step 1: 获取待归档的 id 列表
|
||||
List<Long> ids = archiveMapper.selectArchiveIds(cutoffTime, BATCH_SIZE);
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
log.info("无更多待归档数据,退出循环");
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 2+3: 插入归档表 + 从主表删除,放入同一事务原子执行。
|
||||
// 要么一起提交、要么整批回滚,杜绝"已插未删"造成的两表并存与查询双算;
|
||||
// 归档表 ON DUPLICATE KEY UPDATE 对重复主键 no-op,残留行不报错并照常从主表删除,实现自愈。
|
||||
final List<Long> batchIds = ids;
|
||||
int[] counts = transactionTemplate.execute(status -> {
|
||||
int ins = archiveMapper.insertFromMain(batchIds);
|
||||
int del = archiveMapper.deleteFromMain(batchIds);
|
||||
return new int[]{ins, del};
|
||||
});
|
||||
int inserted = counts == null ? 0 : counts[0];
|
||||
int deleted = counts == null ? 0 : counts[1];
|
||||
|
||||
totalArchived += deleted;
|
||||
// 迁移条数以本批 id 数为准更直观;括号内 inserted 为归档表实际新增行数,
|
||||
// 自愈重复行时因 ON DUPLICATE KEY UPDATE id=id 的 no-op 计为 0,属正常非失败。
|
||||
log.info("第 {} 批:迁移 {} 条(归档表新增 {} 条),累计归档 {} 条",
|
||||
batchNum, batchIds.size(), inserted, totalArchived);
|
||||
|
||||
// 如果本批不足 BATCH_SIZE,说明已处理完
|
||||
if (ids.size() < BATCH_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("FlowArchiveTask done: totalArchived={} ----------", totalArchived);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("FlowArchiveTask error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
package com.ruoyi.task.scheduled;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtPromotionUpdateMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserAmountRecordsMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserCreditsRecordsMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.admin.service.TtRechargeRankingRewardService;
|
||||
import com.ruoyi.admin.service.TtUserAmountRecordsService;
|
||||
import com.ruoyi.admin.service.TtUserCreditsRecordsService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.service.TtVipLevelService;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.common.constant.sys.MoneyType;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtRechargeRankingReward;
|
||||
import com.ruoyi.domain.other.TtVipLevel;
|
||||
import com.ruoyi.domain.vo.TeamDetailVO;
|
||||
import com.ruoyi.domain.vo.UserBERankVO;
|
||||
import com.ruoyi.task.service.TtTaskDoingService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtAccountRecordSource.P_WELFARE;
|
||||
|
||||
@Slf4j
|
||||
@Configuration // 1.主要用于标记配置类,兼备Component的效果。
|
||||
@EnableScheduling // 2.开启定时任务
|
||||
public class TaskUpdate {
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private TtTaskDoingService ttTaskDoingService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService ttUserService;
|
||||
|
||||
@Autowired
|
||||
private TtUserCreditsRecordsService ttUserCreditsRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtUserCreditsRecordsMapper ttUserCreditsRecordsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserAmountRecordsService ttUserAmountRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtUserAmountRecordsMapper ttUserAmountRecordsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private TtPromotionUpdateMapper ttPromotionUpdateMapper;
|
||||
|
||||
@Autowired
|
||||
private TtVipLevelService vipLevelService;
|
||||
|
||||
@Autowired
|
||||
private TtRechargeRankingRewardService ttRechargeRankingRewardService;
|
||||
// 刷新每日任务
|
||||
//@Scheduled(cron = "0 0 0 * * ?")
|
||||
// private void refreshDayTask() {
|
||||
//
|
||||
// log.info("刷新{}任务", "每日流水奖励");
|
||||
//
|
||||
// Timestamp now = new Timestamp(System.currentTimeMillis());
|
||||
//
|
||||
// // {每日流水奖励}任务数据重置
|
||||
// LambdaUpdateWrapper<TtTaskDoing> ttTaskDoingUpdate = new LambdaUpdateWrapper<>();
|
||||
// ttTaskDoingUpdate
|
||||
// .eq(TtTaskDoing::getTaskId, 2)
|
||||
// .set(TtTaskDoing::getBeginTime, now)
|
||||
// .set(TtTaskDoing::getCompeteTime, null)
|
||||
// .set(TtTaskDoing::getCompletionState, TaskCompletionState.DOING.getCode())
|
||||
// .set(TtTaskDoing::getProgress, 0);
|
||||
// ttTaskDoingService.update(ttTaskDoingUpdate);
|
||||
//
|
||||
// }
|
||||
|
||||
// 发放综合流水排行榜前十奖励
|
||||
// 已废弃:该功能由 RechargeRankingRewardTask.distributeRechargeRankingReward 替代,
|
||||
// 并通过 ruoyi 定时任务后台统一管理,避免与 source=29 的消费排行榜奖励重复发放。
|
||||
// @Scheduled(cron = "2 7 0 * * ?", zone = "Asia/Shanghai")
|
||||
public void blendErcashRankPrize() {
|
||||
|
||||
log.info("发放综合流水排行榜前十奖励。");
|
||||
|
||||
// 正式时间区间-------------------------------------
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
Timestamp end = new Timestamp(c.getTimeInMillis());
|
||||
c.add(Calendar.DAY_OF_MONTH, -1);
|
||||
Timestamp begin = new Timestamp(c.getTimeInMillis());
|
||||
|
||||
// 测试时间区间---------------------------------
|
||||
// Calendar c = Calendar.getInstance();
|
||||
// Timestamp end = new Timestamp(c.getTimeInMillis());
|
||||
// c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
// c.set(Calendar.MINUTE, 0);
|
||||
// c.set(Calendar.SECOND, 0);
|
||||
// c.set(Calendar.MILLISECOND, 0);
|
||||
// c.add(Calendar.MINUTE, -2);
|
||||
// Timestamp begin = new Timestamp(c.getTimeInMillis());
|
||||
//-------------------------------------------------
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String beginT = dateFormat.format(begin);
|
||||
String endT = dateFormat.format(end);
|
||||
if (StringUtils.isBlank(beginT) || StringUtils.isBlank(beginT)) {
|
||||
beginT = null;
|
||||
endT = null;
|
||||
}
|
||||
|
||||
var source = TtAccountRecordSource.getGameConsumeCodes();
|
||||
|
||||
List<UserBERankVO> rank = ttUserBlendErcashMapper.rank(
|
||||
new ArrayList<>(source),
|
||||
beginT,
|
||||
endT,
|
||||
0,
|
||||
10);
|
||||
|
||||
var rankingRewards = ttRechargeRankingRewardService.selectTtRechargeRankingRewardList(null);
|
||||
var rankRewardMap = rankingRewards.stream()
|
||||
.collect(Collectors.toMap(TtRechargeRankingReward::getId, t -> t));
|
||||
|
||||
for (int i = 1; i <= rank.size(); i++) {
|
||||
var vo = rank.get(i - 1);
|
||||
vo.setBeRank(i);
|
||||
try {
|
||||
var reward = rankRewardMap.get(vo.getBeRank());
|
||||
if (reward == null) {
|
||||
log.warn("未找到排名奖励配置,排名:{}", vo.getBeRank());
|
||||
continue;
|
||||
}
|
||||
userService.updateOnlyUserAccount(vo.getUserId(), reward.getReward(), TtAccountRecordSource.RANK_BLEND_ERCASH);
|
||||
} catch (Exception e) {
|
||||
log.error("发放综合流水排行榜奖励失败,用户:{},异常:{}", vo.getUserId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("发放综合流水排行榜前十奖励 完成");
|
||||
}
|
||||
|
||||
// 发放主播推广福利奖励
|
||||
// 已禁用:不再每天凌晨批量发放推广福利(充值返佣已改为实时到账)
|
||||
// @Scheduled(cron = "2 4 0 * * ?", zone = "Asia/Shanghai") // 每天0点4分2秒执行
|
||||
public void pWelfarePrize() {
|
||||
|
||||
Long start = System.currentTimeMillis();
|
||||
log.info("发放推广福利奖励。");
|
||||
|
||||
// 正式时间段 ----------------------------------
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
Timestamp end = new Timestamp(c.getTimeInMillis());
|
||||
c.add(Calendar.DAY_OF_MONTH, -1);
|
||||
Timestamp begin = new Timestamp(c.getTimeInMillis());
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String beginT = dateFormat.format(begin);
|
||||
String endT = dateFormat.format(end);
|
||||
|
||||
if (StringUtils.isBlank(beginT) || StringUtils.isBlank(endT)) {
|
||||
log.warn("推广福利统计失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询今天下级有消费的主播
|
||||
List<Integer> bossIdList = ttUserAmountRecordsMapper.bossByHasConsumeEmployee(beginT, endT);
|
||||
|
||||
// 循环所有boss发放福利
|
||||
for (Integer bossId : bossIdList) {
|
||||
try {
|
||||
boolean r = pWelfarePrizeToBoss(bossId, beginT, endT);
|
||||
if (!r) {
|
||||
log.error("推广福利统计失败,用户【{}】发奖失败!", bossId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("推广福利统计失败,用户【{}】发奖异常!{}", bossId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Long stop = System.currentTimeMillis();
|
||||
log.info("每日推广福利发放完成。用时:{}分钟", (stop - start) / 60000);
|
||||
|
||||
}
|
||||
|
||||
// VIP 流水返利已改为实时发放(见 TtUserServiceImpl.tryVipFlowRebate),本定时任务已禁用
|
||||
// @Scheduled(cron = "2 1 0 * * ?", zone = "Asia/Shanghai") // 每天0点1分2秒执行
|
||||
private void vipReward() {
|
||||
Long start = System.currentTimeMillis();
|
||||
log.info("发放充值vip流水福利奖励");
|
||||
|
||||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||||
LocalDateTime beginTime = yesterday.atStartOfDay();
|
||||
LocalDateTime endTime = yesterday.atTime(23, 59, 59);
|
||||
|
||||
var records = new LambdaQueryChainWrapper<>(ttUserBlendErcashMapper).between(TtUserBlendErcash::getCreateTime, beginTime, endTime)
|
||||
.in(TtUserBlendErcash::getSource, TtAccountRecordSource.getGameConsumeCodes()).list();
|
||||
|
||||
if (CollectionUtils.isEmpty(records)) {
|
||||
log.info("昨日无充值VIP流水记录,无需发放奖励");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Integer, List<TtUserBlendErcash>> userRecordsMap = records.stream()
|
||||
.collect(Collectors.groupingBy(TtUserBlendErcash::getUserId));
|
||||
|
||||
// 3. 遍历用户进行发放操作
|
||||
var ttVipLevelMap = vipLevelService.list().stream().collect(Collectors.toMap(TtVipLevel::getId, t -> t));
|
||||
for (Map.Entry<Integer, List<TtUserBlendErcash>> entry : userRecordsMap.entrySet()) {
|
||||
Integer userId = entry.getKey();
|
||||
try {
|
||||
List<TtUserBlendErcash> userRecords = entry.getValue();
|
||||
|
||||
// 计算该用户昨日VIP流水总额
|
||||
BigDecimal totalAmount = userRecords.stream()
|
||||
.map(TtUserBlendErcash::getAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
totalAmount = totalAmount.abs();
|
||||
|
||||
TtUser ttUser = userService.getById(userId);
|
||||
|
||||
if (ttUser.getVipLevel() > 0) {
|
||||
TtVipLevel ttVipLevel = ttVipLevelMap.get(ttUser.getVipLevel());
|
||||
if (ttVipLevel == null) {
|
||||
log.warn("用户【{}】VIP等级不存在![{}]", ttUser.getUserId(), ttUser.getVipLevel());
|
||||
continue;
|
||||
}
|
||||
// 优先使用 flowRebatePercent;若为 null 则回退到 commissions
|
||||
BigDecimal percent = ttVipLevel.getFlowRebatePercent() != null
|
||||
? ttVipLevel.getFlowRebatePercent()
|
||||
: (ttVipLevel.getCommissions() != null ? ttVipLevel.getCommissions() : BigDecimal.ZERO);
|
||||
BigDecimal rebate = percent.divide(BigDecimal.valueOf(100), 4,
|
||||
RoundingMode.HALF_UP)
|
||||
.multiply(totalAmount).setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
userService.updateUserAccount(userId, rebate, TtAccountRecordSource.VIP_CONSUME_AWARD);
|
||||
}
|
||||
|
||||
log.debug("用户ID: {}, 昨日VIP流水总额: {}, 奖励发放处理中...", userId, totalAmount);
|
||||
} catch (Exception e) {
|
||||
log.error("发放VIP流水福利失败,用户ID: {},错误: {}", userId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Long stop = System.currentTimeMillis();
|
||||
log.info("发放充值vip流水福利奖励完成。用时:{}分钟", (stop - start) / 60000);
|
||||
}
|
||||
|
||||
// 根据时间区间内下级的消费发放推广福利
|
||||
private boolean pWelfarePrizeToBoss(Integer bossId, String beginTimeStr, String endTimeStr) {
|
||||
var user = userMapper.selectTtUserById(Long.valueOf(bossId));
|
||||
if (user == null) {
|
||||
log.warn("推广福利统计失败,用户【{}】不存在!", bossId);
|
||||
return false;
|
||||
}
|
||||
if (!UserType.ANCHOR.getCode().equals(user.getUserType())) {
|
||||
log.warn("推广福利统计失败,用户【{}】不是主播!", bossId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析时间
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date beginTime = null;
|
||||
try {
|
||||
beginTime = dateFormat.parse(beginTimeStr);
|
||||
} catch (ParseException e) {
|
||||
log.warn("日期解析异常,【{}】发奖失败。", bossId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查询所有下级id
|
||||
List<Integer> allEmployeesId = userMapper.allEmployeesByParents(Arrays.asList(bossId));
|
||||
|
||||
// 查询下级的最近一个绑定时间
|
||||
List<TeamDetailVO> mit = ttPromotionUpdateMapper.latelyUpdate(allEmployeesId);
|
||||
if (mit.size() < allEmployeesId.size()) log.warn("下级已绑定上级,但未写入更新日志,请及时检查!!!");
|
||||
// 如果最近绑定时间大于本次查询的起始时间,以最近绑定时间为准
|
||||
List<Integer> empIds1 = new ArrayList<>();
|
||||
List<TeamDetailVO> empIds2 = new ArrayList<>();
|
||||
for (TeamDetailVO item : mit) {
|
||||
Timestamp latelyTime = item.getBeginTime();
|
||||
if (latelyTime.compareTo(new Timestamp(beginTime.getTime())) < 0) {
|
||||
empIds1.add(item.getEmployeeId());
|
||||
} else {
|
||||
empIds2.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 昨日没有换过绑定的玩家数据
|
||||
List<TtUserBlendErcash> personBEList1 = new ArrayList<>();
|
||||
if (!empIds1.isEmpty()) {
|
||||
personBEList1 = new LambdaQueryChainWrapper<>(ttUserBlendErcashMapper)
|
||||
.in(TtUserBlendErcash::getUserId, empIds1)
|
||||
.in(TtUserBlendErcash::getSource, TtAccountRecordSource.getWelfarePrizeCodes())
|
||||
.between(TtUserBlendErcash::getCreateTime, beginTimeStr, endTimeStr)
|
||||
.list();
|
||||
}
|
||||
|
||||
// 当日有 更换绑定的下级消费统计
|
||||
List<TtUserBlendErcash> personBEList2 = new ArrayList<>();
|
||||
if (!empIds2.isEmpty()) {
|
||||
for (TeamDetailVO vo : empIds2) {
|
||||
String beginT = dateFormat.format(vo.getBeginTime());
|
||||
var l = new LambdaQueryChainWrapper<>(ttUserBlendErcashMapper)
|
||||
.eq(TtUserBlendErcash::getUserId, vo.getEmployeeId())
|
||||
.in(TtUserBlendErcash::getSource, TtAccountRecordSource.getWelfarePrizeCodes())
|
||||
.between(TtUserBlendErcash::getCreateTime, beginT, endTimeStr)
|
||||
.list();
|
||||
personBEList2.addAll(l);
|
||||
}
|
||||
}
|
||||
|
||||
personBEList1.addAll(personBEList2);
|
||||
|
||||
var totalRecharge = personBEList1.stream().filter(v -> {
|
||||
return Objects.equals(v.getSource(), TtAccountRecordSource.RECHARGE.getCode());
|
||||
}).map(v -> v.getAmount().abs()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
var allConsumeList = personBEList1.stream().filter(v -> {
|
||||
return Objects.equals(v.getType(), TtAccountRecordType.OUTPUT.getCode());
|
||||
}).toList();
|
||||
|
||||
var totalNormalGameConsume = allConsumeList.stream().filter(v -> {
|
||||
return TtAccountRecordSource.isNormalGameConsume(v.getSource());
|
||||
}).map(v -> v.getAmount().abs()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
var totalSpecialGameConsume = allConsumeList.stream().filter(v -> {
|
||||
return TtAccountRecordSource.isSpecialGameConsume(v.getSource());
|
||||
}).map(v -> v.getAmount().abs()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
// 发奖
|
||||
// 0.02*总充值 + 0.03*普通游戏消费 + 0.01*特殊游戏消费
|
||||
BigDecimal prize = totalRecharge.multiply(new BigDecimal("0.02")).add(totalNormalGameConsume.multiply(new BigDecimal("0.03")))
|
||||
.add(totalSpecialGameConsume.multiply(new BigDecimal("0.01")));
|
||||
|
||||
// 更新账户
|
||||
userService.updateUserAccount(bossId, prize, P_WELFARE);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 奖励
|
||||
private void rankPrizeF(MoneyType prizeMoneyType,
|
||||
LambdaUpdateWrapper<TtUser> wrapper,
|
||||
Integer userId,
|
||||
String value,
|
||||
TtAccountRecordSource source) {
|
||||
|
||||
userService.updateUserAccount(userId, new BigDecimal(value), source);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.ruoyi.task.scheduled;
|
||||
|
||||
import com.ruoyi.domain.entity.TtUserSourceReport;
|
||||
import com.ruoyi.task.mapper.TtUserSourceReportTaskMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 用户来源日报表定时任务 —— 每天凌晨 3 点执行。
|
||||
* <p>
|
||||
* 注册到 RuoYi 系统监控-定时任务:
|
||||
* - 任务名称:用户来源日报表生成
|
||||
* - 任务组名:REPORT
|
||||
* - 调用目标:userSourceReportTask.generateDailyReport
|
||||
* - cron:0 0 3 * * ?
|
||||
* <p>
|
||||
* 核心优化:增量递推
|
||||
* - 日常运行:今日 total = 昨日报表 total + 今日 delta(只扫 1 天流水)
|
||||
* - 首次运行:全量扫描 report_date 之前的全部流水
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("userSourceReportTask")
|
||||
public class UserSourceReportTask {
|
||||
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
@Autowired
|
||||
private TtUserSourceReportTaskMapper reportMapper;
|
||||
|
||||
/**
|
||||
* 生成日报(增量/全量自动切换)
|
||||
*/
|
||||
public void generateDailyReport() {
|
||||
log.info("UserSourceReportTask start ----------");
|
||||
try {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate reportDate = today.minusDays(1); // 报表日期 = 昨天
|
||||
LocalDate prevDate = reportDate.minusDays(1); // 前天
|
||||
|
||||
String reportDateStr = reportDate.format(DATE_FMT);
|
||||
String prevDateStr = prevDate.format(DATE_FMT);
|
||||
String yesterdayStart = reportDateStr + " 00:00:00";
|
||||
String todayStart = today.format(DATE_FMT) + " 00:00:00";
|
||||
|
||||
// 幂等检查:如果 reportDate 已有数据则跳过
|
||||
int existCount = reportMapper.countByReportDate(reportDateStr);
|
||||
if (existCount > 0) {
|
||||
log.info("reportDate={} 已有 {} 条记录,跳过(幂等)", reportDateStr, existCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有前日报表(决定增量还是全量)
|
||||
List<TtUserSourceReport> prevReports = reportMapper.selectByReportDate(prevDateStr);
|
||||
boolean hasPrev = prevReports != null && !prevReports.isEmpty();
|
||||
|
||||
// 构建昨日报表 key -> 前日 total 的映射(增量递推用)
|
||||
Map<String, BigDecimal> prevTotalMap = new HashMap<>();
|
||||
if (hasPrev) {
|
||||
for (TtUserSourceReport prev : prevReports) {
|
||||
String key = prev.getUserId() + "_" + prev.getSource();
|
||||
prevTotalMap.put(key, prev.getTotal() != null ? prev.getTotal() : BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
List<TtUserSourceReport> upsertList;
|
||||
|
||||
if (hasPrev) {
|
||||
// ===== 增量模式:昨日 delta + 前日 total =====
|
||||
log.info("增量模式:reportDate={}, 前日记录数={}", reportDateStr, prevReports.size());
|
||||
List<Map<String, Object>> deltas = reportMapper.selectYesterdayDelta(yesterdayStart, todayStart);
|
||||
log.info("昨日 delta 记录数={}", deltas.size());
|
||||
|
||||
upsertList = new ArrayList<>();
|
||||
for (Map<String, Object> delta : deltas) {
|
||||
Integer userId = ((Number) delta.get("userId")).intValue();
|
||||
Integer source = ((Number) delta.get("source")).intValue();
|
||||
BigDecimal deltaTotal = delta.get("delta") != null
|
||||
? new BigDecimal(delta.get("delta").toString()) : BigDecimal.ZERO;
|
||||
Integer parentId = delta.get("parentId") != null
|
||||
? ((Number) delta.get("parentId")).intValue() : null;
|
||||
|
||||
String key = userId + "_" + source;
|
||||
BigDecimal prevTotal = prevTotalMap.getOrDefault(key, BigDecimal.ZERO);
|
||||
BigDecimal newTotal = prevTotal.add(deltaTotal);
|
||||
|
||||
upsertList.add(TtUserSourceReport.builder()
|
||||
.userId(userId)
|
||||
.parentId(parentId)
|
||||
.source(source)
|
||||
.total(newTotal)
|
||||
.build());
|
||||
}
|
||||
|
||||
// 处理前日有记录但昨日无 delta 的 user+source(累计值不变,仍需要写入今日报表行)
|
||||
Set<String> deltaKeys = new HashSet<>();
|
||||
for (Map<String, Object> delta : deltas) {
|
||||
Integer uid = ((Number) delta.get("userId")).intValue();
|
||||
Integer src = ((Number) delta.get("source")).intValue();
|
||||
deltaKeys.add(uid + "_" + src);
|
||||
}
|
||||
for (TtUserSourceReport prev : prevReports) {
|
||||
String key = prev.getUserId() + "_" + prev.getSource();
|
||||
if (!deltaKeys.contains(key)) {
|
||||
// 昨日无 delta,累计值 = 前日 total(原样递推)
|
||||
upsertList.add(TtUserSourceReport.builder()
|
||||
.userId(prev.getUserId())
|
||||
.parentId(prev.getParentId())
|
||||
.source(prev.getSource())
|
||||
.total(prev.getTotal() != null ? prev.getTotal() : BigDecimal.ZERO)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ===== 全量模式:首次运行,扫描全部历史 =====
|
||||
log.info("全量模式:reportDate={}, 无前日数据,扫描全部历史", reportDateStr);
|
||||
List<Map<String, Object>> fullData = reportMapper.selectFullCumulative(todayStart);
|
||||
log.info("全量累计记录数={}", fullData.size());
|
||||
|
||||
upsertList = new ArrayList<>();
|
||||
for (Map<String, Object> row : fullData) {
|
||||
Integer userId = ((Number) row.get("userId")).intValue();
|
||||
Integer source = ((Number) row.get("source")).intValue();
|
||||
BigDecimal total = row.get("total") != null
|
||||
? new BigDecimal(row.get("total").toString()) : BigDecimal.ZERO;
|
||||
Integer parentId = row.get("parentId") != null
|
||||
? ((Number) row.get("parentId")).intValue() : null;
|
||||
|
||||
upsertList.add(TtUserSourceReport.builder()
|
||||
.userId(userId)
|
||||
.parentId(parentId)
|
||||
.source(source)
|
||||
.total(total)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
// 分批 UPSERT(每批 2000 条,避免单次 SQL 过大)
|
||||
int batchSize = 2000;
|
||||
int totalUpserted = 0;
|
||||
for (int i = 0; i < upsertList.size(); i += batchSize) {
|
||||
int end = Math.min(i + batchSize, upsertList.size());
|
||||
List<TtUserSourceReport> batch = upsertList.subList(i, end);
|
||||
reportMapper.batchUpsert(batch, reportDateStr);
|
||||
totalUpserted += batch.size();
|
||||
log.info("UPSERT 批次 {}/{}, 本批 {} 条", (i / batchSize + 1),
|
||||
(upsertList.size() + batchSize - 1) / batchSize, batch.size());
|
||||
}
|
||||
|
||||
log.info("UserSourceReportTask done: reportDate={}, totalUpserted={} ----------",
|
||||
reportDateStr, totalUpserted);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("UserSourceReportTask error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.ruoyi.task.scheduled;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.service.TtVipLevelService;
|
||||
import com.ruoyi.domain.common.constant.MessageCategoryEnum;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtVipLevel;
|
||||
import com.ruoyi.message.service.MessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* VIP 流水返利每日定时任务 —— 每天凌晨执行,发放"昨日"游戏流水的 VIP 返利。
|
||||
* <p>
|
||||
* 由实时发放改造为定时发放:用户玩游戏时不再实时到账,改为每天统一按昨日游戏
|
||||
* 流水总额 × 当前 VIP 等级 flowRebatePercent 计算并一次性发放。
|
||||
* <p>
|
||||
* 注册到 RuoYi 系统监控-定时任务:
|
||||
* - 任务名称:VIP流水返利发放
|
||||
* - 任务组名:VIP_REBATE
|
||||
* - 调用目标:vipFlowRebateTask.grantDailyVipRebate()
|
||||
* - cron:0 10 0 * * ? (每天凌晨 0 点 10 分)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("vipFlowRebateTask")
|
||||
public class VipFlowRebateTask {
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private TtVipLevelService vipLevelService;
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
/**
|
||||
* 发放昨日游戏流水的 VIP 返利。
|
||||
*/
|
||||
public void grantDailyVipRebate() {
|
||||
long start = System.currentTimeMillis();
|
||||
log.info("[VIP流水返利] 每日定时发放任务开始 ----------");
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate yesterday = today.minusDays(1);
|
||||
LocalDateTime beginTime = yesterday.atStartOfDay();
|
||||
LocalDateTime endTime = today.atStartOfDay().minusNanos(1);
|
||||
|
||||
// 幂等:今日若已发放过(存在今日创建的 VIP流水奖励 记录),直接跳过,避免重复发放
|
||||
long grantedToday = new LambdaQueryChainWrapper<>(ttUserBlendErcashMapper)
|
||||
.eq(TtUserBlendErcash::getSource, TtAccountRecordSource.VIP_CONSUME_AWARD.getCode())
|
||||
.ge(TtUserBlendErcash::getCreateTime, today.atStartOfDay())
|
||||
.count();
|
||||
if (grantedToday > 0) {
|
||||
log.info("[VIP流水返利] 今日已发放过 {} 条返利记录,跳过(幂等)", grantedToday);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询昨日全部游戏消费流水
|
||||
List<TtUserBlendErcash> records = new LambdaQueryChainWrapper<>(ttUserBlendErcashMapper)
|
||||
.between(TtUserBlendErcash::getCreateTime, beginTime, endTime)
|
||||
.in(TtUserBlendErcash::getSource, TtAccountRecordSource.getGameBetCodes())
|
||||
.list();
|
||||
|
||||
if (CollectionUtils.isEmpty(records)) {
|
||||
log.info("[VIP流水返利] 昨日无游戏流水记录,无需发放");
|
||||
return;
|
||||
}
|
||||
|
||||
// 按用户分组
|
||||
Map<Integer, List<TtUserBlendErcash>> userRecordsMap = records.stream()
|
||||
.collect(Collectors.groupingBy(TtUserBlendErcash::getUserId));
|
||||
|
||||
// 预加载 VIP 等级配置
|
||||
Map<Integer, TtVipLevel> vipLevelMap = vipLevelService.list().stream()
|
||||
.filter(v -> v != null && v.getId() != null)
|
||||
.collect(Collectors.toMap(TtVipLevel::getId, v -> v, (a, b) -> a));
|
||||
|
||||
int successCount = 0;
|
||||
BigDecimal totalRebate = BigDecimal.ZERO;
|
||||
|
||||
for (Map.Entry<Integer, List<TtUserBlendErcash>> entry : userRecordsMap.entrySet()) {
|
||||
Integer userId = entry.getKey();
|
||||
try {
|
||||
TtUser user = userService.getById(userId);
|
||||
if (user == null) continue;
|
||||
// 白号不发放
|
||||
if (Integer.valueOf(1).equals(user.getIsBlankAccount())) continue;
|
||||
if (user.getVipLevel() == null || user.getVipLevel() <= 0) continue;
|
||||
|
||||
TtVipLevel vipLevel = vipLevelMap.get(user.getVipLevel());
|
||||
if (vipLevel == null) {
|
||||
log.warn("[VIP流水返利] 用户【{}】VIP等级不存在![{}]", userId, user.getVipLevel());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 优先使用 flowRebatePercent;若为 null 则回退到 commissions
|
||||
BigDecimal percent = vipLevel.getFlowRebatePercent() != null
|
||||
? vipLevel.getFlowRebatePercent()
|
||||
: (vipLevel.getCommissions() != null ? vipLevel.getCommissions() : BigDecimal.ZERO);
|
||||
if (percent.compareTo(BigDecimal.ZERO) <= 0) continue;
|
||||
|
||||
// 昨日游戏流水总额(金额为负,取绝对值)
|
||||
BigDecimal consumeAmount = entry.getValue().stream()
|
||||
.map(TtUserBlendErcash::getAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.abs();
|
||||
if (consumeAmount.compareTo(BigDecimal.ZERO) <= 0) continue;
|
||||
|
||||
BigDecimal rebate = consumeAmount
|
||||
.multiply(percent)
|
||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||
if (rebate.compareTo(BigDecimal.ZERO) <= 0) continue;
|
||||
|
||||
// 直接加金币(不动积分,不走 updateUserAccount 避免递归触发佣金/返利)
|
||||
int count = userMapper.addOnlyAccountAmount(Long.valueOf(userId), rebate);
|
||||
if (count > 0) {
|
||||
// 重新读取最新余额,写入流水快照
|
||||
TtUser freshUser = userService.getById(userId);
|
||||
BigDecimal actualBalance = (freshUser != null)
|
||||
? freshUser.getAccountAmount()
|
||||
: user.getAccountAmount().add(rebate);
|
||||
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
|
||||
.userId(userId)
|
||||
.amount(rebate)
|
||||
.finalAmount(actualBalance)
|
||||
.total(rebate)
|
||||
.type(TtAccountRecordType.INPUT.getCode())
|
||||
.source(TtAccountRecordSource.VIP_CONSUME_AWARD.getCode())
|
||||
.remark(TtAccountRecordSource.VIP_CONSUME_AWARD.getMsg())
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
ttUserBlendErcashMapper.insert(blendErcash);
|
||||
successCount++;
|
||||
totalRebate = totalRebate.add(rebate);
|
||||
|
||||
// 发送邮件通知
|
||||
int displayVipLevel = user.getVipLevel() != null ? user.getVipLevel() - 1 : 0;
|
||||
String consumeStr = consumeAmount.stripTrailingZeros().toPlainString();
|
||||
String rebateStr = rebate.stripTrailingZeros().toPlainString();
|
||||
String content = "尊敬的 VIP" + displayVipLevel + " 用户,您昨日累计流水 ¥"
|
||||
+ consumeStr + ",获得返利 " + rebateStr + ",已到账。";
|
||||
messageService.sendNotice(
|
||||
Long.valueOf(userId),
|
||||
MessageCategoryEnum.SYS_VIP_REBATE.getTitle(),
|
||||
content,
|
||||
MessageCategoryEnum.SYS_VIP_REBATE.getCode());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[VIP流水返利] 发放失败,userId={}", userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
long stop = System.currentTimeMillis();
|
||||
log.info("[VIP流水返利] 每日定时发放任务完成,发放用户数={},返利总额={},用时={}ms ----------",
|
||||
successCount, totalRebate, (stop - start));
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.task.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.domain.task.TtTask;
|
||||
import com.ruoyi.domain.task.TtTaskDoing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TtTaskDoingService extends IService<TtTaskDoing> {
|
||||
TtTaskDoing isOwnUser(Integer userId, Integer tid);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ruoyi.task.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.domain.task.TtTask;
|
||||
import com.ruoyi.domain.task.TtTaskDoing;
|
||||
import com.ruoyi.domain.task.VO.TtTaskDoingVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TtTaskService extends IService<TtTask> {
|
||||
List<TtTaskDoingVO> taskOfme(Integer page, Integer size, Integer userId);
|
||||
|
||||
AjaxResult getAward(Integer userId, Integer taskDoingid);
|
||||
|
||||
AjaxResult firsDownLoadTask(Integer userId);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.task.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.domain.task.TtTaskDoing;
|
||||
import com.ruoyi.task.mapper.TtTaskDoingMapper;
|
||||
import com.ruoyi.task.service.TtTaskDoingService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class TtTaskDoingServiceImpl extends ServiceImpl<TtTaskDoingMapper, TtTaskDoing> implements TtTaskDoingService {
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Override
|
||||
public TtTaskDoing isOwnUser(Integer userId, Integer tid) {
|
||||
return baseMapper.isOwnUser(userId,tid);
|
||||
}
|
||||
}
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
package com.ruoyi.task.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.admin.service.TtUserCreditsRecordsService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.entity.recorde.TtUserCreditsRecords;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.task.TtTask;
|
||||
import com.ruoyi.domain.task.TtTaskDoing;
|
||||
import com.ruoyi.domain.task.VO.TtTaskDoingVO;
|
||||
import com.ruoyi.domain.task.constant.*;
|
||||
import com.ruoyi.task.mapper.TtTaskMapper;
|
||||
import com.ruoyi.task.service.TtTaskDoingService;
|
||||
import com.ruoyi.task.service.TtTaskService;
|
||||
import com.ruoyi.task.utils.TasKUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TtTaskServiceImpl extends ServiceImpl<TtTaskMapper, TtTask> implements TtTaskService {
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private TtTaskDoingService ttTaskDoingService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService ttUserService;
|
||||
|
||||
@Autowired
|
||||
private TtUserCreditsRecordsService ttUserCreditsRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TasKUtil tasKUtil;
|
||||
|
||||
@Override
|
||||
public List<TtTaskDoingVO> taskOfme(Integer page, Integer size, Integer userId) {
|
||||
|
||||
// 用户信息
|
||||
TtUser userInfo = userService.getById(userId);
|
||||
|
||||
// 尝试新接任务
|
||||
AjaxResult r1 = tryCreateNewTaskDoing(userInfo);
|
||||
|
||||
// 维护任务
|
||||
AjaxResult r2 = maintainTask(userInfo);
|
||||
|
||||
// 查询结果
|
||||
LambdaQueryWrapper<TtTaskDoing> ttTaskDoingQuery = new LambdaQueryWrapper<>();
|
||||
ttTaskDoingQuery
|
||||
.eq(TtTaskDoing::getUserId,userInfo.getUserId())
|
||||
.eq(TtTaskDoing::getCompletionState,TaskState.UP.getCode());
|
||||
List<TtTaskDoing> ownTask = ttTaskDoingService.list(ttTaskDoingQuery);
|
||||
return ownTask.stream().map((item)->{
|
||||
|
||||
TtTask task = this.getById(item.getTaskId());
|
||||
TtTaskDoingVO vo = new TtTaskDoingVO();
|
||||
BeanUtil.copyProperties(task,vo);
|
||||
vo.setTaskId(task.getId());
|
||||
vo.setTaskDoingId(item.getId());
|
||||
vo.setUserId(item.getUserId());
|
||||
vo.setBeginTime(item.getBeginTime());
|
||||
vo.setCompeteTime(item.getCompeteTime());
|
||||
vo.setCompletionState(item.getCompletionState());
|
||||
vo.setProgress(item.getProgress());
|
||||
return vo;
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
private AjaxResult maintainTask(TtUser userInfo) {
|
||||
|
||||
// 已接任务
|
||||
LambdaQueryWrapper<TtTaskDoing> ttTaskDoingQuery = new LambdaQueryWrapper<>();
|
||||
ttTaskDoingQuery
|
||||
.eq(TtTaskDoing::getUserId,userInfo.getUserId());
|
||||
List<TtTaskDoing> ownTask = ttTaskDoingService.list(ttTaskDoingQuery);
|
||||
|
||||
ownTask.stream().forEach((item)->{
|
||||
if (!tasKUtil.updateTask(item)){
|
||||
log.warn("任务{}-类型{} 数据更新失败,检查任务配置信息!",item.getId(),item.getTaskId());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// LambdaQueryWrapper<TtTask> ttTaskQuery = new LambdaQueryWrapper<>();
|
||||
// LambdaUpdateWrapper<TtTaskDoing> ttTaskDoingUpdate = new LambdaUpdateWrapper<>();
|
||||
//
|
||||
// // 维护普通任务{首次下载奖励}
|
||||
// ttTaskQuery
|
||||
// .eq(TtTask::getId,1);
|
||||
// TtTask task1 = ttTaskService.getOne(ttTaskQuery);
|
||||
//
|
||||
// ttTaskDoingUpdate
|
||||
// .eq(TtTaskDoing::getUserId,userInfo.getUserId())
|
||||
// .eq(TtTaskDoing::getTaskId,1)
|
||||
// .set(TtTaskDoing::getProgress,task1.getTargetValue())
|
||||
// .set(TtTaskDoing::getCompletionState,TaskCompletionState.COMPLETION.getCode())
|
||||
// .set(TtTaskDoing::getCompeteTime,new Timestamp(System.currentTimeMillis()));
|
||||
// ttTaskDoingService.update(ttTaskDoingUpdate);
|
||||
//
|
||||
//
|
||||
// // 维护每日任务{每日流水奖励}
|
||||
// ttTaskQuery.clear();
|
||||
// ttTaskQuery
|
||||
// .eq(TtTask::getId,2);
|
||||
// TtTask task2 = ttTaskService.getOne(ttTaskQuery);
|
||||
//
|
||||
// Calendar nowDay = Calendar.getInstance();
|
||||
// nowDay.set(Calendar.HOUR_OF_DAY, 0);
|
||||
// nowDay.set(Calendar.MINUTE, 0);
|
||||
// nowDay.set(Calendar.SECOND, 0);
|
||||
// nowDay.set(Calendar.MILLISECOND, 0);
|
||||
// Timestamp toDay = new Timestamp(nowDay.getTimeInMillis());
|
||||
//
|
||||
// LambdaQueryWrapper<TtUserCreditsRecords> creditsRecordsQuery = new LambdaQueryWrapper<>();
|
||||
// creditsRecordsQuery
|
||||
// .eq(TtUserCreditsRecords::getUserId,userInfo.getUserId())
|
||||
// .ge(TtUserCreditsRecords::getCreateTime,toDay);
|
||||
// List<TtUserCreditsRecords> todayRecords = ttUserCreditsRecordsService.list(creditsRecordsQuery);
|
||||
//
|
||||
// // 累计今日流水
|
||||
// BigDecimal credits = new BigDecimal("0");
|
||||
// todayRecords.stream().forEach((item)->{
|
||||
// if (item.getCredits().compareTo(BigDecimal.ZERO)>=0){
|
||||
// credits.add(item.getCredits());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// ttTaskDoingUpdate.clear();
|
||||
// ttTaskDoingUpdate
|
||||
// .eq(TtTaskDoing::getUserId,userInfo.getUserId())
|
||||
// .eq(TtTaskDoing::getTaskId,2)
|
||||
// .set(TtTaskDoing::getProgress,credits.intValue())
|
||||
// .set(credits.compareTo(new BigDecimal(task2.getTargetValue()))>=0,TtTaskDoing::getCompletionState,TaskCompletionState.COMPLETION.getCode())
|
||||
// .set(credits.compareTo(new BigDecimal(task2.getTargetValue()))>=0,TtTaskDoing::getCompeteTime,new Timestamp(System.currentTimeMillis()));
|
||||
// ttTaskDoingService.update(ttTaskDoingUpdate);
|
||||
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
private AjaxResult tryCreateNewTaskDoing(TtUser userInfo) {
|
||||
|
||||
// 已接任务
|
||||
LambdaQueryWrapper<TtTaskDoing> ttTaskDoingQuery = new LambdaQueryWrapper<>();
|
||||
ttTaskDoingQuery
|
||||
.eq(TtTaskDoing::getUserId,userInfo.getUserId());
|
||||
List<TtTaskDoing> ownTask = ttTaskDoingService.list(ttTaskDoingQuery);
|
||||
|
||||
// 所有上架的任务
|
||||
List<TtTask> allTask = baseMapper.listByState(TaskState.UP.getCode());
|
||||
|
||||
// 筛选可接的新任务
|
||||
List<TtTask> filterT = filterTask(allTask,ownTask);
|
||||
|
||||
// 自动接手新任务
|
||||
ArrayList<TtTaskDoing> newTaskDoings = new ArrayList<>();
|
||||
for (TtTask task:filterT){
|
||||
|
||||
TtTaskDoing newTaskDoing = TtTaskDoing.builder()
|
||||
.taskId(task.getId())
|
||||
.userId(userInfo.getUserId())
|
||||
.completionState(TaskCompletionState.DOING.getCode())
|
||||
.beginTime(new Timestamp(System.currentTimeMillis()))
|
||||
.progress(0)
|
||||
.build();
|
||||
newTaskDoings.add(newTaskDoing);
|
||||
|
||||
}
|
||||
// 保存新接的任务
|
||||
for (TtTaskDoing taskDoing:newTaskDoings){
|
||||
ttTaskDoingService.save(taskDoing);
|
||||
}
|
||||
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AjaxResult getAward(Integer userId, Integer taskDoingid) {
|
||||
|
||||
// 任务是否存在
|
||||
TtTaskDoing taskDoing = ttTaskDoingService.isOwnUser(userId,taskDoingid);
|
||||
if (ObjectUtil.isEmpty(taskDoing)){
|
||||
return AjaxResult.error("任务不存在");
|
||||
}
|
||||
|
||||
// 是否可领取
|
||||
AjaxResult r = taskIsComplete(taskDoing);
|
||||
if (!r.isSuccess()){
|
||||
return r;
|
||||
}
|
||||
|
||||
// 领奖
|
||||
getPrize(taskDoing);
|
||||
|
||||
return AjaxResult.success("操作成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AjaxResult firsDownLoadTask(Integer userId) {
|
||||
|
||||
// 是否已完成
|
||||
LambdaQueryWrapper<TtTaskDoing> ttTaskDoingQuery = new LambdaQueryWrapper<>();
|
||||
ttTaskDoingQuery
|
||||
.eq(TtTaskDoing::getUserId,userId)
|
||||
.eq(TtTaskDoing::getTaskId,1);
|
||||
TtTaskDoing one = ttTaskDoingService.getOne(ttTaskDoingQuery);
|
||||
if (ObjectUtil.isNotEmpty(one)){
|
||||
return AjaxResult.success("首次下载任务已经完成,不能重复执行。");
|
||||
}
|
||||
|
||||
// 接手任务并直接完成
|
||||
TtTask task = baseMapper.byId(1);
|
||||
TtTaskDoing taskDoing = new TtTaskDoing();
|
||||
BeanUtil.copyProperties(task,taskDoing);
|
||||
taskDoing.setUserId(userId);
|
||||
taskDoing.setTaskId(task.getId());
|
||||
taskDoing.setProgress(1);
|
||||
taskDoing.setCompletionState(TaskCompletionState.COMPLETION.getCode());
|
||||
taskDoing.setCompeteTime(new Timestamp(System.currentTimeMillis()));
|
||||
ttTaskDoingService.save(taskDoing);
|
||||
|
||||
return AjaxResult.success("任务完成。");
|
||||
|
||||
}
|
||||
|
||||
private AjaxResult getPrize(TtTaskDoing taskDoing) {
|
||||
TtTask task = baseMapper.byId(taskDoing.getTaskId());
|
||||
if (task.getAwardType().equals(TaskAwardType.CREDITS.getCode())){
|
||||
|
||||
BigDecimal prize = BigDecimal.ZERO;
|
||||
|
||||
if (ObjectUtil.isEmpty(task.getAwardValue())){
|
||||
// 暂时没有动态奖励的任务
|
||||
// 计算奖金
|
||||
// prize = computerPrize(task, taskDoing.getUserId());
|
||||
}
|
||||
prize = new BigDecimal(task.getAwardValue());
|
||||
|
||||
TtUser user = ttUserService.getById(taskDoing.getUserId());
|
||||
BigDecimal add = user.getAccountCredits().add(prize);
|
||||
user.setAccountCredits(add);
|
||||
|
||||
LambdaUpdateWrapper<TtUser> ttUserUpdate = new LambdaUpdateWrapper<>();
|
||||
ttUserUpdate
|
||||
.eq(TtUser::getUserId,user.getUserId())
|
||||
.set(TtUser::getAccountCredits,add);
|
||||
ttUserService.update(ttUserUpdate);
|
||||
userService.insertUserCreditsRecords(user.getUserId(), TtAccountRecordType.INPUT, TtAccountRecordSource.TASK, prize, add,taskDoing.getTaskId());
|
||||
|
||||
taskDoing.setCompletionState(TaskCompletionState.COMPLETION_PRIZE.getCode());
|
||||
ttTaskDoingService.updateById(taskDoing);
|
||||
|
||||
return AjaxResult.success("领取奖励:积分"+add.toString());
|
||||
}else if (task.getAwardType().equals(TaskAwardType.OTHER.getCode())){
|
||||
System.out.println("其他奖励类型");
|
||||
return AjaxResult.error("其他奖励类型开发中。");
|
||||
}else {
|
||||
System.out.println("非法的奖励类型");
|
||||
return AjaxResult.error("非法的奖励类型。");
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal computerPrize(TtTask task,Integer userId){
|
||||
|
||||
// 流水奖励动态计算奖金
|
||||
if (task.getId().equals(2)){
|
||||
|
||||
Calendar nowDay = Calendar.getInstance();
|
||||
nowDay.set(Calendar.HOUR_OF_DAY, 0);
|
||||
nowDay.set(Calendar.MINUTE, 0);
|
||||
nowDay.set(Calendar.SECOND, 0);
|
||||
nowDay.set(Calendar.MILLISECOND, 0);
|
||||
Timestamp toDay = new Timestamp(nowDay.getTimeInMillis());
|
||||
|
||||
nowDay.add(Calendar.DAY_OF_MONTH,-1);
|
||||
Timestamp yesterday = new Timestamp(nowDay.getTimeInMillis());
|
||||
|
||||
LambdaQueryWrapper<TtUserCreditsRecords> creditsRecordsQuery = new LambdaQueryWrapper<>();
|
||||
creditsRecordsQuery
|
||||
.eq(TtUserCreditsRecords::getUserId,userId)
|
||||
.ge(TtUserCreditsRecords::getCreateTime,yesterday)
|
||||
.le(TtUserCreditsRecords::getCreateTime,toDay);
|
||||
List<TtUserCreditsRecords> list = ttUserCreditsRecordsService.list(creditsRecordsQuery);
|
||||
|
||||
BigDecimal prize = new BigDecimal("0");
|
||||
list.stream().forEach((item)->{
|
||||
if (item.getCredits().compareTo(BigDecimal.ZERO)>=0){
|
||||
prize.add(item.getCredits());
|
||||
}
|
||||
});
|
||||
|
||||
prize.multiply(new BigDecimal("0.01"));
|
||||
return prize;
|
||||
|
||||
}else {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AjaxResult taskIsComplete(TtTaskDoing taskDoing) {
|
||||
|
||||
TtTask task = baseMapper.byId(taskDoing.getTaskId());
|
||||
if (task.getType().equals(TaskType.COMMON.getCode())){
|
||||
|
||||
if (taskDoing.getCompletionState().equals(TaskCompletionState.COMPLETION_PRIZE.getCode())){
|
||||
return AjaxResult.error("不可重复领取奖励。");
|
||||
}
|
||||
if (taskDoing.getCompletionState().equals(TaskCompletionState.COMPLETION.getCode())){
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
// if (taskDoing.getProgress()>=task.getTargetValue()){
|
||||
// return AjaxResult.success();
|
||||
// }
|
||||
return AjaxResult.error("任务未完成,不可领奖。");
|
||||
}else if (task.getType().equals(TaskType.DAY.getCode())){
|
||||
|
||||
if (taskDoing.getCompletionState().equals(TaskCompletionState.COMPLETION.getCode())){
|
||||
return AjaxResult.success();
|
||||
}
|
||||
return AjaxResult.error("未完成/已领取");
|
||||
|
||||
}else if (task.getType().equals(TaskType.WEEK.getCode())){
|
||||
System.out.println("WEEK");
|
||||
return AjaxResult.error("非法taskType");
|
||||
}else if (task.getType().equals(TaskType.MONTH.getCode())){
|
||||
System.out.println("MONTH");
|
||||
return AjaxResult.error("非法taskType");
|
||||
}else {
|
||||
System.out.println("非法任务类型");
|
||||
return AjaxResult.error("非法taskType");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 废弃方法
|
||||
private List<TtTaskDoingVO> getDoingTask(List<TtTask> filterT,List<TtTaskDoing> ownTask, TtUser userInfo) {
|
||||
|
||||
ArrayList<TtTaskDoingVO> result = new ArrayList<>();
|
||||
|
||||
// 自动接手新任务
|
||||
ArrayList<TtTaskDoing> newTaskDoings = new ArrayList<>();
|
||||
for (TtTask task:filterT){
|
||||
|
||||
TtTaskDoing newTaskDoing = TtTaskDoing.builder()
|
||||
.taskId(task.getId())
|
||||
.userId(userInfo.getUserId())
|
||||
.completionState(TaskCompletionState.DOING.getCode())
|
||||
.beginTime(new Timestamp(System.currentTimeMillis()))
|
||||
.progress(0)
|
||||
.build();
|
||||
newTaskDoings.add(newTaskDoing);
|
||||
|
||||
TtTaskDoingVO ttTaskDoingVO = new TtTaskDoingVO();
|
||||
BeanUtil.copyProperties(task,ttTaskDoingVO);
|
||||
ttTaskDoingVO.setCompletionState(newTaskDoing.getCompletionState());
|
||||
ttTaskDoingVO.setProgress(newTaskDoing.getProgress());
|
||||
result.add(ttTaskDoingVO);
|
||||
|
||||
}
|
||||
// 保存新接的任务
|
||||
for (TtTaskDoing taskDoing:newTaskDoings){
|
||||
ttTaskDoingService.save(taskDoing);
|
||||
}
|
||||
|
||||
List<TtTaskDoingVO> collect = ownTask.stream().map((item) -> {
|
||||
TtTaskDoingVO vo = new TtTaskDoingVO();
|
||||
TtTask byId = baseMapper.byId(item.getTaskId());
|
||||
BeanUtil.copyProperties(byId, vo);
|
||||
vo.setTaskId(byId.getId());
|
||||
vo.setTaskDoingId(item.getId());
|
||||
vo.setCompletionState(item.getCompletionState());
|
||||
vo.setProgress(item.getProgress());
|
||||
vo.setBeginTime(item.getBeginTime());
|
||||
vo.setCompeteTime(item.getCompeteTime());
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
// 维护旧任务
|
||||
collect.stream().forEach((item)->{
|
||||
if (item.getType().equals(TaskType.DAY.getCode())){
|
||||
|
||||
// 当天零点时间戳
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
Timestamp currenDay = new Timestamp(c.getTimeInMillis());
|
||||
|
||||
// 任务开始时间
|
||||
Timestamp beginTime = item.getBeginTime();
|
||||
// 任务完成时间
|
||||
Timestamp competeTime = item.getCompeteTime();
|
||||
|
||||
// 尝试重置当日任务
|
||||
// 完成时间为空或小于当天零点 1刷新开始时间,2状态更新为完成
|
||||
if (ObjectUtil.isEmpty(competeTime) || competeTime.compareTo(currenDay)<0){
|
||||
UpdateWrapper<TtTaskDoing> updateWrapper = new UpdateWrapper<TtTaskDoing>();
|
||||
updateWrapper
|
||||
.eq("id",item.getTaskDoingId())
|
||||
.set("begin_time",new Timestamp(System.currentTimeMillis()))
|
||||
.set("completion_state",TaskCompletionState.COMPLETION.getCode());
|
||||
ttTaskDoingService.update();
|
||||
}
|
||||
// 开始时间小于当天零点 并 未完成 刷新开始时间,状态更新为完成
|
||||
// 开始时间大于当天零点 并 进行中 1状态更新为完成,2更新完成时间
|
||||
// if (beginTime.compareTo(currenDay)>=0 && item.getCompletionState().equals(TaskCompletionState.DOING.getCode())){
|
||||
// UpdateWrapper<TtTaskDoing> updateWrapper = new UpdateWrapper<TtTaskDoing>();
|
||||
// updateWrapper
|
||||
// .eq("id",item.getTaskDoingId())
|
||||
// .set("completion_state",TaskCompletionState.COMPLETION.getCode())
|
||||
// .set("compete_time",new Timestamp(System.currentTimeMillis()));
|
||||
// ttTaskDoingService.update();
|
||||
// }
|
||||
// 开始时间大于当天零点 并 已完成 不处理
|
||||
|
||||
}else if (item.getType().equals(TaskType.WEEK.getCode())){
|
||||
|
||||
}else if (item.getType().equals(TaskType.MONTH.getCode())){
|
||||
|
||||
}else {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// 合并所有正在进行的任务
|
||||
result.addAll(collect);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//筛选可接的新任务
|
||||
private List<TtTask> filterTask(List<TtTask> allTask,List<TtTaskDoing> ownTask) {
|
||||
|
||||
ArrayList<TtTask> result = new ArrayList<>();
|
||||
|
||||
// 条件过滤
|
||||
for (TtTask task:allTask){
|
||||
|
||||
String condition = task.getTaskCondition();
|
||||
if (StringUtils.isBlank(condition)||condition.equals("{}")){
|
||||
result.add(task);
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject conditionObj = JSON.parseObject(condition);
|
||||
if (conditionObj.get("key1").equals("key1")){
|
||||
System.out.println("key1");
|
||||
}else if (conditionObj.get("key2").equals("key2")){
|
||||
System.out.println("key2");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 筛选可接的新任务
|
||||
ArrayList<TtTask> canDoingTasks = new ArrayList<>();
|
||||
for (TtTask task:result){
|
||||
|
||||
TtTaskDoing flag = null;
|
||||
for (TtTaskDoing taskDoing:ownTask){
|
||||
if (taskDoing.getTaskId().equals(task.getId())){
|
||||
flag = taskDoing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ObjectUtil.isEmpty(flag)){
|
||||
canDoingTasks.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
return canDoingTasks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.ruoyi.task.utils;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.ruoyi.admin.service.TtUserCreditsRecordsService;
|
||||
import com.ruoyi.domain.entity.recorde.TtUserCreditsRecords;
|
||||
import com.ruoyi.domain.task.TtTask;
|
||||
import com.ruoyi.domain.task.TtTaskDoing;
|
||||
import com.ruoyi.domain.task.constant.TaskCompletionState;
|
||||
import com.ruoyi.task.mapper.TtTaskMapper;
|
||||
import com.ruoyi.task.service.TtTaskDoingService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class TasKUtil {
|
||||
|
||||
@Autowired
|
||||
private TtTaskMapper ttTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private TtTaskDoingService ttTaskDoingService;
|
||||
|
||||
@Autowired
|
||||
private TtUserCreditsRecordsService ttUserCreditsRecordsService;
|
||||
|
||||
// 任务更新
|
||||
public boolean updateTask(TtTaskDoing ttTaskDoing) {
|
||||
|
||||
Integer taskId = ttTaskDoing.getTaskId();
|
||||
|
||||
LambdaQueryWrapper<TtTask> ttTaskQuery = new LambdaQueryWrapper<>();
|
||||
LambdaUpdateWrapper<TtTaskDoing> ttTaskDoingUpdate = new LambdaUpdateWrapper<>();
|
||||
if (taskId.equals(1)) {
|
||||
|
||||
// {首次下载奖励}
|
||||
ttTaskQuery
|
||||
.eq(TtTask::getId, 1);
|
||||
TtTask task1 = ttTaskMapper.selectOne(ttTaskQuery);
|
||||
|
||||
ttTaskDoingUpdate
|
||||
.eq(TtTaskDoing::getUserId, ttTaskDoing.getUserId())
|
||||
.eq(TtTaskDoing::getTaskId, 1)
|
||||
.set(TtTaskDoing::getProgress, task1.getTargetValue())
|
||||
.set(TtTaskDoing::getCompletionState, TaskCompletionState.COMPLETION.getCode())
|
||||
.set(TtTaskDoing::getCompeteTime, new Timestamp(System.currentTimeMillis()));
|
||||
ttTaskDoingService.update(ttTaskDoingUpdate);
|
||||
|
||||
return true;
|
||||
} else if (taskId.equals(2)) {
|
||||
|
||||
// {每日流水奖励}
|
||||
ttTaskQuery.clear();
|
||||
ttTaskQuery
|
||||
.eq(TtTask::getId, 2);
|
||||
TtTask task2 = ttTaskMapper.selectOne(ttTaskQuery);
|
||||
|
||||
Calendar nowDay = Calendar.getInstance();
|
||||
nowDay.set(Calendar.HOUR_OF_DAY, 0);
|
||||
nowDay.set(Calendar.MINUTE, 0);
|
||||
nowDay.set(Calendar.SECOND, 0);
|
||||
nowDay.set(Calendar.MILLISECOND, 0);
|
||||
Timestamp toDay = new Timestamp(nowDay.getTimeInMillis());
|
||||
|
||||
LambdaQueryWrapper<TtUserCreditsRecords> creditsRecordsQuery = new LambdaQueryWrapper<>();
|
||||
creditsRecordsQuery
|
||||
.eq(TtUserCreditsRecords::getUserId, ttTaskDoing.getUserId())
|
||||
.ge(TtUserCreditsRecords::getCreateTime, toDay);
|
||||
List<TtUserCreditsRecords> todayRecords = ttUserCreditsRecordsService.list(creditsRecordsQuery);
|
||||
|
||||
// 累计今日流水
|
||||
BigDecimal credits = new BigDecimal("0");
|
||||
todayRecords.stream().forEach((item) -> {
|
||||
if (item.getCredits().compareTo(BigDecimal.ZERO) >= 0) {
|
||||
credits.add(item.getCredits());
|
||||
}
|
||||
});
|
||||
|
||||
ttTaskDoingUpdate.clear();
|
||||
ttTaskDoingUpdate
|
||||
.eq(TtTaskDoing::getUserId, ttTaskDoing.getUserId())
|
||||
.eq(TtTaskDoing::getTaskId, 2)
|
||||
.set(TtTaskDoing::getProgress, credits.intValue())
|
||||
.set(credits.compareTo(new BigDecimal(task2.getTargetValue())) >= 0, TtTaskDoing::getCompletionState, TaskCompletionState.COMPLETION.getCode())
|
||||
.set(credits.compareTo(new BigDecimal(task2.getTargetValue())) >= 0, TtTaskDoing::getCompeteTime, new Timestamp(System.currentTimeMillis()));
|
||||
ttTaskDoingService.update(ttTaskDoingUpdate);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.ruoyi.task.utils.task;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.task.DTO.pWelfareMQData;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
//
|
||||
|
||||
@AllArgsConstructor
|
||||
public class PromotionWelfareTask implements Runnable {
|
||||
|
||||
private TtUserService userService;
|
||||
|
||||
private TtUser user;
|
||||
|
||||
// 消费金额
|
||||
private BigDecimal price;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// 统计推广福利
|
||||
pWelfareMQData msgDate = pWelfareMQData.builder()
|
||||
.userId(user.getUserId())
|
||||
.account(price)
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
|
||||
TtUser user = userService.getById(msgDate.getUserId());
|
||||
TtUser parent = userService.getById(user.getParentId());
|
||||
if (ObjectUtil.isNotEmpty(parent)){
|
||||
BigDecimal credits = null;
|
||||
if ("01".equals(parent.getUserType())) {
|
||||
credits = msgDate.getAccount().multiply(new BigDecimal("0.045"));
|
||||
} else {
|
||||
credits = msgDate.getAccount().multiply(new BigDecimal("0.01"));
|
||||
}
|
||||
LambdaUpdateWrapper<TtUser> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper
|
||||
.eq(TtUser::getUserId,parent.getUserId())
|
||||
.set(TtUser::getAccountCredits,parent.getAccountCredits().add(credits));
|
||||
// .set(TtUser::getAccountAmount,parent.getAccountAmount().add(credits));
|
||||
userService.update(wrapper);
|
||||
// userService.insertUserCreditsRecords(parent.getUserId(),
|
||||
// TtAccountRecordType.INPUT,
|
||||
// TtAccountRecordSource.P_WELFARE,
|
||||
// credits,
|
||||
// parent.getAccountCredits().add(credits),
|
||||
// user.getUserId(),
|
||||
// user.getNickName(),
|
||||
// msgDate.getAccount());
|
||||
userService.insertUserAmountRecords(parent.getUserId(),
|
||||
TtAccountRecordType.INPUT,
|
||||
TtAccountRecordSource.P_WELFARE,
|
||||
credits,
|
||||
parent.getAccountCredits().add(credits),
|
||||
user.getUserId(),
|
||||
user.getNickName(),
|
||||
msgDate.getAccount());
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?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.ruoyi.task.mapper.TtTaskDoingMapper">
|
||||
|
||||
<!-- <select id="byId" resultType="com.ruoyi.domain.task.TtTask">-->
|
||||
<!-- SELECT-->
|
||||
<!-- id,-->
|
||||
<!-- `describe` AS des,-->
|
||||
<!-- `name` AS na,-->
|
||||
<!-- state,type,target_type,target_value,award_type,award_value-->
|
||||
<!-- FROM-->
|
||||
<!-- tt_task-->
|
||||
<!-- WHERE-->
|
||||
<!-- id=#{id}-->
|
||||
<!-- </select>-->
|
||||
<select id="isOwnUser" resultType="com.ruoyi.domain.task.TtTaskDoing">
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
tt_task_doing
|
||||
WHERE
|
||||
user_id = #{userId}
|
||||
and
|
||||
task_id = #{tid}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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.ruoyi.task.mapper.TtTaskMapper">
|
||||
|
||||
<select id="byId" resultType="com.ruoyi.domain.task.TtTask">
|
||||
SELECT
|
||||
id,
|
||||
`task_describe`,
|
||||
`name`,
|
||||
state,type,target_type,target_value,award_type,award_value
|
||||
FROM
|
||||
tt_task
|
||||
WHERE
|
||||
id=#{id}
|
||||
</select>
|
||||
|
||||
<select id="listByState" resultType="com.ruoyi.domain.task.TtTask">
|
||||
SELECT
|
||||
id,
|
||||
`task_describe`,
|
||||
`name`,
|
||||
state,
|
||||
type,
|
||||
target_type,
|
||||
target_value,
|
||||
award_type,
|
||||
award_value
|
||||
FROM
|
||||
tt_task
|
||||
WHERE
|
||||
state=#{state}
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?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.ruoyi.task.mapper.TtUserBlendErcashArchiveMapper">
|
||||
|
||||
<!-- 获取待归档的 id 列表。
|
||||
排除 source=30(VIP等级达标赠送):该流水是 VIP 奖励的领取判重凭证,
|
||||
归档后主表查不到会导致玩家重复领取,必须永久保留在主表。 -->
|
||||
<select id="selectArchiveIds" resultType="java.lang.Long">
|
||||
SELECT id FROM tt_user_blend_ercash
|
||||
WHERE create_time < #{cutoffTime}
|
||||
AND (source IS NULL OR source != 30)
|
||||
ORDER BY id
|
||||
LIMIT #{batchSize}
|
||||
</select>
|
||||
|
||||
<!-- 从主表批量插入到归档表。
|
||||
🔥 新增:包含 anchor_id 和 business_id 快照字段
|
||||
ON DUPLICATE KEY UPDATE id=自身:仅对重复主键做 no-op(自愈上一轮"已插未删"的残留),
|
||||
而截断/空值等真实错误仍会抛出→整批事务回滚,避免 INSERT IGNORE 吞错导致"删了主表却没归档"的静默丢数据。 -->
|
||||
<insert id="insertFromMain">
|
||||
INSERT INTO tt_user_blend_ercash_archive
|
||||
(id, user_id, anchor_id, business_id, amount, final_amount, credits, final_credits, total, source, type, remark, create_time, update_time, archive_time)
|
||||
SELECT id, user_id, anchor_id, business_id, amount, final_amount, credits, final_credits, total, source, type, remark, create_time, update_time, NOW()
|
||||
FROM tt_user_blend_ercash
|
||||
WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
ON DUPLICATE KEY UPDATE id = tt_user_blend_ercash_archive.id
|
||||
</insert>
|
||||
|
||||
<!-- 从主表删除已归档记录 -->
|
||||
<delete id="deleteFromMain">
|
||||
DELETE FROM tt_user_blend_ercash
|
||||
WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?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.ruoyi.task.mapper.TtUserSourceReportTaskMapper">
|
||||
|
||||
<!-- 增量模式:统计昨日各 user+source 的 delta(ABS(total) 之和) -->
|
||||
<select id="selectYesterdayDelta" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
e.user_id AS userId,
|
||||
u.parent_id AS parentId,
|
||||
e.source AS source,
|
||||
SUM(ABS(e.total)) AS delta
|
||||
FROM tt_user_blend_ercash e
|
||||
INNER JOIN tt_user u ON e.user_id = u.user_id
|
||||
WHERE e.create_time >= #{yesterdayStart}
|
||||
AND e.create_time < #{todayStart}
|
||||
GROUP BY e.user_id, e.source
|
||||
</select>
|
||||
|
||||
<!-- 全量模式:统计 todayStart 之前所有流水的累计值 -->
|
||||
<select id="selectFullCumulative" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
e.user_id AS userId,
|
||||
u.parent_id AS parentId,
|
||||
e.source AS source,
|
||||
SUM(ABS(e.total)) AS total
|
||||
FROM tt_user_blend_ercash e
|
||||
INNER JOIN tt_user u ON e.user_id = u.user_id
|
||||
WHERE e.create_time < #{todayStart}
|
||||
GROUP BY e.user_id, e.source
|
||||
</select>
|
||||
|
||||
<!-- 批量 UPSERT -->
|
||||
<insert id="batchUpsert">
|
||||
INSERT INTO tt_user_source_report (user_id, parent_id, source, total, report_date)
|
||||
VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.userId}, #{item.parentId}, #{item.source}, #{item.total}, #{reportDate})
|
||||
</foreach>
|
||||
ON DUPLICATE KEY UPDATE
|
||||
total = VALUES(total),
|
||||
parent_id = VALUES(parent_id),
|
||||
update_time = NOW()
|
||||
</insert>
|
||||
|
||||
<!-- 幂等检查 -->
|
||||
<select id="countByReportDate" resultType="int">
|
||||
SELECT COUNT(*) FROM tt_user_source_report WHERE report_date = #{reportDate}
|
||||
</select>
|
||||
|
||||
<!-- 查询指定日期报表(用于增量递推读取昨日数据) -->
|
||||
<select id="selectByReportDate" resultType="com.ruoyi.domain.entity.TtUserSourceReport">
|
||||
SELECT id, user_id, parent_id, source, total, report_date, create_time, update_time
|
||||
FROM tt_user_source_report
|
||||
WHERE report_date = #{reportDate}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user