import com.red.circle.OtherServiceApplication; import com.red.circle.common.business.enums.ConsoleAccountStatusEnum; import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.other.app.dto.h5.UserIdentityVO; import com.red.circle.other.app.service.user.user.AppUserDataViolationService; import com.red.circle.other.infra.database.rds.entity.sys.Administrator; import com.red.circle.other.infra.database.rds.service.sys.AdministratorAuthService; import com.red.circle.other.infra.database.rds.service.sys.AdministratorService; import com.red.circle.other.inner.endpoint.team.bd.BdTeamInfoClient; import com.red.circle.other.inner.endpoint.team.bd.BdTeamLeaderClient; import com.red.circle.other.inner.endpoint.team.target.TeamProfileClient; import com.red.circle.other.inner.enums.sys.appmanager.RoomRolesEnum; import com.red.circle.other.inner.enums.team.TeamMemberRole; import com.red.circle.other.inner.model.cmd.user.ApprovalAccountCmd; import com.red.circle.other.inner.model.dto.agency.agency.TeamMemberDTO; import com.red.circle.wallet.inner.endpoint.freight.FreightGoldClient; import dto.BanResultDTO; import dto.DeviceFingerprintDTO; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import util.ExcelUtil; import java.util.*; import java.util.stream.Collectors; @Slf4j @SpringBootTest(classes = OtherServiceApplication.class) @RunWith(SpringRunner.class) public class UserDataViolationTest { @Autowired private AppUserDataViolationService userDataViolationService; @Autowired private TeamProfileClient teamProfileClient; @Autowired private FreightGoldClient freightGoldClient; @Autowired private BdTeamInfoClient bdTeamInfoClient; @Autowired private AdministratorService administratorService; @Autowired private AdministratorAuthService administratorAuthService; @Autowired private BdTeamLeaderClient bdTeamLeaderClient; /** * 测试 Excel 导入功能 * 读取从数据库导出的设备指纹数据 */ @Test public void testImportDeviceFingerprint() { // TODO: 修改为你实际的 Excel 文件路径 String inputFilePath = "C:\\Users\\Administrator\\Documents\\DataGripSql\\11.11同设备指纹用户统计英文版.xlsx"; try { List dataList = ExcelUtil.importDeviceFingerprint(inputFilePath); log.info("==================== 导入测试结果 ===================="); log.info("总共导入 {} 条设备指纹数据", dataList.size()); // 打印前 5 条数据作为示例 int displayCount = Math.min(5, dataList.size()); log.info("显示前 {} 条数据:", displayCount); for (int i = 0; i < displayCount; i++) { DeviceFingerprintDTO dto = dataList.get(i); log.info("数据 {}: 设备指纹={}, 用户数={}, 用户IDs={}", i + 1, dto.getDeviceFingerprint(), dto.getUserCount(), dto.getUserIds()); } log.info("=================================================="); } catch (Exception e) { log.error("导入测试失败", e); } } /** * 测试 Excel 导出功能 * 生成示例封禁结果数据并导出 */ @Test public void testExportBanResults() { // 创建示例数据 List results = new ArrayList<>(); // 示例1:被封禁的用户 BanResultDTO banned = new BanResultDTO(); banned.setUserId(1954059793963393025L); banned.setDeviceFingerprint("iOS|iPhone 14|192.168.1.100"); banned.setUserIdentity("主播"); banned.setBanned("是"); banned.setReason("同设备指纹下存在多个主播"); banned.setStatus("封禁成功"); banned.setRemark("设备指纹下共2个主播"); results.add(banned); // 示例2:不封禁的用户(管理员) BanResultDTO notBanned1 = new BanResultDTO(); notBanned1.setUserId(1000000000000000001L); notBanned1.setDeviceFingerprint("Android|Xiaomi 13|192.168.1.101"); notBanned1.setUserIdentity("超级管理员"); notBanned1.setBanned("否"); notBanned1.setReason("管理员身份,不封禁"); notBanned1.setStatus("跳过"); notBanned1.setRemark("-"); results.add(notBanned1); // 示例3:不封禁的用户(非主播) BanResultDTO notBanned2 = new BanResultDTO(); notBanned2.setUserId(2000000000000000001L); notBanned2.setDeviceFingerprint("iOS|iPhone 15|192.168.1.102"); notBanned2.setUserIdentity("普通用户"); notBanned2.setBanned("否"); notBanned2.setReason("非主播身份,不封禁"); notBanned2.setStatus("跳过"); notBanned2.setRemark("-"); results.add(notBanned2); // TODO: 修改为你想要保存的文件路径 String outputFilePath = "D:\\ban_results_test.xlsx"; try { ExcelUtil.exportBanResults(results, outputFilePath); log.info("==================== 导出测试结果 ===================="); log.info("成功导出 {} 条封禁结果数据", results.size()); log.info("导出文件路径: {}", outputFilePath); log.info("请打开 Excel 文件检查导出结果"); log.info("=================================================="); } catch (Exception e) { log.error("导出测试失败", e); } } @Test public void testUnBanUsers() { String inputFilePath = "C:\\Users\\Administrator\\Documents\\DataGripSql\\11.11同设备指纹用户统计英文版 - 副本.xlsx"; String outputFilePath = "D:\\ban_results_" + System.currentTimeMillis() + ".xlsx"; List deviceData = ExcelUtil.importDeviceFingerprint(inputFilePath); for (DeviceFingerprintDTO device : deviceData) { List userIds = parseUserIds(device.getUserIds()); for (Long userId : userIds) { if (userId == 1963790037740466178L) { System.out.println(); } // 执行解封 ApprovalAccountCmd cmd = new ApprovalAccountCmd(); cmd.setAccountStatusEnum(ConsoleAccountStatusEnum.UNTIE_DEVICE_AND_ACCOUNT); cmd.setDescription("1"); cmd.setBeApprovalUserId(userId); cmd.setReqUserId(userId); userDataViolationService.updateAccountStatus(cmd); } } } /** * 批量用户封禁(完整流程) * 1. 导入设备指纹数据 * 2. 判断用户身份 * 3. 执行封禁操作 * 4. 导出封禁结果 */ @Test public void batchBanUsers() { // TODO: 修改为你实际的文件路径 String inputFilePath = "C:\\Users\\Administrator\\Documents\\DataGripSql\\11.11同设备指纹用户统计英文版.xlsx"; String outputFilePath = "D:\\ban_results_" + System.currentTimeMillis() + ".xlsx"; try { log.info("==================== 开始批量封禁流程 ===================="); // 步骤1: 导入设备指纹数据 log.info("步骤1: 导入设备指纹数据..."); List deviceData = ExcelUtil.importDeviceFingerprint(inputFilePath); log.info("成功导入 {} 条设备指纹数据", deviceData.size()); // 步骤2: 处理每个设备指纹的用户 log.info("步骤2: 开始处理用户封禁逻辑..."); List allResults = new ArrayList<>(); int processedCount = 0; for (DeviceFingerprintDTO device : deviceData) { processedCount++; log.info("处理进度: {}/{} - 设备指纹: {}", processedCount, deviceData.size(), device.getDeviceFingerprint()); // 解析用户ID列表 List userIds = parseUserIds(device.getUserIds()); log.info(" 该设备指纹下有 {} 个用户", userIds.size()); // 判断该设备指纹下的主播数量 List anchorUserIds = new ArrayList<>(); Map identityMap = new HashMap<>(); for (Long userId : userIds) { try { UserIdentityVO identity = getUserIdentity(userId); identityMap.put(userId, identity); // 如果是主播,加入主播列表 if (Boolean.TRUE.equals(identity.getAnchor())) { anchorUserIds.add(userId); } } catch (Exception e) { log.error(" 获取用户 {} 身份失败", userId, e); } } log.info(" 该设备指纹下有 {} 个主播", anchorUserIds.size()); // 处理每个用户 for (Long userId : userIds) { BanResultDTO result = processUser(userId, device, identityMap.get(userId), anchorUserIds.size()); allResults.add(result); } } // 步骤3: 导出结果 log.info("步骤3: 导出封禁结果..."); ExcelUtil.exportBanResults(allResults, outputFilePath); // 步骤4: 统计结果 log.info("==================== 封禁流程完成 ===================="); long bannedCount = allResults.stream().filter(r -> "是".equals(r.getBanned())).count(); long skippedCount = allResults.stream().filter(r -> "否".equals(r.getBanned())).count(); long successCount = allResults.stream().filter(r -> "封禁成功".equals(r.getStatus())).count(); long failedCount = allResults.stream().filter(r -> "封禁失败".equals(r.getStatus())).count(); log.info("总处理用户数: {}", allResults.size()); log.info("封禁用户数: {}", bannedCount); log.info("跳过用户数: {}", skippedCount); log.info("封禁成功数: {}", successCount); log.info("封禁失败数: {}", failedCount); log.info("结果文件路径: {}", outputFilePath); log.info("===================================================="); } catch (Exception e) { log.error("批量封禁流程失败", e); } } /** * 处理单个用户 */ private BanResultDTO processUser(Long userId, DeviceFingerprintDTO device, UserIdentityVO identity, int anchorCount) { BanResultDTO result = new BanResultDTO(); result.setUserId(userId); result.setDeviceFingerprint(device.getDeviceFingerprint()); // 如果获取身份失败 if (identity == null) { result.setUserIdentity("未知"); result.setBanned("否"); result.setReason("获取用户身份失败"); result.setStatus("跳过"); result.setRemark("身份查询异常"); return result; } // 设置用户身份描述 result.setUserIdentity(buildIdentityDescription(identity)); // 判断是否需要封禁 boolean shouldBan = shouldBanUser(identity, anchorCount); if (!shouldBan) { result.setBanned("否"); result.setReason(getSkipReason(identity, anchorCount)); result.setStatus("跳过"); result.setRemark(String.format("设备指纹下共%d个用户,%d个主播", device.getUserCount(), anchorCount)); return result; } // 需要封禁,执行封禁操作 result.setBanned("是"); result.setReason(String.format("设备指纹下存在%d个主播,符合封禁条件", anchorCount)); result.setRemark(String.format("设备指纹下共%d个用户,%d个主播", device.getUserCount(), anchorCount)); try { // 执行封禁 ApprovalAccountCmd cmd = new ApprovalAccountCmd(); cmd.setAccountStatusEnum(ConsoleAccountStatusEnum.ARCHIVE); cmd.setDescription("Multi-device fingerprint blocking"); cmd.setBeApprovalUserId(userId); cmd.setReqUserId(userId); cmd.setDays(365); userDataViolationService.updateAccountStatus(cmd); result.setStatus("封禁成功"); log.info(" 用户 {} 封禁成功", userId); } catch (Exception e) { result.setStatus("封禁失败"); result.setRemark(result.getRemark() + " | 错误: " + e.getMessage()); log.error(" 用户 {} 封禁失败", userId, e); } return result; } /** * 判断是否应该封禁用户 * 封禁条件:同设备指纹下存在2个及以上主播,且该用户是主播 * 不封禁条件: * 1. 超级管理员 * 2. 管理员 * 3. BD Leader * 4. BD * 5. 金币代理(FreightAgent) * 6. 不是主播 */ private boolean shouldBanUser(UserIdentityVO identity, int anchorCount) { // 条件1: 设备指纹下必须有2个及以上主播 if (anchorCount < 2) { return false; } // 条件2: 该用户必须是主播 if (!Boolean.TRUE.equals(identity.getAnchor())) { return false; } // 条件3: 排除特殊身份用户 if (Boolean.TRUE.equals(identity.getSuperAdmin())) { return false; // 超级管理员不封禁 } if (Boolean.TRUE.equals(identity.getAdmin())) { return false; // 管理员不封禁 } if (Boolean.TRUE.equals(identity.getBdLeader())) { return false; // BD Leader 不封禁 } if (Boolean.TRUE.equals(identity.getBd())) { return false; // BD 不封禁 } if (Boolean.TRUE.equals(identity.getFreightAgent()) || Boolean.TRUE.equals(identity.getSuperFreightAgent())) { return false; // 金币代理不封禁 } // 通过所有检查,应该封禁 return true; } /** * 获取跳过原因 */ private String getSkipReason(UserIdentityVO identity, int anchorCount) { if (anchorCount < 2) { return "设备指纹下主播数量不足2个"; } if (!Boolean.TRUE.equals(identity.getAnchor())) { return "用户不是主播"; } if (Boolean.TRUE.equals(identity.getSuperAdmin())) { return "超级管理员身份,不封禁"; } if (Boolean.TRUE.equals(identity.getAdmin())) { return "管理员身份,不封禁"; } if (Boolean.TRUE.equals(identity.getBdLeader())) { return "BD Leader 身份,不封禁"; } if (Boolean.TRUE.equals(identity.getBd())) { return "BD 身份,不封禁"; } if (Boolean.TRUE.equals(identity.getFreightAgent()) || Boolean.TRUE.equals(identity.getSuperFreightAgent())) { return "金币代理身份,不封禁"; } return "其他原因"; } /** * 构建用户身份描述 */ private String buildIdentityDescription(UserIdentityVO identity) { List identities = new ArrayList<>(); if (Boolean.TRUE.equals(identity.getSuperAdmin())) { identities.add("超级管理员"); } if (Boolean.TRUE.equals(identity.getAdmin())) { identities.add("管理员"); } if (Boolean.TRUE.equals(identity.getManager())) { identities.add("经理"); } if (Boolean.TRUE.equals(identity.getBdLeader())) { identities.add("BD Leader"); } if (Boolean.TRUE.equals(identity.getBd())) { identities.add("BD"); } if (Boolean.TRUE.equals(identity.getAgent())) { identities.add("代理"); } if (Boolean.TRUE.equals(identity.getAnchor())) { identities.add("主播"); } if (Boolean.TRUE.equals(identity.getSuperFreightAgent())) { identities.add("超级金币代理"); } if (Boolean.TRUE.equals(identity.getFreightAgent())) { identities.add("金币代理"); } if (identities.isEmpty()) { return "普通用户"; } return String.join(", ", identities); } /** * 解析用户ID字符串(逗号分隔) */ private List parseUserIds(String userIdsStr) { if (userIdsStr == null || userIdsStr.trim().isEmpty()) { return new ArrayList<>(); } return Arrays.stream(userIdsStr.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) .map(Long::parseLong) .collect(Collectors.toList()); } /** * 单个用户封禁测试 */ @Test public void updateUserDataViolation() { Long userId = 1954059793963393025L; ApprovalAccountCmd cmd = new ApprovalAccountCmd(); cmd.setAccountStatusEnum(ConsoleAccountStatusEnum.ARCHIVE_DEVICE); cmd.setDescription("Multi-device fingerprint blocking"); cmd.setBeApprovalUserId(userId); cmd.setReqUserId(userId); userDataViolationService.updateAccountStatus(cmd); } /** * 获取用户身份信息 */ public UserIdentityVO getUserIdentity(Long userId) { TeamMemberDTO teamMember = ResponseAssert.requiredSuccess( teamProfileClient.getByMemberId(userId)); Administrator administrator = administratorService.getByUserId(userId); boolean isValidAdmin = administrator != null && Boolean.TRUE.equals(administrator.getStatus()); boolean isSuperAdmin = isValidAdmin && Optional.of(administrator) .map(e -> RoomRolesEnum.MANAGER.eq(e.getRoles()) || RoomRolesEnum.SUPER_ADMIN.eq(e.getRoles())) .orElse(Boolean.FALSE); boolean isManager = isValidAdmin && Optional.of(administrator) .map(e -> RoomRolesEnum.MANAGER.eq(e.getRoles())) .orElse(Boolean.FALSE); return new UserIdentityVO() .setAgent(Objects.nonNull(teamMember) && TeamMemberRole.OWN.eq(teamMember.getRole())) .setAnchor(Objects.nonNull(teamMember)) .setBd(ResponseAssert.requiredSuccess(bdTeamInfoClient.check(userId))) .setFreightAgent(ResponseAssert.requiredSuccess(freightGoldClient.checkFreightAgent(userId))) .setSuperFreightAgent(ResponseAssert.requiredSuccess(freightGoldClient.checkSuperFreightAgent(userId))) .setSuperAdmin(isSuperAdmin) .setAdmin(isValidAdmin) .setBdLeader(ResponseAssert.requiredSuccess(bdTeamLeaderClient.check(userId))) .setManager(isManager) ; } }