新增roomDailyTask功能

This commit is contained in:
tianfeng 2026-03-03 19:32:53 +08:00
parent 78797b1cc4
commit 9fded39a3d
18 changed files with 668 additions and 0 deletions

View File

@ -0,0 +1,46 @@
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.task.RoomDailyTaskCO;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskClaimCO;
import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskClaimCmd;
import com.red.circle.other.app.service.task.RoomDailyTaskService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 每日任务接口
*/
@RestController
@RequestMapping("/room-task")
@RequiredArgsConstructor
public class RoomDailyTaskRestController {
private final RoomDailyTaskService roomDailyTaskService;
/**
* 获取任务列表
* @eo.name 获取每日任务列表
* @eo.url /list
* @eo.method get
*/
@GetMapping("/list")
public List<RoomDailyTaskCO> getTaskList(AppExtCommand cmd) {
return roomDailyTaskService.getTaskList(cmd.getReqUserId());
}
/**
* 领取任务奖励
* @eo.name 领取任务奖励
* @eo.url /claim
* @eo.method post
* @eo.request-type json
*/
@PostMapping("/claim")
public RoomDailyTaskClaimCO claimReward(@RequestBody @Validated RoomDailyTaskClaimCmd cmd) {
return roomDailyTaskService.claimReward(cmd);
}
}

View File

@ -0,0 +1,93 @@
package com.red.circle.other.app.command.task;
import com.red.circle.common.business.exception.BusinessException;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskClaimCO;
import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskClaimCmd;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskClaimRecord;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskProgress;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskTier;
import com.red.circle.other.infra.database.rds.service.RoomDailyTaskDatabaseService;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
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.LocalDateTime;
/**
* 领取任务奖励
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RoomDailyTaskClaimExe {
private final RoomDailyTaskDatabaseService taskDatabaseService;
@Transactional(rollbackFor = Exception.class)
public RoomDailyTaskClaimCO execute(RoomDailyTaskClaimCmd cmd) {
Long userId = cmd.getReqUserId();
String taskCode = cmd.getTaskCode();
Integer tierIndex = cmd.getTierIndex();
LocalDate today = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
String bizNo = userId + ":" + taskCode + ":" + today + ":" + tierIndex;
// 幂等校验
if (taskDatabaseService.existsClaimRecord(bizNo)) {
throw new BusinessException("该档位奖励已领取");
}
RoomDailyTaskConfig config = taskDatabaseService.getConfigByCode(taskCode);
if (config == null) {
throw new BusinessException("任务不存在");
}
RoomDailyTaskTier tier = taskDatabaseService.getTier(taskCode, tierIndex);
if (tier == null) {
throw new BusinessException("任务档位不存在");
}
RoomDailyTaskProgress progress = taskDatabaseService.getProgress(userId, taskCode, today);
if (progress == null || progress.getMaxTierCompleted() < tierIndex) {
throw new BusinessException("该档位任务未完成");
}
if (progress.getMaxTierClaimed() >= tierIndex) {
throw new BusinessException("该档位奖励已领取");
}
// 更新领取状态
progress.setMaxTierClaimed(tierIndex);
progress.setUpdateTime(LocalDateTime.now());
taskDatabaseService.saveOrUpdateProgress(progress);
// 保存领取记录
RoomDailyTaskClaimRecord record = new RoomDailyTaskClaimRecord();
record.setUserId(userId);
record.setTaskId(config.getId());
record.setTaskCode(taskCode);
record.setTierIndex(tierIndex);
record.setRewardType(tier.getRewardType());
record.setRewardValue(tier.getRewardValue());
record.setCycleDate(today);
record.setBizNo(bizNo);
record.setClaimTime(LocalDateTime.now());
record.setCreateTime(LocalDateTime.now());
taskDatabaseService.saveClaimRecord(record);
// TODO: 调用钱包服务发放金币
log.info("任务奖励领取成功, userId={}, taskCode={}, tierIndex={}, rewardValue={}", userId, taskCode, tierIndex, tier.getRewardValue());
RoomDailyTaskClaimCO co = new RoomDailyTaskClaimCO();
co.setTaskCode(taskCode);
co.setTaskName(config.getTaskName());
co.setTierIndex(tierIndex);
co.setRewardValue(tier.getRewardValue());
co.setClaimTime(LocalDateTime.now());
return co;
}
}

View File

@ -0,0 +1,77 @@
package com.red.circle.other.app.command.task;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskCO;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskTierCO;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskProgress;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskTier;
import com.red.circle.other.infra.database.rds.service.RoomDailyTaskDatabaseService;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 查询用户每日任务列表
*/
@Component
@RequiredArgsConstructor
public class RoomDailyTaskQueryExe {
private final RoomDailyTaskDatabaseService taskDatabaseService;
public List<RoomDailyTaskCO> execute(Long userId) {
List<RoomDailyTaskConfig> configs = taskDatabaseService.listEnabledConfigs();
if (CollectionUtils.isEmpty(configs)) {
return new ArrayList<>();
}
List<String> taskCodes = configs.stream().map(RoomDailyTaskConfig::getTaskCode).collect(Collectors.toList());
List<RoomDailyTaskTier> allTiers = taskDatabaseService.listTiersByTaskCodes(taskCodes);
LocalDate today = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
List<RoomDailyTaskProgress> progressList = taskDatabaseService.listProgressByUser(userId, today);
Map<String, List<RoomDailyTaskTier>> tierMap = allTiers.stream()
.collect(Collectors.groupingBy(RoomDailyTaskTier::getTaskCode));
Map<String, RoomDailyTaskProgress> progressMap = progressList.stream()
.collect(Collectors.toMap(RoomDailyTaskProgress::getTaskCode, p -> p, (a, b) -> a));
return configs.stream()
.map(config -> buildTaskCO(config, tierMap.getOrDefault(config.getTaskCode(), new ArrayList<>()), progressMap.get(config.getTaskCode())))
.collect(Collectors.toList());
}
private RoomDailyTaskCO buildTaskCO(RoomDailyTaskConfig config, List<RoomDailyTaskTier> tiers, RoomDailyTaskProgress progress) {
int currentValue = progress != null ? progress.getCurrentValue() : 0;
int maxTierCompleted = progress != null ? progress.getMaxTierCompleted() : 0;
int maxTierClaimed = progress != null ? progress.getMaxTierClaimed() : 0;
List<RoomDailyTaskTierCO> tierCOs = tiers.stream().map(tier -> {
RoomDailyTaskTierCO co = new RoomDailyTaskTierCO();
co.setTierIndex(tier.getTierIndex());
co.setTargetValue(tier.getTargetValue());
co.setTargetUnit(tier.getTargetUnit());
co.setRewardValue(tier.getRewardValue());
co.setIsCompleted(tier.getTierIndex() <= maxTierCompleted);
co.setIsClaimed(tier.getTierIndex() <= maxTierClaimed);
return co;
}).collect(Collectors.toList());
RoomDailyTaskCO co = new RoomDailyTaskCO();
co.setTaskId(config.getId());
co.setTaskCode(config.getTaskCode());
co.setTaskName(config.getTaskName());
co.setTaskCategory(config.getTaskCategory());
co.setIconUrl(config.getIconUrl());
co.setCurrentValue(currentValue);
co.setTiers(tierCOs);
return co;
}
}

