新增-财富升级发送VIP

This commit is contained in:
tianfeng 2026-05-11 18:11:17 +08:00
parent 14b63e3983
commit 6fdf9c5f09
7 changed files with 304 additions and 3 deletions

View File

@ -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<VipReward> rewards;
WealthLevelRewardEnum(int level, List<VipReward> rewards) {
this.level = level;
this.rewards = rewards;
}
public int getLevel() {
return level;
}
public List<VipReward> getRewards() {
return rewards;
}
public static Optional<WealthLevelRewardEnum> of(int level) {
return Arrays.stream(values())
.filter(e -> e.level == level)
.findFirst();
}
public record VipReward(NobleVipEnum vipType, int days) {}
}

View File

@ -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;
/**
* <p>
@ -60,4 +61,12 @@ public interface PropsCommodityStoreService extends BaseService<PropsCommoditySt
PropsCommodityStore getBySourceId(SysOriginPlatformEnum sysOrigin, Long sourceId,
String propsType);
/**
* 获取 NOBLE_VIP 类型上架未删除的商品key=sourceIdvalue=商品信息.
*
* @param sysOrigin 平台
* @return mapkey props_noble_vip_ability.id
*/
Map<Long, PropsCommodityStore> mapNobleVipBySourceId(SysOriginPlatformEnum sysOrigin);
}

View File

@ -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<Long, PropsCommodityStore> 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));
}
}

View File

@ -55,4 +55,5 @@ public class PropsNobleVipAbilityServiceImpl extends
.in(ids.size() > 1, PropsNobleVipAbility::getId, ids)
.list();
}
}

View File

@ -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)

View File

@ -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<Long, PropsCommodityStore> nobleVipStoreMap = propsCommodityStoreService.mapNobleVipBySourceId(SysOriginPlatformEnum.ATYOU);
// 批量加载所有 abilitykey=ability.id
Set<Long> abilityIds = nobleVipStoreMap.keySet();
Map<Long, PropsNobleVipAbility> abilityMap = propsNobleVipAbilityService.mapByIds(abilityIds);
// vipType -> store发奖时直接 O(1)
Map<String, PropsCommodityStore> 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<String, PropsCommodityStore> vipTypeStoreMap,
Map<Long, PropsNobleVipAbility> 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<String, PropsCommodityStore> vipTypeStoreMap,
Map<Long, PropsNobleVipAbility> 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<String, Object> 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);
}
}

View File

@ -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