cp新功能

(cherry picked from commit 12647246d0b4a94923b991b54c260ebaac291706)
This commit is contained in:
tianfeng 2026-04-22 15:32:45 +08:00
parent 9ff70aa3e4
commit 2d61819a42
6 changed files with 255 additions and 9 deletions

View File

@ -70,7 +70,12 @@ public enum PropsCommodityType {
/**
* 红包封面.
*/
RED_PACKET;
RED_PACKET,
/**
* CP戒指.
*/
CP_RING;
public boolean eq(String name) {

View File

@ -0,0 +1,42 @@
package com.red.circle.other.inner.model.dto.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
/**
* CP戒指信息.
*/
@Data
@Accessors(chain = true)
public class CpRingDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 道具资源ID.
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long propsId;
/**
* 戒指名称.
*/
private String name;
/**
* 戒指封面图.
*/
private String cover;
/**
* 过期时间戳毫秒.
*/
private Long expireTime;
}

View File

@ -82,4 +82,54 @@ public class CpSimpleUserProfileCO implements Serializable {
private Timestamp createTime;
/**
* 当前CP等级未达到Lv1门槛为null.
*/
private Integer cpLevel;
/**
* 下一级所需累计CP值满级为null.
*/
private Long nextLevelExp;
/**
* 爱心样式等级0=, 1-3.
*/
private Integer loveHeartLevel;
/**
* 邻麦动效等级0=, 1-3.
*/
private Integer micEffectLevel;
/**
* 资料卡背景等级0=, 1-3.
*/
private Integer profileCardLevel;
/**
* 赠礼飘窗等级0=, 1-3.
*/
private Integer giftWindowLevel;
/**
* 是否解锁非邻麦动效.
*/
private Boolean hasNonMicEffect;
/**
* 是否解锁CP头饰.
*/
private Boolean hasCpHeadwear;
/**
* 我方当前佩戴的CP戒指未佩戴为null.
*/
private CpRingDTO selfRing;
/**
* CP方当前佩戴的CP戒指未佩戴为null.
*/
private CpRingDTO cpRing;
}

View File

@ -2,10 +2,15 @@ package com.red.circle.other.app.command.material;
import com.red.circle.other.app.dto.cmd.material.SwitchUsePropsCmd;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import java.util.Objects;
import com.red.circle.other.infra.database.cache.service.cp.CpRelationshipCacheService;
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService;
import com.red.circle.other.inner.enums.material.PropsCommodityType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
* 用户切换使用道具.
*
@ -16,6 +21,8 @@ import org.springframework.stereotype.Component;
public class UserSwitchUsePropsCmdExe {
private final UserProfileGateway userProfileGateway;
private final CpRelationshipService cpRelationshipService;
private final CpRelationshipCacheService cpRelationshipCacheService;
public void execute(SwitchUsePropsCmd cmd) {
if (Objects.equals(cmd.getUnload(), Boolean.TRUE)) {
@ -23,13 +30,21 @@ public class UserSwitchUsePropsCmdExe {
cmd.requiredReqUserId(),
Objects.toString(cmd.getType())
);
return;
} else {
userProfileGateway.switchUseProps(
cmd.requiredReqUserId(),
cmd.getPropsId()
);
}
// CP戒指切换后清除双方缓存
if (Objects.equals(cmd.getType(), PropsCommodityType.CP_RING)) {
List<Long> cpUserIds = cpRelationshipService.getCpUserId(cmd.requiredReqUserId());
cpRelationshipCacheService.remove(List.of(cmd.requiredReqUserId()));
if (cpUserIds != null && !cpUserIds.isEmpty()) {
cpRelationshipCacheService.remove(cpUserIds);
}
}
userProfileGateway.switchUseProps(
cmd.requiredReqUserId(),
cmd.getPropsId()
);
}
}

View File

@ -4,11 +4,18 @@ import com.alibaba.fastjson.JSON;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.other.infra.database.cache.key.user.UserKey;
import com.red.circle.other.infra.database.cache.service.cp.CpRelationshipCacheService;
import com.red.circle.other.infra.database.rds.entity.props.PropsBackpack;
import com.red.circle.other.infra.database.rds.entity.props.PropsSourceRecord;
import com.red.circle.other.infra.database.rds.entity.user.user.BaseInfo;
import com.red.circle.other.infra.database.rds.entity.user.user.CpLevelConfig;
import com.red.circle.other.infra.database.rds.entity.user.user.CpRelationship;
import com.red.circle.other.infra.database.rds.service.props.PropsBackpackService;
import com.red.circle.other.infra.database.rds.service.props.PropsSourceRecordService;
import com.red.circle.other.infra.database.rds.service.user.user.BaseInfoService;
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService;
import com.red.circle.other.infra.database.rds.service.user.user.CpValueService;
import com.red.circle.other.inner.enums.material.PropsCommodityType;
import com.red.circle.other.inner.model.dto.user.CpRingDTO;
import com.red.circle.other.inner.model.dto.user.CpSimpleUserProfileCO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.DateUtils;
@ -35,6 +42,8 @@ public class CpRelationshipCacheServiceImpl implements CpRelationshipCacheServic
private final RedisService redisService;
private final BaseInfoService baseInfoService;
private final CpValueService cpValueService;
private final PropsBackpackService propsBackpackService;
private final PropsSourceRecordService propsSourceRecordService;
private static final long CACHE_EXPIRE_SECONDS = 7200L;
@ -84,7 +93,7 @@ public class CpRelationshipCacheServiceImpl implements CpRelationshipCacheServic
String firstDay = cpRelationship.getCreateTime().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate().toString();
return new CpSimpleUserProfileCO()
CpSimpleUserProfileCO co = new CpSimpleUserProfileCO()
.setMeUserId(baseInfo.getId())
.setMeAccount(baseInfo.getAccount())
.setMeUserAvatar(baseInfo.getUserAvatar())
@ -98,6 +107,25 @@ public class CpRelationshipCacheServiceImpl implements CpRelationshipCacheServic
.setCreateTime(cpRelationship.getCreateTime())
.setDays(days)
.setFirstDay(firstDay);
// 填充等级权益
CpLevelConfig levelConfig = CpLevelConfig.of(cpValue);
if (levelConfig != null) {
co.setCpLevel(levelConfig.getLevel())
.setNextLevelExp(levelConfig.nextExpThreshold())
.setLoveHeartLevel(levelConfig.getLoveHeartLevel())
.setMicEffectLevel(levelConfig.getMicEffectLevel())
.setProfileCardLevel(levelConfig.getProfileCardLevel())
.setGiftWindowLevel(levelConfig.getGiftWindowLevel())
.setHasNonMicEffect(levelConfig.isHasNonMicEffect())
.setHasCpHeadwear(levelConfig.isHasCpHeadwear());
}
// 实时查双方背包中正在使用的CP戒指
co.setSelfRing(buildCpRing(userId));
co.setCpRing(buildCpRing(cpRelationship.getCpUserId()));
return co;
})
.filter(Objects::nonNull)
.sorted(Comparator.comparing(CpSimpleUserProfileCO::getCpValue).reversed()
@ -117,6 +145,23 @@ public class CpRelationshipCacheServiceImpl implements CpRelationshipCacheServic
return UserKey.CP.getKey(userId);
}
private CpRingDTO buildCpRing(Long userId) {
PropsBackpack backpack = propsBackpackService
.getUserNotExpiredUseProps(userId, PropsCommodityType.CP_RING);
if (backpack == null) {
return null;
}
PropsSourceRecord source = propsSourceRecordService.getById(backpack.getPropsId());
if (source == null) {
return null;
}
return new CpRingDTO()
.setPropsId(backpack.getPropsId())
.setName(source.getName())
.setCover(source.getCover())
.setExpireTime(backpack.getExpireTime().getTime());
}
@Override
public void remove(List<Long> userIds) {
userIds.forEach(userId -> {

View File

@ -0,0 +1,89 @@
package com.red.circle.other.infra.database.rds.entity.user.user;
import lombok.Getter;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* CP等级配置.
* <p>
* 字段说明
* - expThreshold : 达到该等级所需的累计CP值对应 user_cp_value.cp_val
* - loveHeartLevel : 爱心样式等级0=, 1-3=对应样式展示在资料卡和用户头像旁
* - micEffectLevel : 邻麦动效等级0=, 1-3仅在左右相邻麦位时触发
* - profileCardLevel: 资料卡动态背景等级0=, 1-3展示在CP资料卡内层元素
* - giftWindowLevel : 赠礼飘窗等级0=, 1-3双方赠送CP礼物时触发
* - hasNonMicEffect : 是否解锁非邻麦动效true=解锁非相邻时也可展示动效
* - hasCpHeadwear : 是否解锁CP头饰true=解锁展示在双方头像周期性飞出效果
*
* @author tf
*/
@Getter
public enum CpLevelConfig {
LV1 (1, 500000L, 1, 1, 0, 0, false, false),
LV2 (2, 1000000L, 2, 1, 0, 0, false, false),
LV3 (3, 3000000L, 2, 2, 1, 0, false, false),
LV4 (4, 5000000L, 2, 2, 1, 1, false, false),
LV5 (5, 15500000L, 3, 2, 2, 1, true, true ),
LV6 (6, 40000000L, 3, 3, 2, 1, true, true ),
LV7 (7, 70000000L, 3, 3, 3, 1, true, true ),
LV8 (8, 100000000L, 3, 3, 3, 2, true, true ),
LV9 (9, 130000000L, 3, 3, 3, 3, true, true ),
LV10(10, 200000000L, 3, 3, 3, 3, true, true );
private final int level;
private final long expThreshold;
private final int loveHeartLevel;
private final int micEffectLevel;
private final int profileCardLevel;
private final int giftWindowLevel;
private final boolean hasNonMicEffect;
private final boolean hasCpHeadwear;
CpLevelConfig(int level, long expThreshold,
int loveHeartLevel, int micEffectLevel,
int profileCardLevel, int giftWindowLevel,
boolean hasNonMicEffect, boolean hasCpHeadwear) {
this.level = level;
this.expThreshold = expThreshold;
this.loveHeartLevel = loveHeartLevel;
this.micEffectLevel = micEffectLevel;
this.profileCardLevel = profileCardLevel;
this.giftWindowLevel = giftWindowLevel;
this.hasNonMicEffect = hasNonMicEffect;
this.hasCpHeadwear = hasCpHeadwear;
}
/** 按 expThreshold 降序,用于 of() 从高到低匹配. */
private static final List<CpLevelConfig> DESC = Arrays.stream(values())
.sorted(Comparator.comparingLong(CpLevelConfig::getExpThreshold).reversed())
.toList();
/**
* 根据累计CP值推算当前等级未达到Lv1门槛返回 null.
*/
public static CpLevelConfig of(BigDecimal cpVal) {
if (cpVal == null) {
return null;
}
long val = cpVal.longValue();
return DESC.stream()
.filter(lv -> val >= lv.expThreshold)
.findFirst()
.orElse(null);
}
/**
* 获取下一等级经验门槛满级返回 null.
*/
public Long nextExpThreshold() {
int nextOrdinal = this.ordinal() + 1;
CpLevelConfig[] all = values();
return nextOrdinal < all.length ? all[nextOrdinal].expThreshold : null;
}
}