View File

@ -0,0 +1,29 @@
package com.red.circle.other.app.service.task;
import com.red.circle.other.app.command.task.RoomDailyTaskClaimExe;
import com.red.circle.other.app.command.task.RoomDailyTaskQueryExe;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskCO;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskClaimCO;
import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskClaimCmd;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class RoomDailyTaskServiceImpl implements RoomDailyTaskService {
private final RoomDailyTaskQueryExe queryExe;
private final RoomDailyTaskClaimExe claimExe;
@Override
public List<RoomDailyTaskCO> getTaskList(Long userId) {
return queryExe.execute(userId);
}
@Override
public RoomDailyTaskClaimCO claimReward(RoomDailyTaskClaimCmd cmd) {
return claimExe.execute(cmd);
}
}

View File

@ -0,0 +1,33 @@
package com.red.circle.other.app.dto.clientobject.task;
import lombok.Data;
import java.util.List;
/**
* 每日任务信息
*/
@Data
public class RoomDailyTaskCO {
/** 任务ID */
private Long taskId;
/** 任务编码 */
private String taskCode;
/** 任务名称 */
private String taskName;
/** 任务分类1-Personal 2-Room Owner */
private Integer taskCategory;
/** 任务图标 */
private String iconUrl;
/** 当前进度值 */
private Integer currentValue;
/** 档位列表 */
private List<RoomDailyTaskTierCO> tiers;
}

View File

@ -0,0 +1,27 @@
package com.red.circle.other.app.dto.clientobject.task;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 任务奖励领取结果
*/
@Data
public class RoomDailyTaskClaimCO {
/** 任务编码 */
private String taskCode;
/** 任务名称 */
private String taskName;
/** 领取的档位序号 */
private Integer tierIndex;
/** 奖励金币数 */
private Integer rewardValue;
/** 领取时间 */
private LocalDateTime claimTime;
}

