package com.estate.expert.admin.controller.admin; import com.estate.expert.admin.domain.business.device.DeviceType; import com.estate.expert.admin.domain.common.request.PlanPageRequest; import com.estate.expert.admin.domain.common.request.ProjectPageRequest; import com.estate.expert.admin.domain.common.request.RecordPageRequest; import com.estate.expert.admin.domain.standard.hierachy.HierarchyIds; import com.estate.expert.admin.domain.standard.problem.Problem; import com.estate.expert.admin.domain.user.User; import com.estate.expert.admin.enums.BizResultCode; import com.estate.expert.admin.io.processor.DataProcessorBase; import com.estate.expert.admin.io.processor.RowDataProcessorBase; import com.estate.expert.admin.io.processor.impl.business.DeviceProcessor; import com.estate.expert.admin.io.processor.impl.business.PlanProcessor; import com.estate.expert.admin.io.processor.impl.business.ProjectProcessor; import com.estate.expert.admin.io.processor.impl.matrix.CompareMatrixProcessor; import com.estate.expert.admin.io.processor.impl.matrix.MembershipMatrixProcessor; import com.estate.expert.admin.io.processor.impl.record.DeviceErrorRecordProcessor; import com.estate.expert.admin.io.processor.impl.record.SystemErrorRecordProcessor; import com.estate.expert.admin.io.processor.impl.record.WarningRecordProcessor; import com.estate.expert.admin.io.processor.impl.standard.ProblemProcessor; import com.estate.expert.admin.service.*; import com.estate.expert.admin.service.expert.DeviceExpertService; import com.estate.expert.admin.service.expert.SysExpertService; import com.estate.expert.common.domain.Session; import com.estate.expert.common.rpc.Result; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.InputStreamResource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; @Slf4j @CrossOrigin @Api(tags = "管理后台/导入导出") @RequestMapping("/admin/io") @Controller @Validated public class ExportController { @Resource private ErrorRecordService errorRecordService; @Resource private WarningRecordService warningRecordService; @Resource private ProjectService projectService; @Resource private DeviceService deviceService; @Resource private PlanService planService; @Resource private CheckTaskService checkTaskService; @Resource private ClientService clientService; @Resource private SysExpertService sysExpertService; @Resource private DeviceExpertService deviceExpertService; @Resource private HierarchyService hierarchyService; @Resource private ProblemService problemService; @ApiOperation("综合问题导出") @PostMapping("/systemError/export") public ResponseEntity exportSystemErrorRecords(@Validated @RequestBody RecordPageRequest request){ var result = errorRecordService.listSystemErrors(request.getClientId(), request.getPlanId(), request.getTaskId(), request.getStartDate(), request.getEndDate()); return downloadRowData(new SystemErrorRecordProcessor(), result); } @ApiOperation("设备问题导出") @PostMapping("/deviceError/export") public ResponseEntity exportDeviceErrorRecords(@Validated @RequestBody RecordPageRequest request){ var result = errorRecordService.listDeviceErrors(request.getClientId(), request.getPlanId(), request.getTaskId(), request.getStartDate(), request.getEndDate()); return downloadRowData(new DeviceErrorRecordProcessor(), result); } @ApiOperation("正确做法导出") @PostMapping("/warning/export") public ResponseEntity exportWarningRecords(@Validated @RequestBody RecordPageRequest request){ var result = warningRecordService.list(request.getClientId(), request.getPlanId(), request.getTaskId(), request.getStartDate(), request.getEndDate()); return downloadRowData(new WarningRecordProcessor(), result); } @ApiOperation("项目导出") @PostMapping("/project/export") public ResponseEntity exportProjects(@Validated @RequestBody ProjectPageRequest request){ var result = projectService.list(request.getKeyword(), request.getProjectTypes(), request.getClientIds()); return downloadRowData(new ProjectProcessor(clientService, projectService), result, "项目导出"); } @ApiOperation("项目导入模版") @PostMapping("/project/template") public ResponseEntity downloadProjectTemplate(){ return downloadRowData(new ProjectProcessor(clientService, projectService), List.of()); } @ApiOperation("项目导入") @PostMapping("/project/import") @ResponseBody public Result importProjectRecords( Session session, @RequestParam("file") MultipartFile file) { return importData( "项目", session, file, () -> new ProjectProcessor(clientService, projectService).loadFromWorkbook(file.getInputStream()), records -> projectService.importEntities((User) session.getUser(), records) ); } @ApiOperation("设备导出") @PostMapping("/device/export") public ResponseEntity exportDeviceRecords(@RequestParam(value = "keyword", required = false) String keyword, @RequestParam(value = "deviceType", required = false) DeviceType deviceType){ var result = deviceService.list(keyword, deviceType); return downloadRowData(new DeviceProcessor(), result, "设备导出"); } @ApiOperation("设备导入模版") @GetMapping("/device/template") public ResponseEntity downloadDeviceTemplate(){ return downloadRowData(new DeviceProcessor(), List.of()); } @ApiOperation("设备导入") @PostMapping("/device/import") @ResponseBody public Result importDeviceRecords( Session session, @RequestParam("file") MultipartFile file) { return importData( "设备", session, file, () -> new DeviceProcessor().loadFromWorkbook(file.getInputStream()), records -> deviceService.importEntities((User) session.getUser(), records) ); } ////// 模糊数学专家 - 隶属度矩阵导入导出 ////// @ApiOperation("综合体系-模糊数学-隶属度矩阵导出") @PostMapping("/system/fuzzy/membershipMatrix/export") public ResponseEntity exportSystemFuzzyMembershipMatrix( @ApiParam("专家ID") @RequestParam(value = "expertId", required = false) Integer expertId) { if(expertId == null) { expertId = sysExpertService.getDefaultExpert().getId(); } var matrix = sysExpertService.getMembershipMatrix(expertId); BizResultCode.MEMBERSHIP_MATRIX_LOAD_ERROR.check(matrix == null); var expert = sysExpertService.get(expertId); String filename = expert != null ? "综合体系-" + expert.getName() + "-隶属度矩阵" : "综合体系-隶属度矩阵"; return downloadData(new MembershipMatrixProcessor(), matrix, filename); } @ApiOperation("综合体系-模糊数学-隶属度矩阵导入模版") @PostMapping("/system/fuzzy/membershipMatrix/template") public ResponseEntity downloadSystemFuzzyMembershipMatrixTemplate() { return exportSystemFuzzyMembershipMatrix(null); } @ApiOperation("综合体系-模糊数学-隶属度矩阵导入") @PostMapping("/system/fuzzy/membershipMatrix/import") @ResponseBody public Result importSystemFuzzyMembershipMatrix( @ApiParam("专家ID") @RequestParam("expertId") Integer expertId, @RequestParam("file") MultipartFile file) { try { sysExpertService.importMembershipMatrix(expertId, file.getInputStream()); return Result.success("隶属度矩阵导入成功"); } catch (Exception e) { log.error("综合体系隶属度矩阵导入失败", e); return Result.error("导入失败: " + e.getMessage()); } } @ApiOperation("设备体系-模糊数学-隶属度矩阵导出") @PostMapping("/device/fuzzy/membershipMatrix/export") public ResponseEntity exportDeviceFuzzyMembershipMatrix( @ApiParam("专家ID") @RequestParam(value = "expertId", required = false) Integer expertId) { if(expertId == null) { expertId = deviceExpertService.getDefaultExpert().getId(); } var matrix = deviceExpertService.getMembershipMatrix(expertId); BizResultCode.MEMBERSHIP_MATRIX_LOAD_ERROR.check(matrix == null); var expert = deviceExpertService.get(expertId); String filename = expert != null ? "设备体系-" + expert.getName() + "-隶属度矩阵" : "设备体系-隶属度矩阵"; return downloadData(new MembershipMatrixProcessor(), matrix, filename); } @ApiOperation("设备体系-模糊数学-隶属度矩阵导入模版") @PostMapping("/device/fuzzy/membershipMatrix/template") public ResponseEntity downloadDeviceFuzzyMembershipMatrixTemplate() { return exportDeviceFuzzyMembershipMatrix(null); } @ApiOperation("设备体系-模糊数学-隶属度矩阵导入") @PostMapping("/device/fuzzy/membershipMatrix/import") @ResponseBody public Result importDeviceFuzzyMembershipMatrix( @ApiParam("专家ID") @RequestParam("expertId") Integer expertId, @RequestParam("file") MultipartFile file) { try { deviceExpertService.importMembershipMatrix(expertId, file.getInputStream()); return Result.success("隶属度矩阵导入成功"); } catch (Exception e) { log.error("设备体系隶属度矩阵导入失败", e); return Result.error("导入失败: " + e.getMessage()); } } ////// 模糊数学专家 - 判断矩阵导入导出 ////// @ApiOperation("综合体系-模糊数学-判断矩阵导出") @PostMapping("/system/fuzzy/compareMatrix/export") public ResponseEntity exportSystemFuzzyCompareMatrix( @ApiParam("专家ID") @RequestParam Integer expertId) { var expert = sysExpertService.get(expertId); if(expert == null) { throw new RuntimeException("专家不存在"); } var hierarchyTree = sysExpertService.getHierarchyTree(expertId); var compareMatrixMap = expert.getCompareMatrixMap(); // 如果没有判断矩阵,自动生成默认矩阵(所有比值为1) if(compareMatrixMap == null || compareMatrixMap.isEmpty()) { compareMatrixMap = generateDefaultCompareMatrixMap(hierarchyTree); } String filename = "综合体系-" + expert.getName() + "-判断矩阵"; return downloadData(new CompareMatrixProcessor(hierarchyTree), compareMatrixMap, filename); } @ApiOperation("综合体系-模糊数学-判断矩阵导入模版") @PostMapping("/system/fuzzy/compareMatrix/template") public ResponseEntity downloadSystemFuzzyCompareMatrixTemplate( @ApiParam("专家ID") @RequestParam Integer expertId) { return exportSystemFuzzyCompareMatrix(expertId); } @ApiOperation("综合体系-模糊数学-判断矩阵导入") @PostMapping("/system/fuzzy/compareMatrix/import") @ResponseBody public Result importSystemFuzzyCompareMatrix( @ApiParam("专家ID") @RequestParam Integer expertId, @RequestParam("file") MultipartFile file) { try { var hierarchyTree = sysExpertService.getHierarchyTree(expertId); var compareMatrixMap = new CompareMatrixProcessor(hierarchyTree).loadFromWorkbook(file.getInputStream()); // 验证所有矩阵 for(var matrix : compareMatrixMap.values()) { CompareMatrixProcessor.validate(matrix); } // 更新专家的判断矩阵 var expert = sysExpertService.get(expertId); expert.setCompareMatrixMap(compareMatrixMap); sysExpertService.edit(expert); return Result.success("判断矩阵导入成功,共导入 " + compareMatrixMap.size() + " 个节点"); } catch (Exception e) { log.error("综合体系判断矩阵导入失败", e); return Result.error("导入失败: " + e.getMessage()); } } @ApiOperation("设备体系-模糊数学-判断矩阵导出") @PostMapping("/device/fuzzy/compareMatrix/export") public ResponseEntity exportDeviceFuzzyCompareMatrix( @ApiParam("专家ID") @RequestParam Integer expertId) { var expert = deviceExpertService.get(expertId); if(expert == null) { throw new RuntimeException("专家不存在"); } var hierarchyTree = deviceExpertService.getHierarchyTree(expertId); var compareMatrixMap = expert.getCompareMatrixMap(); // 如果没有判断矩阵,自动生成默认矩阵(所有比值为1) if(compareMatrixMap == null || compareMatrixMap.isEmpty()) { compareMatrixMap = generateDefaultCompareMatrixMap(hierarchyTree); } String filename = "设备体系-" + expert.getName() + "-判断矩阵"; return downloadData(new CompareMatrixProcessor(hierarchyTree), compareMatrixMap, filename); } @ApiOperation("设备体系-模糊数学-判断矩阵导入模版") @PostMapping("/device/fuzzy/compareMatrix/template") public ResponseEntity downloadDeviceFuzzyCompareMatrixTemplate( @ApiParam("专家ID") @RequestParam Integer expertId) { return exportDeviceFuzzyCompareMatrix(expertId); } @ApiOperation("设备体系-模糊数学-判断矩阵导入") @PostMapping("/device/fuzzy/compareMatrix/import") @ResponseBody public Result importDeviceFuzzyCompareMatrix( @ApiParam("专家ID") @RequestParam Integer expertId, @RequestParam("file") MultipartFile file) { try { var hierarchyTree = deviceExpertService.getHierarchyTree(expertId); var compareMatrixMap = new CompareMatrixProcessor(hierarchyTree).loadFromWorkbook(file.getInputStream()); // 验证所有矩阵 for(var matrix : compareMatrixMap.values()) { CompareMatrixProcessor.validate(matrix); } // 更新专家的判断矩阵 var expert = deviceExpertService.get(expertId); expert.setCompareMatrixMap(compareMatrixMap); deviceExpertService.edit(expert); return Result.success("判断矩阵导入成功,共导入 " + compareMatrixMap.size() + " 个节点"); } catch (Exception e) { log.error("设备体系判断矩阵导入失败", e); return Result.error("导入失败: " + e.getMessage()); } } @ApiOperation("检查计划导出") @PostMapping("/plan/export") public ResponseEntity exportPlans(@Validated @RequestBody PlanPageRequest request){ var result = planService.list(request.getKeyword(), request.getPlanTypes(), request.getPlanStates(), request.getClientIds(), request.getCreatorId()); return downloadRowData(new PlanProcessor(checkTaskService), result, "检查计划导出"); } @ApiOperation("体系问题导出(综合体系第6层/其他体系第4层叶子节点)") @PostMapping("/problem/export") public ResponseEntity exportProblems( @ApiParam("体系ID(1=综合体系-6层,其他=设备体系-4层)") @RequestParam Integer hierarchyId, @ApiParam("体系路径(单个节点)") @RequestParam String hierarchyPath) { var result = problemService.findProblems(hierarchyId, List.of(hierarchyPath), null); var hierarchy = hierarchyService.get(hierarchyId); String filename = hierarchy != null ? hierarchy.getName() + "-问题导出" : "问题导出"; return downloadRowData(new ProblemProcessor(hierarchyService, hierarchyId), result, filename); } @ApiOperation("体系问题导入模版(综合体系第6层/其他体系第4层叶子节点)") @PostMapping("/problem/template") public ResponseEntity downloadProblemTemplate( @ApiParam("体系ID(1=综合体系-6层,其他=设备体系-4层)") @RequestParam Integer hierarchyId) { return downloadRowData(new ProblemProcessor(hierarchyService, hierarchyId), List.of()); } @ApiOperation("体系问题导入(综合体系第6层/其他体系第4层叶子节点)") @PostMapping("/problem/import") @ResponseBody public Result importProblems( Session session, @ApiParam("体系ID(1=综合体系-6层,其他=设备体系-4层)") @RequestParam Integer hierarchyId, @ApiParam("体系路径(问题将归属到此路径下)") @RequestParam String hierarchyPath, @RequestParam("file") MultipartFile file) { return importData( "问题", session, file, () -> { var records = new ProblemProcessor(hierarchyService, hierarchyId).loadFromWorkbook(file.getInputStream()); // 设置所有问题的体系路径 if(records != null) { for(var problem : records) { problem.setHierarchyPath(hierarchyPath); problem.setHierarchyId(hierarchyId); } // 验证所有问题必须是叶子节点 validateLeafNodes(hierarchyId, records); } return records; }, records -> problemService.importEntities((User) session.getUser(), records) ); } /** * 生成默认的判断矩阵Map(所有比值为1) * 为体系树中所有有子节点的节点生成默认判断矩阵 */ private java.util.Map generateDefaultCompareMatrixMap( com.estate.expert.admin.utils.tree.TreeRoot hierarchyTree) { java.util.Map matrixMap = new java.util.TreeMap<>(); // 递归遍历所有节点 generateDefaultMatrixForNode(hierarchyTree.root(), matrixMap); return matrixMap; } /** * 递归为节点及其子节点生成默认判断矩阵 */ private void generateDefaultMatrixForNode( com.estate.expert.admin.utils.tree.TreeNode node, java.util.Map matrixMap) { // 如果节点有子节点,则为其生成判断矩阵 if(node.getChildren() != null && !node.getChildren().isEmpty()) { String nodePath = node.getPath(); // 跳过根节点或无效路径 if(!"/".equals(nodePath) && nodePath != null && nodePath.length() > 1) { var children = node.getChildren().values(); String[] childNames = children.stream() .map(com.estate.expert.admin.utils.tree.TreeNode::getName) .toArray(String[]::new); // 创建默认矩阵(所有值为1) var matrix = new com.estate.expert.admin.domain.standard.expert.matrix.CompareMatrix(childNames); matrixMap.put(nodePath, matrix); } // 递归处理子节点 for(var child : node.getChildren().values()) { generateDefaultMatrixForNode(child, matrixMap); } } } /** * 验证问题是否都是叶子节点 * 综合体系(ID=1):第6层 * 其他体系(ID!=1):第4层 */ private void validateLeafNodes(Integer hierarchyId, List problems) { var hierarchy = hierarchyService.get(hierarchyId); if(hierarchy == null) { throw new RuntimeException("体系不存在"); } var root = hierarchy.getContent(); if(root == null) { throw new RuntimeException("体系结构为空"); } // 根据体系ID确定要求的层级 // ID=1 是综合体系(第6层),其他都是设备体系(第4层) int requiredDepth; String hierarchyName; if(hierarchyId == HierarchyIds.SYSTEM) { requiredDepth = 6; hierarchyName = "综合体系"; } else { requiredDepth = 4; hierarchyName = "设备体系"; } // 所有问题共享同一个体系路径,只需验证一次 if(!problems.isEmpty()) { var path = problems.get(0).getHierarchyPath(); if(path == null || path.trim().isEmpty()) { throw new RuntimeException("体系路径为空"); } var node = root.get(path); if(node == null) { throw new RuntimeException("体系路径【" + path + "】不存在"); } // 检查是否是要求的层级 if(node.getDepth() != requiredDepth) { throw new RuntimeException("体系路径不在" + hierarchyName + "第" + requiredDepth + "层(当前在第" + node.getDepth() + "层)"); } // 检查是否是叶子节点 if(node.getChildren() != null && !node.getChildren().isEmpty()) { throw new RuntimeException("体系路径不是叶子节点(存在子节点)"); } } } /** * 通用的数据导入方法 * * @param dataType 数据类型名称(用于日志和错误提示) * @param session 会话信息 * @param file 上传的文件 * @param loader 数据加载函数 * @param importer 数据导入函数 * @param 数据类型 * @return 导入结果 */ private Result importData( String dataType, Session session, MultipartFile file, DataLoader loader, DataImporter importer) { try { // 1. 加载数据 var records = loader.load(); // 2. 验证数据 if(records == null || records.isEmpty()) { return Result.error("导入文件中没有有效数据"); } // 3. 执行导入 importer.doImport(records); // 4. 返回成功结果 return Result.success("成功导入 " + records.size() + " 个" + dataType); } catch (Exception e) { log.error("{}导入失败", dataType, e); return Result.error("导入失败: " + e.getMessage()); } } /** * 数据加载函数接口 */ @FunctionalInterface private interface DataLoader { List load() throws Exception; } /** * 数据导入函数接口 */ @FunctionalInterface private interface DataImporter { void doImport(List records); } // 生成Excel文件并写入下载的流 @SneakyThrows public ResponseEntity downloadRowData(RowDataProcessorBase processor, List records) { return downloadData(processor, records); } // 生成Excel文件并写入下载的流(带文件名) @SneakyThrows public ResponseEntity downloadRowData(RowDataProcessorBase processor, List records, String filename) { return downloadData(processor, records, filename); } // 生成Excel文件并写入下载的流 @SneakyThrows public ResponseEntity downloadData(DataProcessorBase processor, V data) { return downloadData(processor, data, null); } // 生成Excel文件并写入下载的流(带文件名) @SneakyThrows public ResponseEntity downloadData(DataProcessorBase processor, V data, String filename) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); processor.saveWorkbook(baos, data); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); InputStreamResource resource = new InputStreamResource(bais); var responseBuilder = ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/vnd.ms-excel;charset=UTF-8")); // 如果指定了文件名,则设置 Content-Disposition 头(支持中文) if(filename != null && !filename.trim().isEmpty()) { String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()).replace("+", "%20"); responseBuilder.header("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename + ".xlsx"); } return responseBuilder.body(resource); } }