From 0723bb5f8c7a452597a24957b2678e33f8651592 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Wed, 31 Dec 2025 15:47:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E5=A5=96=E6=B4=BB=E5=8A=A8=E9=A2=86?= =?UTF-8?q?=E5=8F=96=E5=BE=BD=E7=AB=A0=E6=8E=A5=E5=8F=A3=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LotteryActivityRestController.java | 16 ++ .../ActivityRewardConfigBadgeQryExe.java | 179 ++++++++++++++++ .../activity/ClaimActivityRewardBadgeExe.java | 201 ++++++++++++++++++ .../activity/MyTotalDrawCountQryExe.java | 2 +- .../impl/ActivityRewardConfigServiceImpl.java | 24 +++ .../activity/ActivityRewardConfigService.java | 10 + 6 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigBadgeQryExe.java create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardBadgeExe.java diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/LotteryActivityRestController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/LotteryActivityRestController.java index 8911d9b7..84abaf1e 100644 --- a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/LotteryActivityRestController.java +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/LotteryActivityRestController.java @@ -324,4 +324,20 @@ public class LotteryActivityRestController extends BaseController { return activityRewardConfigService.claimReward(cmd); } + /** + * 获取活动奖励配置列表-徽章 + */ + @GetMapping("/reward-configs-badge") + public List getRewardConfigsBadge(ActivityRewardConfigCmd cmd) { + return activityRewardConfigService.listRewardConfigsBadge(cmd); + } + + /** + * 领取活动奖励-徽章 + */ + @PostMapping("/claim-reward-badge") + public ClaimRewardResultCO claimRewardBadge(@RequestBody @Validated ClaimActivityRewardCmd cmd) { + return activityRewardConfigService.claimRewardBadge(cmd); + } + } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigBadgeQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigBadgeQryExe.java new file mode 100644 index 00000000..bc558f72 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ActivityRewardConfigBadgeQryExe.java @@ -0,0 +1,179 @@ +package com.red.circle.other.app.command.activity; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +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.clientobject.activity.MyTotalDrawCountCO; +import com.red.circle.other.app.dto.cmd.activity.ActivityRewardConfigCmd; +import com.red.circle.other.app.service.activity.LotteryActivityRestService; +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.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; +import com.red.circle.other.inner.model.dto.activity.props.ActivityRewardProps; +import com.red.circle.tool.core.json.JacksonUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 查询活动奖励配置执行器 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ActivityRewardConfigBadgeQryExe { + + private final ActivitySourceGroupGateway activitySourceGroupGateway; + private final UserActivityRewardClaimDAO userActivityRewardClaimDAO; + private final UserActivityRechargeService userActivityRechargeService; + private final LotteryActivityRestService lotteryActivityRestService; + + public List execute(ActivityRewardConfigCmd cmd) { + PropsActivityTypeEnum activityType = PropsActivityTypeEnum.valueOf(cmd.getActivityType()); + SysOriginPlatformEnum sysOrigin = SysOriginPlatformEnum.valueOf(cmd.getReqSysOrigin().getOrigin()); + + List ruleConfigs = activitySourceGroupGateway.listRule(sysOrigin, activityType); + if (CollectionUtil.isEmpty(ruleConfigs)) { + return Lists.newArrayList(); + } + + List ruleIds = ruleConfigs.stream().map(ActivityPropsRule::getId).collect(Collectors.toList()); + Map claimMap = getUserClaimMap(cmd.getReqUserId(), ruleIds); + + MyTotalDrawCountCO drawCount = lotteryActivityRestService.getMyTotalDrawCount(cmd); + + + BigDecimal finalUserTotalAmount = drawCount != null && drawCount.getUsedCount() != null ? new BigDecimal(drawCount.getUsedCount()) : BigDecimal.ZERO; + return ruleConfigs.stream() + .map(rule -> buildRewardConfig(rule, cmd.getReqSysOrigin().getOrigin(), claimMap, finalUserTotalAmount)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + 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, + BigDecimal userTotalAmount) { + ActivityPropsGroup group = activitySourceGroupGateway.getActivityPropsGroup( + sysOrigin, + rule.getResourceGroupId() + ); + + if (Objects.isNull(group)) { + return null; + } + + ActivityRewardConfigCO config = new ActivityRewardConfigCO() + .setRuleId(rule.getId()) + .setSysOrigin(rule.getSysOrigin()) + .setActivityType(rule.getActivityType()) + .setResourceGroupId(rule.getResourceGroupId()) + .setJsonData(rule.getJsonData()) + .setSort(rule.getSort()) + .setGroupId(group.getId()) + .setGroupName(group.getName()) + .setShelfStatus(group.getShelfStatus()); + + int claimStatus = calculateClaimStatus(rule, claimMap, userTotalAmount); + config.setClaimStatus(claimStatus); + + if (CollectionUtil.isNotEmpty(group.getActivityRewardProps())) { + List propsList = group.getActivityRewardProps().stream() + .map(this::convertToRewardPropsCO) + .collect(Collectors.toList()); + config.setRewardProps(propsList); + } + + return config; + } + + /** + * 计算领取状态 + * -1: 不可领取(充值金额不足) + * 0: 可领取(充值金额达标且未领取) + * 1: 已领取 + */ + private int calculateClaimStatus(ActivityPropsRule rule, + Map claimMap, + BigDecimal userTotalAmount) { + UserActivityRewardClaim claim = claimMap.get(rule.getId()); + if (Objects.nonNull(claim)) { + return claim.getStatus(); + } + + BigDecimal requiredAmount = parseRequiredAmount(rule.getJsonData()); + if (Objects.isNull(requiredAmount)) { + return -1; + } + + if (userTotalAmount.compareTo(requiredAmount) < 0) { + return -1; + } + + return 0; + } + + private BigDecimal parseRequiredAmount(String jsonData) { + if (StrUtil.isBlank(jsonData)) { + return null; + } + + try { + Map dataMap = JacksonUtils.readValue(jsonData, Map.class); + Object quantity = dataMap.get("quantity"); + if (Objects.isNull(quantity)) { + return null; + } + + if (quantity instanceof Number) { + return new BigDecimal(quantity.toString()); + } + + if (quantity instanceof String) { + return new BigDecimal((String) quantity); + } + + return null; + } catch (Exception e) { + log.error("解析jsonData失败: {}", jsonData, e); + return null; + } + } + + private ActivityRewardConfigCO.ActivityRewardPropsCO convertToRewardPropsCO(ActivityRewardProps props) { + return new ActivityRewardConfigCO.ActivityRewardPropsCO() + .setId(props.getId()) + .setGroupId(props.getGroupId()) + .setType(props.getType()) + .setDetailType(props.getDetailType()) + .setContent(props.getContent()) + .setSort(props.getSort()) + .setCover(props.getCover()) + .setQuantity(props.getQuantity()) + .setAmount(props.getAmount()) + .setRemark(props.getRemark()); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardBadgeExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardBadgeExe.java new file mode 100644 index 00000000..692e56e7 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/ClaimActivityRewardBadgeExe.java @@ -0,0 +1,201 @@ +package com.red.circle.other.app.command.activity; + +import cn.hutool.core.util.StrUtil; +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.clientobject.activity.MyTotalDrawCountCO; +import com.red.circle.other.app.dto.cmd.activity.ClaimActivityRewardCmd; +import com.red.circle.other.app.service.activity.LotteryActivityRestService; +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.LotteryDeviceRecord; +import com.red.circle.other.infra.database.rds.entity.activity.UserActivityRewardClaim; +import com.red.circle.other.infra.database.rds.service.activity.LotteryDeviceRecordService; +import com.red.circle.other.infra.database.rds.service.activity.UserActivityRechargeService; +import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService; +import com.red.circle.other.inner.asserts.DynamicErrorCode; +import com.red.circle.other.inner.asserts.OtherErrorCode; +import com.red.circle.other.inner.endpoint.activity.LotteryTicketClient; +import com.red.circle.other.inner.endpoint.material.props.BadgeBackpackClient; +import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum; +import com.red.circle.other.inner.enums.material.BadgeBackpackExpireTypeEnum; +import com.red.circle.other.inner.enums.sys.SysBadgeConfigTypeEnum; +import com.red.circle.other.inner.model.cmd.material.GiveBadgeCmd; +import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup; +import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule; +import com.red.circle.other.inner.model.dto.activity.props.ActivityRewardProps; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.json.JacksonUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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; + +/** + * 领取活动奖励执行器 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ClaimActivityRewardBadgeExe { + + private final ActivitySourceGroupGateway activitySourceGroupGateway; + private final UserActivityRechargeService userActivityRechargeService; + private final UserActivityRewardClaimDAO userActivityRewardClaimDAO; + private final LotteryTicketClient lotteryTicketClient; + private final LatestMobileDeviceService latestMobileDeviceService; + private final LotteryDeviceRecordService lotteryDeviceRecordService; + private final LotteryActivityRestService lotteryActivityRestService; + private final BadgeBackpackClient badgeBackpackClient; + + @Transactional(rollbackFor = Exception.class) + public ClaimRewardResultCO execute(ClaimActivityRewardCmd cmd) { + PropsActivityTypeEnum activityType = PropsActivityTypeEnum.valueOf(cmd.getActivityType()); + SysOriginPlatformEnum sysOrigin = SysOriginPlatformEnum.valueOf(cmd.getReqSysOrigin().getOrigin()); + + // 2. 校验设备唯一性(防止小号薅羊毛) + checkDeviceUnique(cmd.getActivityId(), cmd.getReqUserId()); + + List ruleConfigs = activitySourceGroupGateway.listRule(sysOrigin, activityType); + ActivityPropsRule targetRule = ruleConfigs.stream() + .filter(rule -> Objects.equals(rule.getId(), cmd.getRuleId())) + .findFirst() + .orElse(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.notNull(DynamicErrorCode.NO_RELATED_INFORMATION_FOUND, requiredAmount); + + MyTotalDrawCountCO drawCount = lotteryActivityRestService.getMyTotalDrawCount(cmd); + + BigDecimal finalUserTotalAmount = drawCount != null && drawCount.getUsedCount() != null ? new BigDecimal(drawCount.getUsedCount()) : BigDecimal.ZERO; + + ResponseAssert.isFalse(OtherErrorCode.USER_RECHARGE_INVALID, finalUserTotalAmount.compareTo(requiredAmount) < 0); + + ActivityPropsGroup group = activitySourceGroupGateway.getActivityPropsGroup( + sysOrigin.name(), + ruleConfigs.get(0).getResourceGroupId() + ); + + List activityRewardProps = group.getActivityRewardProps(); + ResponseAssert.isFalse(DynamicErrorCode.NO_RELATED_INFORMATION_FOUND, activityRewardProps.isEmpty()); + ActivityRewardProps rewardProps = activityRewardProps.get(0); + + badgeBackpackClient.giveBadge(new GiveBadgeCmd() + .setAcceptUserId(cmd.getReqUserId()) + .setBadgeId(Long.parseLong(rewardProps.getContent())) + .setExpireType(BadgeBackpackExpireTypeEnum.TEMPORARY) + .setDays(rewardProps.getQuantity()) + .setBadgeType(SysBadgeConfigTypeEnum.ACTIVITY) + ); + + UserActivityRewardClaim claim = new UserActivityRewardClaim() + .setUserId(cmd.getReqUserId()) + .setActivityId(cmd.getActivityId()) + .setRuleId(cmd.getRuleId()) + .setActivityType(cmd.getActivityType()) + .setStatus(1) + .setLotteryTicketIds(String.join(",", "BADGE")) + .setCreateTime(TimestampUtils.now()) + .setUpdateTime(TimestampUtils.now()); + + userActivityRewardClaimDAO.insert(claim); + + return new ClaimRewardResultCO() + .setSuccess(true) + .setLotteryTicketIds(List.of(claim.getId())); + } + + /** + * 校验设备唯一性(防止小号薅羊毛) + */ + private void checkDeviceUnique(Long activityId, Long userId) { + String fingerprint = latestMobileDeviceService.getEnhancedDeviceFingerprintByUserId(userId); + + if (com.red.circle.tool.core.text.StringUtils.isBlank(fingerprint)) { + log.warn("Failed to get device fingerprint, userId: {}", userId); + return; + } + + LotteryDeviceRecord existingRecord = lotteryDeviceRecordService + .getByActivityAndFingerprint(activityId, fingerprint); + + if (existingRecord != null && !existingRecord.getUserId().equals(userId)) { + log.warn("Duplicate device detected, activityId: {}, currentUser: {}, boundUser: {}, fingerprint: {}", + activityId, userId, existingRecord.getUserId(), fingerprint); + ResponseAssert.isTrue(OtherErrorCode.DEVICE_ALREADY_USED, false); + } + } + + private BigDecimal parseRequiredAmount(String jsonData) { + if (StrUtil.isBlank(jsonData)) { + return null; + } + + try { + Map dataMap = JacksonUtils.readValue(jsonData, Map.class); + Object quantity = dataMap.get("quantity"); + if (Objects.isNull(quantity)) { + return null; + } + + if (quantity instanceof Number) { + return new BigDecimal(quantity.toString()); + } + + if (quantity instanceof String) { + return new BigDecimal((String) quantity); + } + + return null; + } catch (Exception 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/command/activity/MyTotalDrawCountQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/MyTotalDrawCountQryExe.java index dce9aef9..7bbeffea 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/MyTotalDrawCountQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/MyTotalDrawCountQryExe.java @@ -19,7 +19,7 @@ public class MyTotalDrawCountQryExe { private final LotteryTicketService lotteryTicketService; public MyTotalDrawCountCO execute(AppExtCommand cmd) { - Long userId = cmd.requiredReqUserId(); + Long userId = cmd.getReqUserId(); List tickets = lotteryTicketService.query() .eq(LotteryTicket::getUserId, userId) 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 8db312f1..6114ad29 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 @@ -1,6 +1,8 @@ package com.red.circle.other.app.service.activity.impl; +import com.red.circle.other.app.command.activity.ActivityRewardConfigBadgeQryExe; import com.red.circle.other.app.command.activity.ActivityRewardConfigQryExe; +import com.red.circle.other.app.command.activity.ClaimActivityRewardBadgeExe; import com.red.circle.other.app.command.activity.ClaimActivityRewardExe; import com.red.circle.other.app.dto.clientobject.activity.ActivityRewardConfigCO; import com.red.circle.other.app.dto.clientobject.activity.ClaimRewardResultCO; @@ -22,6 +24,8 @@ public class ActivityRewardConfigServiceImpl implements ActivityRewardConfigServ private final ActivityRewardConfigQryExe activityRewardConfigQryExe; private final ClaimActivityRewardExe claimActivityRewardExe; + private final ActivityRewardConfigBadgeQryExe activityRewardConfigBadgeQryExe; + private final ClaimActivityRewardBadgeExe claimActivityRewardBadgeExe; private final DistributedLockUtil distributedLockUtil; private static final String CLAIM_LOCK_PREFIX = "activity:reward:claim:lock"; @@ -44,4 +48,24 @@ public class ActivityRewardConfigServiceImpl implements ActivityRewardConfigServ () -> claimActivityRewardExe.execute(cmd) ); } + + @Override + public List listRewardConfigsBadge(ActivityRewardConfigCmd cmd) { + return activityRewardConfigBadgeQryExe.execute(cmd); + } + + @Override + public ClaimRewardResultCO claimRewardBadge(ClaimActivityRewardCmd cmd) { + String lockKey = DistributedLockUtil.buildLockKey( + CLAIM_LOCK_PREFIX, + cmd.getReqUserId(), + cmd.getRuleId() + ); + + return distributedLockUtil.executeWithLock( + lockKey, + () -> claimActivityRewardBadgeExe.execute(cmd) + ); + } + } diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/ActivityRewardConfigService.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/ActivityRewardConfigService.java index 6404fa69..b719f19f 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/ActivityRewardConfigService.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/ActivityRewardConfigService.java @@ -21,4 +21,14 @@ public interface ActivityRewardConfigService { * 领取活动奖励 */ ClaimRewardResultCO claimReward(ClaimActivityRewardCmd cmd); + + /** + * 获取活动奖励配置列表 + */ + List listRewardConfigsBadge(ActivityRewardConfigCmd cmd); + + /** + * 领取活动奖励 + */ + ClaimRewardResultCO claimRewardBadge(ClaimActivityRewardCmd cmd); }