抽奖活动奖励配置和领取功能完善

This commit is contained in:
tianfeng 2025-12-30 17:46:28 +08:00
parent d279c549c1
commit bb8b1c5c1f
7 changed files with 171 additions and 15 deletions

View File

@ -1,11 +1,14 @@
package com.red.circle.other.app.command.activity;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.other.app.dto.clientobject.activity.ActivityRewardConfigCO;
import com.red.circle.other.app.dto.cmd.activity.ActivityRewardConfigCmd;
import com.red.circle.other.domain.gateway.props.ActivitySourceGroupGateway;
import com.red.circle.other.infra.database.rds.dao.activity.UserActivityRewardClaimDAO;
import com.red.circle.other.infra.database.rds.entity.activity.UserActivityRewardClaim;
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup;
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
@ -14,6 +17,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@ -25,6 +29,7 @@ import java.util.stream.Collectors;
public class ActivityRewardConfigQryExe {
private final ActivitySourceGroupGateway activitySourceGroupGateway;
private final UserActivityRewardClaimDAO userActivityRewardClaimDAO;
public List<ActivityRewardConfigCO> execute(ActivityRewardConfigCmd cmd) {
PropsActivityTypeEnum activityType = PropsActivityTypeEnum.valueOf(cmd.getActivityType());
@ -35,13 +40,28 @@ public class ActivityRewardConfigQryExe {
return Lists.newArrayList();
}
List<Long> ruleIds = ruleConfigs.stream().map(ActivityPropsRule::getId).collect(Collectors.toList());
Map<Long, UserActivityRewardClaim> claimMap = getUserClaimMap(cmd.getReqUserId(), ruleIds);
return ruleConfigs.stream()
.map(rule -> buildRewardConfig(rule, cmd.getReqSysOrigin().getOrigin()))
.map(rule -> buildRewardConfig(rule, cmd.getReqSysOrigin().getOrigin(), claimMap))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private ActivityRewardConfigCO buildRewardConfig(ActivityPropsRule rule, String sysOrigin) {
private Map<Long, UserActivityRewardClaim> getUserClaimMap(Long userId, List<Long> ruleIds) {
List<UserActivityRewardClaim> claims = userActivityRewardClaimDAO.selectList(
new LambdaQueryWrapper<UserActivityRewardClaim>()
.eq(UserActivityRewardClaim::getUserId, userId)
.in(UserActivityRewardClaim::getRuleId, ruleIds)
);
return claims.stream()
.collect(Collectors.toMap(UserActivityRewardClaim::getRuleId, claim -> claim, (a, b) -> a));
}
private ActivityRewardConfigCO buildRewardConfig(ActivityPropsRule rule, String sysOrigin,
Map<Long, UserActivityRewardClaim> claimMap) {
ActivityPropsGroup group = activitySourceGroupGateway.getActivityPropsGroup(
sysOrigin,
rule.getResourceGroupId()
@ -62,6 +82,9 @@ public class ActivityRewardConfigQryExe {
.setGroupName(group.getName())
.setShelfStatus(group.getShelfStatus());
UserActivityRewardClaim claim = claimMap.get(rule.getId());
config.setClaimStatus(Objects.isNull(claim) ? 0 : claim.getStatus());
if (CollectionUtil.isNotEmpty(group.getActivityRewardProps())) {
List<ActivityRewardConfigCO.ActivityRewardPropsCO> propsList = group.getActivityRewardProps().stream()
.map(this::convertToRewardPropsCO)

View File

@ -1,18 +1,22 @@
package com.red.circle.other.app.command.activity;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.app.dto.clientobject.activity.ClaimRewardResultCO;
import com.red.circle.other.app.dto.cmd.activity.ClaimActivityRewardCmd;
import com.red.circle.other.domain.gateway.props.ActivitySourceGroupGateway;
import com.red.circle.other.infra.database.rds.dao.activity.UserActivityRewardClaimDAO;
import com.red.circle.other.infra.database.rds.entity.activity.UserActivityRewardClaim;
import com.red.circle.other.infra.database.rds.service.activity.UserActivityRechargeService;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.endpoint.activity.LotteryTicketClient;
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
import com.red.circle.other.inner.asserts.DynamicErrorCode;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.json.JacksonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -20,6 +24,7 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -34,6 +39,8 @@ public class ClaimActivityRewardExe {
private final ActivitySourceGroupGateway activitySourceGroupGateway;
private final UserActivityRechargeService userActivityRechargeService;
private final UserActivityRewardClaimDAO userActivityRewardClaimDAO;
private final LotteryTicketClient lotteryTicketClient;
@Transactional(rollbackFor = Exception.class)
public ClaimRewardResultCO execute(ClaimActivityRewardCmd cmd) {
@ -43,12 +50,21 @@ public class ClaimActivityRewardExe {
List<ActivityPropsRule> ruleConfigs = activitySourceGroupGateway.listRule(sysOrigin, activityType);
ActivityPropsRule targetRule = ruleConfigs.stream()
.filter(rule -> Objects.equals(rule.getId(), cmd.getRuleId()))
.findFirst().orElse(null);
.findFirst()
.orElse(null);
ResponseAssert.isTrue(CommonErrorCode.DATA_ERROR, targetRule != null);
ResponseAssert.notNull(DynamicErrorCode.NO_RELATED_INFORMATION_FOUND, targetRule);
long count = userActivityRewardClaimDAO.selectCount(
new LambdaQueryWrapper<UserActivityRewardClaim>()
.eq(UserActivityRewardClaim::getUserId, cmd.getReqUserId())
.eq(UserActivityRewardClaim::getRuleId, cmd.getRuleId())
);
ResponseAssert.isFalse(CommonErrorCode.REPEATED_SUBMISSION, count > 0);
BigDecimal requiredAmount = parseRequiredAmount(targetRule.getJsonData());
ResponseAssert.isTrue(CommonErrorCode.DATA_ERROR, requiredAmount != null);
ResponseAssert.notNull(DynamicErrorCode.NO_RELATED_INFORMATION_FOUND, requiredAmount);
BigDecimal userTotalAmount = userActivityRechargeService.getUserTotalAmount(
cmd.getActivityId(),
@ -57,15 +73,39 @@ public class ClaimActivityRewardExe {
ResponseAssert.isFalse(OtherErrorCode.USER_RECHARGE_INVALID, Objects.isNull(userTotalAmount) || userTotalAmount.compareTo(requiredAmount) < 0);
Integer ticketCount = parseTicketCount(targetRule.getJsonData());
if (Objects.isNull(ticketCount) || ticketCount <= 0) {
ticketCount = 1;
}
// TODO: 这里需要实现实际的奖励发放逻辑
// 1. 记录用户已领取该奖励
// 2. 发放奖励到用户账户
// 3. 发送通知等
List<Long> ticketIds = new ArrayList<>();
for (int i = 0; i < ticketCount; i++) {
Long ticketId = ResponseAssert.requiredSuccess(
lotteryTicketClient.addTicket(
cmd.getReqUserId(),
1,
"ACTIVITY_REWARD",
cmd.getRuleId().toString()
)
);
ticketIds.add(ticketId);
}
UserActivityRewardClaim claim = new UserActivityRewardClaim()
.setUserId(cmd.getReqUserId())
.setActivityId(cmd.getActivityId())
.setRuleId(cmd.getRuleId())
.setActivityType(cmd.getActivityType())
.setStatus(1)
.setLotteryTicketIds(String.join(",", ticketIds.stream().map(String::valueOf).toArray(String[]::new)))
.setCreateTime(TimestampUtils.now())
.setUpdateTime(TimestampUtils.now());
userActivityRewardClaimDAO.insert(claim);
return new ClaimRewardResultCO()
.setSuccess(true)
.setMessage("Successfully claimed");
.setLotteryTicketIds(ticketIds);
}
private BigDecimal parseRequiredAmount(String jsonData) {
@ -90,8 +130,35 @@ public class ClaimActivityRewardExe {
return null;
} catch (Exception e) {
log.error("解析jsonData失败: {}", jsonData, e);
log.error("parse jsonData error: {}", jsonData, e);
return null;
}
}
private Integer parseTicketCount(String jsonData) {
if (StrUtil.isBlank(jsonData)) {
return 1;
}
try {
Map<String, Object> dataMap = JacksonUtils.readValue(jsonData, Map.class);
Object ticketCount = dataMap.get("ticketCount");
if (Objects.isNull(ticketCount)) {
return 1;
}
if (ticketCount instanceof Number) {
return ((Number) ticketCount).intValue();
}
if (ticketCount instanceof String) {
return Integer.parseInt((String) ticketCount);
}
return 1;
} catch (Exception e) {
log.error("parse ticketCount error: {}", jsonData, e);
return 1;
}
}
}

View File

@ -7,6 +7,7 @@ import com.red.circle.other.app.dto.clientobject.activity.ClaimRewardResultCO;
import com.red.circle.other.app.dto.cmd.activity.ActivityRewardConfigCmd;
import com.red.circle.other.app.dto.cmd.activity.ClaimActivityRewardCmd;
import com.red.circle.other.app.service.activity.ActivityRewardConfigService;
import com.red.circle.other.app.util.DistributedLockUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -21,6 +22,9 @@ public class ActivityRewardConfigServiceImpl implements ActivityRewardConfigServ
private final ActivityRewardConfigQryExe activityRewardConfigQryExe;
private final ClaimActivityRewardExe claimActivityRewardExe;
private final DistributedLockUtil distributedLockUtil;
private static final String CLAIM_LOCK_PREFIX = "activity:reward:claim:lock";
@Override
public List<ActivityRewardConfigCO> listRewardConfigs(ActivityRewardConfigCmd cmd) {
@ -29,6 +33,15 @@ public class ActivityRewardConfigServiceImpl implements ActivityRewardConfigServ
@Override
public ClaimRewardResultCO claimReward(ClaimActivityRewardCmd cmd) {
return claimActivityRewardExe.execute(cmd);
String lockKey = DistributedLockUtil.buildLockKey(
CLAIM_LOCK_PREFIX,
cmd.getReqUserId(),
cmd.getRuleId()
);
return distributedLockUtil.executeWithLock(
lockKey,
() -> claimActivityRewardExe.execute(cmd)
);
}
}

View File

@ -37,6 +37,8 @@ public class ActivityRewardConfigCO implements Serializable {
private Boolean shelfStatus;
private Integer claimStatus;
private List<ActivityRewardPropsCO> rewardProps;
@Data

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 领取奖励结果CO
@ -14,5 +15,5 @@ public class ClaimRewardResultCO implements Serializable {
private Boolean success;
private String message;
private List<Long> lotteryTicketIds;
}

View File

@ -0,0 +1,12 @@
package com.red.circle.other.infra.database.rds.dao.activity;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.activity.UserActivityRewardClaim;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户活动奖励领取记录DAO
*/
@Mapper
public interface UserActivityRewardClaimDAO extends BaseDAO<UserActivityRewardClaim> {
}

View File

@ -0,0 +1,38 @@
package com.red.circle.other.infra.database.rds.entity.activity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* 用户活动奖励领取记录
*/
@Data
@Accessors(chain = true)
@TableName("user_activity_reward_claim")
public class UserActivityRewardClaim implements Serializable {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long userId;
private Long activityId;
private Long ruleId;
private String activityType;
private Integer status;
private String lotteryTicketIds;
private Timestamp createTime;
private Timestamp updateTime;
}