From fa93075a8c8a1c336c7ced319678ad74615b23e9 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Tue, 11 Nov 2025 19:15:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=89=B9=E9=87=8F=E5=B0=81?= =?UTF-8?q?=E7=A6=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rc-service-other/other-start/pom.xml | 12 + .../src/test/java/UserDataViolationTest.java | 469 +++++++++++++++++- .../src/test/java/dto/BanResultDTO.java | 45 ++ .../test/java/dto/DeviceFingerprintDTO.java | 46 ++ .../src/test/java/util/ExcelUtil.java | 193 +++++++ 5 files changed, 763 insertions(+), 2 deletions(-) create mode 100644 rc-service/rc-service-other/other-start/src/test/java/dto/BanResultDTO.java create mode 100644 rc-service/rc-service-other/other-start/src/test/java/dto/DeviceFingerprintDTO.java create mode 100644 rc-service/rc-service-other/other-start/src/test/java/util/ExcelUtil.java diff --git a/rc-service/rc-service-other/other-start/pom.xml b/rc-service/rc-service-other/other-start/pom.xml index eb8605f3..d667949a 100644 --- a/rc-service/rc-service-other/other-start/pom.xml +++ b/rc-service/rc-service-other/other-start/pom.xml @@ -22,6 +22,18 @@ com.red.circle component-redis + + + + org.apache.poi + poi + 5.2.3 + + + org.apache.poi + poi-ooxml + 5.2.3 + diff --git a/rc-service/rc-service-other/other-start/src/test/java/UserDataViolationTest.java b/rc-service/rc-service-other/other-start/src/test/java/UserDataViolationTest.java index 21693d56..71eab2b4 100644 --- a/rc-service/rc-service-other/other-start/src/test/java/UserDataViolationTest.java +++ b/rc-service/rc-service-other/other-start/src/test/java/UserDataViolationTest.java @@ -1,26 +1,461 @@ 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); @@ -30,4 +465,34 @@ public class UserDataViolationTest { 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) + ; + } + } diff --git a/rc-service/rc-service-other/other-start/src/test/java/dto/BanResultDTO.java b/rc-service/rc-service-other/other-start/src/test/java/dto/BanResultDTO.java new file mode 100644 index 00000000..e6ab2b3d --- /dev/null +++ b/rc-service/rc-service-other/other-start/src/test/java/dto/BanResultDTO.java @@ -0,0 +1,45 @@ +package dto; + +import lombok.Data; + +/** + * 封禁结果数据传输对象 + */ +@Data +public class BanResultDTO { + + /** + * 用户ID + */ + private Long userId; + + /** + * 设备指纹 + */ + private String deviceFingerprint; + + /** + * 用户身份 + */ + private String userIdentity; + + /** + * 是否封禁 + */ + private String banned; + + /** + * 封禁原因/不封禁原因 + */ + private String reason; + + /** + * 处理状态 + */ + private String status; + + /** + * 备注 + */ + private String remark; +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-start/src/test/java/dto/DeviceFingerprintDTO.java b/rc-service/rc-service-other/other-start/src/test/java/dto/DeviceFingerprintDTO.java new file mode 100644 index 00000000..dd6f06c9 --- /dev/null +++ b/rc-service/rc-service-other/other-start/src/test/java/dto/DeviceFingerprintDTO.java @@ -0,0 +1,46 @@ +package dto; + +import lombok.Data; + +/** + * 设备指纹数据传输对象 + * 对应 SQL 查询结果的结构 + */ +@Data +public class DeviceFingerprintDTO { + + /** + * 设备指纹(请求平台|手机型号|IP) + */ + private String deviceFingerprint; + + /** + * 请求平台 + */ + private String requestClient; + + /** + * 手机型号 + */ + private String phoneModel; + + /** + * IP地址 + */ + private String ipAddress; + + /** + * 共同设备指纹用户数 + */ + private Integer userCount; + + /** + * 共同设备指纹用户ID(逗号分隔) + */ + private String userIds; + + /** + * 总抽奖次数 + */ + private Integer totalDrawCount; +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-start/src/test/java/util/ExcelUtil.java b/rc-service/rc-service-other/other-start/src/test/java/util/ExcelUtil.java new file mode 100644 index 00000000..914764ea --- /dev/null +++ b/rc-service/rc-service-other/other-start/src/test/java/util/ExcelUtil.java @@ -0,0 +1,193 @@ +package util; + +import dto.BanResultDTO; +import dto.DeviceFingerprintDTO; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Excel 工具类 + * 用于导入设备指纹数据和导出封禁结果 + */ +@Slf4j +public class ExcelUtil { + + /** + * 导入设备指纹数据 + * + * @param filePath Excel 文件路径 + * @return 设备指纹数据列表 + */ + public static List importDeviceFingerprint(String filePath) { + List result = new ArrayList<>(); + + try (FileInputStream fis = new FileInputStream(filePath); + Workbook workbook = new XSSFWorkbook(fis)) { + + Sheet sheet = workbook.getSheetAt(0); + + // 跳过表头,从第二行开始读取 + for (int i = 1; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row == null) { + continue; + } + + DeviceFingerprintDTO dto = new DeviceFingerprintDTO(); + dto.setDeviceFingerprint(getCellValueAsString(row.getCell(0))); + dto.setRequestClient(getCellValueAsString(row.getCell(1))); + dto.setPhoneModel(getCellValueAsString(row.getCell(2))); + dto.setIpAddress(getCellValueAsString(row.getCell(3))); + dto.setUserCount(getCellValueAsInteger(row.getCell(4))); + dto.setUserIds(getCellValueAsString(row.getCell(5))); + dto.setTotalDrawCount(getCellValueAsInteger(row.getCell(6))); + + result.add(dto); + } + + log.info("成功导入 {} 条设备指纹数据", result.size()); + + } catch (IOException e) { + log.error("导入 Excel 文件失败: {}", filePath, e); + throw new RuntimeException("导入 Excel 文件失败", e); + } + + return result; + } + + /** + * 导出封禁结果 + * + * @param results 封禁结果列表 + * @param filePath 导出文件路径 + */ + public static void exportBanResults(List results, String filePath) { + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("封禁结果"); + + // 创建表头样式 + CellStyle headerStyle = createHeaderStyle(workbook); + + // 创建表头 + Row headerRow = sheet.createRow(0); + String[] headers = {"用户ID", "设备指纹", "用户身份", "是否封禁", "原因", "处理状态", "备注"}; + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + // 创建数据行 + for (int i = 0; i < results.size(); i++) { + Row row = sheet.createRow(i + 1); + BanResultDTO dto = results.get(i); + + row.createCell(0).setCellValue(dto.getUserId() != null ? dto.getUserId().toString() : ""); + row.createCell(1).setCellValue(dto.getDeviceFingerprint() != null ? dto.getDeviceFingerprint() : ""); + row.createCell(2).setCellValue(dto.getUserIdentity() != null ? dto.getUserIdentity() : ""); + row.createCell(3).setCellValue(dto.getBanned() != null ? dto.getBanned() : ""); + row.createCell(4).setCellValue(dto.getReason() != null ? dto.getReason() : ""); + row.createCell(5).setCellValue(dto.getStatus() != null ? dto.getStatus() : ""); + row.createCell(6).setCellValue(dto.getRemark() != null ? dto.getRemark() : ""); + } + + // 自动调整列宽 + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + } + + // 写入文件 + try (FileOutputStream fos = new FileOutputStream(filePath)) { + workbook.write(fos); + } + + log.info("成功导出 {} 条封禁结果到: {}", results.size(), filePath); + + } catch (IOException e) { + log.error("导出 Excel 文件失败: {}", filePath, e); + throw new RuntimeException("导出 Excel 文件失败", e); + } + } + + /** + * 创建表头样式 + */ + private static CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + + // 设置背景色 + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // 设置边框 + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + + // 设置字体 + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + + // 设置居中 + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + return style; + } + + /** + * 获取单元格字符串值 + */ + private static String getCellValueAsString(Cell cell) { + if (cell == null) { + return ""; + } + + switch (cell.getCellType()) { + case STRING: + return cell.getStringCellValue().trim(); + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + return cell.getDateCellValue().toString(); + } + return String.valueOf((long) cell.getNumericCellValue()); + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + case FORMULA: + return cell.getCellFormula(); + default: + return ""; + } + } + + /** + * 获取单元格整数值 + */ + private static Integer getCellValueAsInteger(Cell cell) { + if (cell == null) { + return 0; + } + + switch (cell.getCellType()) { + case NUMERIC: + return (int) cell.getNumericCellValue(); + case STRING: + try { + return Integer.parseInt(cell.getStringCellValue().trim()); + } catch (NumberFormatException e) { + return 0; + } + default: + return 0; + } + } +} \ No newline at end of file