diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigQryExe.java index 16828047..405857f3 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigQryExe.java @@ -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 execute(ActivityRewardConfigCmd cmd) { PropsActivityTypeEnum activityType = PropsActivityTypeEnum.valueOf(cmd.getActivityType()); @@ -35,13 +40,28 @@ public class ActivityRewardConfigQryExe { return Lists.newArrayList(); } + List ruleIds = ruleConfigs.stream().map(ActivityPropsRule::getId).collect(Collectors.toList()); + Map 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 getUserClaimMap(Long userId, List ruleIds) { + List claims = userActivityRewardClaimDAO.selectList( + new LambdaQueryWrapper() + .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 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 propsList = group.getActivityRewardProps().stream() .map(this::convertToRewardPropsCO) diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardExe.java index 67723aa9..d095786a 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardExe.java @@ -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 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() + .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 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 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; + } + } } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/impl/ActivityRewardConfigServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/impl/ActivityRewardConfigServiceImpl.java index 4c842c7e..8db312f1 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/impl/ActivityRewardConfigServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/impl/ActivityRewardConfigServiceImpl.java @@ -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 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) + ); } } diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ActivityRewardConfigCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ActivityRewardConfigCO.java index bd23018e..e453c7cb 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ActivityRewardConfigCO.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ActivityRewardConfigCO.java @@ -37,6 +37,8 @@ public class ActivityRewardConfigCO implements Serializable { private Boolean shelfStatus; + private Integer claimStatus; + private List rewardProps; @Data diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ClaimRewardResultCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ClaimRewardResultCO.java index 1ae934f5..70d1dee1 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ClaimRewardResultCO.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/ClaimRewardResultCO.java @@ -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 lotteryTicketIds; } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/activity/UserActivityRewardClaimDAO.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/activity/UserActivityRewardClaimDAO.java new file mode 100644 index 00000000..eafb53d0 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/activity/UserActivityRewardClaimDAO.java @@ -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 { +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/activity/UserActivityRewardClaim.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/activity/UserActivityRewardClaim.java new file mode 100644 index 00000000..0bf83893 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/activity/UserActivityRewardClaim.java @@ -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; +}