This commit is contained in:
hy001 2026-05-21 15:43:38 +08:00
parent c02050274b
commit 3f59e5b653
21 changed files with 448 additions and 167 deletions

View File

@ -46,9 +46,14 @@ public class GiveGiftConfig implements Serializable {
*/ */
private String type; private String type;
/** /**
* 礼物tab. * 礼物tab.
*/ */
private String giftTab; private String giftTab;
} /**
* CP礼物关系类型CP/BROTHER/SISTERS.
*/
private String cpRelationType;
}

View File

@ -91,15 +91,20 @@ public class GiftConfigDTO implements Serializable {
*/ */
private String type; private String type;
/** /**
* 礼物tab. * 礼物tab.
*/ */
private String giftTab; private String giftTab;
/** /**
* 0.未删除 1.已删除. * CP礼物关系类型CP/BROTHER/SISTERS.
*/ */
private Boolean del; private String cpRelationType;
/**
* 0.未删除 1.已删除.
*/
private Boolean del;
/** /**
* 幸运礼物规格ID. * 幸运礼物规格ID.

View File

@ -86,15 +86,20 @@ public class SysGiftConfigCO implements Serializable {
*/ */
private String type; private String type;
/** /**
* 礼物tab. * 礼物tab.
*/ */
private String giftTab; private String giftTab;
/** /**
* 0.未删除 1.已删除. * CP礼物关系类型CP/BROTHER/SISTERS.
*/ */
private Boolean del; private String cpRelationType;
/**
* 0.未删除 1.已删除.
*/
private Boolean del;
/** /**
* 幸运礼物规格ID. * 幸运礼物规格ID.

View File

@ -37,9 +37,12 @@ import org.springframework.stereotype.Component;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class ProcessCpApplyCmd { public class ProcessCpApplyCmd {
private final CpApplyService cpApplyService; private static final int CP_MAX_COUNT = 1;
private static final int FRIEND_MAX_COUNT = 3;
private final CpApplyService cpApplyService;
private final CpValueService cpValueService; private final CpValueService cpValueService;
private final ImMessageClient imMessageClient; private final ImMessageClient imMessageClient;
private final WalletGoldClient walletGoldClient; private final WalletGoldClient walletGoldClient;
@ -73,12 +76,12 @@ public class ProcessCpApplyCmd {
cpRelationshipService.existsCp(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), cpRelationshipService.existsCp(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(),
relationType)); relationType));
// 检查CP数量限制最多4个 int maxCount = relationMaxCount(relationType);
int senderCpCount = cpRelationshipService.countCp(cpApply.getSendApplyUserId()); ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED,
int receiverCpCount = cpRelationshipService.countCp(cpApply.getAcceptApplyUserId()); cpRelationshipService.countCp(cpApply.getSendApplyUserId(), relationType) >= maxCount);
ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED, senderCpCount >= 4); ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED,
ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED, receiverCpCount >= 4); cpRelationshipService.countCp(cpApply.getAcceptApplyUserId(), relationType) >= maxCount);
} }
// 操作失败 // 操作失败
ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE, ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE,
@ -89,8 +92,6 @@ public class ProcessCpApplyCmd {
return; return;
} }
refuseStatusWait(cmd, cpApply, relationType);
if (isReconcile) { if (isReconcile) {
// 复合更新状态为NORMAL // 复合更新状态为NORMAL
cpRelationshipService.reconcile(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), cpRelationshipService.reconcile(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(),
@ -100,19 +101,32 @@ public class ProcessCpApplyCmd {
cpRelationshipService cpRelationshipService
.addCp(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), createCpValue(), .addCp(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), createCpValue(),
cmd.requireReqSysOrigin(), relationType); cmd.requireReqSysOrigin(), relationType);
} }
cpRelationshipCacheService.remove(Arrays.asList(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId())); refuseStatusWaitIfLimitReached(cmd, cpApply, relationType);
}
cpRelationshipCacheService.remove(Arrays.asList(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId()));
private void refuseStatusWait(ProcessApplyCmd cmd, CpApply cpApply, String relationType) { }
private void refuseStatusWaitIfLimitReached(ProcessApplyCmd cmd, CpApply cpApply,
String relationType) {
if (cpRelationshipService.countCp(cpApply.getAcceptApplyUserId(), relationType)
< relationMaxCount(relationType)) {
return;
}
cpApplyService.refuseStatusWait(cmd.requiredReqUserId(), relationType); cpApplyService.refuseStatusWait(cmd.requiredReqUserId(), relationType);
userMqMessageService.cpApplyDelayed( userMqMessageService.cpApplyDelayed(
new CpApplyEvent() new CpApplyEvent()
.setId(cpApply.getId()) .setId(cpApply.getId())
.setSysOrigin(SysOriginPlatformEnum.valueOf(cmd.requireReqSysOrigin())), .setSysOrigin(SysOriginPlatformEnum.valueOf(cmd.requireReqSysOrigin())),
TimestampUtils.nowPlusMinutes(1)); TimestampUtils.nowPlusMinutes(1));
} }
private int relationMaxCount(String relationType) {
return Objects.equals(CpRelationshipType.CP, CpRelationshipType.of(relationType))
? CP_MAX_COUNT
: FRIEND_MAX_COUNT;
}
private void returnGoldCoins(ProcessApplyCmd cmd, CpApply cpApply) { private void returnGoldCoins(ProcessApplyCmd cmd, CpApply cpApply) {
if (Objects.isNull(cpApply.getApplyConsumeGold()) if (Objects.isNull(cpApply.getApplyConsumeGold())

View File

@ -46,8 +46,11 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class SendCpApplyCmdExe { public class SendCpApplyCmdExe {
private final CpApplyService cpApplyService; private static final int CP_MAX_COUNT = 1;
private static final int FRIEND_MAX_COUNT = 3;
private final CpApplyService cpApplyService;
private final OfficialNoticeClient officialNoticeClient; private final OfficialNoticeClient officialNoticeClient;
private final WalletGoldClient walletGoldClient; private final WalletGoldClient walletGoldClient;
private final UserMqMessageService userMqMessageService; private final UserMqMessageService userMqMessageService;
@ -63,12 +66,18 @@ public class SendCpApplyCmdExe {
} }
public boolean autoCreateFromCpGift(Long sendUserId, Long acceptUserId, String sysOrigin) { public boolean autoCreateFromCpGift(Long sendUserId, Long acceptUserId, String sysOrigin) {
return autoCreateFromCpGift(sendUserId, acceptUserId, sysOrigin, CpRelationshipType.CP.name());
}
public boolean autoCreateFromCpGift(Long sendUserId, Long acceptUserId, String sysOrigin,
String relationTypeValue) {
try { try {
process(sendUserId, acceptUserId, sysOrigin, CpRelationshipType.CP.name(), false); process(sendUserId, acceptUserId, sysOrigin, relationTypeValue, false);
return true; return true;
} catch (Exception ex) { } catch (Exception ex) {
log.info("[CP] skip auto create cp apply from gift, sendUserId={}, acceptUserId={}, reason={}", log.info(
sendUserId, acceptUserId, ex.getMessage()); "[CP] skip auto create cp apply from gift, sendUserId={}, acceptUserId={}, relationType={}, reason={}",
sendUserId, acceptUserId, relationTypeValue, ex.getMessage());
return false; return false;
} }
} }
@ -101,16 +110,12 @@ public class SendCpApplyCmdExe {
cpRelationshipService cpRelationshipService
.existsCp(sendUserId, acceptUserId, relationType.name())); .existsCp(sendUserId, acceptUserId, relationType.name()));
// 检查CP数量限制最多4个 validateRelationLimit(sendUserId, acceptUserId, relationType);
int senderCpCount = cpRelationshipService.countCp(sendUserId);
int receiverCpCount = cpRelationshipService.countCp(acceptUserId);
ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED, senderCpCount >= 4);
ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED, receiverCpCount >= 4);
} }
// 您已发送申请 // 您已发送申请
ResponseAssert.isFalse(UserRelationErrorCode.YOU_HAVE_SENT_AN_APPLICATION, ResponseAssert.isFalse(UserRelationErrorCode.YOU_HAVE_SENT_AN_APPLICATION,
cpApplyService.existsNotProcessSendApply(sendUserId, relationType.name())); cpApplyService.exists(acceptUserId, sendUserId, relationType.name()));
// 对方已向你发送申请 // 对方已向你发送申请
ResponseAssert.isFalse(UserRelationErrorCode.OTHER_SIDE_ALREADY_APPLIED, ResponseAssert.isFalse(UserRelationErrorCode.OTHER_SIDE_ALREADY_APPLIED,
@ -166,6 +171,19 @@ public class SendCpApplyCmdExe {
TimestampUtils.nowPlusDays(1)); TimestampUtils.nowPlusDays(1));
} }
private void validateRelationLimit(Long sendUserId, Long acceptUserId,
CpRelationshipType relationType) {
int maxCount = relationMaxCount(relationType);
ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED,
cpRelationshipService.countCp(sendUserId, relationType.name()) >= maxCount);
ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED,
cpRelationshipService.countCp(acceptUserId, relationType.name()) >= maxCount);
}
private int relationMaxCount(CpRelationshipType relationType) {
return Objects.equals(relationType, CpRelationshipType.CP) ? CP_MAX_COUNT : FRIEND_MAX_COUNT;
}
private CpRelationshipType parseRelationType(String relationTypeValue) { private CpRelationshipType parseRelationType(String relationTypeValue) {
try { try {
return CpRelationshipType.of(relationTypeValue); return CpRelationshipType.of(relationTypeValue);
@ -177,12 +195,10 @@ public class SendCpApplyCmdExe {
private void validateRelationTypeGender(CpRelationshipType relationType, UserProfileDTO userProfile, private void validateRelationTypeGender(CpRelationshipType relationType, UserProfileDTO userProfile,
UserProfileDTO acceptUserProfile) { UserProfileDTO acceptUserProfile) {
if (Objects.equals(relationType, CpRelationshipType.CP)) { if (!Objects.equals(relationType, CpRelationshipType.CP)) {
ResponseAssert.isFalse(UserRelationErrorCode.UNAVAILABLE_SAME_GENDER,
Objects.equals(userProfile.getUserSex(), acceptUserProfile.getUserSex()));
return; return;
} }
ResponseAssert.isTrue(UserRelationErrorCode.RELATION_TYPE_GENDER_NOT_MATCH, ResponseAssert.isTrue(UserRelationErrorCode.UNAVAILABLE_SAME_GENDER,
relationType.genderMatched(userProfile.getUserSex(), acceptUserProfile.getUserSex())); relationType.genderMatched(userProfile.getUserSex(), acceptUserProfile.getUserSex()));
} }

View File

@ -81,11 +81,12 @@ public interface GiftAppConvertor {
return new GiveGiftConfig() return new GiveGiftConfig()
.setId(giftConfig.getId()) .setId(giftConfig.getId())
.setGiftCandy(giftConfig.getGiftCandy()) .setGiftCandy(giftConfig.getGiftCandy())
.setGiftIntegral(giftConfig.getGiftIntegral()) .setGiftIntegral(giftConfig.getGiftIntegral())
.setSpecial(giftConfig.getSpecial()) .setSpecial(giftConfig.getSpecial())
.setType(giftConfig.getType()) .setType(giftConfig.getType())
.setGiftTab(giftConfig.getGiftTab()); .setGiftTab(giftConfig.getGiftTab())
} .setCpRelationType(giftConfig.getCpRelationType());
}
default GiveAwayGiftBatchEvent toBackpackGift(GiveAwayGiftBackpackCmd cmd, default GiveAwayGiftBatchEvent toBackpackGift(GiveAwayGiftBackpackCmd cmd,
GiftConfigDTO giftConfig, Long trackId) { GiftConfigDTO giftConfig, Long trackId) {

View File

@ -807,12 +807,13 @@ public class GiveGiftsListener implements MessageListener {
GiveGiftConfig giftConfig = event.getGiftConfig(); GiveGiftConfig giftConfig = event.getGiftConfig();
return new GiftValue() return new GiftValue()
.setCurrencyType(giftConfig.getType()) .setCurrencyType(giftConfig.getType())
.setGiftType(getGiftType(event, giftConfig)) .setGiftType(getGiftType(event, giftConfig))
.setUnitPrice(giftConfig.getGiftCandy()) .setCpRelationType(giftConfig.getCpRelationType())
.setQuantity(event.getQuantity()) .setUnitPrice(giftConfig.getGiftCandy())
.setUserSize(event.acceptUserSize()) .setQuantity(event.getQuantity())
.setUserSize(event.acceptUserSize())
.setActualAmount(event.countConsumAmountTotal()) .setActualAmount(event.countConsumAmountTotal())
.setGiftValue(event.checkGiftTypeEqGold() ? .setGiftValue(event.checkGiftTypeEqGold() ?
event.countSingleGiftGolds() event.countSingleGiftGolds()

View File

@ -57,9 +57,9 @@ import com.red.circle.other.infra.database.rds.entity.live.RoomMember;
import com.red.circle.other.infra.database.rds.entity.user.user.ConfessionChance; import com.red.circle.other.infra.database.rds.entity.user.user.ConfessionChance;
import com.red.circle.other.infra.database.rds.entity.user.user.CpBlessRecord; import com.red.circle.other.infra.database.rds.entity.user.user.CpBlessRecord;
import com.red.circle.other.infra.database.rds.entity.user.user.CpRelationship; import com.red.circle.other.infra.database.rds.entity.user.user.CpRelationship;
import com.red.circle.other.infra.database.rds.entity.user.user.CpValue; import com.red.circle.other.infra.database.rds.entity.user.user.CpValue;
import com.red.circle.other.infra.database.rds.entity.user.user.FriendshipCard; import com.red.circle.other.infra.database.rds.entity.user.user.FriendshipCard;
import com.red.circle.other.infra.database.rds.service.dynamic.DynamicContentService; import com.red.circle.other.infra.database.rds.service.dynamic.DynamicContentService;
import com.red.circle.other.infra.database.rds.service.dynamic.DynamicGiftService; import com.red.circle.other.infra.database.rds.service.dynamic.DynamicGiftService;
import com.red.circle.other.infra.database.rds.service.dynamic.DynamicMessageService; import com.red.circle.other.infra.database.rds.service.dynamic.DynamicMessageService;
import com.red.circle.other.infra.database.rds.service.game.GameRoomPkIntegralRecordService; import com.red.circle.other.infra.database.rds.service.game.GameRoomPkIntegralRecordService;
@ -70,9 +70,10 @@ import com.red.circle.other.infra.database.rds.service.user.user.ConfessionChanc
import com.red.circle.other.infra.database.rds.service.user.user.ConsumptionLevelService; import com.red.circle.other.infra.database.rds.service.user.user.ConsumptionLevelService;
import com.red.circle.other.infra.database.rds.service.user.user.CpBlessRecordService; import com.red.circle.other.infra.database.rds.service.user.user.CpBlessRecordService;
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService; 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.infra.database.rds.service.user.user.CpValueService;
import com.red.circle.other.infra.database.rds.service.user.user.FriendshipCardService; import com.red.circle.other.infra.database.rds.service.user.user.FriendshipCardService;
import com.red.circle.other.inner.enums.config.EnumConfigKey; import com.red.circle.other.infra.enums.user.user.CpRelationshipType;
import com.red.circle.other.inner.enums.config.EnumConfigKey;
import com.red.circle.other.inner.enums.game.GamePkTypeEnum; import com.red.circle.other.inner.enums.game.GamePkTypeEnum;
import com.red.circle.other.inner.enums.material.GiftCurrencyType; import com.red.circle.other.inner.enums.material.GiftCurrencyType;
import com.red.circle.other.inner.enums.material.GiftSpecialEnum; import com.red.circle.other.inner.enums.material.GiftSpecialEnum;
@ -378,12 +379,15 @@ public class GiftCountStrategy implements GiftStrategy {
if (CollectionUtils.isEmpty(runningWater.getAcceptUsers())) { if (CollectionUtils.isEmpty(runningWater.getAcceptUsers())) {
return; return;
} }
String relationType = CpRelationshipType.normalize(
runningWater.getGiftValue().getCpRelationType());
runningWater.getAcceptUsers().forEach(accept -> { runningWater.getAcceptUsers().forEach(accept -> {
if (Objects.equals(runningWater.getUserId(), accept.getAcceptUserId())) { if (Objects.equals(runningWater.getUserId(), accept.getAcceptUserId())) {
return; return;
} }
sendCpApplyCmdExe.autoCreateFromCpGift( sendCpApplyCmdExe.autoCreateFromCpGift(
runningWater.getUserId(), accept.getAcceptUserId(), runningWater.getSysOrigin()); runningWater.getUserId(), accept.getAcceptUserId(), runningWater.getSysOrigin(),
relationType);
}); });
} catch (Exception ex) { } catch (Exception ex) {
log.warn("[CP] skip auto create cp apply from gift, runningWaterId={}, giftId={}, sendUserId={}, reason={}", log.warn("[CP] skip auto create cp apply from gift, runningWaterId={}, giftId={}, sendUserId={}, reason={}",

View File

@ -0,0 +1,110 @@
package com.red.circle.other.app.command.user;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.red.circle.external.inner.endpoint.message.OfficialNoticeClient;
import com.red.circle.external.inner.model.cmd.message.notice.official.NoticeExtTemplateTypeCmd;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.mq.rocket.business.producer.UserMqMessageService;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway;
import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService;
import com.red.circle.other.infra.database.rds.entity.user.user.CpApply;
import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService;
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
class SendCpApplyCmdExeTest {
@Test
void autoCreateFromCpGiftShouldNotRestrictBrotherAndSistersGender() {
Fixture fixture = new Fixture(1, 0);
assertTrue(fixture.exe.autoCreateFromCpGift(1001L, 2001L, "LIKEI", "BROTHER"));
assertEquals("BROTHER", fixture.savedApply().getRelationType());
fixture = new Fixture(0, 1);
assertTrue(fixture.exe.autoCreateFromCpGift(1001L, 2001L, "LIKEI", "SISTERS"));
assertEquals("SISTERS", fixture.savedApply().getRelationType());
}
@Test
void autoCreateFromCpGiftShouldSkipSameGenderCp() {
Fixture fixture = new Fixture(1, 1);
assertFalse(fixture.exe.autoCreateFromCpGift(1001L, 2001L, "LIKEI", "CP"));
verify(fixture.cpApplyService, never()).save(any(CpApply.class));
}
private static class Fixture {
private final CpApplyService cpApplyService = mock(CpApplyService.class);
private final SendCpApplyCmdExe exe;
private Fixture(Integer sendSex, Integer acceptSex) {
OfficialNoticeClient officialNoticeClient = mock(OfficialNoticeClient.class);
WalletGoldClient walletGoldClient = mock(WalletGoldClient.class);
UserMqMessageService userMqMessageService = mock(UserMqMessageService.class);
CpRelationshipService cpRelationshipService = mock(CpRelationshipService.class);
EnumConfigCacheService enumConfigCacheService = mock(EnumConfigCacheService.class);
UserProfileGateway userProfileGateway = mock(UserProfileGateway.class);
UserProfileAppConvertor userProfileAppConvertor = mock(UserProfileAppConvertor.class);
UserRegionGateway userRegionGateway = mock(UserRegionGateway.class);
UserProfile sendProfile = new UserProfile();
UserProfile acceptProfile = new UserProfile();
UserProfileDTO sendProfileDTO = profile(1001L, sendSex);
UserProfileDTO acceptProfileDTO = profile(2001L, acceptSex);
when(userRegionGateway.checkEqRegion(1001L, 2001L)).thenReturn(true);
when(userProfileGateway.getByUserId(1001L)).thenReturn(sendProfile);
when(userProfileGateway.getByUserId(2001L)).thenReturn(acceptProfile);
when(userProfileAppConvertor.toUserProfileDTO(sendProfile)).thenReturn(sendProfileDTO);
when(userProfileAppConvertor.toUserProfileDTO(acceptProfile)).thenReturn(acceptProfileDTO);
when(officialNoticeClient.send(any(NoticeExtTemplateTypeCmd.class)))
.thenReturn(ResultResponse.success());
exe = new SendCpApplyCmdExe(
cpApplyService,
officialNoticeClient,
walletGoldClient,
userMqMessageService,
cpRelationshipService,
enumConfigCacheService,
userProfileGateway,
userProfileAppConvertor,
userRegionGateway
);
}
private CpApply savedApply() {
ArgumentCaptor<CpApply> captor = ArgumentCaptor.forClass(CpApply.class);
verify(cpApplyService).save(captor.capture());
return captor.getValue();
}
private static UserProfileDTO profile(Long userId, Integer sex) {
UserProfileDTO userProfile = new UserProfileDTO();
userProfile.setId(userId);
userProfile.setUserSex(sex);
userProfile.setAccount(userId.toString());
userProfile.setUserNickname("user-" + userId);
userProfile.setCountryCode("SA");
userProfile.setCountryName("Saudi Arabia");
return userProfile;
}
}
}

View File

@ -63,15 +63,20 @@ public class GiftConfigCO extends ClientObject {
*/ */
private String type; private String type;
/** /**
* 礼物tab. * 礼物tab.
*/ */
private String giftTab; private String giftTab;
/** /**
* 规格ID. * CP礼物关系类型CP/BROTHER/SISTERS.
*/ */
@JsonSerialize(using = ToStringSerializer.class) private String cpRelationType;
/**
* 规格ID.
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long standardId; private Long standardId;
/** /**

View File

@ -27,15 +27,20 @@ public class GiftValue implements Serializable {
*/ */
private String currencyType; private String currencyType;
/** /**
* 礼物类型. * 礼物类型.
*/ */
private String giftType; private String giftType;
/** /**
* 礼物单价. * CP礼物关系类型CP/BROTHER/SISTERS.
*/ */
private BigDecimal unitPrice; private String cpRelationType;
/**
* 礼物单价.
*/
private BigDecimal unitPrice;
/** /**
* 发送礼物数量. * 发送礼物数量.

View File

@ -106,17 +106,23 @@ public class GiftConfig extends TimestampBaseEntity implements Serializable {
@TableField("type") @TableField("type")
private String type; private String type;
/** /**
* 礼物tab. * 礼物tab.
*/ */
@TableField("gift_tab") @TableField("gift_tab")
private String giftTab; private String giftTab;
/** /**
* 0.未删除 1.已删除. * CP礼物关系类型CP/BROTHER/SISTERS.
*/ */
@TableField("is_del") @TableField("cp_relation_type")
private Boolean del; private String cpRelationType;
/**
* 0.未删除 1.已删除.
*/
@TableField("is_del")
private Boolean del;
/** /**
* 幸运礼物规格ID. * 幸运礼物规格ID.

View File

@ -5,12 +5,13 @@ import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.framework.dto.PageResult; import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.mybatis.constant.PageConstant; import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl; import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.other.infra.database.rds.dao.gift.GiftConfigDAO; import com.red.circle.other.infra.database.rds.dao.gift.GiftConfigDAO;
import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig; import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig;
import com.red.circle.other.infra.database.rds.service.gift.GiftConfigService; import com.red.circle.other.infra.database.rds.service.gift.GiftConfigService;
import com.red.circle.other.inner.enums.material.GiftCurrencyType; import com.red.circle.other.infra.enums.user.user.CpRelationshipType;
import com.red.circle.other.inner.enums.material.GiftSpecialEnum; import com.red.circle.other.inner.enums.material.GiftCurrencyType;
import com.red.circle.other.inner.enums.material.GiftTabEnum; import com.red.circle.other.inner.enums.material.GiftSpecialEnum;
import com.red.circle.other.inner.enums.material.GiftTabEnum;
import com.red.circle.other.inner.model.cmd.sys.GiftQryCmd; import com.red.circle.other.inner.model.cmd.sys.GiftQryCmd;
import com.red.circle.tool.core.collection.CollectionUtils; import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.num.NumUtils; import com.red.circle.tool.core.num.NumUtils;
@ -223,20 +224,29 @@ public class GiftConfigServiceImpl extends BaseServiceImpl<GiftConfigDAO, GiftCo
.set(GiftConfig::getSpecial, giftConfig.getSpecial()) .set(GiftConfig::getSpecial, giftConfig.getSpecial())
.set(GiftConfig::getRegions, giftConfig.getRegions()) .set(GiftConfig::getRegions, giftConfig.getRegions())
.set(GiftConfig::getType, giftConfig.getType()) .set(GiftConfig::getType, giftConfig.getType())
.set(GiftConfig::getSort, giftConfig.getSort()) .set(GiftConfig::getSort, giftConfig.getSort())
.set(GiftConfig::getGiftTab, giftConfig.getGiftTab()) .set(GiftConfig::getGiftTab, giftConfig.getGiftTab())
.set(GiftConfig::getStandardId, giftConfig.getStandardId()) .set(GiftConfig::getCpRelationType, normalizeCpRelationType(giftConfig))
.set(GiftConfig::getExpiredTime, giftConfig.getExpiredTime()) .set(GiftConfig::getStandardId, giftConfig.getStandardId())
.set(GiftConfig::getExplanationGift, .set(GiftConfig::getExpiredTime, giftConfig.getExpiredTime())
.set(GiftConfig::getExplanationGift,
Objects.nonNull(giftConfig.getExplanationGift()) ? giftConfig.getExplanationGift() Objects.nonNull(giftConfig.getExplanationGift()) ? giftConfig.getExplanationGift()
: Boolean.FALSE) : Boolean.FALSE)
.set(GiftConfig::getUserId, giftConfig.getUserId()) .set(GiftConfig::getUserId, giftConfig.getUserId())
.eq(GiftConfig::getId, giftConfig.getId()) .eq(GiftConfig::getId, giftConfig.getId())
.last(PageConstant.LIMIT_ONE) .last(PageConstant.LIMIT_ONE)
.execute(); .execute();
} }
@Override private String normalizeCpRelationType(GiftConfig giftConfig) {
if (Objects.isNull(giftConfig)
|| !Objects.equals(giftConfig.getGiftTab(), GiftTabEnum.CP.name())) {
return null;
}
return CpRelationshipType.normalize(giftConfig.getCpRelationType());
}
@Override
public Boolean switchDelStatus(Long id, Boolean status) { public Boolean switchDelStatus(Long id, Boolean status) {
return update().set(GiftConfig::getDel, status) return update().set(GiftConfig::getDel, status)
.eq(GiftConfig::getId, id) .eq(GiftConfig::getId, id)

View File

@ -123,16 +123,21 @@ public interface CpRelationshipService extends BaseService<CpRelationship> {
*/ */
CpRelationship getByUserIdOrCpUserId(Long userId, String relationType); CpRelationship getByUserIdOrCpUserId(Long userId, String relationType);
/** /**
* 获取用户正常CP数量. * 获取用户正常CP数量.
* *
* @param userId 用户id * @param userId 用户id
* @return CP数量 * @return CP数量
*/ */
int countCp(Long userId); int countCp(Long userId);
/** /**
* 获取分手中的CP关系. * 获取用户指定类型正常关系数量.
*/
int countCp(Long userId, String relationType);
/**
* 获取分手中的CP关系.
* *
* @param userId 用户id * @param userId 用户id
* @param cpUserId cp用户id * @param cpUserId cp用户id

View File

@ -201,13 +201,23 @@ public class CpRelationshipServiceImpl extends
return cpUser; return cpUser;
} }
@Override @Override
public int countCp(Long userId) { public int countCp(Long userId) {
return Math.toIntExact(query() return Math.toIntExact(query()
.eq(CpRelationship::getUserId, userId) .eq(CpRelationship::getUserId, userId)
.count()); .eq(CpRelationship::getStatus, CpRelationshipStatus.NORMAL.name())
} .count());
}
@Override
public int countCp(Long userId, String relationType) {
return Math.toIntExact(query()
.eq(CpRelationship::getUserId, userId)
.eq(CpRelationship::getStatus, CpRelationshipStatus.NORMAL.name())
.eq(CpRelationship::getRelationType, CpRelationshipType.normalize(relationType))
.count());
}
@Override @Override
public CpRelationship getDismissingCp(Long userId, Long cpUserId) { public CpRelationship getDismissingCp(Long userId, Long cpUserId) {
return getDismissingCp(userId, cpUserId, CpRelationshipType.CP.name()); return getDismissingCp(userId, cpUserId, CpRelationshipType.CP.name());

View File

@ -15,18 +15,15 @@ public enum CpRelationshipType {
CP, CP,
/** /**
* Brothers, requires both users to be male. * Brothers.
*/ */
BROTHER, BROTHER,
/** /**
* Sisters, requires both users to be female. * Sisters.
*/ */
SISTERS; SISTERS;
private static final int FEMALE = 0;
private static final int MALE = 1;
public static CpRelationshipType of(String value) { public static CpRelationshipType of(String value) {
if (StringUtils.isBlank(value)) { if (StringUtils.isBlank(value)) {
return CP; return CP;
@ -48,8 +45,7 @@ public enum CpRelationshipType {
public boolean genderMatched(Integer sendUserSex, Integer acceptUserSex) { public boolean genderMatched(Integer sendUserSex, Integer acceptUserSex) {
return switch (this) { return switch (this) {
case CP -> !Objects.equals(sendUserSex, acceptUserSex); case CP -> !Objects.equals(sendUserSex, acceptUserSex);
case BROTHER -> Objects.equals(sendUserSex, MALE) && Objects.equals(acceptUserSex, MALE); case BROTHER, SISTERS -> true;
case SISTERS -> Objects.equals(sendUserSex, FEMALE) && Objects.equals(acceptUserSex, FEMALE);
}; };
} }

View File

@ -0,0 +1,30 @@
package com.red.circle.other.infra.enums.user.user;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class CpRelationshipTypeTest {
@Test
void cpRequiresDifferentGender() {
assertTrue(CpRelationshipType.CP.genderMatched(1, 0));
assertTrue(CpRelationshipType.CP.genderMatched(0, 1));
assertFalse(CpRelationshipType.CP.genderMatched(1, 1));
assertFalse(CpRelationshipType.CP.genderMatched(0, 0));
}
@Test
void brotherAndSistersDoNotRestrictGender() {
assertTrue(CpRelationshipType.BROTHER.genderMatched(1, 1));
assertTrue(CpRelationshipType.BROTHER.genderMatched(1, 0));
assertTrue(CpRelationshipType.BROTHER.genderMatched(0, 1));
assertTrue(CpRelationshipType.BROTHER.genderMatched(0, 0));
assertTrue(CpRelationshipType.SISTERS.genderMatched(1, 1));
assertTrue(CpRelationshipType.SISTERS.genderMatched(1, 0));
assertTrue(CpRelationshipType.SISTERS.genderMatched(0, 1));
assertTrue(CpRelationshipType.SISTERS.genderMatched(0, 0));
}
}

View File

@ -6,10 +6,12 @@ import com.red.circle.other.app.inner.convertor.material.GiftInnerConvertor;
import com.red.circle.other.app.inner.service.material.gift.GiftConfigClientService; import com.red.circle.other.app.inner.service.material.gift.GiftConfigClientService;
import com.red.circle.other.infra.database.cache.service.other.GameLuckyGiftCacheService; import com.red.circle.other.infra.database.cache.service.other.GameLuckyGiftCacheService;
import com.red.circle.other.infra.database.cache.service.other.GiftCacheService; import com.red.circle.other.infra.database.cache.service.other.GiftCacheService;
import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig; import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig;
import com.red.circle.other.infra.database.rds.service.gift.GiftConfigService; import com.red.circle.other.infra.database.rds.service.gift.GiftConfigService;
import com.red.circle.other.infra.database.rds.service.gift.GiftWallService; import com.red.circle.other.infra.database.rds.service.gift.GiftWallService;
import com.red.circle.other.inner.model.cmd.sys.GiftQryCmd; import com.red.circle.other.infra.enums.user.user.CpRelationshipType;
import com.red.circle.other.inner.enums.material.GiftTabEnum;
import com.red.circle.other.inner.model.cmd.sys.GiftQryCmd;
import com.red.circle.other.inner.model.dto.gift.UserGiftWallDTO; import com.red.circle.other.inner.model.dto.gift.UserGiftWallDTO;
import com.red.circle.other.inner.model.dto.material.GiftConfigDTO; import com.red.circle.other.inner.model.dto.material.GiftConfigDTO;
import java.sql.Timestamp; import java.sql.Timestamp;
@ -65,10 +67,11 @@ public class GiftConfigClientServiceImpl implements GiftConfigClientService {
return pageResult.convert(giftInnerConvertor::toGiftConfigDTO); return pageResult.convert(giftInnerConvertor::toGiftConfigDTO);
} }
@Override @Override
public Boolean updateGift(GiftConfigDTO giftConfigDTO) { public Boolean updateGift(GiftConfigDTO giftConfigDTO) {
return giftConfigService.updateGift(giftInnerConvertor.toGiftConfig(giftConfigDTO)); return giftConfigService.updateGift(
} normalizeCpRelationType(giftInnerConvertor.toGiftConfig(giftConfigDTO)));
}
@Override @Override
public void removeGiftCache() { public void removeGiftCache() {
@ -80,10 +83,20 @@ public class GiftConfigClientServiceImpl implements GiftConfigClientService {
gameLuckyGiftCacheService.removeRemainingInventory(id, standardId); gameLuckyGiftCacheService.removeRemainingInventory(id, standardId);
} }
@Override @Override
public Boolean saveGift(GiftConfigDTO giftConfig) { public Boolean saveGift(GiftConfigDTO giftConfig) {
return giftConfigService.save(giftInnerConvertor.toGiftConfig(giftConfig)); return giftConfigService.save(normalizeCpRelationType(giftInnerConvertor.toGiftConfig(giftConfig)));
} }
private GiftConfig normalizeCpRelationType(GiftConfig giftConfig) {
if (Objects.isNull(giftConfig)) {
return null;
}
if (!Objects.equals(giftConfig.getGiftTab(), GiftTabEnum.CP.name())) {
return giftConfig.setCpRelationType(null);
}
return giftConfig.setCpRelationType(CpRelationshipType.normalize(giftConfig.getCpRelationType()));
}
@Override @Override
public Boolean switchDelStatus(Long id, Boolean status) { public Boolean switchDelStatus(Long id, Boolean status) {

View File

@ -367,24 +367,23 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
List<TeamSalaryPayment> cyclePayments = listCyclePayments(salaryPaymentMap, List<TeamSalaryPayment> cyclePayments = listCyclePayments(salaryPaymentMap,
target.getUserId(), billBelong); target.getUserId(), billBelong);
PaymentSummary paymentSummary = summarizePayments(cyclePayments);
PaymentSummary receivedPaymentSummary = summarizeReceivedPayments( PaymentSummary receivedPaymentSummary = summarizeReceivedPayments(
new ArrayList<>(salaryPaymentMap.values()), target.getUserId()); new ArrayList<>(salaryPaymentMap.values()), target.getUserId());
BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary()); BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary());
BigDecimal expectedAgentSalary = salaryValue(billTarget.getOwnSalary()); BigDecimal expectedAgentSalary = salaryValue(billTarget.getOwnSalary());
BigDecimal expectedSalary = expectedTotalSalary(billTarget); BigDecimal expectedSalary = expectedTotalSalary(billTarget);
BigDecimal payableSalary = expectedSalary.subtract(paymentSummary.getTotalIssuedSalary()) BigDecimal payableSalary = expectedSalary.subtract(receivedPaymentSummary.getTotalIssuedSalary())
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal payableMemberSalary = expectedMemberSalary BigDecimal payableMemberSalary = expectedMemberSalary
.subtract(paymentSummary.getMemberIssuedSalary()) .subtract(receivedPaymentSummary.getMemberIssuedSalary())
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal payableAgentSalary = expectedAgentSalary BigDecimal payableAgentSalary = expectedAgentSalary
.subtract(paymentSummary.getAgentIssuedSalary()) .subtract(receivedPaymentSummary.getAgentIssuedSalary())
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
boolean currentLevelSettled = areCurrentRolesSettled(cyclePayments, target.getUserId(), region, boolean currentLevelSettled = areCurrentRolesSettled(cyclePayments, target.getUserId(), region,
billTarget, payableSalary); billTarget, payableSalary);
List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget, List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget,
cyclePayments, target.getUserId(), region, paymentSummary, payableSalary, cyclePayments, target.getUserId(), region, receivedPaymentSummary, payableSalary,
currentLevelSettled); currentLevelSettled);
TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO() TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO()

View File

@ -151,10 +151,10 @@ public class CpLocalFlowSmokeTest {
UserPair cpPair = findPair(candidates, 1, 0, "CP"); UserPair cpPair = findPair(candidates, 1, 0, "CP");
runCpGiftApplyBlessDismissFlow(cpPair); runCpGiftApplyBlessDismissFlow(cpPair);
UserPair brotherPair = findPair(candidates, 1, 1, "BROTHER"); UserPair brotherPair = findPair(candidates, 1, 0, "BROTHER");
runNoChargeApplyAndAgreeFlow(brotherPair, "BROTHER"); runNoChargeApplyAndAgreeFlow(brotherPair, "BROTHER");
UserPair sistersPair = findPair(candidates, 0, 0, "SISTERS"); UserPair sistersPair = findPair(candidates, 0, 1, "SISTERS");
runNoChargeApplyAndAgreeFlow(sistersPair, "SISTERS"); runNoChargeApplyAndAgreeFlow(sistersPair, "SISTERS");
} }
@ -197,6 +197,36 @@ public class CpLocalFlowSmokeTest {
cpApplyService.getId(sameGenderPair.acceptUserId, sameGenderPair.sendUserId, "CP")); cpApplyService.getId(sameGenderPair.acceptUserId, sameGenderPair.sendUserId, "CP"));
} }
@Test
public void cpGiftAutoApplyUsesGiftRelationType() {
List<BaseInfo> candidates = baseInfoService.query()
.select(BaseInfo::getId, BaseInfo::getUserSex, BaseInfo::getOriginSys,
BaseInfo::getCountryCode)
.eq(BaseInfo::getOriginSys, SYS_ORIGIN)
.in(BaseInfo::getUserSex, Arrays.asList(0, 1))
.orderByDesc(BaseInfo::getId)
.last("LIMIT 500")
.list();
assertFalse("local LIKEI candidates should not be empty", candidates.isEmpty());
assertGiftAutoApplyRelationType(findPair(candidates, 1, 0, "BROTHER"), "BROTHER");
assertGiftAutoApplyRelationType(findPair(candidates, 0, 1, "SISTERS"), "SISTERS");
}
private void assertGiftAutoApplyRelationType(UserPair pair, String relationType) {
boolean created = sendCpApplyCmdExe.autoCreateFromCpGift(pair.sendUserId,
pair.acceptUserId, SYS_ORIGIN, relationType);
assertTrue(relationType + " gift should auto create apply", created);
Long applyId = cpApplyService.getId(pair.acceptUserId, pair.sendUserId, relationType);
assertNotNull(relationType + " auto apply id", applyId);
createdApplyIds.add(applyId);
CpApply apply = cpApplyService.getById(applyId);
assertEquals(relationType, apply.getRelationType());
assertEquals(0, BigDecimal.ZERO.compareTo(apply.getApplyConsumeGold()));
}
private void runCpGiftApplyBlessDismissFlow(UserPair pair) throws Exception { private void runCpGiftApplyBlessDismissFlow(UserPair pair) throws Exception {
boolean created = sendCpApplyCmdExe.autoCreateFromCpGift(pair.sendUserId, pair.acceptUserId, boolean created = sendCpApplyCmdExe.autoCreateFromCpGift(pair.sendUserId, pair.acceptUserId,
SYS_ORIGIN); SYS_ORIGIN);
@ -394,14 +424,14 @@ public class CpLocalFlowSmokeTest {
String relationType) { String relationType) {
for (BaseInfo send : candidates) { for (BaseInfo send : candidates) {
if (!Objects.equals(send.getUserSex(), sendSex) || hasWaitApply(send.getId(), relationType) if (!Objects.equals(send.getUserSex(), sendSex) || hasWaitApply(send.getId(), relationType)
|| cpRelationshipService.countCp(send.getId()) >= 4) { || cpRelationshipService.countCp(send.getId(), relationType) >= relationMaxCount(relationType)) {
continue; continue;
} }
for (BaseInfo accept : candidates) { for (BaseInfo accept : candidates) {
if (Objects.equals(send.getId(), accept.getId()) if (Objects.equals(send.getId(), accept.getId())
|| !Objects.equals(accept.getUserSex(), acceptSex) || !Objects.equals(accept.getUserSex(), acceptSex)
|| hasReceivedWaitApply(accept.getId(), relationType) || hasReceivedWaitApply(accept.getId(), relationType)
|| cpRelationshipService.countCp(accept.getId()) >= 4 || cpRelationshipService.countCp(accept.getId(), relationType) >= relationMaxCount(relationType)
|| cpRelationshipService.existsCp(send.getId(), accept.getId(), relationType)) { || cpRelationshipService.existsCp(send.getId(), accept.getId(), relationType)) {
continue; continue;
} }
@ -417,6 +447,10 @@ public class CpLocalFlowSmokeTest {
return cpApplyService.existsNotProcessSendApply(sendUserId, relationType); return cpApplyService.existsNotProcessSendApply(sendUserId, relationType);
} }
private int relationMaxCount(String relationType) {
return Objects.equals("CP", relationType) ? 1 : 3;
}
private boolean hasReceivedWaitApply(Long acceptUserId, String relationType) { private boolean hasReceivedWaitApply(Long acceptUserId, String relationType) {
return cpApplyService.query() return cpApplyService.query()
.eq(CpApply::getAcceptApplyUserId, acceptUserId) .eq(CpApply::getAcceptApplyUserId, acceptUserId)

View File

@ -0,0 +1,7 @@
ALTER TABLE `sys_gift_config`
ADD COLUMN `cp_relation_type` varchar(32) DEFAULT NULL COMMENT 'CP礼物关系类型CP/BROTHER/SISTERS' AFTER `gift_tab`;
UPDATE `sys_gift_config`
SET `cp_relation_type` = 'CP'
WHERE `gift_tab` = 'CP'
AND (`cp_relation_type` IS NULL OR `cp_relation_type` = '');