新增spins task

This commit is contained in:
tianfeng 2025-10-21 12:20:36 +08:00
parent da9819085f
commit 83f79ce3d8
26 changed files with 1575 additions and 10 deletions

View File

@ -34,7 +34,7 @@ public interface LotteryTicketClientApi {
* 增加用户抽奖券.
*/
@PostMapping("/addTicket")
ResultResponse<Boolean> addTicket(
ResultResponse<Long> addTicket(
@RequestParam("userId") Long userId,
@RequestParam("count") Integer count,
@RequestParam(value = "source", required = false) String source,

View File

@ -0,0 +1,55 @@
package com.red.circle.other.adapter.app.task;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.other.app.dto.clientobject.SpinsTaskCO;
import com.red.circle.other.app.dto.clientobject.SpinsTaskRewardCO;
import com.red.circle.other.app.dto.cmd.SpinsTaskReceiveRewardCmd;
import com.red.circle.other.app.service.SpinsTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Spins任务接口
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@RestController
@RequestMapping("/spins/task")
@RequiredArgsConstructor
public class SpinsTaskRestController {
private final SpinsTaskService spinsTaskService;
/**
* 获取用户任务列表
*
* @param cmd 用户ID
* @return 任务列表
*/
@GetMapping("/list")
public List<SpinsTaskCO> getTaskList(AppExtCommand cmd) {
Long userId = cmd.getReqUserId();
log.info("获取用户任务列表, userId={}", userId);
List<SpinsTaskCO> taskList = spinsTaskService.getUserTaskList(userId);
return taskList;
}
/**
* 领取任务奖励
*
* @param cmd 领取奖励命令
* @return 奖励信息
*/
@PostMapping("/receive/reward")
public SpinsTaskRewardCO receiveReward(@Validated @RequestBody SpinsTaskReceiveRewardCmd cmd) {
log.info("领取任务奖励, userId={}, taskCode={}", cmd.getUserId(), cmd.getTaskCode());
SpinsTaskRewardCO reward = spinsTaskService.receiveTaskReward(cmd);
return reward;
}
}

View File

@ -0,0 +1,153 @@
package com.red.circle.other.app.command;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.infra.database.rds.dao.task.SpinsTaskConfigDAO;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskProgressDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 增加任务进度执行器
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SpinsTaskProgressIncrementExe {
private final SpinsTaskConfigDAO spinsTaskConfigDAO;
private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO;
/**
* 执行增加任务进度
*
* @param userId 用户ID
* @param taskCode 任务编码
* @param incrementValue 增加值
*/
@Transactional(rollbackFor = Exception.class)
public void execute(Long userId, String taskCode, Integer incrementValue) {
// 1. 查询任务配置
List<SpinsTaskConfig> taskConfigs = spinsTaskConfigDAO.selectList(
new LambdaQueryWrapper<SpinsTaskConfig>()
.eq(SpinsTaskConfig::getTaskCode, taskCode)
.eq(SpinsTaskConfig::getStatus, 1)
);
if (taskConfigs.isEmpty()) {
log.warn("任务配置不存在或已下线, taskCode={}", taskCode);
return;
}
SpinsTaskConfig taskConfig = taskConfigs.get(0);
// 2. 获取或创建用户任务进度
String cycleKey = getCurrentCycleKey(taskConfig.getCycleType());
SpinsUserTaskProgress progress = getOrCreateProgress(userId, taskConfig, cycleKey);
// 3. 增加进度值
int newValue = progress.getCurrentValue() + incrementValue;
progress.setCurrentValue(newValue);
// 4. 计算进度百分比
BigDecimal progressRate = calculateProgressRate(newValue, taskConfig.getTargetValue());
progress.setProgressRate(progressRate);
// 5. 检查是否完成
if (newValue >= taskConfig.getTargetValue() && progress.getTaskStatus() == 0) {
progress.setTaskStatus(1);
progress.setCompleteTime(new Timestamp(System.currentTimeMillis()));
progress.setCompleteTimes(progress.getCompleteTimes() + 1);
log.info("任务完成, userId={}, taskCode={}, newValue={}", userId, taskCode, newValue);
}
progress.setUpdateTime(new Timestamp(System.currentTimeMillis()));
spinsUserTaskProgressDAO.updateById(progress);
log.info("任务进度增加成功, userId={}, taskCode={}, incrementValue={}, newValue={}",
userId, taskCode, incrementValue, newValue);
}
/**
* 获取或创建用户任务进度
*/
private SpinsUserTaskProgress getOrCreateProgress(Long userId, SpinsTaskConfig taskConfig, String cycleKey) {
List<SpinsUserTaskProgress> progressList = spinsUserTaskProgressDAO.selectList(
new LambdaQueryWrapper<SpinsUserTaskProgress>()
.eq(SpinsUserTaskProgress::getUserId, userId)
.eq(SpinsUserTaskProgress::getTaskCode, taskConfig.getTaskCode())
.eq(SpinsUserTaskProgress::getCycleKey, cycleKey)
);
SpinsUserTaskProgress progress = null;
if (!progressList.isEmpty()) {
progress = progressList.get(0);
}
if (progress == null) {
progress = new SpinsUserTaskProgress();
progress.setUserId(userId);
progress.setTaskId(taskConfig.getId());
progress.setTaskCode(taskConfig.getTaskCode());
progress.setCurrentValue(0);
progress.setTargetValue(taskConfig.getTargetValue());
progress.setProgressRate(BigDecimal.ZERO);
progress.setTaskStatus(0);
progress.setCycleType(taskConfig.getCycleType());
progress.setCycleDate(LocalDate.now());
progress.setCycleKey(cycleKey);
progress.setCompleteTimes(0);
progress.setRewardType(taskConfig.getRewardType());
progress.setRewardValue(taskConfig.getRewardValue());
progress.setCreateTime(new Timestamp(System.currentTimeMillis()));
progress.setUpdateTime(new Timestamp(System.currentTimeMillis()));
spinsUserTaskProgressDAO.insert(progress);
}
return progress;
}
/**
* 计算进度百分比
*/
private BigDecimal calculateProgressRate(Integer currentValue, Integer targetValue) {
if (targetValue == null || targetValue == 0) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(currentValue)
.divide(BigDecimal.valueOf(targetValue), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 获取当前周期key
*/
private String getCurrentCycleKey(Integer cycleType) {
LocalDate now = LocalDate.now();
if (cycleType == 1) {
// 每日任务格式 yyyyMMdd
return now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
} else if (cycleType == 2) {
// 每周任务格式 yyyyWw例如2025W03
int weekOfYear = now.getDayOfYear() / 7 + 1;
return now.getYear() + "W" + String.format("%02d", weekOfYear);
} else {
// 一次性任务固定标识
return "ONCE";
}
}
}

View File

@ -0,0 +1,111 @@
package com.red.circle.other.app.command;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.infra.database.rds.dao.task.SpinsTaskConfigDAO;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskProgressDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 初始化任务进度执行器
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SpinsTaskProgressInitExe {
private final SpinsTaskConfigDAO spinsTaskConfigDAO;
private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO;
/**
* 执行初始化用户任务进度
*
* @param userId 用户ID
* @param taskCode 任务编码
*/
@Transactional(rollbackFor = Exception.class)
public void execute(Long userId, String taskCode) {
// 1. 查询任务配置
List<SpinsTaskConfig> taskConfigs = spinsTaskConfigDAO.selectList(
new LambdaQueryWrapper<SpinsTaskConfig>()
.eq(SpinsTaskConfig::getTaskCode, taskCode)
.eq(SpinsTaskConfig::getStatus, 1)
);
if (taskConfigs.isEmpty()) {
log.warn("任务配置不存在或已下线, taskCode={}", taskCode);
return;
}
SpinsTaskConfig taskConfig = taskConfigs.get(0);
// 2. 获取当前周期key
String cycleKey = getCurrentCycleKey(taskConfig.getCycleType());
// 3. 检查是否已存在
List<SpinsUserTaskProgress> existProgressList = spinsUserTaskProgressDAO.selectList(
new LambdaQueryWrapper<SpinsUserTaskProgress>()
.eq(SpinsUserTaskProgress::getUserId, userId)
.eq(SpinsUserTaskProgress::getTaskCode, taskCode)
.eq(SpinsUserTaskProgress::getCycleKey, cycleKey)
);
if (!existProgressList.isEmpty()) {
log.info("任务进度已存在, userId={}, taskCode={}", userId, taskCode);
return;
}
// 4. 创建新的任务进度
SpinsUserTaskProgress progress = new SpinsUserTaskProgress();
progress.setUserId(userId);
progress.setTaskId(taskConfig.getId());
progress.setTaskCode(taskConfig.getTaskCode());
progress.setCurrentValue(0);
progress.setTargetValue(taskConfig.getTargetValue());
progress.setProgressRate(BigDecimal.ZERO);
progress.setTaskStatus(0);
progress.setCycleType(taskConfig.getCycleType());
progress.setCycleDate(LocalDate.now());
progress.setCycleKey(cycleKey);
progress.setCompleteTimes(0);
progress.setRewardType(taskConfig.getRewardType());
progress.setRewardValue(taskConfig.getRewardValue());
progress.setCreateTime(new Timestamp(System.currentTimeMillis()));
progress.setUpdateTime(new Timestamp(System.currentTimeMillis()));
spinsUserTaskProgressDAO.insert(progress);
log.info("初始化用户任务进度成功, userId={}, taskCode={}", userId, taskCode);
}
/**
* 获取当前周期key
*/
private String getCurrentCycleKey(Integer cycleType) {
LocalDate now = LocalDate.now();
if (cycleType == 1) {
// 每日任务格式 yyyyMMdd
return now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
} else if (cycleType == 2) {
// 每周任务格式 yyyyWw例如2025W03
int weekOfYear = now.getDayOfYear() / 7 + 1;
return now.getYear() + "W" + String.format("%02d", weekOfYear);
} else {
// 一次性任务固定标识
return "ONCE";
}
}
}

View File

@ -0,0 +1,54 @@
package com.red.circle.other.app.command;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskProgressDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 重置任务进度执行器
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SpinsTaskProgressResetExe {
private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO;
/**
* 执行重置用户每日任务
*
* @param userId 用户ID
*/
@Transactional(rollbackFor = Exception.class)
public void execute(Long userId) {
// 获取昨天的周期key
String yesterdayCycleKey = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
// 查询昨天的每日任务进度
List<SpinsUserTaskProgress> progressList = spinsUserTaskProgressDAO.selectList(
new LambdaQueryWrapper<SpinsUserTaskProgress>()
.eq(SpinsUserTaskProgress::getUserId, userId)
.eq(SpinsUserTaskProgress::getCycleType, 1) // 每日任务
.eq(SpinsUserTaskProgress::getCycleKey, yesterdayCycleKey)
);
if (progressList.isEmpty()) {
log.info("没有需要重置的每日任务, userId={}", userId);
return;
}
log.info("重置用户每日任务成功, userId={}, 重置任务数={}", userId, progressList.size());
}
}

View File

@ -0,0 +1,155 @@
package com.red.circle.other.app.command;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.app.dto.cmd.SpinsTaskProgressUpdateCmd;
import com.red.circle.other.infra.database.rds.dao.task.SpinsTaskConfigDAO;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskProgressDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 更新任务进度执行器
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SpinsTaskProgressUpdateExe {
private final SpinsTaskConfigDAO spinsTaskConfigDAO;
private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO;
/**
* 执行更新任务进度
*
* @param cmd 更新命令
*/
@Transactional(rollbackFor = Exception.class)
public void execute(SpinsTaskProgressUpdateCmd cmd) {
Long userId = cmd.getUserId();
String taskCode = cmd.getTaskCode();
Integer progressValue = cmd.getProgressValue();
// 1. 查询任务配置
List<SpinsTaskConfig> taskConfigs = spinsTaskConfigDAO.selectList(
new LambdaQueryWrapper<SpinsTaskConfig>()
.eq(SpinsTaskConfig::getTaskCode, taskCode)
.eq(SpinsTaskConfig::getStatus, 1)
);
if (taskConfigs.isEmpty()) {
log.warn("任务配置不存在或已下线, taskCode={}", taskCode);
return;
}
SpinsTaskConfig taskConfig = taskConfigs.get(0);
// 2. 获取或创建用户任务进度
String cycleKey = getCurrentCycleKey(taskConfig.getCycleType());
SpinsUserTaskProgress progress = getOrCreateProgress(userId, taskConfig, cycleKey);
// 3. 更新进度值
progress.setCurrentValue(progressValue);
// 4. 计算进度百分比
BigDecimal progressRate = calculateProgressRate(progressValue, taskConfig.getTargetValue());
progress.setProgressRate(progressRate);
// 5. 检查是否完成
if (progressValue >= taskConfig.getTargetValue() && progress.getTaskStatus() == 0) {
progress.setTaskStatus(1);
progress.setCompleteTime(new Timestamp(System.currentTimeMillis()));
progress.setCompleteTimes(progress.getCompleteTimes() + 1);
log.info("任务完成, userId={}, taskCode={}, progressValue={}", userId, taskCode, progressValue);
}
progress.setUpdateTime(new Timestamp(System.currentTimeMillis()));
spinsUserTaskProgressDAO.updateById(progress);
log.info("任务进度更新成功, userId={}, taskCode={}, progressValue={}, progressRate={}",
userId, taskCode, progressValue, progressRate);
}
/**
* 获取或创建用户任务进度
*/
private SpinsUserTaskProgress getOrCreateProgress(Long userId, SpinsTaskConfig taskConfig, String cycleKey) {
List<SpinsUserTaskProgress> progressList = spinsUserTaskProgressDAO.selectList(
new LambdaQueryWrapper<SpinsUserTaskProgress>()
.eq(SpinsUserTaskProgress::getUserId, userId)
.eq(SpinsUserTaskProgress::getTaskCode, taskConfig.getTaskCode())
.eq(SpinsUserTaskProgress::getCycleKey, cycleKey)
);
SpinsUserTaskProgress progress = null;
if (!progressList.isEmpty()) {
progress = progressList.get(0);
}
if (progress == null) {
progress = new SpinsUserTaskProgress();
progress.setUserId(userId);
progress.setTaskId(taskConfig.getId());
progress.setTaskCode(taskConfig.getTaskCode());
progress.setCurrentValue(0);
progress.setTargetValue(taskConfig.getTargetValue());
progress.setProgressRate(BigDecimal.ZERO);
progress.setTaskStatus(0);
progress.setCycleType(taskConfig.getCycleType());
progress.setCycleDate(LocalDate.now());
progress.setCycleKey(cycleKey);
progress.setCompleteTimes(0);
progress.setRewardType(taskConfig.getRewardType());
progress.setRewardValue(taskConfig.getRewardValue());
progress.setCreateTime(new Timestamp(System.currentTimeMillis()));
progress.setUpdateTime(new Timestamp(System.currentTimeMillis()));
spinsUserTaskProgressDAO.insert(progress);
}
return progress;
}
/**
* 计算进度百分比
*/
private BigDecimal calculateProgressRate(Integer currentValue, Integer targetValue) {
if (targetValue == null || targetValue == 0) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(currentValue)
.divide(BigDecimal.valueOf(targetValue), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 获取当前周期key
*/
private String getCurrentCycleKey(Integer cycleType) {
LocalDate now = LocalDate.now();
if (cycleType == 1) {
// 每日任务格式 yyyyMMdd
return now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
} else if (cycleType == 2) {
// 每周任务格式 yyyyWw例如2025W03
int weekOfYear = now.getDayOfYear() / 7 + 1;
return now.getYear() + "W" + String.format("%02d", weekOfYear);
} else {
// 一次性任务固定标识
return "ONCE";
}
}
}

View File

@ -0,0 +1,121 @@
package com.red.circle.other.app.command;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.app.dto.clientobject.SpinsTaskCO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
import com.red.circle.other.infra.database.rds.dao.task.SpinsTaskConfigDAO;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskProgressDAO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 查询用户任务列表执行器
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SpinsTaskQueryExe {
private final SpinsTaskConfigDAO spinsTaskConfigDAO;
private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO;
/**
* 执行查询用户任务列表
*
* @param userId 用户ID
* @return 任务列表
*/
public List<SpinsTaskCO> execute(Long userId) {
// 1. 查询所有启用的任务配置
List<SpinsTaskConfig> taskConfigs = spinsTaskConfigDAO.selectList(
new LambdaQueryWrapper<SpinsTaskConfig>()
.eq(SpinsTaskConfig::getStatus, 1)
.orderByAsc(SpinsTaskConfig::getSortOrder)
);
if (CollectionUtils.isEmpty(taskConfigs)) {
log.warn("未查询到启用的任务配置");
return new ArrayList<>();
}
// 2. 获取当前周期key
String cycleKey = getCurrentCycleKey();
// 3. 查询用户任务进度
List<String> taskCodes = taskConfigs.stream()
.map(SpinsTaskConfig::getTaskCode)
.collect(Collectors.toList());
List<SpinsUserTaskProgress> progressList = spinsUserTaskProgressDAO.selectList(
new LambdaQueryWrapper<SpinsUserTaskProgress>()
.eq(SpinsUserTaskProgress::getUserId, userId)
.in(SpinsUserTaskProgress::getTaskCode, taskCodes)
.eq(SpinsUserTaskProgress::getCycleKey, cycleKey)
);
Map<String, SpinsUserTaskProgress> progressMap = progressList.stream()
.collect(Collectors.toMap(SpinsUserTaskProgress::getTaskCode, p -> p, (p1, p2) -> p1));
// 4. 组装返回数据
List<SpinsTaskCO> result = new ArrayList<>();
for (SpinsTaskConfig config : taskConfigs) {
SpinsTaskCO taskCO = buildTaskCO(config, progressMap.get(config.getTaskCode()));
result.add(taskCO);
}
return result;
}
/**
* 构建任务CO对象
*/
private SpinsTaskCO buildTaskCO(SpinsTaskConfig config, SpinsUserTaskProgress progress) {
SpinsTaskCO taskCO = new SpinsTaskCO();
taskCO.setTaskId(config.getId());
taskCO.setTaskCode(config.getTaskCode());
taskCO.setTaskName(config.getTaskName());
taskCO.setTaskType(config.getTaskType());
taskCO.setTaskDesc(config.getTaskDesc());
taskCO.setIconUrl(config.getIconUrl());
taskCO.setTargetType(config.getTargetType());
taskCO.setTargetValue(config.getTargetValue());
taskCO.setTargetUnit(config.getTargetUnit());
taskCO.setRewardType(config.getRewardType());
taskCO.setRewardValue(config.getRewardValue());
if (progress != null) {
taskCO.setCurrentValue(progress.getCurrentValue());
taskCO.setTaskStatus(progress.getTaskStatus());
taskCO.setProgressRate(progress.getProgressRate());
taskCO.setCompleteTime(progress.getCompleteTime());
} else {
// 未开始的任务
taskCO.setCurrentValue(0);
taskCO.setTaskStatus(0);
taskCO.setProgressRate(BigDecimal.ZERO);
}
return taskCO;
}
/**
* 获取当前周期key每日任务格式yyyyMMdd
*/
private String getCurrentCycleKey() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
}

View File

@ -0,0 +1,145 @@
package com.red.circle.other.app.command;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.app.dto.clientobject.SpinsTaskRewardCO;
import com.red.circle.other.app.dto.cmd.SpinsTaskReceiveRewardCmd;
import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskRecord;
import com.red.circle.other.infra.database.rds.dao.task.SpinsTaskConfigDAO;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskProgressDAO;
import com.red.circle.other.infra.database.rds.dao.task.SpinsUserTaskRecordDAO;
import com.red.circle.other.inner.endpoint.activity.LotteryTicketClient;
import com.red.circle.tool.core.date.TimestampUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* 领取任务奖励执行器
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SpinsTaskReceiveRewardExe {
private final SpinsTaskConfigDAO spinsTaskConfigDAO;
private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO;
private final SpinsUserTaskRecordDAO spinsUserTaskRecordDAO;
private final LotteryTicketClient lotteryTicketClient;
/**
* 执行领取任务奖励
*
* @param cmd 领取奖励命令
* @return 奖励信息
*/
@Transactional(rollbackFor = Exception.class)
public SpinsTaskRewardCO execute(SpinsTaskReceiveRewardCmd cmd) {
Long userId = cmd.getUserId();
String taskCode = cmd.getTaskCode();
// 1. 查询任务配置
List<SpinsTaskConfig> spinsTaskConfigs = spinsTaskConfigDAO.selectList(
new LambdaQueryWrapper<SpinsTaskConfig>()
.eq(SpinsTaskConfig::getTaskCode, taskCode)
.eq(SpinsTaskConfig::getStatus, 1)
);
if (spinsTaskConfigs.isEmpty()) {
throw new RuntimeException("任务不存在或已下线");
}
SpinsTaskConfig taskConfig = spinsTaskConfigs.get(0);
// 2. 查询用户任务进度
String cycleKey = getCurrentCycleKey();
SpinsUserTaskProgress progress = spinsUserTaskProgressDAO.selectById(
new LambdaQueryWrapper<SpinsUserTaskProgress>()
.eq(SpinsUserTaskProgress::getUserId, userId)
.eq(SpinsUserTaskProgress::getTaskCode, taskCode)
.eq(SpinsUserTaskProgress::getCycleKey, cycleKey)
);
if (progress == null) {
throw new RuntimeException("任务未开始");
}
// 3. 校验任务状态
if (progress.getTaskStatus() == 0) {
throw new RuntimeException("任务未完成,无法领取奖励");
}
if (progress.getTaskStatus() == 2) {
throw new RuntimeException("奖励已领取,请勿重复领取");
}
// 4. 发放抽奖券
List<Long> ticketIds = new ArrayList<>();
try {
for (int i = 0; i < taskConfig.getRewardValue(); i++) {
Long ticketId = lotteryTicketClient.addTicket(
userId,
1,
"SPINS_TASK",
taskCode
).getBody();
ticketIds.add(ticketId);
}
} catch (Exception e) {
log.error("发放抽奖券失败, userId={}, taskCode={}", userId, taskCode, e);
throw new RuntimeException("发放奖励失败,请稍后重试");
}
// 5. 更新任务进度状态
progress.setTaskStatus(2);
progress.setReceiveTime(TimestampUtils.now());
progress.setLotteryTicketIds(String.join(",", ticketIds.stream().map(String::valueOf).toArray(String[]::new)));
progress.setUpdateTime(TimestampUtils.now());
spinsUserTaskProgressDAO.updateById(progress);
// 6. 记录任务完成记录
SpinsUserTaskRecord record = new SpinsUserTaskRecord();
record.setUserId(userId);
record.setTaskId(taskConfig.getId());
record.setTaskCode(taskCode);
record.setTaskName(taskConfig.getTaskName());
record.setCompleteValue(progress.getCurrentValue());
record.setCompleteTime(progress.getCompleteTime());
record.setCycleKey(cycleKey);
record.setRewardType(taskConfig.getRewardType());
record.setRewardValue(taskConfig.getRewardValue());
record.setLotteryActivityId(taskConfig.getLotteryActivityId());
record.setLotteryTicketIds(progress.getLotteryTicketIds());
record.setReceiveStatus(1);
record.setCreateTime(TimestampUtils.now());
spinsUserTaskRecordDAO.insert(record);
// 7. 组装返回结果
SpinsTaskRewardCO rewardCO = new SpinsTaskRewardCO();
rewardCO.setTaskCode(taskCode);
rewardCO.setTaskName(taskConfig.getTaskName());
rewardCO.setRewardType(taskConfig.getRewardType());
rewardCO.setRewardValue(taskConfig.getRewardValue());
rewardCO.setLotteryTicketIds(ticketIds);
rewardCO.setReceiveTime(TimestampUtils.now());
log.info("任务奖励领取成功, userId={}, taskCode={}, ticketIds={}", userId, taskCode, ticketIds);
return rewardCO;
}
/**
* 获取当前周期key每日任务格式yyyyMMdd
*/
private String getCurrentCycleKey() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
}

View File

@ -184,15 +184,11 @@ public class TaskListener implements MessageListener {
if ("10".equals(inc) || Integer.parseInt(inc) >= 10) {
// 更新任务状态的逻辑 直接更新
updateTaskStatus(eventBody);
//判断一下是否有工会裂变任务
// checkMemberActive(eventBody.getUserId(), MemberActiveEnum.TASK_2);
} else {
redisService.increment(redisKey, 1);
}
}
// System.out.println("处理任务-1上麦10分钟: " + eventBody);
// task list 接口单独处理
}
private void handleTask99(TaskApprovalEvent eventBody) {

View File

@ -0,0 +1,42 @@
package com.red.circle.other.app.service.task;
import com.red.circle.other.app.command.SpinsTaskQueryExe;
import com.red.circle.other.app.command.SpinsTaskReceiveRewardExe;
import com.red.circle.other.app.dto.clientobject.SpinsTaskCO;
import com.red.circle.other.app.dto.clientobject.SpinsTaskRewardCO;
import com.red.circle.other.app.dto.cmd.SpinsTaskReceiveRewardCmd;
import com.red.circle.other.app.service.SpinsTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* Spins任务服务实现
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SpinsTaskServiceImpl implements SpinsTaskService {
private final SpinsTaskQueryExe spinsTaskQueryExe;
private final SpinsTaskReceiveRewardExe spinsTaskReceiveRewardExe;
@Override
public List<SpinsTaskCO> getUserTaskList(Long userId) {
log.info("执行获取用户任务列表, userId={}", userId);
return spinsTaskQueryExe.execute(userId);
}
@Override
public SpinsTaskRewardCO receiveTaskReward(SpinsTaskReceiveRewardCmd cmd) {
log.info("执行领取任务奖励, userId={}, taskCode={}", cmd.getUserId(), cmd.getTaskCode());
return spinsTaskReceiveRewardExe.execute(cmd);
}
}

View File

@ -0,0 +1,56 @@
package com.red.circle.other.app.service.task;
import com.red.circle.other.app.command.SpinsTaskProgressInitExe;
import com.red.circle.other.app.command.SpinsTaskProgressIncrementExe;
import com.red.circle.other.app.command.SpinsTaskProgressResetExe;
import com.red.circle.other.app.command.SpinsTaskProgressUpdateExe;
import com.red.circle.other.app.dto.cmd.SpinsTaskProgressUpdateCmd;
import com.red.circle.other.app.service.SpinsUserTaskProgressService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* Spins用户任务进度服务实现
*
* @author system
* @date 2025-01-21
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SpinsUserTaskProgressServiceImpl implements SpinsUserTaskProgressService {
private final SpinsTaskProgressUpdateExe spinsTaskProgressUpdateExe;
private final SpinsTaskProgressIncrementExe spinsTaskProgressIncrementExe;
private final SpinsTaskProgressResetExe spinsTaskProgressResetExe;
private final SpinsTaskProgressInitExe spinsTaskProgressInitExe;
@Override
public void updateTaskProgress(SpinsTaskProgressUpdateCmd cmd) {
log.info("更新用户任务进度, userId={}, taskCode={}, progressValue={}",
cmd.getUserId(), cmd.getTaskCode(), cmd.getProgressValue());
spinsTaskProgressUpdateExe.execute(cmd);
}
@Override
public void incrementTaskProgress(Long userId, String taskCode, Integer incrementValue) {
log.info("增加用户任务进度, userId={}, taskCode={}, incrementValue={}",
userId, taskCode, incrementValue);
spinsTaskProgressIncrementExe.execute(userId, taskCode, incrementValue);
}
@Override
public void resetDailyTasks(Long userId) {
log.info("重置用户每日任务, userId={}", userId);
spinsTaskProgressResetExe.execute(userId);
}
@Override
public void initUserTaskProgress(Long userId, String taskCode) {
log.info("初始化用户任务进度, userId={}, taskCode={}", userId, taskCode);
spinsTaskProgressInitExe.execute(userId, taskCode);
}
}

View File

@ -0,0 +1,94 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Timestamp;
/**
* Spins任务客户端对象
*
* @author system
* @date 2025-01-21
*/
@Data
public class SpinsTaskCO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务ID
*/
private Long taskId;
/**
* 任务编码
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 任务类型1-开麦任务2-送礼任务3-邀请任务
*/
private Integer taskType;
/**
* 任务描述
*/
private String taskDesc;
/**
* 任务图标
*/
private String iconUrl;
/**
* 目标类型MIC_TIME-开麦时长SEND_GIFT-送礼INVITE_USER-邀请用户
*/
private String targetType;
/**
* 目标值
*/
private Integer targetValue;
/**
* 目标单位MINUTES-分钟COUNT-次数HOURS-小时
*/
private String targetUnit;
/**
* 当前进度值
*/
private Integer currentValue;
/**
* 进度百分比
*/
private BigDecimal progressRate;
/**
* 任务状态0-进行中1-已完成2-已领取奖励
*/
private Integer taskStatus;
/**
* 奖励类型1-抽奖券
*/
private Integer rewardType;
/**
* 奖励数量
*/
private Integer rewardValue;
/**
* 完成时间
*/
private Timestamp completeTime;
}

View File

@ -0,0 +1,49 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
/**
* Spins任务奖励客户端对象
*
* @author system
* @date 2025-01-21
*/
@Data
public class SpinsTaskRewardCO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务编码
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 奖励类型1-抽奖券
*/
private Integer rewardType;
/**
* 奖励数量
*/
private Integer rewardValue;
/**
* 抽奖券ID列表
*/
private List<Long> lotteryTicketIds;
/**
* 领取时间
*/
private Timestamp receiveTime;
}

View File

@ -0,0 +1,38 @@
package com.red.circle.other.app.dto.cmd;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* 更新任务进度命令
*
* @author system
* @date 2025-01-21
*/
@Data
public class SpinsTaskProgressUpdateCmd implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "用户ID不能为空")
private Long userId;
@NotBlank(message = "任务编码不能为空")
private String taskCode;
@NotNull(message = "进度值不能为空")
private Integer progressValue;
/**
* 业务类型
*/
private String bizType;
/**
* 业务ID
*/
private String bizId;
}

View File

@ -0,0 +1,25 @@
package com.red.circle.other.app.dto.cmd;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* 领取任务奖励命令
*
* @author system
* @date 2025-01-21
*/
@Data
public class SpinsTaskReceiveRewardCmd implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "用户ID不能为空")
private Long userId;
@NotBlank(message = "任务编码不能为空")
private String taskCode;
}

View File

@ -0,0 +1,32 @@
package com.red.circle.other.app.service;
import com.red.circle.other.app.dto.clientobject.SpinsTaskCO;
import com.red.circle.other.app.dto.clientobject.SpinsTaskRewardCO;
import com.red.circle.other.app.dto.cmd.SpinsTaskReceiveRewardCmd;
import java.util.List;
/**
* Spins任务服务接口
*
* @author system
* @date 2025-01-21
*/
public interface SpinsTaskService {
/**
* 获取用户任务列表
*
* @param userId 用户ID
* @return 任务列表
*/
List<SpinsTaskCO> getUserTaskList(Long userId);
/**
* 领取任务奖励
*
* @param cmd 领取奖励命令
* @return 奖励信息
*/
SpinsTaskRewardCO receiveTaskReward(SpinsTaskReceiveRewardCmd cmd);
}

View File

@ -0,0 +1,43 @@
package com.red.circle.other.app.service;
import com.red.circle.other.app.dto.cmd.SpinsTaskProgressUpdateCmd;
/**
* Spins用户任务进度服务接口
*
* @author system
* @date 2025-01-21
*/
public interface SpinsUserTaskProgressService {
/**
* 更新用户任务进度
*
* @param cmd 更新进度命令
*/
void updateTaskProgress(SpinsTaskProgressUpdateCmd cmd);
/**
* 增加用户任务进度
*
* @param userId 用户ID
* @param taskCode 任务编码
* @param incrementValue 增加值
*/
void incrementTaskProgress(Long userId, String taskCode, Integer incrementValue);
/**
* 重置用户每日任务
*
* @param userId 用户ID
*/
void resetDailyTasks(Long userId);
/**
* 初始化用户任务进度
*
* @param userId 用户ID
* @param taskCode 任务编码
*/
void initUserTaskProgress(Long userId, String taskCode);
}

View File

@ -0,0 +1,13 @@
package com.red.circle.other.infra.database.rds.dao.task;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig;
/**
* Spins任务配置DAO
*
* @author system
* @date 2025-01-21
*/
public interface SpinsTaskConfigDAO extends BaseDAO<SpinsTaskConfig> {
}

View File

@ -0,0 +1,13 @@
package com.red.circle.other.infra.database.rds.dao.task;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress;
/**
* Spins用户任务进度DAO
*
* @author system
* @date 2025-01-21
*/
public interface SpinsUserTaskProgressDAO extends BaseDAO<SpinsUserTaskProgress> {
}

View File

@ -0,0 +1,13 @@
package com.red.circle.other.infra.database.rds.dao.task;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskRecord;
/**
* Spins用户任务完成记录DAO
*
* @author system
* @date 2025-01-21
*/
public interface SpinsUserTaskRecordDAO extends BaseDAO<SpinsUserTaskRecord> {
}

View File

@ -0,0 +1,140 @@
package com.red.circle.other.infra.database.rds.entity.task;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.sql.Timestamp;
/**
* Spins任务配置实体
*
* @author system
* @date 2025-01-21
*/
@Data
@TableName("spins_task_config")
public class SpinsTaskConfig {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 任务编码唯一标识
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 任务类型1-开麦任务2-送礼任务3-邀请任务
*/
private Integer taskType;
/**
* 任务描述
*/
private String taskDesc;
/**
* 任务图标
*/
private String iconUrl;
/**
* 目标类型MIC_TIME-开麦时长SEND_GIFT-送礼INVITE_USER-邀请用户
*/
private String targetType;
/**
* 目标值15分钟=15送3个礼物=3
*/
private Integer targetValue;
/**
* 目标单位MINUTES-分钟COUNT-次数HOURS-小时
*/
private String targetUnit;
/**
* 奖励类型1-抽奖券
*/
private Integer rewardType;
/**
* 奖励数量
*/
private Integer rewardValue;
/**
* 关联的抽奖活动ID
*/
private Long lotteryActivityId;
/**
* 周期类型1-每日2-每周3-一次性
*/
private Integer cycleType;
/**
* 重置时间每日任务
*/
private String resetTime;
/**
* 每周期最大完成次数
*/
private Integer maxTimes;
/**
* 状态0-禁用1-启用
*/
private Integer status;
/**
* 排序
*/
private Integer sortOrder;
/**
* 任务开始时间
*/
private Timestamp startTime;
/**
* 任务结束时间
*/
private Timestamp endTime;
/**
* 扩展配置JSON格式
*/
private String extraConfig;
/**
* 创建时间
*/
private Timestamp createTime;
/**
* 更新时间
*/
private Timestamp updateTime;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
}

View File

@ -0,0 +1,117 @@
package com.red.circle.other.infra.database.rds.entity.task;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
/**
* Spins用户任务进度实体
*
* @author system
* @date 2025-01-21
*/
@Data
@TableName("spins_user_task_progress")
public class SpinsUserTaskProgress {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 任务配置ID
*/
private Long taskId;
/**
* 任务编码
*/
private String taskCode;
/**
* 当前进度值
*/
private Integer currentValue;
/**
* 目标值冗余方便查询
*/
private Integer targetValue;
/**
* 进度百分比
*/
private BigDecimal progressRate;
/**
* 任务状态0-进行中1-已完成2-已领取奖励
*/
private Integer taskStatus;
/**
* 完成时间
*/
private Timestamp completeTime;
/**
* 领取奖励时间
*/
private Timestamp receiveTime;
/**
* 周期类型1-每日2-每周3-一次性
*/
private Integer cycleType;
/**
* 周期日期用于每日任务
*/
private LocalDate cycleDate;
/**
* 周期标识如20250121202503W1
*/
private String cycleKey;
/**
* 当前周期完成次数
*/
private Integer completeTimes;
/**
* 奖励类型
*/
private Integer rewardType;
/**
* 奖励数量
*/
private Integer rewardValue;
/**
* 已发放的抽奖券ID列表逗号分隔
*/
private String lotteryTicketIds;
/**
* 创建时间
*/
private Timestamp createTime;
/**
* 更新时间
*/
private Timestamp updateTime;
}

View File

@ -0,0 +1,100 @@
package com.red.circle.other.infra.database.rds.entity.task;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.sql.Timestamp;
/**
* Spins用户任务完成记录实体
*
* @author system
* @date 2025-01-21
*/
@Data
@TableName("spins_user_task_record")
public class SpinsUserTaskRecord {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 任务配置ID
*/
private Long taskId;
/**
* 任务编码
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 完成值
*/
private Integer completeValue;
/**
* 完成时间
*/
private Timestamp completeTime;
/**
* 周期标识
*/
private String cycleKey;
/**
* 奖励类型
*/
private Integer rewardType;
/**
* 奖励数量
*/
private Integer rewardValue;
/**
* 抽奖活动ID
*/
private Long lotteryActivityId;
/**
* 抽奖券ID列表
*/
private String lotteryTicketIds;
/**
* 领取状态1-已发放2-发放失败
*/
private Integer receiveStatus;
/**
* 业务类型
*/
private String bizType;
/**
* 业务ID
*/
private String bizId;
/**
* 创建时间
*/
private Timestamp createTime;
}

View File

@ -36,8 +36,8 @@ public class LotteryTicketClientEndpoint implements LotteryTicketClientApi {
}
@Override
public ResultResponse<Boolean> addTicket(Long userId, Integer count, String source, String sourceId) {
Boolean result = lotteryTicketClientService.addTicket(userId, count, source, sourceId);
public ResultResponse<Long> addTicket(Long userId, Integer count, String source, String sourceId) {
Long result = lotteryTicketClientService.addTicket(userId, count, source, sourceId);
return ResultResponse.success(result);
}

View File

@ -34,6 +34,6 @@ public interface LotteryTicketClientService {
* @param sourceId 来源ID
* @return 是否成功
*/
Boolean addTicket(Long userId, Integer count, String source, String sourceId);
Long addTicket(Long userId, Integer count, String source, String sourceId);
}

View File

@ -86,7 +86,7 @@ public class LotteryTicketClientServiceImpl implements LotteryTicketClientServic
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addTicket(Long userId, Integer count, String source, String sourceId) {
public Long addTicket(Long userId, Integer count, String source, String sourceId) {
LotteryTicket ticket = new LotteryTicket();
ticket.setUserId(userId);
ticket.setTicketSource(source != null ? source : "SYSTEM");
@ -107,7 +107,7 @@ public class LotteryTicketClientServiceImpl implements LotteryTicketClientServic
ticketRecord.setRemark("系统发放");
lotteryTicketRecordService.save(ticketRecord);
return true;
return ticket.getId();
}
}