package com.ruoyi.quality.utils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.quality.mapper.QualityInspectMapper; import com.ruoyi.quality.pojo.QualityInspect; import com.ruoyi.quality.pojo.QualityInspectParam; import com.ruoyi.quality.service.IQualityInspectParamService; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.usermodel.Range; import org.apache.poi.hwpf.usermodel.Table; import org.apache.poi.hwpf.usermodel.TableCell; import org.apache.poi.hwpf.usermodel.TableIterator; import org.apache.poi.hwpf.usermodel.TableRow; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; /** * Word template export helper for process inspection. */ @Component @RequiredArgsConstructor public class QualityInspectTemplateExportHelper { private static final String WEILONG_TEMPLATE = "/static/伟龙模版.doc"; private static final String BAISHI_TEMPLATE = "/static/百事模版.doc"; private static final String DALI_TEMPLATE = "/static/达利模版.doc"; private final QualityInspectMapper qualityInspectMapper; private final IQualityInspectParamService qualityInspectParamService; public void exportWeiLong(HttpServletResponse response, Long inspectId) { export(response, inspectId, WEILONG_TEMPLATE, "伟龙模版检验结果"); } public void exportBaiShi(HttpServletResponse response, Long inspectId) { export(response, inspectId, BAISHI_TEMPLATE, "百事模版检验结果"); } public void exportDaLi(HttpServletResponse response, Long inspectId) { export(response, inspectId, DALI_TEMPLATE, "达利模版检验结果"); } private void export(HttpServletResponse response, Long inspectId, String templatePath, String fileName) { if (inspectId == null) { throw new ServiceException("检验单ID不能为空"); } QualityInspect inspect = qualityInspectMapper.selectById(inspectId); if (inspect == null) { throw new ServiceException("检验单不存在"); } List paramList = qualityInspectParamService.list( Wrappers.lambdaQuery() .eq(QualityInspectParam::getInspectId, inspectId) .orderByAsc(QualityInspectParam::getId)); Map valueMap = buildValueMap(paramList, inspect); try (InputStream inputStream = getClass().getResourceAsStream(templatePath)) { if (inputStream == null) { throw new ServiceException("模板文件不存在:" + templatePath); } HWPFDocument document = new HWPFDocument(inputStream); fillDocument(document, valueMap); response.reset(); response.setContentType("application/msword"); response.setCharacterEncoding("UTF-8"); String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); response.setHeader("Content-Disposition", "attachment;filename=" + encodedName + ".doc"); try (OutputStream outputStream = response.getOutputStream()) { document.write(outputStream); outputStream.flush(); } } catch (IOException e) { throw new RuntimeException("导出失败", e); } } private void fillDocument(HWPFDocument document, Map valueMap) { Range range = document.getRange(); TableIterator iterator = new TableIterator(range); while (iterator.hasNext()) { Table table = iterator.next(); fillTable(table, valueMap); } } private void fillTable(Table table, Map valueMap) { String currentGroupLabel = ""; // 记录结论列的位置,避免在结论列下面继续填充 int conclusionCellIndex = -1; for (int rowIndex = 0; rowIndex < table.numRows(); rowIndex++) { TableRow row = table.getRow(rowIndex); String firstCellText = getCellText(row, 0); String secondCellText = getCellText(row, 1); if (StringUtils.isNotBlank(firstCellText)) { currentGroupLabel = firstCellText; } // 先判断是否为结论行 String normalizedFirstCell = normalizeKey(firstCellText); boolean isConclusionRow = matchesSummaryRow(normalizedFirstCell); // 如果是结论行,结论列始终是最后一列 if (isConclusionRow) { conclusionCellIndex = row.numCells() - 1; } String value = resolveValue(valueMap, currentGroupLabel, firstCellText, secondCellText, isConclusionRow); if (StringUtils.isBlank(value)) { continue; } // 查找结果列位置 int resultCellIndex; if (isConclusionRow) { resultCellIndex = conclusionCellIndex; } else { // 普通行:找第一个空白单元格作为结果列,但要排除结论列 resultCellIndex = findResultCellIndex(row, conclusionCellIndex); if (resultCellIndex < 0) { // 不使用最后一列,避免与结论列冲突 int lastCellIndex = row.numCells() - 1; if (lastCellIndex > 1 && lastCellIndex != conclusionCellIndex) { resultCellIndex = lastCellIndex - 1; } } } if (resultCellIndex < 0 || resultCellIndex == conclusionCellIndex && !isConclusionRow) { continue; } TableCell resultCell = row.getCell(resultCellIndex); String cellText = cleanCellText(resultCell.text()); // 严格检查:只有空白或占位符才填充 if (!isBlankLike(cellText) && !isPlaceholder(cellText)) { continue; } // 使用更安全的填充方式 safeWriteCellText(resultCell, value); } } /** * 查找结果列,排除结论列 */ private int findResultCellIndex(TableRow row, int excludeIndex) { for (int i = 1; i < row.numCells(); i++) { if (i == excludeIndex) { continue; } String cellText = cleanCellText(row.getCell(i).text()); if (isBlankLike(cellText) || isPlaceholder(cellText)) { return i; } } return -1; } /** * 安全写入单元格文本,避免影响其他行 */ private void safeWriteCellText(TableCell cell, String value) { if (StringUtils.isBlank(value)) { return; } String originalText = cell.text(); String cleanedOriginal = cleanCellText(originalText); // 如果单元格已有内容且不是占位符,不写入 if (StringUtils.isNotBlank(cleanedOriginal) && !isPlaceholder(cleanedOriginal)) { return; } // 只使用replaceText,不使用insertBefore try { if (StringUtils.isNotBlank(originalText)) { cell.replaceText(originalText, value); } else { // 空单元格直接设置文本 cell.insertBefore(value); } } catch (Exception e) { // 备用方案:使用getRange方式 try { // cell.getRange().insertAfter(value); } catch (Exception ignored) {} } } /** * 判断是否为占位符 */ private boolean isPlaceholder(String text) { if (StringUtils.isBlank(text)) { return false; } String cleaned = text.trim(); // 常见占位符模式 return cleaned.equals("—") || cleaned.equals("-") || cleaned.equals("_") || cleaned.equals("/") || cleaned.equals("\\") || cleaned.equals("□") || cleaned.equals("■") || cleaned.equals("○") || cleaned.equals("●") || cleaned.equals("※") || cleaned.equals("*") || cleaned.matches("^\\.{2,}$") || // 多个点 cleaned.matches("^{2,}$") || // 多个大括号 cleaned.equalsIgnoreCase("N/A") || cleaned.equalsIgnoreCase("NA") || cleaned.equals("待检") || cleaned.equals("待填") || cleaned.equals("空白"); } private int findResultCellIndex(TableRow row) { for (int i = 1; i < row.numCells(); i++) { if (isBlankLike(row.getCell(i).text())) { return i; } } return -1; } private String resolveValue(Map valueMap, String currentGroupLabel, String firstCellText, String secondCellText, boolean isConclusionRow) { LinkedHashSet candidates = new LinkedHashSet<>(); addCandidate(candidates, firstCellText); addCandidate(candidates, secondCellText); addCandidate(candidates, firstCellText + secondCellText); if (StringUtils.isNotBlank(currentGroupLabel) && StringUtils.isNotBlank(secondCellText)) { addCandidate(candidates, currentGroupLabel + secondCellText); } if (StringUtils.isNotBlank(currentGroupLabel) && StringUtils.isNotBlank(firstCellText) && StringUtils.isBlank(secondCellText)) { addCandidate(candidates, currentGroupLabel); } // 结论行不填充,保留模板原始数据 if (isConclusionRow) { return null; } // 普通行:先查检验参数值,最后才匹配结论行关键词 for (String candidate : candidates) { String normalized = normalizeKey(candidate); // 普通行跳过结论关键词匹配 if (matchesSummaryRow(normalized)) { continue; } String value = lookupValue(valueMap, normalized); if (StringUtils.isNotBlank(value)) { return value; } } return null; } private boolean matchesSummaryRow(String normalizedText) { if (StringUtils.isBlank(normalizedText)) { return false; } return normalizedText.contains("质量评定") || normalizedText.contains("检验结论") || normalizedText.contains("Gradeestimation") || normalizedText.contains("Conclusion") || normalizedText.contains("Evaluation") || normalizedText.contains("评定") || normalizedText.contains("结论"); } private String lookupValue(Map valueMap, String normalizedCandidate) { if (StringUtils.isBlank(normalizedCandidate)) { return null; } String value = valueMap.get(normalizedCandidate); if (StringUtils.isNotBlank(value)) { return value; } String chineseCandidate = stripEnglishLetters(normalizedCandidate); value = valueMap.get(chineseCandidate); if (StringUtils.isNotBlank(value)) { return value; } for (Map.Entry entry : valueMap.entrySet()) { String key = entry.getKey(); if (StringUtils.contains(key, normalizedCandidate) || StringUtils.contains(normalizedCandidate, key) || StringUtils.contains(key, chineseCandidate) || StringUtils.contains(chineseCandidate, key)) { return entry.getValue(); } } return null; } private Map buildValueMap(List paramList, QualityInspect inspect) { Map valueMap = new LinkedHashMap<>(); for (QualityInspectParam param : paramList) { String value = StringUtils.trimToNull(param.getTestValue()); if (StringUtils.isBlank(value)) { continue; } putValue(valueMap, param.getParameterItem(), value); } String checkResult = StringUtils.trimToNull(inspect.getCheckResult()); if (StringUtils.isNotBlank(checkResult)) { putValue(valueMap, "质量评定", checkResult); putValue(valueMap, "检验结果", checkResult); putValue(valueMap, "检验结论", checkResult); putValue(valueMap, "Grade estimation", checkResult); putValue(valueMap, "Test Results", checkResult); } return valueMap; } private void putValue(Map valueMap, String key, String value) { String normalizedKey = normalizeKey(key); if (StringUtils.isBlank(normalizedKey)) { return; } valueMap.put(normalizedKey, value); String chineseKey = stripEnglishLetters(normalizedKey); if (StringUtils.isNotBlank(chineseKey)) { valueMap.putIfAbsent(chineseKey, value); } } private void writeCellText(TableCell cell, String value) { if (StringUtils.isBlank(value)) { return; } String originalText = cell.text(); try { cell.replaceText(originalText, value); } catch (Exception ignored) { // Fallback below. } String cleanedText = cleanCellText(cell.text()); if (!cleanedText.contains(value)) { cell.insertBefore(value); } } private String getCellText(TableRow row, int index) { if (row.numCells() <= index) { return ""; } return cleanCellText(row.getCell(index).text()); } private String cleanCellText(String text) { if (text == null) { return ""; } return text.replace("\u0007", "") .replace("\r", "") .replace("\n", "") .trim(); } private boolean isBlankLike(String text) { String cleaned = cleanCellText(text); return StringUtils.isBlank(cleaned) || "-".equals(cleaned) || "_".equals(cleaned) || "—".equals(cleaned) || "·".equals(cleaned); } private void addCandidate(LinkedHashSet candidates, String text) { if (StringUtils.isBlank(text)) { return; } candidates.add(text); } private String normalizeKey(String text) { String value = cleanCellText(text); if (StringUtils.isBlank(value)) { return ""; } value = value.replace("\u00A0", ""); value = value.replaceAll("\\s+", ""); value = value.replace("(", ""); value = value.replace(")", ""); value = value.replace("(", ""); value = value.replace(")", ""); value = value.replace(",", ""); value = value.replace(",", ""); value = value.replace("。", ""); value = value.replace(":", ""); value = value.replace(":", ""); value = value.replace(";", ""); value = value.replace(";", ""); value = value.replace("、", ""); value = value.replace("“", ""); value = value.replace("”", ""); value = value.replace("【", ""); value = value.replace("】", ""); value = value.replace("%", ""); return value; } private String stripEnglishLetters(String text) { if (StringUtils.isBlank(text)) { return ""; } return text.replaceAll("[A-Za-z]", ""); } /** * 调试方法:分析模板表格结构 */ public String analyzeTemplate(String templatePath) { StringBuilder sb = new StringBuilder(); try (InputStream inputStream = getClass().getResourceAsStream(templatePath)) { if (inputStream == null) { return "模板文件不存在:" + templatePath; } HWPFDocument document = new HWPFDocument(inputStream); Range range = document.getRange(); TableIterator iterator = new TableIterator(range); int tableIndex = 0; while (iterator.hasNext()) { Table table = iterator.next(); sb.append("=== 表格 ").append(tableIndex++).append(" ===\n"); sb.append("行数: ").append(table.numRows()).append("\n"); for (int rowIndex = 0; rowIndex < table.numRows(); rowIndex++) { TableRow row = table.getRow(rowIndex); sb.append("行").append(rowIndex).append(": "); for (int cellIndex = 0; cellIndex < row.numCells(); cellIndex++) { String cellText = cleanCellText(row.getCell(cellIndex).text()); sb.append("[列").append(cellIndex).append(": ").append(cellText).append("] "); } sb.append("\n"); } sb.append("\n"); } } catch (Exception e) { sb.append("分析失败: ").append(e.getMessage()); } return sb.toString(); } }