View File

@ -0,0 +1,28 @@
package com.red.circle.other.app.dto.clientobject.task;
import lombok.Data;
/**
* 任务档位信息
*/
@Data
public class RoomDailyTaskTierCO {
/** 档位序号 */
private Integer tierIndex;
/** 目标值 */
private Integer targetValue;
/** 目标单位MINUTES/COUNT/GOLD */
private String targetUnit;
/** 奖励金币数 */
private Integer rewardValue;
/** 是否已完成 */
private Boolean isCompleted;
/** 是否已领取 */
private Boolean isClaimed;
}

View File

@ -0,0 +1,21 @@
package com.red.circle.other.app.dto.cmd.task;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 领取任务奖励命令
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RoomDailyTaskClaimCmd extends AppExtCommand {
@NotBlank(message = "任务编码不能为空")
private String taskCode;
@NotNull(message = "档位序号不能为空")
private Integer tierIndex;
}

View File

@ -0,0 +1,14 @@
package com.red.circle.other.app.service.task;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskCO;
import com.red.circle.other.app.dto.clientobject.task.RoomDailyTaskClaimCO;
import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskClaimCmd;
import java.util.List;
public interface RoomDailyTaskService {
List<RoomDailyTaskCO> getTaskList(Long userId);
RoomDailyTaskClaimCO claimReward(RoomDailyTaskClaimCmd cmd);
}

View File

@ -0,0 +1,7 @@
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.RoomDailyTaskClaimRecord;
public interface RoomDailyTaskClaimRecordDAO extends BaseDAO<RoomDailyTaskClaimRecord> {
}

View File

@ -0,0 +1,7 @@
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.RoomDailyTaskConfig;
public interface RoomDailyTaskConfigDAO extends BaseDAO<RoomDailyTaskConfig> {
}

View File

@ -0,0 +1,7 @@
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.RoomDailyTaskProgress;
public interface RoomDailyTaskProgressDAO extends BaseDAO<RoomDailyTaskProgress> {
}

View File

@ -0,0 +1,7 @@
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.RoomDailyTaskTier;
public interface RoomDailyTaskTierDAO extends BaseDAO<RoomDailyTaskTier> {
}

View File

@ -0,0 +1,44 @@
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.time.LocalDate;
import java.time.LocalDateTime;
/**
* 任务奖励领取记录
*/
@Data
@TableName("room_daily_task_claim_record")
public class RoomDailyTaskClaimRecord {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long taskId;
private String taskCode;
/** 领取的档位序号 */
private Integer tierIndex;
/** 奖励类型1-金币 */
private Integer rewardType;
/** 奖励金币数 */
private Integer rewardValue;
private LocalDate cycleDate;
/** 幂等keyuserId:taskCode:cycleDate:tierIndex */
private String bizNo;
private LocalDateTime claimTime;
private LocalDateTime createTime;
}

View File

