feat: 取消设备维度的主播/代理拦截

业务调整:checkDeviceFingerprintHasAnchor 不再按设备指纹关联判断同设备
是否已有主播/代理,8 个业务拦截点(BD邀请代理/接受邀请建团/代理邀请主播/
申请加入公会/后台加成员等)设备维度全部放行。

保留运营手动黑白名单(user_expand.device_list_block):BLOCK 仍强制拦截。
移除随之失效的指纹关联查询与白名单匹配逻辑及未使用依赖。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hy001 2026-07-10 19:37:19 +08:00
parent 48d2090458
commit 13a573083e
2 changed files with 18 additions and 165 deletions

View File

@ -1,11 +1,5 @@
package com.red.circle.other.infra.gateway.user;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
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.mongo.entity.team.team.TeamMember;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberService;
import com.red.circle.other.infra.database.rds.service.sys.EnumConfigService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.infra.database.rds.service.user.user.ExpandService;
import com.red.circle.tool.core.text.StringUtils;
@ -15,7 +9,6 @@ import com.red.circle.other.infra.database.rds.service.user.device.DeviceRegiste
import com.red.circle.other.infra.database.rds.service.user.device.IpRegisterQuantityService;
import com.red.circle.other.inner.model.cmd.user.IncrDeviceRegisterCmd;
import com.red.circle.other.inner.model.cmd.user.device.AllowDeviceRegisterCmd;
import com.red.circle.other.inner.enums.config.EnumConfigKey;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@ -31,9 +24,6 @@ public class RegisterDeviceGatewayImpl implements RegisterDeviceGateway {
private final IpRegisterQuantityService ipRegisterQuantityService;
private final DeviceRegisterQuantityService deviceRegisterQuantityService;
private final LatestMobileDeviceService latestMobileDeviceService;
private final UserProfileGateway userProfileGateway;
private final TeamMemberService teamMemberService;
private final EnumConfigCacheService enumConfigCacheService;
private final ExpandService expandService;
/**
@ -95,71 +85,13 @@ public class RegisterDeviceGatewayImpl implements RegisterDeviceGateway {
return false;
}
// 运营手动黑白名单仍然生效BLOCK 强制拦截显式放行返回 false
String status = checkDeviceListStatus(userId);
if (status != null) {
return "BLOCK".equals(status);
}
UserProfile userProfile = userProfileGateway.getByUserId(userId);
if (userProfile == null) {
return false;
}
String fingerprint = latestMobileDeviceService.getDeviceFingerprintAndIPByUserId(userId);
if (StringUtils.isBlank(fingerprint)) {
return false;
}
// 跳过白名单用户
String whitelistConfig = enumConfigCacheService.getValue(
EnumConfigKey.DEVICE_REGISTER_WHITELIST, userProfile.getOriginSys());
if (matchDeviceWhitelist(whitelistConfig, String.valueOf(userProfile.getAccount()))) {
return false;
}
java.util.List<Long> userIds = latestMobileDeviceService.listUserIdsByDeviceFingerprint(
fingerprint, userProfile.getOriginSys());
if (userIds == null || userIds.isEmpty()) {
return false;
}
for (Long otherUserId : userIds) {
if (java.util.Objects.equals(otherUserId, userId)) {
continue;
}
// 判断是否为主播检查用户是否为团队成员
TeamMember teamMember =
teamMemberService.getByMemberId(otherUserId);
if (teamMember == null) {
continue;
}
// 同设备账号已被封禁checkAccountArchive或已注销profile null不再占用该设备的主播/代理名额
UserProfile otherProfile = userProfileGateway.getByUserId(otherUserId);
if (otherProfile == null || otherProfile.checkAccountArchive()) {
continue;
}
return true;
}
return false;
}
/**
* 设备白名单按分隔符切分后精确比对账号避免子串误命中如账号 123 被白名单 51234 命中.
*/
private boolean matchDeviceWhitelist(String whitelistConfig, String account) {
if (StringUtils.isBlank(whitelistConfig) || StringUtils.isBlank(account)) {
return false;
}
for (String item : whitelistConfig.split("[,;\\s]+")) {
if (account.equals(item.trim())) {
return true;
}
}
// 业务调整不再按设备指纹限制同设备成为主播/代理设备维度全部放行
return false;
}

View File

@ -2,135 +2,56 @@ package com.red.circle.other.infra.gateway.user;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.red.circle.common.business.enums.AccountStatusEnum;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
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.mongo.entity.team.team.TeamMember;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberService;
import com.red.circle.other.infra.database.rds.service.user.device.DeviceRegisterQuantityService;
import com.red.circle.other.infra.database.rds.service.user.device.IpRegisterQuantityService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.infra.database.rds.service.user.user.ExpandService;
import com.red.circle.tool.core.date.TimestampUtils;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class RegisterDeviceGatewayImplTest {
private static final Long SELF_ID = 100L;
private static final Long OTHER_ID = 200L;
private static final String FINGERPRINT = "Android|Infinix X6871|1.2.3.4";
private static final Long USER_ID = 100L;
private IpRegisterQuantityService ipRegisterQuantityService;
private DeviceRegisterQuantityService deviceRegisterQuantityService;
private LatestMobileDeviceService latestMobileDeviceService;
private UserProfileGateway userProfileGateway;
private TeamMemberService teamMemberService;
private EnumConfigCacheService enumConfigCacheService;
private ExpandService expandService;
private RegisterDeviceGatewayImpl gateway;
@BeforeEach
void setUp() {
ipRegisterQuantityService = mock(IpRegisterQuantityService.class);
deviceRegisterQuantityService = mock(DeviceRegisterQuantityService.class);
latestMobileDeviceService = mock(LatestMobileDeviceService.class);
userProfileGateway = mock(UserProfileGateway.class);
teamMemberService = mock(TeamMemberService.class);
enumConfigCacheService = mock(EnumConfigCacheService.class);
expandService = mock(ExpandService.class);
gateway = new RegisterDeviceGatewayImpl(
ipRegisterQuantityService,
deviceRegisterQuantityService,
mock(IpRegisterQuantityService.class),
mock(DeviceRegisterQuantityService.class),
latestMobileDeviceService,
userProfileGateway,
teamMemberService,
enumConfigCacheService,
expandService);
when(expandService.getDeviceListBlock(anyLong())).thenReturn(null);
when(userProfileGateway.getByUserId(SELF_ID)).thenReturn(profile(SELF_ID, "1001", AccountStatusEnum.NORMAL, 0));
when(latestMobileDeviceService.getDeviceFingerprintAndIPByUserId(SELF_ID)).thenReturn(FINGERPRINT);
when(latestMobileDeviceService.listUserIdsByDeviceFingerprint(FINGERPRINT, "LIKEI"))
.thenReturn(List.of(SELF_ID, OTHER_ID));
when(teamMemberService.getByMemberId(OTHER_ID)).thenReturn(new TeamMember().setMemberId(OTHER_ID));
}
private UserProfile profile(Long id, String account, AccountStatusEnum status, int freezeDays) {
UserProfile profile = new UserProfile();
profile.setId(id);
profile.setAccount(account);
profile.setOriginSys("LIKEI");
profile.setAccountStatus(status.name());
profile.setFreezingTime(freezeDays == 0 ? TimestampUtils.now() : TimestampUtils.nowPlusDays(freezeDays));
return profile;
}
@Test
void blocksWhenSameDeviceHasActiveTeamMember() {
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(profile(OTHER_ID, "1002", AccountStatusEnum.NORMAL, 0));
void deviceFingerprintNoLongerBlocks() {
// 业务调整设备维度全部放行不再查询指纹关联
when(expandService.getDeviceListBlock(USER_ID)).thenReturn(null);
assertTrue(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
assertFalse(gateway.checkDeviceFingerprintHasAnchor(USER_ID));
verifyNoInteractions(latestMobileDeviceService);
}
@Test
void exemptsArchivedAccountOnSameDevice() {
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(profile(OTHER_ID, "1002", AccountStatusEnum.ARCHIVE, 365));
void manualDeviceListStillWorks() {
when(expandService.getDeviceListBlock(USER_ID)).thenReturn(Boolean.TRUE);
assertTrue(gateway.checkDeviceFingerprintHasAnchor(USER_ID));
assertFalse(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
when(expandService.getDeviceListBlock(USER_ID)).thenReturn(Boolean.FALSE);
assertFalse(gateway.checkDeviceFingerprintHasAnchor(USER_ID));
}
@Test
void exemptsArchiveDeviceAccountOnSameDevice() {
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(profile(OTHER_ID, "1002", AccountStatusEnum.ARCHIVE_DEVICE, 0));
assertFalse(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
}
@Test
void exemptsDeletedAccountOnSameDevice() {
// 已注销账号 getByUserId 返回 null逻辑删过滤不应永久占用设备名额
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(null);
assertFalse(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
}
@Test
void expiredArchiveStillBlocks() {
// ARCHIVE 但冻结期已过 = 已自动解封重新占用名额
UserProfile expired = profile(OTHER_ID, "1002", AccountStatusEnum.ARCHIVE, 0);
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(expired);
assertTrue(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
}
@Test
void whitelistMatchesExactAccountOnly() {
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(profile(OTHER_ID, "1002", AccountStatusEnum.NORMAL, 0));
// 子串不再误命中白名单 51001 不应豁免账号 1001
when(enumConfigCacheService.getValue(com.red.circle.other.inner.enums.config.EnumConfigKey.DEVICE_REGISTER_WHITELIST, "LIKEI"))
.thenReturn("51001,9999");
assertTrue(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
// 精确命中豁免
when(enumConfigCacheService.getValue(com.red.circle.other.inner.enums.config.EnumConfigKey.DEVICE_REGISTER_WHITELIST, "LIKEI"))
.thenReturn("51001,1001");
assertFalse(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
}
@Test
void manualDeviceListOverridesFingerprintLogic() {
when(expandService.getDeviceListBlock(SELF_ID)).thenReturn(Boolean.TRUE);
assertTrue(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
when(expandService.getDeviceListBlock(SELF_ID)).thenReturn(Boolean.FALSE);
assertFalse(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
void nullUserIdIsAllowed() {
assertFalse(gateway.checkDeviceFingerprintHasAnchor(null));
}
}