From 6fdf9c5f0985168dc9519da660e8187cf7170a74 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Mon, 11 May 2026 18:11:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E-=E8=B4=A2=E5=AF=8C=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E5=8F=91=E9=80=81VIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/enums/user/WealthLevelRewardEnum.java | 77 +++++++ .../props/PropsCommodityStoreService.java | 9 + .../impl/PropsCommodityStoreServiceImpl.java | 13 ++ .../impl/PropsNobleVipAbilityServiceImpl.java | 1 + .../impl/ConsumptionLevelServiceImpl.java | 3 - .../user/user/WealthLevelUpRewardService.java | 200 ++++++++++++++++++ .../user/impl/UserLevelClientServiceImpl.java | 4 + 7 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/enums/user/WealthLevelRewardEnum.java create mode 100644 rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/WealthLevelUpRewardService.java diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/enums/user/WealthLevelRewardEnum.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/enums/user/WealthLevelRewardEnum.java new file mode 100644 index 00000000..3f182b75 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/enums/user/WealthLevelRewardEnum.java @@ -0,0 +1,77 @@ +package com.red.circle.other.app.enums.user; + +import com.red.circle.other.inner.enums.material.NobleVipEnum; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * 财富等级升级 VIP 奖励配置. + * 升到对应等级时发放奖励,等级 40+ 暂无奖励。 + */ +public enum WealthLevelRewardEnum { + + LEVEL_2(2, List.of(new VipReward(NobleVipEnum.VISCOUNT, 3))), + LEVEL_3(3, List.of(new VipReward(NobleVipEnum.VISCOUNT, 5))), + LEVEL_4(4, List.of(new VipReward(NobleVipEnum.VISCOUNT, 7))), + LEVEL_5(5, List.of(new VipReward(NobleVipEnum.VISCOUNT, 15))), + LEVEL_6(6, List.of(new VipReward(NobleVipEnum.EARL, 2))), + LEVEL_7(7, List.of(new VipReward(NobleVipEnum.EARL, 2))), + LEVEL_8(8, List.of(new VipReward(NobleVipEnum.EARL, 3))), + LEVEL_9(9, List.of(new VipReward(NobleVipEnum.EARL, 3))), + LEVEL_10(10, List.of(new VipReward(NobleVipEnum.EARL, 3), new VipReward(NobleVipEnum.VISCOUNT, 999))), + LEVEL_11(11, List.of(new VipReward(NobleVipEnum.EARL, 5))), + LEVEL_12(12, List.of(new VipReward(NobleVipEnum.EARL, 7))), + LEVEL_13(13, List.of(new VipReward(NobleVipEnum.EARL, 14))), + LEVEL_14(14, List.of(new VipReward(NobleVipEnum.EARL, 20))), + LEVEL_15(15, List.of(new VipReward(NobleVipEnum.MARQUIS, 5))), + LEVEL_16(16, List.of(new VipReward(NobleVipEnum.MARQUIS, 5))), + LEVEL_17(17, List.of(new VipReward(NobleVipEnum.MARQUIS, 7))), + LEVEL_18(18, List.of(new VipReward(NobleVipEnum.MARQUIS, 7))), + LEVEL_19(19, List.of(new VipReward(NobleVipEnum.MARQUIS, 14))), + LEVEL_20(20, List.of(new VipReward(NobleVipEnum.MARQUIS, 14), new VipReward(NobleVipEnum.EARL, 999))), + LEVEL_21(21, List.of(new VipReward(NobleVipEnum.MARQUIS, 15))), + LEVEL_22(22, List.of(new VipReward(NobleVipEnum.MARQUIS, 20))), + LEVEL_23(23, List.of(new VipReward(NobleVipEnum.MARQUIS, 25))), + LEVEL_24(24, List.of(new VipReward(NobleVipEnum.MARQUIS, 30))), + LEVEL_25(25, List.of(new VipReward(NobleVipEnum.DUKE, 15))), + LEVEL_26(26, List.of(new VipReward(NobleVipEnum.DUKE, 15))), + LEVEL_27(27, List.of(new VipReward(NobleVipEnum.DUKE, 20))), + LEVEL_28(28, List.of(new VipReward(NobleVipEnum.DUKE, 20))), + LEVEL_29(29, List.of(new VipReward(NobleVipEnum.DUKE, 30))), + LEVEL_30(30, List.of(new VipReward(NobleVipEnum.DUKE, 30), new VipReward(NobleVipEnum.MARQUIS, 999))), + LEVEL_31(31, List.of(new VipReward(NobleVipEnum.DUKE, 30))), + LEVEL_32(32, List.of(new VipReward(NobleVipEnum.DUKE, 30))), + LEVEL_33(33, List.of(new VipReward(NobleVipEnum.DUKE, 30))), + LEVEL_34(34, List.of(new VipReward(NobleVipEnum.DUKE, 30))), + LEVEL_35(35, List.of(new VipReward(NobleVipEnum.KING, 30))), + LEVEL_36(36, List.of(new VipReward(NobleVipEnum.KING, 30))), + LEVEL_37(37, List.of(new VipReward(NobleVipEnum.KING, 30))), + LEVEL_38(38, List.of(new VipReward(NobleVipEnum.KING, 30))), + LEVEL_39(39, List.of(new VipReward(NobleVipEnum.KING, 30))), + LEVEL_40(40, List.of(new VipReward(NobleVipEnum.KING, 30), new VipReward(NobleVipEnum.DUKE, 999))); + + private final int level; + private final List rewards; + + WealthLevelRewardEnum(int level, List rewards) { + this.level = level; + this.rewards = rewards; + } + + public int getLevel() { + return level; + } + + public List getRewards() { + return rewards; + } + + public static Optional of(int level) { + return Arrays.stream(values()) + .filter(e -> e.level == level) + .findFirst(); + } + + public record VipReward(NobleVipEnum vipType, int days) {} +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/PropsCommodityStoreService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/PropsCommodityStoreService.java index 315fc975..79908cb5 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/PropsCommodityStoreService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/PropsCommodityStoreService.java @@ -4,6 +4,7 @@ import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; import com.red.circle.framework.mybatis.service.BaseService; import com.red.circle.other.infra.database.rds.entity.props.PropsCommodityStore; import java.util.List; +import java.util.Map; /** *

@@ -60,4 +61,12 @@ public interface PropsCommodityStoreService extends BaseService mapNobleVipBySourceId(SysOriginPlatformEnum sysOrigin); + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsCommodityStoreServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsCommodityStoreServiceImpl.java index 80cb884d..630c0e7c 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsCommodityStoreServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsCommodityStoreServiceImpl.java @@ -10,6 +10,7 @@ import com.red.circle.other.inner.enums.material.PropsCommodityType; import com.red.circle.other.inner.enums.material.PropsCurrencyType; import com.red.circle.tool.core.collection.CollectionUtils; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -98,4 +99,16 @@ public class PropsCommodityStoreServiceImpl extends .getOne(); } + @Override + public Map mapNobleVipBySourceId(SysOriginPlatformEnum sysOrigin) { + return query() + .eq(PropsCommodityStore::getSysOrigin, sysOrigin) + .eq(PropsCommodityStore::getPropsType, PropsCommodityType.NOBLE_VIP) + .eq(PropsCommodityStore::getShelfStatus, Boolean.TRUE) + .eq(PropsCommodityStore::getDel, Boolean.FALSE) + .list() + .stream() + .collect(Collectors.toMap(PropsCommodityStore::getSourceId, v -> v)); + } + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsNobleVipAbilityServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsNobleVipAbilityServiceImpl.java index b596dc97..64406e5d 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsNobleVipAbilityServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/props/impl/PropsNobleVipAbilityServiceImpl.java @@ -55,4 +55,5 @@ public class PropsNobleVipAbilityServiceImpl extends .in(ids.size() > 1, PropsNobleVipAbility::getId, ids) .list(); } + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/ConsumptionLevelServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/ConsumptionLevelServiceImpl.java index 4817cee6..7bb75fe2 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/ConsumptionLevelServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/ConsumptionLevelServiceImpl.java @@ -57,9 +57,6 @@ public class ConsumptionLevelServiceImpl extends @Override public BigDecimal getConsumptionGolds(Long userId) { - if(userId==1807604920249827329L||userId ==1808800573464178690L){ - return BigDecimal.ZERO; - } return Optional.ofNullable( query().select(ConsumptionLevel::getConsumptionGolds) .eq(ConsumptionLevel::getUserId, userId) diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/WealthLevelUpRewardService.java b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/WealthLevelUpRewardService.java new file mode 100644 index 00000000..65afd594 --- /dev/null +++ b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/WealthLevelUpRewardService.java @@ -0,0 +1,200 @@ +package com.red.circle.other.app.inner.service.user.user; + +import com.alibaba.fastjson.JSON; +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.common.business.core.level.LevelUtils; +import com.red.circle.common.business.enums.SendPropsOrigin; +import com.red.circle.component.redis.service.RedisService; +import com.red.circle.external.inner.endpoint.message.OfficialNoticeClient; +import com.red.circle.external.inner.model.cmd.message.notice.official.NoticeExtTemplateCustomizeCmd; +import com.red.circle.external.inner.model.enums.message.OfficialNoticeTypeEnum; +import com.red.circle.other.app.enums.user.WealthLevelRewardEnum; +import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; +import com.red.circle.other.domain.model.user.ability.RegionConfig; +import com.red.circle.other.infra.database.rds.entity.props.PropsCommodityStore; +import com.red.circle.other.infra.database.rds.entity.props.PropsNobleVipAbility; +import com.red.circle.other.infra.database.rds.entity.props.PropsSourceRecord; +import com.red.circle.other.infra.database.rds.service.props.PropsCommodityStoreService; +import com.red.circle.other.infra.database.rds.service.props.PropsNobleVipAbilityService; +import com.red.circle.other.infra.database.rds.service.props.PropsSourceRecordService; +import com.red.circle.other.infra.utils.I18nUtils; +import com.red.circle.other.inner.endpoint.material.props.PropsBackpackClient; +import com.red.circle.other.inner.endpoint.material.props.PropsNobleVipClient; +import com.red.circle.other.inner.endpoint.user.user.UserProfileClient; +import com.red.circle.other.inner.enums.material.ConsolePropsTypeEnum; +import com.red.circle.other.inner.enums.material.PropsCommodityType; +import com.red.circle.other.inner.model.cmd.material.GivePropsBackpackCmd; +import com.red.circle.other.inner.model.dto.material.props.PropsNobleVipAbilityDTO; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 财富等级升级奖励服务. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WealthLevelUpRewardService { + + private static final String REWARD_KEY_PREFIX = "wealth:level_reward:"; + + private final RedisService redisService; + private final PropsBackpackClient propsBackpackClient; + private final PropsNobleVipClient propsNobleVipClient; + private final PropsNobleVipAbilityService propsNobleVipAbilityService; + private final PropsCommodityStoreService propsCommodityStoreService; + private final PropsSourceRecordService propsSourceRecordService; + private final UserProfileClient userProfileClient; + private final UserRegionGateway userRegionGateway; + private final OfficialNoticeClient officialNoticeClient; + + /** + * 检查财富等级是否升级,并发放对应 VIP 奖励. + * + * @param userId 用户id + * @param beforeExp 累加前的经验值 + * @param addedExp 本次累加量 + */ + public void checkAndReward(Long userId, BigDecimal beforeExp, BigDecimal addedExp) { + int oldLevel = LevelUtils.getWealthLevel(SysOriginPlatformEnum.ATYOU, beforeExp.longValue()).getLevel(); + int newLevel = LevelUtils.getWealthLevel(SysOriginPlatformEnum.ATYOU, beforeExp.add(addedExp).longValue()).getLevel(); + + if (newLevel <= oldLevel) { + return; + } + + // key=sourceId(即 ability.id),一次加载,复用 + Map nobleVipStoreMap = propsCommodityStoreService.mapNobleVipBySourceId(SysOriginPlatformEnum.ATYOU); + + // 批量加载所有 ability,key=ability.id + Set abilityIds = nobleVipStoreMap.keySet(); + Map abilityMap = propsNobleVipAbilityService.mapByIds(abilityIds); + + // vipType -> store,发奖时直接 O(1) 取 + Map vipTypeStoreMap = abilityMap.values().stream() + .filter(a -> nobleVipStoreMap.containsKey(a.getId())) + .collect(Collectors.toMap(PropsNobleVipAbility::getVipType, a -> nobleVipStoreMap.get(a.getId()))); + + RegionConfig region = userRegionGateway.getRegionConfigByUserId(userId); + + for (int level = oldLevel + 1; level <= newLevel; level++) { + WealthLevelRewardEnum.of(level).ifPresent(reward -> sendRewards(userId, reward, vipTypeStoreMap, abilityMap, region)); + } + + useMaxNobleVip(userId); + } + + private void sendRewards(Long userId, WealthLevelRewardEnum reward, + Map vipTypeStoreMap, + Map abilityMap, + RegionConfig region) { + for (WealthLevelRewardEnum.VipReward vipReward : reward.getRewards()) { + String idempotentKey = REWARD_KEY_PREFIX + userId + ":" + reward.getLevel() + ":" + vipReward.vipType().name(); + if (!redisService.setIfAbsent(idempotentKey, "1")) { + log.warn("wealth level reward already sent, skip. userId={}, level={}, vipType={}", userId, reward.getLevel(), vipReward.vipType()); + continue; + } + try { + giveVipWithAttachments(userId, vipReward, vipTypeStoreMap, abilityMap, region); + } catch (Exception e) { + redisService.delete(idempotentKey); + log.error("wealth level reward send failed. userId={}, level={}, vipType={}", userId, reward.getLevel(), vipReward.vipType(), e); + } + } + } + + private void giveVipWithAttachments(Long userId, WealthLevelRewardEnum.VipReward vipReward, + Map vipTypeStoreMap, + Map abilityMap, + RegionConfig region) { + PropsCommodityStore store = vipTypeStoreMap.get(vipReward.vipType().name()); + if (Objects.isNull(store)) { + log.error("wealth level reward: noble vip store not found. userId={}, vipType={}", userId, vipReward.vipType()); + return; + } + + PropsNobleVipAbility ability = abilityMap.get(store.getSourceId()); + if (Objects.isNull(ability)) { + log.error("wealth level reward: ability not found. userId={}, sourceId={}", userId, store.getSourceId()); + return; + } + + // 主 VIP + sendPropsRecord(userId, store.getSourceId(), PropsCommodityType.NOBLE_VIP.name(), vipReward.days(), region); + + // 座驾 + if (Objects.nonNull(ability.getCarId()) && !Objects.equals(ability.getCarId(), 0L)) { + sendPropsRecord(userId, ability.getCarId(), ConsolePropsTypeEnum.RIDE.getName(), vipReward.days(), region); + } + + // 头像框 + if (Objects.nonNull(ability.getAvatarFrameId()) && !Objects.equals(ability.getAvatarFrameId(), 0L)) { + sendPropsRecord(userId, ability.getAvatarFrameId(), ConsolePropsTypeEnum.AVATAR_FRAME.getName(), vipReward.days(), region); + } + + // 聊天气泡 + if (Objects.nonNull(ability.getChatBubbleId()) && !Objects.equals(ability.getChatBubbleId(), 0L)) { + sendPropsRecord(userId, ability.getChatBubbleId(), ConsolePropsTypeEnum.CHAT_BUBBLE.getName(), vipReward.days(), region); + } + + // 资料卡 + if (Objects.nonNull(ability.getDataCardId()) && !Objects.equals(ability.getDataCardId(), 0L)) { + sendPropsRecord(userId, ability.getDataCardId(), ConsolePropsTypeEnum.DATA_CARD.getName(), vipReward.days(), region); + } + } + + private void sendPropsRecord(Long userId, Long propsId, String type, int days, RegionConfig region) { + propsBackpackClient.giveProps(new GivePropsBackpackCmd() + .setAcceptUserId(userId) + .setPropsId(propsId) + .setType(type) + .setOrigin(SendPropsOrigin.ACTIVITY_REWARD.name()) + .setOriginDesc(SendPropsOrigin.ACTIVITY_REWARD.getDesc()) + .setDays(days) + .setUseProps(Boolean.TRUE) + .setAllowGive(Boolean.FALSE) + .setOpUser(userId)); + + try { + if (region == null) { + return; + } + String url = null; + PropsSourceRecord props = propsSourceRecordService.getPropsById(propsId); + if (props != null) { + url = props.getCover(); + } + Map map = new HashMap<>(); + map.put("propType", type); + officialNoticeClient.send( + NoticeExtTemplateCustomizeCmd.builder() + .toAccount(userId) + .noticeType(OfficialNoticeTypeEnum.GIVE_AWAY_PROPS) + .content(I18nUtils.getPropsRewardContent(type, days, I18nUtils.getLanguageByRegion(region.getRegionName()))) + .title(JSON.toJSONString(map)) + .expand(url) + .build()); + } catch (Exception e) { + log.error("wealth level reward notice failed. userId={}, propsId={}, type={}", userId, propsId, type, e); + } + } + + private void useMaxNobleVip(Long userId) { + PropsNobleVipAbilityDTO abilityDTO = propsNobleVipClient.getUserMaxAbilityDTO(userId).getBody(); + if (Objects.isNull(abilityDTO)) { + return; + } + propsBackpackClient.switchUseProps(userId, abilityDTO.getId()); + propsBackpackClient.switchUseProps(userId, abilityDTO.getAvatarFrameId()); + propsBackpackClient.switchUseProps(userId, abilityDTO.getDataCardId()); + propsBackpackClient.switchUseProps(userId, abilityDTO.getCarId()); + userProfileClient.removeCacheAll(userId); + } +} diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/impl/UserLevelClientServiceImpl.java b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/impl/UserLevelClientServiceImpl.java index ea27260c..85148e4a 100644 --- a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/impl/UserLevelClientServiceImpl.java +++ b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/user/user/impl/UserLevelClientServiceImpl.java @@ -6,6 +6,7 @@ import com.red.circle.other.infra.database.rds.service.user.user.ConsumptionLeve import com.red.circle.other.app.inner.convertor.user.UserProfileInnerConvertor; import com.red.circle.other.inner.model.dto.user.WealthAndCharmExpLevelDTO; import com.red.circle.other.app.inner.service.user.user.UserLevelClientService; +import com.red.circle.other.app.inner.service.user.user.WealthLevelUpRewardService; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -25,6 +26,7 @@ public class UserLevelClientServiceImpl implements UserLevelClientService { private final UserProfileGateway userProfileGateway; private final ConsumptionLevelService consumptionLevelService; private final UserProfileInnerConvertor userProfileInnerConvertor; + private final WealthLevelUpRewardService wealthLevelUpRewardService; @Override public WealthAndCharmExpLevelDTO getUserLevelExp(SysOriginPlatformEnum sysOrigin, Long userId) { @@ -45,7 +47,9 @@ public class UserLevelClientServiceImpl implements UserLevelClientService { @Override public void incrWealthExp(Long userId, BigDecimal quantity) { + BigDecimal beforeExp = consumptionLevelService.getConsumptionGolds(userId); consumptionLevelService.incrConsumptionGolds(userId, quantity); + wealthLevelUpRewardService.checkAndReward(userId, beforeExp, quantity); } @Override