@ -0,0 +1,44 @@
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.time.LocalDateTime;
/**
* 每日任务配置
*/
@Data
@TableName("room_daily_task_config")
public class RoomDailyTaskConfig {
@TableId(type = IdType.AUTO)
private Long id;
/** 任务编码,唯一 */
private String taskCode;
/** 任务名称 */
private String taskName;
/** 任务分类1-Personal 2-Room Owner */
private Integer taskCategory;
/** 任务类型MIC_TIME/INVITE_USER/ONLINE_USER/SEND_GIFT/GAME_CONSUME等 */
private String taskType;
/** 任务图标 */
private String iconUrl;
/** 排序 */
private Integer sortOrder;
/** 状态0-禁用 1-启用 */
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,45 @@
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.time.LocalDate;
import java.time.LocalDateTime;
/**
* 用户每日任务进度
*/
@Data
@TableName("room_daily_task_progress")
public class RoomDailyTaskProgress {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long taskId;
private String taskCode;
/** 任务分类1-Personal 2-Room Owner */
private Integer taskCategory;
/** 任务日期每日重置keyRiyadh时区 */
private LocalDate cycleDate;
/** 当前进度值 */
private Integer currentValue;
/** 已完成的最高档位序号0=未完成任何档位 */
private Integer maxTierCompleted;
/** 已领取的最高档位序号0=未领取 */
private Integer maxTierClaimed;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,42 @@
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.time.LocalDateTime;
/**
* 任务档位配置
*/
@Data
@TableName("room_daily_task_tier")
public class RoomDailyTaskTier {
@TableId(type = IdType.AUTO)
private Long id;
/** 关联任务配置ID */
private Long taskId;
/** 任务编码(冗余) */
private String taskCode;
/** 档位序号1/2/3/4 */
private Integer tierIndex;
/** 达到该档位的目标值 */
private Integer targetValue;
/** 目标单位MINUTES/COUNT/GOLD */
private String targetUnit;
/** 奖励类型1-金币 */
private Integer rewardType;
/** 奖励金币数 */
private Integer rewardValue;
private LocalDateTime createTime;
}

View File

@ -0,0 +1,97 @@
package com.red.circle.other.infra.database.rds.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.other.infra.database.rds.dao.task.RoomDailyTaskClaimRecordDAO;
import com.red.circle.other.infra.database.rds.dao.task.RoomDailyTaskConfigDAO;
import com.red.circle.other.infra.database.rds.dao.task.RoomDailyTaskProgressDAO;
import com.red.circle.other.infra.database.rds.dao.task.RoomDailyTaskTierDAO;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskClaimRecord;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskConfig;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskProgress;
import com.red.circle.other.infra.database.rds.entity.task.RoomDailyTaskTier;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
@Service
@RequiredArgsConstructor
public class RoomDailyTaskDatabaseService {
private final RoomDailyTaskConfigDAO configDAO;
private final RoomDailyTaskTierDAO tierDAO;
private final RoomDailyTaskProgressDAO progressDAO;
private final RoomDailyTaskClaimRecordDAO claimRecordDAO;
public List<RoomDailyTaskConfig> listEnabledConfigs() {
return configDAO.selectList(
new LambdaQueryWrapper<RoomDailyTaskConfig>()
.eq(RoomDailyTaskConfig::getStatus, 1)
.orderByAsc(RoomDailyTaskConfig::getSortOrder)
);
}
public List<RoomDailyTaskTier> listTiersByTaskCodes(List<String> taskCodes) {
return tierDAO.selectList(
new LambdaQueryWrapper<RoomDailyTaskTier>()
.in(RoomDailyTaskTier::getTaskCode, taskCodes)
.orderByAsc(RoomDailyTaskTier::getTierIndex)
);
}
public List<RoomDailyTaskProgress> listProgressByUser(Long userId, LocalDate cycleDate) {
return progressDAO.selectList(
new LambdaQueryWrapper<RoomDailyTaskProgress>()
.eq(RoomDailyTaskProgress::getUserId, userId)
.eq(RoomDailyTaskProgress::getCycleDate, cycleDate)
);
}
public RoomDailyTaskProgress getProgress(Long userId, String taskCode, LocalDate cycleDate) {
return progressDAO.selectList(
new LambdaQueryWrapper<RoomDailyTaskProgress>()
.eq(RoomDailyTaskProgress::getUserId, userId)
.eq(RoomDailyTaskProgress::getTaskCode, taskCode)
.eq(RoomDailyTaskProgress::getCycleDate, cycleDate)
.last("limit 1")
).stream().findFirst().orElse(null);
}
public void saveOrUpdateProgress(RoomDailyTaskProgress progress) {
if (progress.getId() == null) {
progressDAO.insert(progress);
} else {
progressDAO.updateById(progress);
}
}
public boolean existsClaimRecord(String bizNo) {
return claimRecordDAO.selectCount(
new LambdaQueryWrapper<RoomDailyTaskClaimRecord>()
.eq(RoomDailyTaskClaimRecord::getBizNo, bizNo)
) > 0;
}
public void saveClaimRecord(RoomDailyTaskClaimRecord record) {
claimRecordDAO.insert(record);
}
public RoomDailyTaskConfig getConfigByCode(String taskCode) {
return configDAO.selectList(
new LambdaQueryWrapper<RoomDailyTaskConfig>()
.eq(RoomDailyTaskConfig::getTaskCode, taskCode)
.eq(RoomDailyTaskConfig::getStatus, 1)
.last("limit 1")
).stream().findFirst().orElse(null);
}
public RoomDailyTaskTier getTier(String taskCode, Integer tierIndex) {
return tierDAO.selectList(
new LambdaQueryWrapper<RoomDailyTaskTier>()
.eq(RoomDailyTaskTier::getTaskCode, taskCode)
.eq(RoomDailyTaskTier::getTierIndex, tierIndex)
.last("limit 1")
).stream().findFirst().orElse(null);
}
}