fix: 设备指纹代理限制的封禁豁免补到真正的调用链上
上次修复(0a29464)只改了 other-inner-endpoint 的 RegisterDeviceClientServiceImpl, 但全部 8 个业务拦截点(BD邀请代理/接受邀请建团/代理邀请主播/申请加入公会/后台加成员等) 走的都是 other-infrastructure 的 RegisterDeviceGatewayImpl(领域网关唯一实现), 该实现没有封禁豁免,导致线上 BD 邀请仍报 6054。 本次修复: 1. RegisterDeviceGatewayImpl 循环内补豁免:同设备账号已封禁(checkAccountArchive) 或已注销(profile 为 null,逻辑删过滤)的,不再占用设备主播/代理名额; 2. 设备白名单改为按分隔符切分后精确比对账号,修复子串误命中(账号123被白名单51234命中); 3. RegisterDeviceClientServiceImpl 的重复实现改为单行委托领域网关,消除双份逻辑漂移; 4. 新增 RegisterDeviceGatewayImplTest 覆盖豁免/白名单/手动黑白名单 7 个场景。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0a29464a82
commit
431f73469e
@ -1,6 +1,7 @@
|
|||||||
package com.red.circle.other.infra.gateway.user;
|
package com.red.circle.other.infra.gateway.user;
|
||||||
|
|
||||||
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
|
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.cache.service.other.EnumConfigCacheService;
|
||||||
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamMember;
|
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.mongo.service.team.team.TeamMemberService;
|
||||||
@ -99,7 +100,7 @@ public class RegisterDeviceGatewayImpl implements RegisterDeviceGateway {
|
|||||||
return "BLOCK".equals(status);
|
return "BLOCK".equals(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
com.red.circle.other.domain.model.user.UserProfile userProfile = userProfileGateway.getByUserId(userId);
|
UserProfile userProfile = userProfileGateway.getByUserId(userId);
|
||||||
if (userProfile == null) {
|
if (userProfile == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -112,7 +113,7 @@ public class RegisterDeviceGatewayImpl implements RegisterDeviceGateway {
|
|||||||
// 跳过白名单用户
|
// 跳过白名单用户
|
||||||
String whitelistConfig = enumConfigCacheService.getValue(
|
String whitelistConfig = enumConfigCacheService.getValue(
|
||||||
EnumConfigKey.DEVICE_REGISTER_WHITELIST, userProfile.getOriginSys());
|
EnumConfigKey.DEVICE_REGISTER_WHITELIST, userProfile.getOriginSys());
|
||||||
if (StringUtils.isNotBlank(whitelistConfig) && whitelistConfig.contains(String.valueOf(userProfile.getAccount()))) {
|
if (matchDeviceWhitelist(whitelistConfig, String.valueOf(userProfile.getAccount()))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,11 +132,34 @@ public class RegisterDeviceGatewayImpl implements RegisterDeviceGateway {
|
|||||||
// 判断是否为主播:检查用户是否为团队成员
|
// 判断是否为主播:检查用户是否为团队成员
|
||||||
TeamMember teamMember =
|
TeamMember teamMember =
|
||||||
teamMemberService.getByMemberId(otherUserId);
|
teamMemberService.getByMemberId(otherUserId);
|
||||||
if (teamMember != null) {
|
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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,136 @@
|
|||||||
|
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.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 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,
|
||||||
|
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));
|
||||||
|
|
||||||
|
assertTrue(gateway.checkDeviceFingerprintHasAnchor(SELF_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exemptsArchivedAccountOnSameDevice() {
|
||||||
|
when(userProfileGateway.getByUserId(OTHER_ID)).thenReturn(profile(OTHER_ID, "1002", AccountStatusEnum.ARCHIVE, 365));
|
||||||
|
|
||||||
|
assertFalse(gateway.checkDeviceFingerprintHasAnchor(SELF_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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,17 +7,14 @@ import com.red.circle.other.app.inner.convertor.user.RegisterDeviceInnerConverto
|
|||||||
import com.red.circle.other.app.inner.convertor.user.UserProfileInnerConvertor;
|
import com.red.circle.other.app.inner.convertor.user.UserProfileInnerConvertor;
|
||||||
import com.red.circle.other.app.inner.service.user.device.RegisterDeviceClientService;
|
import com.red.circle.other.app.inner.service.user.device.RegisterDeviceClientService;
|
||||||
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
|
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
|
||||||
|
import com.red.circle.other.domain.gateway.user.ability.RegisterDeviceGateway;
|
||||||
import com.red.circle.other.domain.model.user.UserProfile;
|
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.entity.user.user.LatestMobileDevice;
|
import com.red.circle.other.infra.database.rds.entity.user.user.LatestMobileDevice;
|
||||||
import com.red.circle.other.infra.database.rds.service.user.device.ArchiveDeviceService;
|
import com.red.circle.other.infra.database.rds.service.user.device.ArchiveDeviceService;
|
||||||
import com.red.circle.other.infra.database.rds.service.user.device.DeviceRegisterQuantityService;
|
import com.red.circle.other.infra.database.rds.service.user.device.DeviceRegisterQuantityService;
|
||||||
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
|
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
|
||||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||||
import com.red.circle.tool.core.text.StringUtils;
|
import com.red.circle.tool.core.text.StringUtils;
|
||||||
import com.red.circle.other.inner.enums.config.EnumConfigKey;
|
|
||||||
import com.red.circle.other.inner.model.cmd.user.device.ArchiveDeviceQryCmd;
|
import com.red.circle.other.inner.model.cmd.user.device.ArchiveDeviceQryCmd;
|
||||||
import com.red.circle.other.inner.model.cmd.user.device.DeviceRegisterQuantityQryCmd;
|
import com.red.circle.other.inner.model.cmd.user.device.DeviceRegisterQuantityQryCmd;
|
||||||
import com.red.circle.other.inner.model.cmd.user.device.PageLatestMobileDeviceQryCmd;
|
import com.red.circle.other.inner.model.cmd.user.device.PageLatestMobileDeviceQryCmd;
|
||||||
@ -53,8 +50,7 @@ public class RegisterDeviceClientServiceImpl implements RegisterDeviceClientServ
|
|||||||
private final LatestMobileDeviceService latestMobileDeviceService;
|
private final LatestMobileDeviceService latestMobileDeviceService;
|
||||||
private final RegisterDeviceInnerConvertor registerDeviceInnerConvertor;
|
private final RegisterDeviceInnerConvertor registerDeviceInnerConvertor;
|
||||||
private final DeviceRegisterQuantityService deviceRegisterQuantityService;
|
private final DeviceRegisterQuantityService deviceRegisterQuantityService;
|
||||||
private final TeamMemberService teamMemberService;
|
private final RegisterDeviceGateway registerDeviceGateway;
|
||||||
private final EnumConfigCacheService enumConfigCacheService;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean existsDevice(String deviceNo, String sysOrigin) {
|
public Boolean existsDevice(String deviceNo, String sysOrigin) {
|
||||||
@ -243,55 +239,7 @@ public class RegisterDeviceClientServiceImpl implements RegisterDeviceClientServ
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean checkDeviceFingerprintHasAnchor(Long userId) {
|
public Boolean checkDeviceFingerprintHasAnchor(Long userId) {
|
||||||
if (userId == null) {
|
// 统一委托领域网关实现,避免两份指纹校验逻辑漂移(封禁/注销豁免、白名单、手动黑白名单前置均以网关为准)。
|
||||||
return false;
|
return registerDeviceGateway.checkDeviceFingerprintHasAnchor(userId);
|
||||||
}
|
|
||||||
|
|
||||||
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 (StringUtils.isNotBlank(whitelistConfig) && whitelistConfig.contains(String.valueOf(userProfile.getAccount()))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> userIds = latestMobileDeviceService.listUserIdsByDeviceFingerprint(
|
|
||||||
fingerprint, userProfile.getOriginSys());
|
|
||||||
|
|
||||||
if (CollectionUtils.isEmpty(userIds)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Long otherUserId : userIds) {
|
|
||||||
if (Objects.equals(otherUserId, userId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为主播:检查用户是否为团队成员
|
|
||||||
TeamMember teamMember =
|
|
||||||
teamMemberService.getByMemberId(otherUserId);
|
|
||||||
if (teamMember == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同设备账号已被封禁的,不再占用该设备的主播/代理名额
|
|
||||||
UserProfile otherProfile = userProfileGateway.getByUserId(otherUserId);
|
|
||||||
if (otherProfile != null && otherProfile.checkAccountArchive()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user